Skip to content

Instantly share code, notes, and snippets.

@thodeveloper
Forked from ReedD/AppModel.php
Created February 18, 2016 07:32
Show Gist options
  • Save thodeveloper/99cedcf5360aedca8858 to your computer and use it in GitHub Desktop.
Save thodeveloper/99cedcf5360aedca8858 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(
'*',
String::insert(
'3956 * 2 *
ASIN(SQRT(
POWER(SIN((:latitude - ABS(:alias.latitude)) * PI() / 180 / 2), 2) +
COS(:latitude * PI() / 180) *
COS(ABS(:alias.latitude) * PI() / 180) *
POWER(SIN((:longitude - :alias.longitude) * PI() / 180 / 2), 2)
)) AS distance',
array('alias' => $opts['alias'], 'latitude' => $opts['latitude'], 'longitude' => $opts['longitude'])
)
),
'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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment