Create a PHP-MySQL zip code radius search

If you’re looking to create a zip code radius search utility for your website, it’s much easier than it seems. I’ve based this tutorial off of http://zips.sourceforge.net/ which is not currently maintained, but has a good majority of US zip codes to test our concept.

Setup

To setup the schema for this tutorial, download the MySQL dump here and import it into your database. Once you import the dump, you will see a table called zip_codes with the following columns:

  • zip_codes.zip – US zip code
  • zip_codes.state – State abbreviation
  • zip_codes.longitute
  • zip_codes.latitude
  • zip_codes.full_state – Full state name

Here is a utility class that I created to do all of the search operations.

<?php
/**
 * Zip Code Utility Class
 */
class ZipCodeUtility
{
	/**
	 * MySQLi Object
	 * @var object
	 */
	private $db;

	/**
	 * Constructor
	 */
	public function __construct ( )
	{
		$this->db = new mysqli( 'host', 'user', 'pass', 'database' );
	}

	/**
	 * Zip Radius Search
	 * @param  string  $zip_code US zip code
	 * @param  integer $radius   Radius in miles
	 * @return array|bool   flat array of zip codes or bool false
	 */
	public function search_radius ( $zip_code, $radius = 20 )
	{
		$zip = $this->get_zipcode( $zip_code );
		if ( $zip ) {
			$zip_codes = $this->get_zipcodes_by_radius( $zip, $radius );
		}
		return $zip_codes ? $zip_codes : false;
	}

	/**
	 * Get Zip Code Object
	 * @param  string $zipcode US zip code
	 * @return object|bool   Zip code object or bool false
	 */
	protected function get_zipcode ( $zipcode )
	{
		$sql = "SELECT * FROM zip_codes WHERE zip = $zipcode LIMIT 1";
		if ( $result = $this->db->query( $sql ) ) {

		    while ( $obj = $result->fetch_object() )
		    {
		    	$result->close();
		        return $obj;
		    }
		    $result->close();
		}
		return false;
	}

	/**
	 * Get Zipcode By Radius
	 * @param  string $zip    US zip code
	 * @param  ing $radius    Radius in miles
	 * @return array         List of nearby zipcodes
	 */
	protected function get_zipcodes_by_radius ( $zip, $radius )
	{
	    $sql = 'SELECT distinct(zip) FROM zip_codes WHERE (3958*3.1415926*sqrt((latitude-'.$zip->latitude.')*(latitude-'.$zip->latitude.') + cos(latitude/57.29578)*cos('.$zip->latitude.'/57.29578)*(longitude-'.$zip->longitude.')*(longitude-'.$zip->longitude.'))/180) <= '.$radius.';';
	    $zip_codes = array();
	    if ( $result = $this->db->query( $sql ) )
	    {
		    while( $row = $result->fetch_object() )
		    {
		        array_push( $zip_codes, $row->zip );
		    }
		    $result->close();
		}
	    return $zip_codes;
	}
}

Usage

I created a simple JSON endpoint where I could pass in a zip code and see if it was in the radius area of an existing service location zip. For instance, if you have a septic pumping service, you can only service homes and business within a twenty mile radius from each location. Here is a sample of how to implement the utility class.

$service_areas = array(
	'32205',
	'64088',
	'32210',
	'10001'
);

# Set endpoint state which can be overriden
$serviceable = false;
$error = false;
$data = array();

# Get zip from query string
$zip = isset( $_GET['zip']) && $_GET['zip'] ? $_GET['zip'] : false;

if( $zip )
{
	# Check format
	if ( preg_match( '/[0-9]{5}/', $zip ) )
	{
		# Create new instance
		$zip_search = new ZipCodeUtility();

		# Nearby zip codes within radius of 25 miles
		$zip_codes = $zip_search->search_radius( $zip, 25 );

		if( $zip_codes )
		{
			# Data for JSON endpoint
			$data['zip_codes'] = $zip_codes;

			# Set false message to be overriden if success found
			$message = "$zip is not serviceable";

			foreach( $zip_codes as $zip_code )
			{
				# Check if one of our service areas are in the returned list
				if( in_array( $zip_code, $zip_codes ) )
				{
					# If so, we don't have to go any further, override state and message
					$serviceable = true;
					$message = "$zip is serviceable";
				}
			}
		}
		else
		{
			$message = "No nearby zip codes for $zip";
		}
	}
	else
	{
		# Formatting error!
		$error = true;
		$message = '`zip` parameter must be a five digit US zip code';
	}
}
else
{
	# Missing parameter error!
	$error = true;
	$message = 'Missing paramater `zip`';
}

# Output JSON
header( 'Content-Type: application/json;charset=utf-8' );
echo json_encode(
	array(
		'serviceable' => $serviceable,
		'message'     => $message,
		'error'       => $error,
		'data'        => $data
	)
);
exit;

To implement on the front-end, simply use jQuery to consume the JSON endpoint.

jQuery.get( 'http://url/to/endpoint/?zip=' + zip_value, function( response )
{
	if( response.serviceable ){}
	else if( response.error ){}
	else{ // not serviceable }
} );

That’s it! You can probably see how easy you it is to implement a nearby search of other locations by querying against the returned list of nearby zip codes. From there, you could return the client a list of location objects. The sky is the limit…well within the US.

If you are putting this in production, I would purchase a recent zip code list from https://www.zip-codes.com/. You may have to change your queries a bit in the utility class to match their schema. Have fun!

Leave a Reply

Your email address will not be published. Required fields are marked *