-
-
Save SwabTheDeck/0056d6785377d86deaa8 to your computer and use it in GitHub Desktop.
A CakePHP model function to find and sort by the distance from a given a latitude and longitude coordinate with the option to restrict to a given radius.
This file contains 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
<?php | |
App::uses('Model', 'Model'); | |
App::uses('String', 'Utility'); | |
class AppModel extends Model { | |
/** | |
* @author Reed Dadoune | |
* distanceQuery | |
* A genral case distance query builder | |
* Pass a number of options to this function and recieve a query | |
* you can pass to either the find or paginate functions to get | |
* objects back by distance | |
* | |
* Example: | |
* $query = $this->Model->distanceQuery(array( | |
* 'latitude' => 34.2746405, | |
* 'longitude' => -119.2290053 | |
* )); | |
* $query['conditions']['published'] = true; | |
* $results = $this->Model->find('all', $query); | |
* | |
* @param array $opts Options | |
* - latitude The latitude coordinate of the center point | |
* - longitude The longitude coordinate of the center point | |
* - alias The model name of the query this is for | |
* defaults to the current model alias | |
* - radius The distance to at which to find objects at | |
* defaults to false in which case distance is calculated | |
* only for the sort order | |
* @return array A query that can be modified and passed to find or paginate | |
*/ | |
public function distanceQuery($opts = array()) { | |
$defaults = array( | |
'latitude' => 0, | |
'longitude' => 0, | |
'alias' => $this->alias, | |
'radius' => false | |
); | |
$opts = Set::merge($defaults, $opts); | |
$query = array( | |
'fields' => array( | |
'*', | |
'( 3959 * acos( cos( radians('.$opts['latitude'].') ) * cos( radians( '.$opts['alias'].'.latitude ) ) * cos( radians( '.$opts['alias'].'.longitude ) - radians('.$opts['longitude'].') ) + sin( radians('.$opts['latitude'].') ) * sin( radians( '.$opts['alias'].'.latitude ) ) ) ) AS distance' //formula based on: https://developers.google.com/maps/articles/phpsqlsearch_v3#findnearsql | |
) | |
), | |
'order' => array('distance' => 'ASC') | |
); | |
if ($opts['radius']) { | |
$longitudeLower = $opts['longitude'] - $opts['radius'] / abs(cos(deg2rad($opts['latitude'])) * 69); | |
$longitudeUpper = $opts['longitude'] + $opts['radius'] / abs(cos(deg2rad($opts['latitude'])) * 69); | |
$latitudeLower = $opts['latitude'] - ($opts['radius'] / 69); | |
$latitudeUpper = $opts['latitude'] + ($opts['radius'] / 69); | |
$query['conditions'] = array( | |
String::insert(':alias.latitude BETWEEN ? AND ?', array('alias' => $opts['alias'])) => array($latitudeLower, $latitudeUpper), | |
String::insert(':alias.longitude BETWEEN ? AND ?', array('alias' => $opts['alias'])) => array($longitudeLower, $longitudeUpper) | |
); | |
$query['group'] = sprintf('%s.id HAVING distance < %f', $opts['alias'], $opts['radius']); | |
} | |
return $query; | |
} | |
} | |
How do you actually go about adding this to your cake setup? I've never added a model function.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a great piece of code indeed!
You can also add a parameter to switch from miles to kilometres (multiplying by 6371 instead of 3959). See https://developers.google.com/maps/articles/phpsqlsearch_v3#finding-locations-with-mysql