Codeigniter Zipcode/Distance Calculator

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

Ridiculously low rent/phishing scam on Craigslist

This is a brilliant scam.

Post on apartments for rent sign with dirt cheap rent, then reply back with somewhat true-story sounding email (of course, the landlord is out of the country)… then ask the potential renter to email/fax back their personal info.

Example:

May the Peace of the Lord be Upon you and,Thanks for your interest in our  my property and sorry for the late
response. Our  names are Mr and mrs Peter and my wife is  Mary
the owner of the house.

bdrm.,\\ full bath home features granite counter tops,living roomand dining room or den, tiled floors and all appliances.  The kitchenopens to a private backyard.. The home is on a no-thru street creating
minimal traffic. State of the art recreation center My wife and family have been relocated to France,Europe while i am here in Lagos West  Africa for a missionary work with human right awareness in conjuction with UN(www.humanrightstour.org).We will not be coming  back
until five yrs from now. Before we depart from Home, Iwanted to sell the house before My wife advised that we should rent itout than to sell it. On getting here, I discovered that my missionary work will last for 5 years so I decided to look for a responsible and caring person to maintain and take proper care of my House as I do.we search for a God fearing and reputable agent who can take good care of
the house, but we couldn’t get anyone then.

We left with the key, codes and papers. I’m willing to rent out the house to a tenant who is able to come up with 1months and security deposit rent as a deposit and I will like him/her to assure me that the house will be properly managed. Concerning the lease, duration is minimum of 6months or as the tenant pleases. I’m giving out the property with such a reasonable amount of $1000 a month and $500 for deposit,utilities included because I am  only paying God back for his kindness and mercy over me and my family with what I have, that`s why I asked for such a reasonable amount. It`s also a pride for me to work for him on a missionary work. Also I need someone/a family that will take good care of the house as their
own property. Firstly, you can  go take a look at the Neighborhood and exterior part of the house because you will not be able to go inside to see the interior hence I have all the keys and security code here
with me.

Let me also tell you a little about my self,I own a three restaurant in New York I have so many cars I leave fine in my whole life. I don’t really believe in God I smoke and I Drunk in my entire life,suddenly I got married to my Wife we married for 4years without Children we are hoping that one day we are going to have a children after 5years we  moved closer  to God and I started praying after 6month God said yes to our prayer,
we had a baby boy after 1year I discovered that the boy can’t work nor stand by  himself we spent so many money we do everything but nothing as been done.
when the boy clock 5plus,I was advised by my pastor that I should fast for 3days and God is going to say yes to our prayer and I do as he said and after seven days of the fasting the boy was illed.Since then I decided to move closer to him I own three house in the State.And I want you to believe in him I want to tell you that he is existing you are Blessed.

If you can assure me that you can take proper care of the property and also you can afford 1months rent and deposit to move in,  below is the rent application form which I will like you to fill and Send back to me. If you can do all this for me, then I will be willing to rent
my house to you.
Looking forward to hear from you. Your full information will be used
to process all documents that will be coming together with the keys
leading to the house and this will come to you through FedEx courier
service. Thank you and remain Blessed.
You can reach me on +2348085-137-047 or dial 011-2348085-137-047
So kindly confirm your interest  by filling  out the  rental
application form below.
RENTAL APPLICATION FORM
TENANT’S PERSONAL INFORMATION FIRST NAME: _________________MIDDLE NAME : __________________LAST NAME: __________________
PROFESSION: ________________PHONE (____) __________ (WORK)
PHONE (____) __________ (HOME) MARITAL STATUS: _________KIDS _____ (YES/NO), HOW MANY ________
PRESENT ADDRESS: _________________________CITY: _______________STATE: ______________ZIPCODE: ____________
HOW LONG? ___________IF RENTINGWHY ARE YOU LEAVING? ______________________________
______________________________CURRENT RENT: _______
PREVIOUS ADDRESS: ___________________________CITY: ________________STATE: _______________
ZIPCODE: _____________HOW LONG? ____________ IF RENTING,APARTMENT..
WHY DID YOU LEAVE? ______________________________
_________________________________________PREVIOUS RENT: _______IF THIS APARTMENT IS BEING GIVEN TO YOU,HOW LONG DO YOU INTEND STAYING? ____________
WHEN DO YOU INTEND MOVING IN? ______________PERSONAL REFERENCES (LIST 3 PERSONS, OTHER THAN YOUR RELATIVES, THAT WE CAN CONTACT TO
VERIFY YOUR CHARACTER.)FIRST PERSONFIRST NAME: _______________MIDDLE NAME: ______________
LAST NAME: ________________ RELATIONSHIP______________PHONE (___) ________________SECOND PERSON
FIRST NAME: _______________MIDDLE NAME: ______________LAST NAME: ________________
RELATIONSHIP______________PHONE (___) ________________THIRD PERSON FIRST NAME: _______________
MIDDLE NAME: ______________LAST NAME: ________________RELATIONSHIP______________
PHONE (___)________________
EMERGENCY(IN AN EMERGENCY, LIST 2 PERSONS THAT CAN BE CONTACTED STARTING WITHNEAREST RELATIVES)FIRST PERSON
FIRST NAME: _______________MIDDLE NAME: ______________
LAST NAME: ________________RELATIONSHIP______________PHONE (___) ________________SECOND PERSON
FIRST NAME: _______________MIDDLE NAME: ______________LAST NAME: ________________
RELATIONSHIP______________PHONE (___) ________________*PETS* IF YOU HAVE A PET,
NAME OF PET: ______________KIND OF PETS: _____________WEIGHT: ___________________
HABITS DO YOU SMOKE? ______________DO YOU DRINK? ______________DO YOU WORK LATE NIGHT? ____

God Bless You