Skip to content

Instantly share code, notes, and snippets.

@SwabTheDeck
Forked from ReedD/AppModel.php
Last active July 19, 2016 16:29
Show Gist options
  • Save SwabTheDeck/0056d6785377d86deaa8 to your computer and use it in GitHub Desktop.
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.
<?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;
}
}
@gborgonovo
Copy link

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

@KyleGoslan
Copy link

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