|
|
![]() |
|
The first step is to obtain a US zip code database that includes latitude and longitude data. As a high-school geography refresher, latitude is the amount north or south of the equator, while longitude is the amount east or west of Greenwich, England. Many vendors offer this data for varying prices; the data used in this example came from TPS Products and Services, Inc of Newcastle, CA www.zipcodedatabases.com. TPS offers the full US zip code database with a wealth of data (including population, elevation and demographic data) for $150 a year, including 12 monthly updates. Getting the updates is important because the USPS is adding and reassigning zip codes. Each vendor supplies the data in a slightly different format. The example presented here uses TPS's comma-seperated format, which looks like: "D","00501","V13916","U","HOLTSVILLE","","P","Y","V13916","HOLTSVILLE",
Note that because all US zip codes are east of Greenwich, TPS has cheated a bit and dropped the minus sign that would normally precede the longitude. We will correct this during importation. We will assume that either we or an administrator has already set up a database in MySQL for us, and we're ready to start populating it. We begin by creating a table for the zip code data. % mysql -u us7716b -p db7716b Type 'help' for help. mysql> create table zipcodes ( mysql> quit This table is fairly straightforward; the only interesting point is that we are going to enforce uniqueness on the zip code field. Now we're ready to import our data. (Note: for presentation here on the HTML page, there is one artificial line break in this code. The line that begins \" is a continuation of the line above.) parse-zipcodes.pl #!/usr/local/bin/perl Let's take a walk through this bad boy. It begins by defining loading the perl DBI database interface and making a connection to the database. Next, it clears the whole zip code table out so we can start fresh. After reading a line from the standard input, it strips off any pesky ^M characters that might have been introduced in file transfer, and then feeds it to the monster regexp that's the heart of the program. In summary, it skips the first field, reads the zipcode, skips 7 fields, reads the city, skips 4 fields, reads the state, skips 2 fields, and reads the latitude and longitude. Because some zip codes have multiple entries, we skip if this zip code is the same as the last (remembering that the database will only allow one entry per zip code.) Assuming the data was read correctly (and there are some entries that are not complete and won't, such as APO and FPO zip codes,) the program next checks for apostrophes in the city name and doubles them so SQL won't choke on them. The formulas that are used to compute distances use radians instead of degrees, so the program makes the conversion before writing to the database, as well as clearing up the sign issue with the longitude. Once run, you'll find yourself with a database full of zipcodes. mysql> select count(*) from zipcodes; Now you're ready to import your dealer data. mysql> create table dealers ( You should note that because we are placing an explicit foreign key reference between the dealer_zipcode field and the zipcode field in zipcodes, you will not be able to input a dealer unless the zip code for the dealer is legitimate. You will also need to delete the contents of the dealers table before you will be allowed to delete from zipcodes. Let's populate the table with a few dealers for example purposes. mysql> insert into dealers (dealer_name, dealer_zipcode, dealer_phone) mysql> insert into dealers (dealer_name, dealer_zipcode, dealer_phone) mysql> insert into dealers (dealer_name, dealer_zipcode, dealer_phone) mysql> With all the data in place, all that's left is to write the actual CGI program to look things up. #!/usr/local/bin/perl use DBI; require './cgi-lib.pl'; print "Content-type: text/plain\n\n"; $zipcode = $in{"zipcode"}; $dbh = DBI->connect("DBI:mysql:db7716b", "us7716b", "demo") || $PI = 3.1415926; $sth = $dbh->prepare("select city, state, latitude, longitude " . $rv = $sth->execute ($city, $state, $latitude, $longitude) = $sth->fetchrow_array; print "Looking for close matches to $city, $state\n"; $sth = $dbh->prepare("select dealer_name, dealer_phone, city, state, " . $rv = $sth->execute undef %dealer_distances; while (($name, $d_phone, $d_city, $d_state, $d_lat, $d_long) = $prodsin=sin($latitude)*sin($d_lat); $dist=$dist*(180.0/$PI)*69.0; $dealer_distances{"$name ($d_city, $d_state) [$d_phone]"} = $dist; } @ordered = sort {$dealer_distances{$b} > foreach $item (@ordered) { sub acos { return(atan2(sqrt(1-($val * $val)),$val)); sub asin { return(atan2($val, sqrt(1-($val * $val)))); Once again, we begin by loading the DBI package. We also load the standard cgi-lib package to parse the arguments handed to us from the user. Once we have a zip code to check, we look it up to find latitude and longitude (and the name of the place as well, while we're at it.) Next we get a list of all the dealers and their position by joining the dealers and zipcodes tables. Looping through the dealers, we apply the magic equation:
prodsin = sin lat1 x sin lat2 Which (given latitudes and longitudes in radians) gives us distance in miles. The important thing to remember is that you don't need to understand the math to use the formula. Because perl (or at least the version that the example was run using) doesn't have acos, it is defined later in the file. After determining all the distances and storing them in a hash, the program sorts the hash keys by distance and then displays the dealers in nearest to farthest order. http://www.yourhost.com/cgi-bin/find-closest-dealer.pl?zipcode=23432 Looking for close matches to SUFFOLK, VA A few final notes. Similar postal code databases are available for other countries (notably the UK and Canada,) so it is possible to extend the functionality of the locator to handle multiple geographic areas. You should also remember to update your zip code database regularly to avoid leaving customers with new codes out in the cold. James Turner is currently employed as a Senior Software Engineer at Viridien Technologies in Boxborough, MA, working on e-Commerce projects. He is also an adjunct professor of Internet Studies at the Massachusetts Communications College and a regular columnist on the Internet for the Christian Science Monitor. |
| Suits | Ponytails | Propheads | Contact WDJ | Discuss | Web Audio | Search |