Click here to Skip to main content
Click here to Skip to main content

ZIP Code Utility

By , 2 Jan 2005
 

Introduction

Intrigued by Ben Fry's zipdecode [^] applet, I decided to write a little ZIP Code utility that allows lookups of U.S. locations by ZIP Code, City/State, or all three. Since the data were already in the database in the form of latitude/longitude pairs, I added the capability to find the distance between two points, and to find all other ZIP Codes within a radius of X miles from the original location.

Background

Database

The MS Access database contains the following fields:

Field Name Description
ZIP The ZIP Code
LATITUDE Latitude coordinate (decimal degrees)
LONGITUDE Longitude coordinate (decimal degrees)
CITY City name
STATE State abbreviation
COUNTY County name
ZIP_CLASS ZIP Code class

ZIP Code — City/State lookups

The lookups are straightforward database queries using the OleDb* classes.

Distance calculation

To calculate the distance between two points, I used the Haversine Formula, which I found on the Ask Dr. Math web site.

ZIP Codes within a radius of X miles

Most ZIP Codes in the database contain latitude/longitude coordinates. To make the SQL query as simple as possible, I used a square of size 2Rx2R (where R is the radius of the circle) to encompass the search area as shown in the figure below.

This has the unfortunate side effect of searching an area ~22% larger than needed, but these "outliers" are filtered out of the result set on the client side before being returned to the calling application. I could have added a stored procedure to perform the distance calculation, but I didn't want to modify the database in any way. That way, if the author decides to update the data, (hopefully) all the users of this library will have to replace the old database file with the new one.

Now, using this approximation, the SQL query becomes as simple as this:

SELECT * 
FROM ZIP_CODES 
WHERE
    LATITUDE >= <Southern Latitude Line> AND 
    LATITUDE <= <Northern Latitude Line> AND 
    LONGITUDE >= <Western Longitude Line> AND
    LONGITUDE <= <Eastern Longitude Line>

To calculate the Northern/Southern Latitude and Western/Eastern Longitude lines, I again turned to Ask Dr. Math.

Important classes

Class Name Description
ZipCodeUtil The ZipCodeUtil class provides methods to lookup City/State by ZIP Code, or ZIP Code by City/State.
Location A Location represents a City, State, ZIP Code, County, Latitude, Longitude, and ZIP Class. This just so happens to correspond to the columns of the ZIP_CODES table.
LocationInRadius Derives from Location, and adds the DistanceToCenter property.
Distance The Distance class' static GetDistance method takes two Location objects and uses their Latitudes and Longitudes to determine the distance between them.
Radius Provides a static method that takes a Location and a radius (in miles), and returns the LocationInRadiuses that fall within that radius.

Using the code

Using the code is very straightforward.

  1. Download the ZIP Codes database (see link at top of article).
  2. Compile the ZipCodeUtil library in VS.NET.
  3. Add a reference to the new DLL (SagaraSoftware.ZipCodeUtil.dll) to your application.
  4. Add the following appSettings to your application's config file (you'll need to add a config file if one doesn't already exist):
    <add key="ZipCodeProviderType" value="Access" />
    <add key="ZipCodeConnString" value=
    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=
              D:\Example\Path\To\Database\zipbase.mdb" />
  5. Add code to use the ZIP Code Utility library.

Here is the sample code from the example application:

