* ZipDist.prg

#Define cdbl
#Define _PI 3.1415926535893
#Define _StatuteMile 5280 &&' 5280 feet = 1 statute mile
#Define _NauticalMile 6076.11549 &&' 6076.11549 feet = 1 nautical mile
#Define _Seconds 60 && ' 60 seconds = 1 nautical mile
#Define KMRAD 6377
#Define MIRAD 3963
#Define Radians Dtor

mkDbf()  && Create dbf's from the .csv

PlotDots()  && puts dots on the desktop in the shape of the US
SnagWindow( _Screen.HWnd, "ZipMap.bmp" ) && makes a file

ZipRadius( "60714", 15 )  && get all zips within 15 miles of 60714
Browse && view the results

Return

Function SnagWindow(tHWnd, tcFileName)

	* creates an image file from a Windows window.

	Set Path To D:\foxbin\bmpstuff\bmp4
	Set Library To Home(0) + "FoxTools.FLL" Additive
	Set Classlib To scrnprnt.vcx Additive
	oCap = Createobject("CaptureScreen")
	lnRetVal = oCap.CaptureWindow( tHWnd, tcFileName)

	Return

EndFunc

Function PlotDots()

	Local ;
		loForm, ;
		lnX, lnY

	Select ;
		Min( latitude ) As MinLat, ;
		Min(longitude) As MinLon, ;
		Max( latitude ) As MaxLat, ;
		Max(longitude) As MaxLon ;
		from latlon ;
		into Cursor qMinMax

	Select State, ;
		latitude, longitude ;
		from latlon join zipcode on latlon.zipcode = zipcode.zipcode;
		order By zipcode.zipcode ;
		into Cursor qLatLon

	loForm = _Screen
	Scan

		lnX = (qLatLon.longitude - qMinMax.MinLon) / ((qMinMax.MaxLon-qMinMax.MinLon)/loForm.Width)
		lnY = (qLatLon.latitude - qMinMax.MinLat) / ((qMinMax.MaxLat-qMinMax.MinLat)/loForm.Height)
		lnCollor = ((Asc(Substr(State,2,1))-65)*26+Asc(Substr(State,1,1))-65) /(26^2)
		loForm.ForeColor = lnCollor*256^3
		loForm.PSet( lnX*.98, (loForm.Height-lnY)*.98 )

	Endscan

	Return

Endfunc


Function mkDbf()

	* Clean up previous runs
	Close Tables All
	Erase latlon.Dbf
	Erase ZipPop.Dbf
	Erase zipcode.dbf

	Create Table zipcode (;
		zipcode Char(5), ;
		CITY Varchar(100), ;
		State Char(2) , ;
		VCODE Char(2) )

	Append From zipcode.Csv Type Csv
	Index on zipcode tag zipcode

*	Create Table ZipPop ( ;
		State 		Char(2), ;
		zipcode		Char(5), ;
		MystryData	Char(59), ;
		Population 	N(9), ;
		Housing		N(9), ;
		LandAreaMe	N(14), ;
		WatAreaMe	N(14), ;
		LandAreaMi	N(12,6), ;
		WatAreaMi	N(12,6), ;
		latitude 	N(10,6), ;
		longitude 	N(10,6) )

*	Append From zcta5.txt Type Sdf

	Create Table latlon ( ;
		zipcode Varchar(10), ;
		latitude Float(12,7), ;
		longitude Float(12,7) )

	Append From latlon.Csv Type Csv

	Index On zipcode Tag zipcode
	Index On latitude Tag latitude
	Index On longitude Tag longitude

	Return

Endfunc

Func ZipRadius( tcZipcode, tnRadius )

	* Select Zipcodes that are w/in X miles of a given zipcode

	Local ;
		loZip, ;
		lnResults, ;
		lnCenterLat, lnCenterLon, ;
		lnLatRange, lnLonRange, ;
		lnLowLat, lnLowLon, lnHighLat, lnHighLon

	loZip = ZipToLatLong( tcZipcode )
	lnCenterLat = loZip.latitude
	lnCenterLon = loZip.longitude

	*' 1 degree of latitude = 60 nautical miles or 1 minute = 1 nautical mile
	lnLatRange = tnRadius / ((_NauticalMile / _StatuteMile) * _Seconds )

	*' 1 degree of longitude at the equator = 60 nautical miles,
	*' AT the poles 1 DEGREE = 0 nautical miles.

	lnLonRange = tnRadius / (((Cos(cdbl(lnCenterLat * _PI / 180)) * ;
		_NauticalMile) / _StatuteMile) * _Seconds)

	lnLowLat = lnCenterLat - lnLatRange
	lnHighLat = lnCenterLat + lnLatRange
	lnLowLon = lnCenterLon - lnLonRange
	lnHighLon = lnCenterLon + lnLonRange

	* Then use a SQL statement to select all locations within the tnRadius mile RANGE

	Select *, ;
		ZipDistance( tcZipcode, zipcode, .F. ) As distance ;
		FROM latlon ;
		WHERE latitude <= lnHighLat ;
		AND latitude >= lnLowLat ;
		AND longitude >= lnLowLon ;
		AND longitude <= lnHighLon ;
		HAVING distance <= tnRadius ;
		Order By distance ;
		INTO Cursor ZipRadius

	Return

Endfunc

Func ZipDistance( tcZip1, tcZip2, tlKM )

	* Calculate the distance between two zipcodes

	* tlKM: true = Killometers, false = Miles

	Local ;
		loZip1, loZip2, ;
		lnDistance, ;
		ln1Lat, ln1Lon, ln2Lat, ln2Lon

	loZip1 = ZipToLatLong( tcZip1 )
	loZip2 = ZipToLatLong( tcZip2 )

	lnDistance=Iif(tlKM, KMRAD, MIRAD)*;
		ACOS(Cos(Radians(90-loZip1.latitude))*Cos(Radians(90-loZip2.latitude))+;
		SIN(Radians(90-loZip1.latitude))*Sin(Radians(90-loZip2.latitude))*;
		COS(Radians(loZip1.longitude - loZip2.longitude)))

	Return lnDistance

Endfunc

Function ZipToLatLong( tcZip )

	* given a zip,
	* returns an object with .latitude and .longitude

	Local ;
		loLatLon

	Select latitude, longitude ;
		from latlon;
		where zipcode == tcZip ;
		into Cursor qLatLon

	Scatter Name loLatLon
	Return loLatLon

Endfunc