I\'m working on a laravel appliciation in which I need to find all the products that are within a certain radius of the user\'s coordinates. Products have a one to many rela
This was my implementation of it. I've chosen to alias my query out ahead of time, this way I can take advantage of Pagination
. Furthermore, you need to explicitly select the columns that you wish to retrieve from the query. add them at the ->select()
. Such as users.latitude, users.longitude, products.name
, or whatever they may be.
I have created a scope which looks something like this:
public function scopeIsWithinMaxDistance($query, $location, $radius = 25) {
$haversine = "(6371 * acos(cos(radians($location->latitude))
* cos(radians(model.latitude))
* cos(radians(model.longitude)
- radians($location->longitude))
+ sin(radians($location->latitude))
* sin(radians(model.latitude))))";
return $query
->select() //pick the columns you want here.
->selectRaw("{$haversine} AS distance")
->whereRaw("{$haversine} < ?", [$radius]);
}
You can apply this scope to any model with a latitude
andlongitude
.
Replace the $location->latitude
with your latitude
that you wish to search against, and replace the $location->longitude
with the longitude that you wish to search against.
Replace the model.latitude
and model.longitude
with the Models you wish to find around the $location
based on the distance defined in the $radius
.
I know you have a functioning Haversine formula, but if you need to Paginate you can't use the code you've supplied.
Hopefully this helps.
Using Haversine method, you can calculate distance between two points using this function. It works but I don't know how to implement this in Laravel. Thought of sharing this anyway.
$lat1 //latitude of first point
$lon1 //longitude of first point
$lat2 //latitude of second point
$lon2 //longitude of second point
$unit- unit- km or mile
function point2point_distance($lat1, $lon1, $lat2, $lon2, $unit='K')
{
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);
if ($unit == "K")
{
return ($miles * 1.609344);
}
else if ($unit == "N")
{
return ($miles * 0.8684);
}
else
{
return $miles;
}
}
I got a solution in Laravel.
public function near($myLon, $myLat, $areaLon, $areaLat)
{
$this->applyCriteria();
$this->applyScope();
$results = $this->model->select(DB::raw("SQRT(
POW(69.1 * (latitude - " . $myLat . "), 2) +
POW(69.1 * (" . $myLon . " - longitude) * COS(latitude / 57.3), 2)) AS distance, SQRT(
POW(69.1 * (latitude - " . $areaLat . "), 2) +
POW(69.1 * (" . $areaLon . " - longitude) * COS(latitude / 57.3), 2)) AS area"), "YOUR_TABLE.*")->get();
$this->resetModel();
$this->resetScope();
return $this->parserResult($results);
}
The answer is in Miles, you will have to replace YOUR_TABLE with the name of your database table. Thank you hope it helps
If you are willing to use an external package instead, I suggest the infinitely useful PHPGeo library. I used it on a project that relied on these exact calculations, and it worked just fine. It saves you writing the calculations yourself from scratch and is tested to work.
https://github.com/mjaschen/phpgeo
Here is the documentation for Harvesine: https://phpgeo.marcusjaschen.de/#_distance_between_two_coordinates_haversine_formula
This is a code I am using:
$ownerLongitude = $request['longitude'];
$ownerLatitude = $request['latitude'];
$careType = 1;
$distance = 3;
$raw = DB::raw(' ( 6371 * acos( cos( radians(' . $ownerLatitude . ') ) *
cos( radians( latitude ) ) * cos( radians( longitude ) - radians(' . $ownerLongitude . ') ) +
sin( radians(' . $ownerLatitude . ') ) *
sin( radians( latitude ) ) ) ) AS distance');
$cares = DB::table('users')->select('*', $raw)
->addSelect($raw)->where('type', $careType)
->orderBy('distance', 'ASC')
->having('distance', '<=', $distance)->get();
Create this function in your Model
public static function getNearBy($lat, $lng, $distance,
$distanceIn = 'miles')
{
if ($distanceIn == 'km') {
$results = self::select(['*', DB::raw('( 0.621371 * 3959 * acos( cos( radians('.$lat.') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('.$lng.') ) + sin( radians('.$lat.') ) * sin( radians(lat) ) ) ) AS distance')])->havingRaw('distance < '.$distance)->get();
} else {
$results = self::select(['*', DB::raw('( 3959 * acos( cos( radians('.$lat.') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('.$lng.') ) + sin( radians('.$lat.') ) * sin( radians(lat) ) ) ) AS distance')])->havingRaw('distance < '.$distance)->get();
}
return $results;
}
And you can use orderby
, groupBy
as per your requirement.