Port of Micah Carrick’s PHP Zip Code Range and Distance Calculation for Codeigniter:
application/plugins/zipcode_pi.php
* v1.0.0 [Apr 12, 2005] - Initial Version
*
*******************************************************************************
* DESCRIPTION:
* A PHP Class and MySQL table to find the distance between zip codes and
* find all zip codes within a given mileage or kilometer range.
*
*******************************************************************************
*/
// constants for setting the $units data member
define('_UNIT_MILES', 'm');
define('_UNIT_KILOMETERS', 'k');
// constants for passing $sort to get_zips_in_range()
define('_ZIPS_SORT_BY_DISTANCE_ASC', 1);
define('_ZIPS_SORT_BY_DISTANCE_DESC', 2);
define('_ZIPS_SORT_BY_ZIP_ASC', 3);
define('_ZIPS_SORT_BY_ZIP_DESC', 4);
// constant for miles to kilometers conversion
define('_M2KM_FACTOR', 1.609344);
class Zipcode {
var $last_error = ""; // last error message set by this class
var $last_time = 0; // last function execution time (debug info)
var $units = _UNIT_MILES; // miles or kilometers
var $decimals = 2; // decimal places for returned distance
var $CI;
function Zipcode()
{
$this->CI =& get_instance();
$this->CI->load->model('zipcode_model');
}
function get_distance($zip1, $zip2) {
// returns the distance between to zip codes. If there is an error, the
// function will return false and set the $last_error variable.
$this->chronometer(); // start the clock
if ($zip1 == $zip2) return 0; // same zip code means 0 miles between.
// get details from database about each zip and exit if there is an error
$details1 = $this->get_zip_point($zip1);
$details2 = $this->get_zip_point($zip2);
if ($details1 == false) {
$this->last_error = "No details found for zip code: $zip1";
return false;
}
if ($details2 == false) {
$this->last_error = "No details found for zip code: $zip2";
return false;
}
// calculate the distance between the two points based on the lattitude
// and longitude pulled out of the database.
$miles = $this->calculate_mileage($details1[0], $details2[0], $details1[1], $details2[1]);
$this->last_time = $this->chronometer();
if ($this->units == _UNIT_KILOMETERS) return round($miles * _M2KM_FACTOR, $this->decimals);
else return round($miles, $this->decimals); // must be miles
}
function get_zip_code($city, $state)
{
$rows = $this->CI->zipcode_model->get_zip_code($city, $state);
if(count($rows) == 0)
return FALSE;
return $rows[0]->zip_code;
}
function get_zip_details($zip) {
// This function pulls the details from the database for a
// given zip code.
$row = $this->CI->zipcode_model->get_zip_details($zip);
if(count($row) == 0 )
{
return FALSE;
}
return array("latitude" => $row[0]->latitude,
"longitude" => $row[0]->longitude,
"city" => $row[0]->city,
"county" => $row[0]->county,
"state_prefix" =>$row[0]->state_prefix,
"state_name" =>$row[0]->state_name,
"area_code" =>$row[0]->area_code,
"time_zone" => $row[0]->time_zone);
}
function get_zip_point($zip) {
$row = $this->CI->zipcode_model->get_zip_point($zip);
if(count($row) == 0 )
{
return FALSE;
}
return array($row[0]->lat,$row[0]->lon);
}
function calculate_mileage($lat1, $lat2, $lon1, $lon2) {
// used internally, this function actually performs that calculation to
// determine the mileage between 2 points defined by lattitude and
// longitude coordinates. This calculation is based on the code found
// at http://www.cryptnet.net/fsp/zipdy/
// Convert lattitude/longitude (degrees) to radians for calculations
$lat1 = deg2rad($lat1);
$lon1 = deg2rad($lon1);
$lat2 = deg2rad($lat2);
$lon2 = deg2rad($lon2);
// Find the deltas
$delta_lat = $lat2 - $lat1;
$delta_lon = $lon2 - $lon1;
// Find the Great Circle distance
$temp = pow(sin($delta_lat/2.0),2) + cos($lat1) * cos($lat2) * pow(sin($delta_lon/2.0),2);
$distance = 3956 * 2 * atan2(sqrt($temp),sqrt(1-$temp));
return $distance;
}
function get_city_states($zips)
{
$rows = $this->CI->zipcode_model->get_city_states($zips);
if(count($rows) == 0)
return FALSE;
$city_states = array();
foreach($rows as $row)
{
$city_states["city"] = $row->city;
$city_states["state_prefix"] = $row->state_prefix;
}
return $city_states;
}
function get_zips_in_range($zip, $range, $sort=1, $include_base) {
// returns an array of the zip codes within $range of $zip. Returns
// an array with keys as zip codes and values as the distance from
// the zipcode defined in $zip.
$this->chronometer(); // start the clock
$details = $this->get_zip_point($zip); // base zip details
if ($details == false) return false;
// This portion of the routine calculates the minimum and maximum lat and
// long within a given range. This portion of the code was written
// by Jeff Bearer (http://www.jeffbearer.com). This significanly decreases
// the time it takes to execute a query. My demo took 3.2 seconds in
// v1.0.0 and now executes in 0.4 seconds! Greate job Jeff!
// Find Max - Min Lat / Long for Radius and zero point and query
// only zips in that range.
$lat_range = $range/69.172;
$lon_range = abs($range/(cos($details[0]) * 69.172));
$min_lat = number_format($details[0] - $lat_range, "4", ".", "");
$max_lat = number_format($details[0] + $lat_range, "4", ".", "");
$min_lon = number_format($details[1] - $lon_range, "4", ".", "");
$max_lon = number_format($details[1] + $lon_range, "4", ".", "");
$rows = $this->CI->zipcode_model->get_zips_in_range($zip,$min_lat, $max_lat,$min_lon,$max_lon,$include_base);
if(count($rows) == 0 )
{
return FALSE;
}
$return = array(); // declared here for scope
foreach($rows as $row)
{
// loop through all 40 some thousand zip codes and determine whether
// or not it's within the specified range.
// calculate_mileage($lat1, $lat2, $lon1, $lon2)
// $dist = $this->calculate_mileage($details[0],$row[1],$details[1],$row[2]);
$dist = $this->calculate_mileage($details[0], $row->lat, $details[1], $row->lon);
if ($this->units == _UNIT_KILOMETERS) $dist = $dist * _M2KM_FACTOR;
if ($dist <= $range) {
$return[] = array(
"city" => $row->city,
"state" => $row->state_prefix,
"zip_code" => $row->zip_code,
"distance" => round($dist, $this->decimals));
}
}
// sort array
switch($sort)
{
case _ZIPS_SORT_BY_DISTANCE_ASC:
usort($return, array($this, "distance_sort_asc"));
break;
case _ZIPS_SORT_BY_DISTANCE_DESC:
usort($return, array($this, "distance_sort_desc"));
break;
case _ZIPS_SORT_BY_ZIP_ASC:
usort($return, array($this, "zip_code_sort_asc"));
break;
case _ZIPS_SORT_BY_ZIP_DESC:
usort($return, array($this, "zip_code_sort_desc"));
break;
}
$this->last_time = $this->chronometer();
if (empty($return)) return false;
return $return;
}
function chronometer()
{
// chronometer function taken from the php manual. This is used primarily
// for debugging and anlyzing the functions while developing this class.
$now = microtime(TRUE); // float, in _seconds_
$now = $now + time();
$malt = 1;
$round = 7;
if ($this->last_time > 0) {
/* Stop the chronometer : return the amount of time since it was started,
in ms with a precision of 3 decimal places, and reset the start time.
We could factor the multiplication by 1000 (which converts seconds
into milliseconds) to save memory, but considering that floats can
reach e+308 but only carry 14 decimals, this is certainly more precise */
$retElapsed = round($now * $malt - $this->last_time * $malt, $round);
$this->last_time = $now;
return $retElapsed;
} else {
// Start the chronometer : save the starting time
$this->last_time = $now;
return 0;
}
}
function zip_code_sort_asc($a, $b)
{
return $this->sortAscend($a, $b, "zip_code");
}
function zip_code_sort_desc($a, $b)
{
return $this->sortDescend($a, $b, "zip_code");
}
function distance_sort_asc($a, $b)
{
return $this->sortAscend($a, $b, "distance");
}
function distance_sort_desc($a, $b)
{
return $this->sortDescend($a, $b, "distance");
}
function sortAscend($a, $b, $key)
{
if ($a[$key] == $b[$key]) {
return 0;
}
return ($a[$key] < $b[$key] ) ? -1 : 1;
}
function sortDescend($a, $b, $key)
{
if ($a[$key] == $b[$key]) {
return 0;
}
return ($a[$key] < $b[$key] ) ? 1 : -1;
}
}
?>
And the model
application/model/zipcode_model.php
db->query($select.$from.$where.$order);
return $query->result();
}
function get_zip_details($zip)
{
$sql = "SELECT lat AS latitude, lon AS longitude, city, county, state_prefix,
state_name, area_code, time_zone
FROM zip_code
WHERE zip_code='$zip'";
$query = $this->db->query($sql);
return $query->result();
}
function get_zip_point($zip)
{
$sql = "SELECT lat, lon from zip_code WHERE zip_code='$zip'";
$query = $this->db->query($sql);
return $query->result();
}
function get_city_states($zip_codes)
{
$sql = "select distinct city, state_prefix from zip_code where zip_code in (". implode(", ", $zip_codes).")";
$query = $this->db->query($sql);
return $query->result();
}
function get_zips_in_range($zip,$min_lat, $max_lat,$min_lon,$max_lon, $include_base)
{
$sql = "SELECT * FROM zip_code ";
if (!$include_base) $sql .= "WHERE zip_code <> '$zip' AND ";
else $sql .= "WHERE ";
$sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon'";
$query = $this->db->query($sql);
return $query->result();
}
function get_zip_code($city, $state)
{
$sql = "select zip_code from zip_code where city = '".($city)."' and state_prefix='".$state."' limit 1";
$query = $this->db->query($sql);
return $query->result();
}
}
?>
Related Blogs
- Related Blogs on codeigniter
- CodeIgniter – Open source PHP web application framework | GuiDesigner
- Frameworks we like: 4. Codeigniter, and Why? | Macronimous Web Blog