Created
August 20, 2014 08:32
-
-
Save Gerifield/2d35c911dced6b893ed5 to your computer and use it in GitHub Desktop.
Haversine distance formula calculator for PHP, with some little hacks and a lot of parameters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Haversine distance formula. | |
* | |
* This calculates the distance between geo coordinates in km<br> | |
* Algorithm: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL<br> | |
* Full calculation with Haversine formula is slow, so limit the whole distance to a rectangle, | |
* then calculate the distance. | |
* <br> | |
* 1° of lat -> 111km (69 miles)<br> | |
* 1° of lon -> cos(lat)*111<br> | |
* <br> | |
* The "countit" version is a dirty hack, but it works....<br><br> | |
* <br> | |
* IMPORTANT: Be very careful with the parameters and NEVER use unfiltered user input! | |
* <br><br> | |
* @param exp Expression, what you want to give back (SELECT <exp>, distance FROM...) | |
* @param table Database for the calculation (user, question, etc..) | |
* @param lat Latitude | |
* @param lon Longtitude | |
* @param radius Radius in meter | |
* @param limit SQL limit (DEFAULT: 1000) | |
* @param limitMax SQL limit maximum | |
* @param wheres Possible more WHERE conditions | |
* @param latname Name of the lat fieald | |
* @param lonname Name of the lon fieald | |
* @param areaname Name of the area fieald | |
* @param countit Only count the result or not | |
* @param countit_distinct Limitations for the count | |
* @param groupby Group by param | |
* | |
* @return An array with the 'exp' or all the values and a 'distance' key value | |
* <br> | |
* <br> | |
* Example: haversineCalculator(array('table' => 'user', 'lat' => 19.01213, 'lon' => 21.00232, 'radius' => 400));<br> | |
* This'll give back the results from the 'user' table (where you need to have a lat, lon and area column). | |
*/ | |
function haversineCalculator($params = array()){ | |
$defaults = array( | |
'exp' => '*', | |
'table' => '', | |
'lat' => 0, | |
'lon' => 0, | |
'radius' => 5000, //you should put this in a config file for example | |
'limit' => 1000, | |
'limitMax' => '', | |
'order' => 'distance', | |
'wheres' => '', | |
'latname' => 'lat', | |
'lonname' => 'lon', | |
'areaname' => 'area', | |
'countit' => false, | |
'countit_distinct' => '*', | |
'groupby' => '' | |
); | |
$params = array_merge($defaults, $params); | |
//Not too beautiful, but I don't want to touch the code below | |
$exp = $params['exp']; | |
$db = $params['table']; | |
$lat = $params['lat']; | |
$lon = $params['lon']; | |
$dist = ($params['radius'] + 5000) / 1000; //km -> m conversion, the database calculates with km + the '5000' value should come from the max distance config file or somewhere | |
//get a bigger area for circle engraving | |
$latname = $params['latname']; | |
$lonname = $params['lonname']; | |
$radiusname = $params['areaname']; | |
$wheres = $params['wheres']; | |
//calcluate a rectangular area first | |
$lon1 = $lon - ($dist / abs( cos(deg2rad($lat))*111.0447 )); | |
$lon2 = $lon + ($dist / abs( cos(deg2rad($lat))*111.0447 )); | |
$lat1 = $lat - ($dist / 111.0447); | |
$lat2 = $lat + ($dist / 111.0447); | |
//echo "DST: ".$dist."km, " .$lon1." ".$lon2." - ".$lat1." ".$lat2."<br />"; | |
if($params['countit']){ | |
//changed the count stuff | |
$res = R::getAll('SELECT count('.$params['countit_distinct'].') as number from (SELECT '.$exp.', | |
( 6371 * 2 * | |
ASIN(SQRT( POWER(SIN(( :lat - ('.$db.'.'.$latname.')) * pi()/180 / 2), 2) | |
+COS( :lat *pi()/180) * COS( ('.$db.'.'.$latname.')*pi()/180)* | |
POWER(SIN( ( :lon - '.$db.'.'.$lonname.') * pi()/180 / 2 ),2 ) | |
)) ) AS distance | |
FROM '.$db.' WHERE '.$lonname.' BETWEEN '.$lon1.' and '.$lon2.' AND '.$latname.' BETWEEN '.$lat1.' and '.$lat2.' '.$wheres.' | |
'. ( !empty($params['groupby'])? 'GROUP BY '.$params['groupby']:'' ) .' | |
HAVING distance < ( :area + ('.$db.'.'.$radiusname.'/1000) ) ORDER BY '.$params['order'].' LIMIT :limit '.$params['limitMax'].') as t;', | |
array(':lat' => $lat, | |
':lon' => $lon, | |
':area' => ($params['radius']/1000), | |
/*':order' => $params['order'],*/ | |
':limit' => $params['limit'], | |
)); | |
}else{ | |
$res = R::getAll('SELECT '.$exp.', | |
( 6371 * 2 * | |
ASIN(SQRT( POWER(SIN(( :lat - ('.$db.'.'.$latname.')) * pi()/180 / 2), 2) | |
+COS( :lat *pi()/180) * COS( ('.$db.'.'.$latname.')*pi()/180)* | |
POWER(SIN( ( :lon - '.$db.'.'.$lonname.') * pi()/180 / 2 ),2 ) | |
)) ) AS distance | |
FROM '.$db.' WHERE '.$lonname.' BETWEEN '.$lon1.' and '.$lon2.' AND '.$latname.' BETWEEN '.$lat1.' and '.$lat2.' '.$wheres.' | |
'. ( !empty($params['groupby'])? 'GROUP BY '.$params['groupby']:'' ) .' | |
HAVING distance < ( :area + ('.$db.'.'.$radiusname.'/1000) ) ORDER BY '.$params['order'].' LIMIT :limit '.$params['limitMax'], | |
array(':lat' => $lat, | |
':lon' => $lon, | |
':area' => ($params['radius']/1000), | |
/*':order' => $params['order'],*/ | |
':limit' => $params['limit'], | |
)); | |
} | |
//Maybe try this | |
//https://developers.google.com/maps/articles/phpsqlsearch_v3 | |
return $res; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment