Eyesis Home
  > Projects > PHP > Zip/Postal Code Lookup Class

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

eyegeodata-1.0.zip 4kb, Released: 02-Nov-08

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($yEyeGeoData::KMS2) . ' kms'// prints "6923.05 kms"

Checking Status Codes

Here is a list of possible status codes. I used HTTP status codes for simplicity.

-200All is good
-400Bad request, not properly formatted zip/postal code
-403Forbidden, exceeded daily lookups
-404Could 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 $queryNULLtrue))
            {

                
$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->resulttrue);
        else
            return 
"I've got nothing to say";

    }
}