PHP > Zip/Postal Code Lookup Class
If you've ever needed the ability to lookup geographical information for a particular zip or postal code in PHP, look no further! This is your all-in-one geo data lookup class. This class will query a database and get the city, province (or state), country, area code, time zone, and coordinates. It can also calculate distances between two different zip/postal codes.
What sets this class apart from others is its simplicity. At only 172 lines, most of the processing is done on the server end. This class can also learn! If you query for a postal/zip code that isn't found in the database the server will go out and fetch information from other sources, parse it, save it, and return it to you. If all those sources fail it will try to get you details of the next closest zip/postal code and when that fails you'll receive a 404 status error.
Details of zip/postal codes are stored on my server and is available to anyone at anytime. The data has been gathered from sources such as Canada Post and the USPS. I currently have a database of 900,000 codes and growing daily the more it's used.
All coordinates are pulled from Google's MAP API because of the accuracy provided by Google (the coordinates provided by Canada Post and the USPS are of by about 1 or 2 kms in rural areas)
Enjoy and don't forget to let me know what you think!
Demo
Try it out here: http://www.eyesis.ca/demos/eyegeodata/
Limits
I've applied a lookup limit of 1000 queries a day. I think this is more than reasonable for the average person. If you require more just drop me a line and we'll work something out.
By default, SimpleXML is used to fetch data from the feed. Alternatively, you can configure this class to use PHP's serialize function to retrieve data. PHP 5 only.
Download
Looking Up Information
To fetch information on a particular zip/postal code, use the "query" function. Results are returned in an array. Shown below is a Postal code in Toronto and a Zip code for Beverly Hills.
<?php
include 'class.geodata.inc.php';
$lookup = new EyeGeoData();
// Lookup postal code M5X 1J2
print_r($lookup->query('M5X 1J2'));
/*Array
(
[PostalCode] => M5X1J2
[City] => TORONTO
[Province] => ON
[Country] => CA
[AreaCode] => 416
[TimeZone] => 5
[Coordinates] => Array
(
[Latitude] => 43.648306
[Longitude] => -79.38224
)
)*/
// Lookup zip code 90210
print_r($lookup->query('90210'));
/*Array
(
[PostalCode] => 90210
[City] => BEVERLY HILLS
[Province] => CA
[Country] => US
[AreaCode] => 310
[TimeZone] => 8
[Coordinates] => Array
(
[Latitude] => 34.103131
[Longitude] => -118.416253
)
)*/
Calculating Distance Between Zip/Postal Codes
Need to calculate the distance between two different postal or zip codes? Easy! Check it out. The results can be returned in miles or kms and rounded to any number of decimal places.
<?php
include 'class.geodata.inc.php';
// Compare the distance between two different zip/postal codes
$x = new EyeGeoData();
$x->query('M5X 1J2');
$y = new EyeGeoData();
$y->query('90210');
echo $x->calcDistance($y, EyeGeoData::KMS, 2) . ' kms'; // prints "6923.05 kms"
Checking Status Codes
Here is a list of possible status codes. I used HTTP status codes for simplicity.
| - | 200 | All is good |
| - | 400 | Bad request, not properly formatted zip/postal code |
| - | 403 | Forbidden, exceeded daily lookups |
| - | 404 | Could not find zip/postal code |
Check the status code with something like this:
<?php
require 'class.geodata.inc.php';
$geo = new EyeGeoData();
$result = $geo->query($postal);
$status = $geo->getStatusCode();
// Status code of 200 means success
if ($status == 200)
print_r($result);
else
echo 'Error: ' . $status;
The EyeGeoData Class
Here is the EyeGeoData class, you can download it below:
<?php
/**
* EyeGeoData
* Retrieve geographical information of a postal/zip/post code
*
* LICENSE: This source file is subject to the BSD license
* that is available through the world-wide-web at the following URI:
* http://www.eyesis.ca/license.txt. If you did not receive a copy of
* the BSD License and are unable to obtain it through the web, please
* send a note to mike@eyesis.ca so I can send you a copy immediately.
*
* @author Micheal Frank <mike@eyesis.ca>
* @copyright 2008 Eyesis
* @license http://www.eyesis.ca/license.txt BSD License
* @version v1.0.0 6/18/2008 3:13:34 PM
* @link http://www.eyesis.ca/projects/geodata.html
*/
class EyeGeoData
{
const FEED = 'http://api.eyesis.ca/geo.xml';
const KMS = 1;
const MIS = 2;
private $key = false;
private $status_code, $lookups_left;
public $result = false;
public $out = 'xml';
/**
* Set a key for premium accounts
*
* @param string $key Key provided by Eyesis for premium accounts
*/
public function setKey($key)
{
$this->key = $key;
}
/**
* Get postal/zip code information
*
* @param string $code The postal/zip code you want to lookup
* @return mixed The result of the query, false on error
*/
public function query($code)
{
// Build the query
$query = '?code=' . urlencode($code);
if ($this->key)
$query .= '&key=' . urlencode($this->key);
$info = false;
if ($this->out == 'xml')
{
// Use XML feed and SimpleXML
if ($feed = new SimpleXMLElement(self::FEED . $query, NULL, true))
{
$this->status_code = (integer) $feed->Request->StatusCode;
$this->lookups_left = (integer) $feed->Client->LookupsLeft;
if ($this->status_code == 200)
{
$info = array (
'PostalCode' => (string) $feed->Details->PostalCode,
'City' => (string) $feed->Details->City,
'Province' => (string) $feed->Details->Province,
'Country' => (string) $feed->Details->Country,
'AreaCode' => (integer) $feed->Details->AreaCode,
'TimeZone' => (integer) $feed->Details->TimeZone,
'Coordinates' => array (
'Latitude' => (float) $feed->Details->Coordinates->Latitude,
'Longitude' => (float) $feed->Details->Coordinates->Longitude
)
);
}
}
} else {
// Use Serialized feed
if ($feed = file_get_contents(self::FEED . $query . '&out=php'))
{
$feed = unserialize($feed);
$this->status_code = (integer) $feed['PostalCode']['Request']['StatusCode'];
$this->lookups_left = (integer) $feed['PostalCode']['Client']['LookupsLeft'];
if ($this->status_code == 200)
$info = $feed['PostalCode']['Details'];
}
}
$this->result = $info;
return $this->result;
}
/**
* Get the status code of the previous result
*
* @return mixed
*/
public function getStatusCode()
{
return ($this->status_code) ? $this->status_code : false;
}
/**
* Get the amount of lookups left
*
* @return mixed
*/
public function getLookupsLeft()
{
return ($this->lookups_left) ? $this->lookups_left : false;
}
/**
* Calculates the distance between this result and another GeoData object's result
*
* @param GeoData $dest The object to compare to
* @param integer $units The distance units
* @return float
*/
public function calcDistance(EyeGeoData $dest, $units = self::KMS, $round = 3)
{
if (!$dest->result or !$this->result)
trigger_error('Result empty; query either failed or has not been run', E_USER_ERROR);
$dist = rad2deg(acos(sin(deg2rad($this->result['Latitude'])) * sin(deg2rad($dest->result['Coordinates']['Latitude'])) +
cos(deg2rad($this->result['Coordinates']['Latitude'])) * cos(deg2rad($dest->result['Coordinates']['Latitude'])) *
cos(deg2rad($this->result['Coordinates']['Longitude'] - $dest->result['Coordinates']['Longitude'])))) * 60 * 1.1515;
// Convert to kms
if ($units == self::KMS)
$dist *= 1.609344;
$dist = round($dist, $round); // Round off
return (float) $dist;
}
/**
* Convert this object to a string
*
* @return string
*/
public function __toString()
{
if ($this->result)
return print_r($this->result, true);
else
return "I've got nothing to say";
}
}