(Note that in order to run the sample application, you'll first need to download the database and modify the config file to point to the database on your hard disk.)

//    Location by ZIP Code.
Location location = ZipCodeUtil.LookupByZipCode ("93275");
if (null != location)
    Console.WriteLine (location.ToString ());

//    Location(s) by City/State.
Location[] locs = ZipCodeUtil.LookupByCityState ("Tulare", "CA");
if (null != locs && locs.Length > 0)
{
    foreach (Location loc in locs)
    {
        Console.WriteLine (loc.ToString ());
    }
}

//    Location by City/State/Zip
location = ZipCodeUtil.LookupByCityStateZip ("Tulare", "CA", "93275");
if (null != location)
    Console.WriteLine (location.ToString ());

//    Distance between two locations.
Location sf = ZipCodeUtil.LookupByZipCode ("94175");
Location la = ZipCodeUtil.LookupByZipCode ("90185");
Double dDistance = sf.DistanceFrom (la);
Console.WriteLine ("{0} is {1} miles from {2}", sf.City, dDistance, la.City);

//    Other Locations within an X-mile radius of a specific location.
locs = sf.LocationsWithinRadius (5.0);
if (null != locs && locs.Length > 0)
{
    foreach (Location loc in locs)
    {
        Console.WriteLine (loc.ToString ());
    }
}

Limitations

This library relies on data from a free database that doesn't look like it has been updated since September 2001. I cannot vouch for the accuracy of this data. If you plan on using this in a production environment, you may want to invest in a commercial ZIP Codes database that is guaranteed by its maker and that is updated regularly.

To do List

  • Pending approval from the creator of the database, provide MS SQL and MySQL versions.

History

  • 2nd Jan 2005 - Version 1.0.0
    • Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jon Sagara
Software Developer (Senior) Sagara Software, Inc.
United States United States
Member
Jon graduated from Cal Poly with a B.S. Computer Engineering. He is currently building a Silverlight-based report scheduling interface for a pharmaceutical reporting company.
 
When he's not fooling around with computers or reading, he's busy spending time with his super wife, Kelly, his two boys, and their rambunctious dog, Homer.
 
Visit my blog
 
---
 
View my old profile pictures

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
AnswerRe: Stuffing data in a DLL?memberJon Sagara15 Feb '06 - 6:51 
I imagine you could. Why would you want to do that, though? You'd effectively be creating your own database system because you'd have to create your own internal file structure and write code to seek and sort the data. That's a lot of work to do, especially when all that functionality is provided by the database itself.
 
Plus, if you ever want to update the data, you'll have to recompile and redistribute the DLL.
 
If your hangup is that the database is an Access database, then you can easily convert it to another format -- say, MySQL or SQL Server. You'll want to get the original database creator's permission before you do that, of course.
 
Jon Sagara
Look at him. He runs like a Welshman. Doesn't he run like a Welshman? Doesn't he? I think he runs like a Welshman.
Sagara.org | Blog | My Articles
AnswerRe: Stuffing data in a DLL?memberSpiff Dog7 Dec '11 - 7:39 
If your concern is the inefficiency of using a database call or if you are using the data a lot, you could consider using the Enterprise Library Caching Block to cache the city/state info. That way you could still have your information in a database AND have the performance of a locally stored dataset.
 
Just be sure to refresh your cache on a semi-regular basis.
-------------------------
Spiffdog Design
It's ok... he doesn't bite...

GeneralMissing dllsmembermtone1 Feb '06 - 11:13 
THis looks great and I really need this. It appears to be missing the AcccesPrvider and IDataProvider files
 
Do you have them?
 
Thanks
GeneralRe: Missing dllsmemberJon Sagara1 Feb '06 - 11:17 
They're in ZipCodeUtil_src.zip[^], under the "DataProvider" directory.
 
Edit: Regarding the binaries, SagaraSoftware.ZipCodeUtil.dll contains both AccessProvider and IDataProvider.
 
Jon Sagara
Look at him. He runs like a Welshman. Doesn't he run like a Welshman? Doesn't he? I think he runs like a Welshman.
Sagara.org | Blog | My Articles
 
-- modified at 17:20 Wednesday 1st February, 2006
GeneralRe: Missing dllsmembermtone1 Feb '06 - 11:26 
Sorry. I unzipped to one location then I copied to another I think I missed the files when copying.
 
Thanks Jon, I appreciate your efforts.

GeneralFiltering &quot;Outliers&quot;membereanderson12 Jan '05 - 10:04 
Trust me. Keep the outlier filter in your client code. I tried doing the necessary trig on MSSQL and it literally took thousands of times longer than we could tolerate.
 
Lesson: MSSQL does not do trig very well.
 
once I moved the filter code to VB.NET it sped up some obscene amount.
 
Then I reworked the algorithm and sped it up again by as little as 5x and as much as 500 times, depending on the set of zips to filter.
 
I don't know how other DB engines handle trig, but don't be surprised if you end up with a dog.
 
In our case we had to filter down thousands of jobs according to their zips, and it had to be a web-usuable response time. eg: less than 5 seconds at worst.
 

 

GeneralI knew I shoulda written!membereanderson12 Jan '05 - 8:46 
I've been using a similar algorithm that I developed a while a go with great success at our job search site, to allow users to search for jobs within a given radius of a chosen zip code.
 
We get our data right from the post office in either Excel or CSV text. I don't remember which (something pretty mundane). We get regular updates and then import it into our MS SQL database.
 
The trick to using this effectively is to filter the qualifying zipcodes quickly, as there are some 35,000+ valid zips in the US at the current time, and trig math is not fast by any account.
 
I probably woulda kissed you had this come out at that time, but alas, I had to go do it myself and learn something. Smile | :)
GeneralRe: I knew I shoulda written!memberDavidCrow12 Jan '05 - 9:55 
eanderson wrote:
...there are some 35,000+ valid zips in the US at the current time...
 
Wow! I would have thought that number to be closer to 42,000, though.
 

"Opinions are neither right nor wrong. I cannot change your opinion. I can, however, change what influences your opinion." - David Crow


GeneralRe: I knew I shoulda written!memberJon Sagara12 Jan '05 - 9:57 
I think it's just under 43,000 now.
 

Jon Sagara
I bent my wookie.

My Articles

GeneralRe: I knew I shoulda written!membereanderson12 Jan '05 - 10:00 
42602 by our database right now. Guess I had some other number stuck in my head.
 
Technically tho, I DID say 35,000+ Cool | :cool: Big Grin | :-D
GeneralRe: I knew I shoulda written!memberethanselzer10 Feb '05 - 14:45 
Hi,
 
Do you have a link to the USPS Zip Code resource you are using? I know there are commercial sources but would rather get (buy) the information from the USPS.
 
Ethan Selzer
GeneralRe: I knew I shoulda written!memberrenzea1 May '06 - 11:59 
I've written something similar. The easiest way I found to filter the zip codes first was to rule out anything that could not possibly be in range. To do this, you can use the following code:
 
double milesPerDegree = 69.172;
double searchDistance = 50.0;
double searchMod = searchDistance / milesPerDegree;
 
double latMax = lat + searchMod;
double latMin = lat - searchMod;
double lonMax = lon + searchMod;
double lonMin = lon - searchMod;
 
SQL = "SELECT * FROM SomeTable WHERE (Latitude BETWEEN @Lat1 AND @Lat2) AND (Longitude BETWEEN @Lon1 AND @Lon2)
 
Simply plug in the values as parameters. That will knock out most zip codes exceeding the specified range, but not all. The rest will have to be kicked out by code.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 2 Jan 2005
Article Copyright 2005 by Jon Sagara
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid