My Projects
Search Blog

Categories
Archives
Photo Albums
RSS

Powered by
BlogCFM v1.15

06 February 2007
Determining Zip Code Proximity
How to find results in your database based on their proximity to a specified zip code

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:

SELECT
 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

Posted by rickroot at 12:51 PM | Link | 15 comments
Subscription Options

You are not logged in, so your subscription status for this entry is unknown. You can login or register here.

Re: Determining Zip Code Proximity
Hi Rick! The link you gave as far as the source of the zip code data seemed to not be working...would you be willing to sell a copy of yours for five bucks? :)
dougboude@gmail.com
Posted by dougboude on February 9, 2007 at 10:41 AM

Re: Determining Zip Code Proximity
No, but here is the correct URL

http://www.teamredline.com/
Posted by rickroot on February 9, 2007 at 10:43 AM

Re: Determining Zip Code Proximity
There is a free version out there, with different examples of calculating distance. I can't remember where I got it from. So feel free to download it from me... keep your $5 =)
http://www.webveteran.com/demos/CFZipLocator.zip
Posted by jules on February 11, 2007 at 5:50 PM

Re: Determining Zip Code Proximity
No offense, but why would I want a zip code database that is 18 months old?
Posted by rickroot on February 11, 2007 at 7:25 PM

Re: Determining Zip Code Proximity
do the zip codes really change that frequently? How often should you refresh your data?
Posted by altimage on February 13, 2007 at 2:27 PM

Re: Determining Zip Code Proximity
Yes, zip codes do change pretty frequently across the country. We're not actually using this data in production right now so we haven't talked about frequency, but I suspect that we'd probably get quarterly updates at a minimum.
Posted by rickroot on February 14, 2007 at 5:37 AM

Re: Determining Zip Code Proximity
"No offense, but why would I want a zip code database that is 18 months old?"

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.
Posted by jules on February 25, 2007 at 11:00 AM

Re: Determining Zip Code Proximity
You can receive up-to-date zipcodes, along with latitude and longitude, county, and country information from www.meridianworlddata.com. You can also download complete geographical databases for cities, land features, water features, and man made features around the world. You can then use this data within your calculations to make sure you have the most up-to-date data.
Posted by meridian on June 14, 2007 at 4:24 PM

Re: Determining Zip Code Proximity
Hey Rick,

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.
Posted by ceegee on July 4, 2007 at 5:57 PM

Re: Determining Zip Code Proximity
I actually figured out something that seems to work pretty good. The code I posted before is supposedly bad for short distances. I just stick this code in my cfoutput to figure the info out there, cuz I really dont need it anywhere else.












I found this somewhere else and then converted the kilometers to miles.
Posted by ceegee on July 4, 2007 at 8:50 PM

Re: Determining Zip Code Proximity
code got lost cuz its cfml, and u probably strip it out!!!!
Posted by ceegee on July 4, 2007 at 8:50 PM

Re: Determining Zip Code Proximity
This post helped me a ton! I converted the SQL above to work with MySQL and posted it to my blog:

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!
Posted by goondocks on January 22, 2008 at 4:52 PM

Re: Determining Zip Code Proximity
Question, are the zip codes returned in order of closest to searched zip code? If not, how would that be possible?
Posted by dbinaz on April 20, 2008 at 8:33 PM

Re: Determining Zip Code Proximity
No you'd have to add an order by clause.. somethign like

order by dbo.getDistance( ... )

You might have to also include that in your from clause
Posted by rickroot on April 21, 2008 at 5:54 AM

Re: Determining Zip Code Proximity
So I have the team redline zip db and I have all the code set up and working.I got an email from someone saying it was not returning correct data.
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
Posted by dbinaz on May 15, 2008 at 11:37 PM

Post a comment (login required)