Finding good examples for Calculating Distance in Miles from Latitude and Longitude Using Mysql and PHP wasn’t as easy as I thought it would be. I thought I would compile the working quires and the functions that I wrote/found.
I found a very good paper on the topic at Scribd. I used one the the examples from this paper.
Here are a couple of PHP functions that I thought would be helpful. Just a heads up, you will need and XML parser for the first function listed here(I have posted the one I used for this function below).
getGeoCords
/** * getGeoCords() * uses geonames.org to get lat and long of postal codes * @param string $zip * @param string $country | use 2 letter , US * @author Jesse Baird <jebaird@gmail.com> * @return array('lat','long') */ function getGeoCords($zip='',$country=''){ if($zip!=''&&$country!=''){ $url = 'http://ws.geonames.org/findNearbyPostalCodes?postalcode='.$zip.'&country='.$country.'&maxRows=1&style=short'; $myFile = new XMLParser($url); //print_r($myFile); $xmlRoot = $myFile->data[0]; // GEO Element if (sizeof($xmlRoot) > 0 ) { $geoLocation = $xmlRoot['child']; if (sizeof($geoLocation) > 0) { $geoLocationProperties = $geoLocation[0]['child']; $long = $geoLocationProperties[4]['content']; $lat = $geoLocationProperties[3]['content']; return array('lat'=>$lat,'long'=>$long); } }else return false; }else return false; }
useage
$cords=getGeoCords($zip,'US');
XMLParser Class
xml_url = $xml_url; // print($xml_url." "); $this->xml = xml_parser_create(); xml_set_object($this->xml, $this); xml_set_element_handler($this->xml, 'startHandler', 'endHandler'); xml_set_character_data_handler($this->xml, 'dataHandler'); $this->parse($xml_url); } function parse($xml_url) { //$bad_chr = array("x00" => "chr(0)", "x01" => "chr(1)", "x02" => "chr(2)", "x03" => "chr(3)", "x04" => "chr(4)", "x05" => "chr(5)", "x06" => "chr(6)", "x07" => "chr(7)", "x08" => "chr(8)", "x09" => "chr(9)", "x0a" => "chr(10)", "x0b" => "chr(11)", "x0c" => "chr(12)", "x0d" => "chr(13)", "x0e" => "chr(14)", "x0f" => "chr(15)", "x10" => "chr(16)", "x11" => "chr(17)", "x12" => "chr(18)", "x13" => "chr(19)", "x14" => "chr(20)", "x15" => "chr(21)", "x16" => "chr(22)", "x17" => "chr(23)", "x18" => "chr(24)", "x19" => "chr(25)", "x1a" => "chr(26)", "x1b" => "chr(27)", "x1c" => "chr(28)", "x1d" => "chr(29)", "x1e" => "chr(30)", "x1f" => "chr(31)"); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $xml_url); curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); //$store = curl_exec ($ch); $data = curl_exec ($ch); // print_r(sizeof($data)." "); curl_close ($ch); $parse = xml_parse($this->xml, $data, sizeof($data)); //$parse = xml_parse($xml_parser, strtr($data, $bad_chr),sizeof($data)); if (!$parse) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->xml)), xml_get_current_line_number($this->xml))); xml_parser_free($this->xml ); } return true; } function startHandler($parser, $name, $attributes) { $data['name'] = $name; if ($attributes) { $data['attributes'] = $attributes; } $this->data[] = $data; } function dataHandler($parser, $data) { if ($data = trim($data)) { $index = count($this->data) - 1; // begin multi-line bug fix (use the .= operator) $this->data[$index]['content'] .= $data; // end multi-line bug fix } } function endHandler($parser, $name) { if (count($this->data) > 1) { $data = array_pop($this->data); $index = count($this->data) - 1; $this->data[$index]['child'][] = $data; } } } ?>
getDistanceSQL
I wrote the php for this function to make life easy on me. It is very flexible and allows you to use it on any table.
/** * getDistanceSQL() * * @param mixed $lat * @param mixed $long * @param string $sql_lat_col | place_lat -- alows you to use it on any table * @param string $sql_long_col place_long * @return */ function getDistanceSQL($lat,$long,$sql_lat_col="",$sql_long_col=""){ /* SELECT *, 3956 * 2 * ASIN(SQRT( POWER(SIN((41.648207 - abs(tournament_lat)) * pi()/180 / 2), 2) + COS(41.648207 * pi()/180 ) * COS(abs(tournament_lat) * pi()/180) * POWER(SIN((-91.541579-tournament_long) * pi()/180 / 2), 2) )) as distance From tournament */ return ', 3956 * 2 * ASIN(SQRT( POWER(SIN(('.$lat.' - abs('.$sql_lat_col.')) * pi()/180 / 2), 2) + COS('.$lat.' * pi()/180 ) * COS(abs('.$sql_lat_col.') * pi()/180) * POWER(SIN(('.$long.'-'.$sql_long_col.') * pi()/180 / 2), 2) )) as distance'; }
useage
$sql="SELECT *"; //check to see if user is using this option if($cords['lat']!='') $sql.=getDistanceSQL($cords['lat'],$cords['long'],'tournament_lat','tournament_long'); $sql.=" FROM tournament ".$whereClause; //having clause if(is_numeric($r['miles'])){ if($r['miles']>199) //using the HAVING because WHERE you will end up with an error $sql.='HAVING distance < '.$r['miles']; else $sql.='HAVING distance > '.$r['miles']; } $sql.= " ORDER BY tournament_date ASC"; //you end up with something like this SELECT *, 3956 * 2 * ASIN(SQRT( POWER(SIN((42.524071 - abs(tournament_lat)) * pi()/180 / 2), 2) + COS(42.524071 * pi()/180 ) * COS(abs(tournament_lat) * pi()/180) * POWER(SIN((-92.449725-tournament_long) * pi()/180 / 2), 2) )) as distance FROM tournament WHERE tournament_date between '1199167200' AND '1228024800' AND 1 HAVING distance > 25 ORDER BY tournament_date ASC