How to Code a Simple Zip Code Locator in PHP
This tutorial is now out of date since Yahoo has changed the map API. I'll try to make a new tutorial using the new Yahoo AJAX API soon.
Why pay hundreds of dollars for a commercial zip code locator when you can make your own in a few hours? This post is a start to finish tutorial on how to make a simple zip code locator using PHP, MySQL, a free zip database, and the Yahoo Maps API. Here's a demo if you want to see it first.
Let's get started:
1. Requirements
- You'll need PHP and MySQL running on your webserver
- You need a zip code database. You can find a free one from the 2000 census here, or you can purchase one. Using the free one is good for getting started, but it is quite out of date.
- You'll need a Yahoo Maps API key, which we'll get to later.
2. Get the Zip Database
Download the zip database.
It comes in CSV or SQL format. Download the SQL version (it's under zipcodedb-MySQLdump).
3. Set up database and tables
Make a database, or use an existing one you have. I used phpMyAdmin to make a database called 'Locator'.
You'll need to set up the table for your zipcode now. Since this table is so huge, I had to do this in steps. Create the table first:
CREATE TABLE `zip_codes` (
`zip` varchar(5) NOT NULL default '',
`state` char(2) NOT NULL default '',
`latitude` varchar(10) NOT NULL default '',
`longitude` varchar(10) NOT NULL default '',
`city` varchar(50) default NULL,
`full_state` varchar(50) default NULL,
UNIQUE KEY `zip` (`zip`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Now import the sql downloaded in step 2. This is a giant table, so if you are working on a remote server and using phpMyAdmin or a similar tool, you might have to insert the records in sections (I could do 10,000 at a time).
Now we need something to locate. I created a store locator, and my 'stores' table looks like this:
CREATE TABLE `stores` (
`id` int(11) NOT NULL auto_increment,
`company` varchar(50) NOT NULL default '',
`address1` varchar(50) default NULL,
`address2` varchar(50) default NULL,
`city` varchar(25) default NULL,
`state` char(2) default NULL,
`zip` varchar(5) NOT NULL default '0',
`phone1` varchar(10) default NULL,
`phone2` varchar(10) default NULL,
`email` varchar(50) default NULL,
PRIMARY KEY (`id`),
KEY `zip` (`zip`)
);
Obviously, the important part in your store table is the zip, and you can add any other info you want.
4. Write some code already
First we need a form to collect the zip code:
<p>Enter your 5 digit zip code:</p>
<form name="locate" method="POST"
action="<?=$_SERVER['PHP_SELF']?>">
<input name="zip" maxlength="5" size="6" type="text" />
<input type="submit" value="Find" />
</form>
Below our form we create the code that connects to the database and finds the closest store. This example will show how to return only one store, but that can easily be changed. I'll let the comments speak for themselves (remember to replace the items in double square brackets with your information):
<?php
// see if our zip code has been posted, and if it is a 5 digit #
if (isset($_POST['zip'])) {
// remember to sanitize your inputs
// this removes whitespace around the data and the next line makes sure its a 5 digit number
$findzip = trim($_POST['zip']);
if (preg_match("#^\d{5}$#",$findzip)) {
// if we have the right kind of data, connect to the database
$conn = mysql_connect('[[localhost]]','[[databse-user]]','[[database-password]]');
// select the database with the tables we created in step 3
mysql_select_db('[[database-name]]',$conn);
// create a query that will select the zip code (if we can't find the user entered zip, we return an error)
$query = "SELECT latitude,longitude,state,city FROM zip_codes WHERE zip=".$findzip." LIMIT 1";
$result = mysql_query($query);
if (mysql_num_rows($result) > 0) {
// grab the latitude, longitude,state and city from our result
list($flat,$flon,$fstate,$fcity) = mysql_fetch_row($result);
// now get all stores in the same state, or all stores if no states match
// we want to reduce the amount of work the database has to do so we see if a state matches
$query = "SELECT * FROM stores WHERE state='".$fstate."'";
$result = mysql_query($query);
if (mysql_num_rows($result) < 1) {
$query = "SELECT * FROM stores";
$result = mysql_query($query);
}
// now we process the stores to gather their data,
//this result should not be empty, so I was lazy and didn't write an else case
if (mysql_num_rows($result) > 0) {
while($row = mysql_fetch_assoc($result)) {
// first put all of the data for this store into an array, with the store id as the key
$storeinfo[$row['id']] = $row;
// get the store zip
$dzip = $row['zip'];
// query for the store's latitude and longitude
$query = "SELECT latitude,longitude FROM zip_codes WHERE zip=".$dzip." LIMIT 1";
$result2 = mysql_query($query);
if (mysql_num_rows($result2) > 0) {
list($dlat,$dlon) = mysql_fetch_row($result2);
// now get the distance from the user entered zip
// this function can be found on the same site as the one where you downloaded the zip codes from
$stores[$row['id']] = calcDist($flat,$flon,$dlat,$dlon);
}
}
}
// compare the distances (by sorting them least to greatest) to find the least
asort($stores);
// now we can display closest store
// optionally you could loop over this to list multiple stores
$store = $storeinfo[key($stores)];
// here is the lazy way to print out the data - I leave the pretty printing up to you
print_r($store);
// we'll need this variable for the Yahoo Maps integration
$maploc = $store['address1'] .", ".$store['city']." ".$store['state']." ".$store['zip'];
?>
// Paste Yahoo Maps API code from next section here.
<?php
}
//if can't find zip return error
else {
printError("The zip code you entered was not found in our database. Please try another one.");
}
} else {
printError("The zip code must be a 5 digit number. Please try again.");
}
} else {
//do nothing
}
// functions
function printError($e)
{
echo "<p class='error'>$e";
}
function calcDist($lat_A, $long_A, $lat_B, $long_B)
{
$distance = sin(deg2rad($lat_A))
* sin(deg2rad($lat_B))
+ cos(deg2rad($lat_A))
* cos(deg2rad($lat_B))
* cos(deg2rad($long_A - $long_B));
$distance = (rad2deg(acos($distance))) * 69.09;
return $distance;
}
?>
5. Integrate with Yahoo Maps API
Integrating with Yahoo Maps was a lot easier than I thought it was going to be. It's actually pretty simple.
First you need to get an API Key. Get one here.
Once you get your once you get that you can place this code into your <head>
<script type="text/javascript"
src="http://maps.yahooapis.com/v3.5/fl/javascript/apiloader.js?&appid=PASTE YOUR API KEY HERE">
</script>
<style type="text/css">
#mapContainer{
height: 400px;
width: 400px;
border:1px solid #333;
}
</style>
Then place this javascript in the code where it says to in step 4.
<div id="mapContainer"></div>
<script>
// this is the address we want to map (you can list several and it will mark each one)
var addresses = new Array("<?=$maploc?>");
// Create and display Map object at the address with zoom level 3
// Include your application ID.
var map = new Map("mapContainer", "PASTE YOUR API KEY HERE", "<?=$maploc?>", 3);
map.addEventListener(Map.EVENT_INITIALIZE, onMapInit);
map.addEventListener(Map.EVENT_MARKER_GEOCODE_SUCCESS,onMarkerGeocode);
function onMapInit(eventObj) {
map.addTool( new PanTool(), true );
for(var i=0; i<addresses .length; i++) {
// if you did do multiple addresses, then this would have to change some (because it would be the same store each iteration)
var marker = new CustomPOIMarker('<?php echo str_replace("'","\'",$store['company']); ?>', '', '<?=$store['address1']?><br /><?=$store['city']?> <?=$store['state']?>, <?=$store['zip']?>', '0x000000', '0xFFFFFF');
map.addMarkerByAddress( marker, addresses[i] );
}
map.addWidget( new ZoomBarWidget() );
}
function onMarkerGeocode(eventObj) {
var geocodeResponse = eventObj.response;
}
</script>
That's all there is to it. I hope this has shown you how easy it is to code a simple zip code locator. Below is the code in it's entirety, and you can see an example here.
<html> <head> <script type="text/javascript" src="http://maps.yahooapis.com/v3.5/fl/javascript/apiloader.js?&appid=PASTE YOUR API KEY HERE"> </script> <style type="text/css"> #mapContainer{ height: 400px; width: 400px; border:1px solid #333; } </style> </head> <body> <p>Enter your 5 digit zip code:</p> <form name="locate" method="POST" action="<?=$_SERVER['PHP_SELF']?>"> <input name="zip" maxlength="5" size="6" type="text" /> <input type="submit" value="Find" /> </form> <?php // see if our zip code has been posted, and if it is a 5 digit # if (isset($_POST['zip'])) { // remember to sanitize your inputs // this removes whitespace around the data and the next line makes sure its a 5 digit number $findzip = trim($_POST['zip']); if (preg_match("#^\d{5}$#",$findzip)) { // if we have the right kind of data, connect to the database $conn = mysql_connect('[[localhost]]','[[databse-user]]','[[database-password]]'); // select the database with the tables we created in step 3 mysql_select_db('[[database-name]]',$conn); // create a query that will select the zip code (if we can't find the user entered zip, we return an error) $query = "SELECT latitude,longitude,state,city FROM zip_codes WHERE zip=".$findzip." LIMIT 1"; $result = mysql_query($query); if (mysql_num_rows($result) > 0) { // grab the latitude, longitude,state and city from our result list($flat,$flon,$fstate,$fcity) = mysql_fetch_row($result); // now get all stores in the same state, or all stores if no states match // we want to reduce the amount of work the database has to do so we see if a state matches $query = "SELECT * FROM stores WHERE state='".$fstate."'"; $result = mysql_query($query); if (mysql_num_rows($result) < 1) { $query = "SELECT * FROM stores"; $result = mysql_query($query); } // now we process the stores to gather their data, //this result should not be empty, so I was lazy and didn't write an else case if (mysql_num_rows($result) > 0) { while($row = mysql_fetch_assoc($result)) { // first put all of the data for this store into an array, with the store id as the key $storeinfo[$row['id']] = $row; // get the store zip $dzip = $row['zip']; // query for the store's latitude and longitude $query = "SELECT latitude,longitude FROM zip_codes WHERE zip=".$dzip." LIMIT 1"; $result2 = mysql_query($query); if (mysql_num_rows($result2) > 0) { list($dlat,$dlon) = mysql_fetch_row($result2); // now get the distance from the user entered zip // this function can be found on the same site as the one where you downloaded the zip codes from $stores[$row['id']] = calcDist($flat,$flon,$dlat,$dlon); } } } // compare the distances (by sorting them least to greatest) to find the least asort($stores); // now we can display closest store // optionally you could loop over this to list multiple stores $store = $storeinfo[key($stores)]; // here is the lazy way to print out the data - I leave the pretty printing up to you print_r($store); // we'll need this variable for the Yahoo Maps integration $maploc = $store['address1'] .", ".$store['city']." ".$store['state']." ".$store['zip']; ?> <div id="mapContainer"></div> <script> // this is the address we want to map (you can list several and it will mark each one) var addresses = new Array("<?=$maploc?>"); // Create and display Map object at the address with zoom level 3 // Include your application ID. var map = new Map("mapContainer", "PASTE YOUR API KEY HERE", "<?=$maploc?>", 3); map.addEventListener(Map.EVENT_INITIALIZE, onMapInit); map.addEventListener(Map.EVENT_MARKER_GEOCODE_SUCCESS,onMarkerGeocode); function onMapInit(eventObj) { map.addTool( new PanTool(), true ); for(var i=0; i<addresses .length; i++) { // if you did do multiple addresses, then this would have to change some (because it would be the same store each iteration) var marker = new CustomPOIMarker('<?php echo str_replace("'","\'",$store['company']); ?>', '', '<?=$store['address1']?><br /><?=$store['city']?> <?=$store['state']?>, <?=$store['zip']?>', '0x000000', '0xFFFFFF'); map.addMarkerByAddress( marker, addresses[i] ); } map.addWidget( new ZoomBarWidget() ); } function onMarkerGeocode(eventObj) { var geocodeResponse = eventObj.response; } </script> <?php } //if can't find zip return error else { printError("The zip code you entered was not found in our database. Please try another one."); } } else { printError("The zip code must be a 5 digit number. Please try again."); } } else { //do nothing } // functions function printError($e) { echo "<p class='error'>$e"; } function calcDist($lat_A, $long_A, $lat_B, $long_B) { $distance = sin(deg2rad($lat_A)) * sin(deg2rad($lat_B)) + cos(deg2rad($lat_A)) * cos(deg2rad($lat_B)) * cos(deg2rad($long_A - $long_B)); $distance = (rad2deg(acos($distance))) * 69.09; return $distance; } ?> </body> </html>