Skip to content

Instantly share code, notes, and snippets.

@bbatsche
Created September 29, 2015 20:06
Show Gist options
  • Save bbatsche/35e7f3e0b127f9fab653 to your computer and use it in GitHub Desktop.
Save bbatsche/35e7f3e0b127f9fab653 to your computer and use it in GitHub Desktop.
Haversine Query Scope
<?php
class BaseModel {
const UNIT_KM = 111.045;
const UNIT_MI = 69.0;
// Throw this function in any model with latitude/longitude columns
// Or if multiple models have those values you can put it in your BaseModel
public function scopeNearLatLong($query, $lat, $lng, $radius = 10, $unit = 69.0)
{
if (!is_numeric($lat) || -90 >= $lat || $lat >= 90) {
throw new RangeException("Latitude must be between -90 and 90 degrees.");
}
if (!is_numeric($lng) || -180 >= $lng || $lng >= 180) {
throw new RangeException("Longitude must be between -180 and 180 degrees.");
}
$subQuery = clone $query;
$latDistance = $radius / $unit;
$latNorth = $lat - $latDistance;
$latSouth = $lat + $latDistance;
$lngDistance = $radius / ($unit * cos(deg2rad($lat)));
$lngEast = $lng - $lngDistance;
$lngWest = $lng + $lngDistance;
$subQuery->selectRaw(DB::raw('*, (? * DEGREES(
ACOS(
COS(RADIANS(?)) * COS(RADIANS(latitude)) * COS(RADIANS(? - longitude))
+ SIN(RADIANS(?)) * SIN(RADIANS(latitude))
)
)) AS distance'))->addBinding([$unit, $lat, $lng, $lat], 'select');
$subQuery->whereBetween('latitude', [$latNorth, $latSouth]);
$subQuery->whereBetween('longitude', [$lngEast, $lngWest]);
$query->from(DB::raw('(' . $subQuery->toSql() . ') as d'));
$query->mergeBindings($subQuery->getQuery());
$query->where('distance', '<=', $radius);
}
}
// To use:
$latitude = 29.4284595;
$longitude = -98.492433;
// Default radius is 10; default unit is miles.
Model::nearLatLong($latitude, $longitude)->get();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment