ø Don’t waste your time reading this blog ø

Codeigniter Zipcode/Distance Calculator

Filed under: random, web 2.0 — Tags: , — taewoo @ 3:27 pm April 8, 2010

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

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Spam protection by WP Captcha-Free