So I got a request from one of our development officers the other day. They wanted to be able to perform prospect reports based on zip code, or a range of zip codes. So I started investigating, and found numerous resources thanks to the members of the coldfusion community on the cf-talk mailing list.
First, someone pointed me to http://www.teamredline.com/, a company that sells zip code databases for $5. The database contains city, state, state abbreviation, zip code, latitude, and longitude. I forked out the $5 and downloaded the data, and then imported it into SQL Server.
Russ also provided a SQL Server function that would determine the distance between two points on the globe, and here it is:
CREATE FUNCTION [dbo].[getDistance]
(
@lat1 numeric(9,6),
@lon1 numeric(9,6),
@lat2 numeric(9,6),
@lon2 numeric(9,6)
)
RETURNS NUMERIC( 10, 5 )
AS
BEGIN
DECLARE @x decimal(20,10)
DECLARE @pi decimal(21,20)
SET @pi = 3.14159265358979323846
SET @x = sin( @lat1 * @pi/180 ) * sin( @lat2 * @pi/180 ) + cos(
@lat1 *@pi/180 ) * cos( @lat2 * @pi/180 ) * cos( abs( (@lon2 * @pi/180) -
(@lon1 *@pi/180) ) )
SET @x = atan( ( sqrt( 1- power( @x, 2 ) ) ) / @x )
RETURN ( 1.852 * 60.0 * ((@x/@pi)*180) ) / 1.609344
END
He also provided a method of querying based on zip code that used a cross join. Several other users had recommended modifying his query to restrict the number of zip codes it compares by looking for latitude between A-X and A+X where A is the latitude of the origin zip code, and X is some factor of degrees that would contain the search radius. You can do the same for longitude (though the further north you go, the wider the area you end up including because the longitude is only 69 degrees per mile at the equator... but enough math! Here's the query, cfquery style:
z2.CITY, z2.ABBR, z2.ZIPCODE
FROM
ADSPRD.dbo.zipcodes z
cross join ADSPRD.dbo.zipcodes z2
WHERE
z.zipCode=<cfqueryparam cfsqltype="cf_sql_char" value="#zipcode#">
AND z2.LATITUDE BETWEEN z.latitude-(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*0.014457*2) and z.latitude+(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*0.014457*2)
AND z2.LONGITUDE BETWEEN z.longitude-(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*0.014457*2) and z.longitude+(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*0.014457*2)
and ADSPRD.dbo.getDistance(z.Latitude, z.longitude,z2.latitude, z2.longitude)
<= <cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">;
But I found an even faster way, as follows. It uses some Transact-SQL statements, but gets the same results without the cross join.
DECLARE @lat1 decimal(5,2);
DECLARE @long1 decimal(5,2);
DECLARE @rangeFactor decimal(7,6);
SET @rangeFactor = 0.014457;
SELECT @lat1 = LATITUDE, @long1 = LONGITUDE from ADSPRD.dbo.zipcodes where zipcode = <cfqueryparam cfsqltype="cf_sql_char" value="#zipcode#">;
SELECT B.CITY, B.ABBR, B.zipcode
FROM ADSPRD.dbo.zipcodes B
WHERE
B.LATITUDE BETWEEN @lat1-(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*@rangeFactor) and @lat1+(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*@rangeFactor)
AND B.LONGITUDE BETWEEN @long1-(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*@rangeFactor) and @long1+(<cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">*@rangeFactor)
AND ADSPRD.dbo.getDistance(@lat1,@long1,B.latitude,B.longitude)
<= <cfqueryparam cfsqltype="cf_sql_smallint" value="#range#">;
The difference in query execution time is pretty minimal in terms of actual milliseconds, but it is significant, anywhere from 2x to 3x faster, depending on the radius of the area you're looking in.
UPDATE - Mysql GetDistance Function
goondocs.com took the above code and produced a MySQL function for the same purpose. Since links in comments in my blog don't do anything for search engine visibility due to the nofollow attribute, I'm posting the link up here as an update:
MySQL function to compute the distance between two zip codes
You are not logged in, so your subscription status for this entry is unknown. You can login or register here.
dougboude@gmail.com
http://www.webveteran.com/demos/CFZipLocator.zip
Because it's an alternate version you can do testing against. See if you like it better, or not. Then decide which one to keep. The point of my post is the programming, not the zip data itself.
Your zip code search is the only one I found that actually works, is fast and is accurate. I went through a bunch of stuff and yours is the best. Im using the search without a cross, it works good. I had to take away the end part of the code with the getdistance function in it because I cant figure out how to get it to work for my life. I want to know the distance but have no idea how to combine the top [create function]thing together with your bottom search.
I have used a different thing, but the results seem to be complete crap, way too far away and inaccurate.
ROUND((ACOS((SIN(/57.2958) * SIN(latitude/57.2958)) +
(COS(/57.2958) * COS(latitude/57.2958) *
COS(longitude/57.2958 - /57.2958))))
* 3963,3) AS distance
I stick that code in my select statement. Im trying to figure out how to put your stuff in the statement, but having a real hard time. Thanks for your work, you rule.
I found this somewhere else and then converted the kilometers to miles.
http://www.goondocks.com/blog/08-01-22/zip_code_radius_search_using_mysql.aspx
I have posted it in the hope that it might help others. Thanks for your help with this!
order by dbo.getDistance( ... )
You might have to also include that in your from clause
There was a zip in my address db of 20236, she entered 20716 and a radius of 100 miles. the query returns 0 even tho the zips are about 32miles apart.
Here is the actual query used. I tried it in it's raw form and it does not return the correct data.
DECLARE @lat1 decimal(5,2)
DECLARE @long1 decimal(5,2)
DECLARE @rangeFactor decimal(7,6)
SET @rangeFactor = 0.014457
SELECT @lat1 = LATITUDE, @long1 = LONGITUDE from zipcodes where zipcode = '20716'
SELECT TOP 10 ZipCodes.ZIPCODE, dbo.getDistance(@lat1, @long1, ZipCodes.LATITUDE, ZipCodes.LONGITUDE) AS distance, tbl_CompInfo.comp_name, tbl_CompInfo.comp_id
FROM ZipCodes RIGHT OUTER JOIN
tbl_CompInfo ON ZipCodes.ZIPCODE = tbl_CompInfo.comp_zip
WHERE (ZipCodes.LATITUDE BETWEEN @lat1 - 100 * @rangeFactor AND @lat1 + 100 * @rangeFactor) AND (ZipCodes.LONGITUDE BETWEEN @long1 - 100 * @rangeFactor AND
@long1 + 100 * @rangeFactor) AND (dbo.getDistance(@lat1, @long1, ZipCodes.LATITUDE, ZipCodes.LONGITUDE) <= 100) AND (tbl_CompInfo.inQueue = 0)
ORDER BY distance
Post a comment (login required)