Расчет расстояний между двумя точками в Laravel

Я работаю над применением laravel, в котором мне нужно найти все продукты, находящиеся в определенном радиусе координат пользователя. Продукты имеют отношение «один к большому» с пользователями, поэтому пользователи могут иметь несколько продуктов.

Я обнаружил, что формула haversine может рассчитать расстояние между двумя точками, но я не могу заставить ее работать.

У меня есть следующий запрос в моем контроллере:

$latitude = 51.0258761; $longitude = 4.4775362; $radius = 20000; $products = Product::with('user') ->selectRaw("*, ( 6371 * acos( cos( radians(" . $latitude . ") ) * cos( radians(user.latitude) ) * cos( radians(user.longitude) - radians(" . $longitude . ") ) + sin( radians(" . $latitude . ") ) * sin( radians(user.latitude) ) ) ) AS distance") ->having("distance", "<", $radius) ->orderBy("distance") ->get(); 

Я установил радиус 20000 для целей тестирования, и кажется, что все продукты имеют расстояние 5687. Проблема заключается в том, что широта и долгота продуктов хранятся в таблице User, но я не уверен как я могу получить доступ к тем в моем запросе. Я пробовал user.latitude и «user-> latitude», но ничего не работает.

Вот мои модели:

Модель продукта

 namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = [ 'soort', 'hoeveelheid', 'hoeveelheidSoort', 'prijsPerStuk', 'extra', 'foto', 'bio' ]; public function User() { return $this->belongsTo('App\User'); } public $timestamps = true; } 

Модель пользователя

 namespace App; use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword; protected $table = 'users'; protected $fillable = [ 'firstName', 'lastName', 'adres', 'profilepic', 'description', 'longitude', 'latitude', 'email', 'password' ]; protected $hidden = ['password', 'remember_token']; public function product() { return $this->hasMany('App\Product'); } } 

Это была моя реализация. Я выбрал вариант запроса моего запроса раньше времени, таким образом, я могу использовать Pagination . Кроме того, вам нужно явно выбрать столбцы, которые вы хотите получить из запроса. добавьте их на ->select() . Например, users.latitude, users.longitude, products.name или что бы они ни были.

Я создал область, которая выглядит примерно так:

 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]); } 

Вы можете применить эту область к любой модели с latitude и longitude .

Замените $location->latitude своей latitude которую вы хотите выполнить поиск, и замените $location->longitude с которой вы хотите искать.

Замените model.latitude и model.longitude на Модели, которые вы хотите найти вокруг $location на основе расстояния, определенного в $radius .

Я знаю, что у вас есть действующая формула Haversine, но если вам нужно Paginate, вы не сможете использовать код, который вы предоставили.

Надеюсь, это поможет.

Если вы хотите использовать внешний пакет вместо этого, я предлагаю бесконечно полезную библиотеку PHPGeo. Я использовал его в проекте, который полагался на эти точные вычисления, и он работал нормально. Это избавляет вас от необходимости писать расчеты с нуля и проверено на работу.

https://github.com/mjaschen/phpgeo

Вот документация для Harvesine: https://phpgeo.marcusjaschen.de/#_distance_between_two_coordinates_haversine_formula

Используя метод Haversine, вы можете рассчитать расстояние между двумя точками, используя эту функцию. Это работает, но я не знаю, как реализовать это в Laravel. Думал об обмене этим в любом случае.

 $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; } } 

Я думаю, что вам нужен построитель запросов для создания соединения . При соединении у вас есть поля обеих таблиц, доступных в вашем запросе. В настоящее время вы используете отношения с высокой загрузкой , это предварительно загрузит связанных пользователей, но они не могут использоваться внутри SQL (Laravel фактически выполнит 2 запроса).

Во всяком случае, я бы не попытался вычислить формулу haversine за один шаг с SQL, это не может быть действительно результативным, и запрос, возможно, станет трудно поддерживать, на мой взгляд. Это то, что я сделал бы вместо этого:

  1. Вычислите конверт с минимумом / максимумом широты и долготы, он должен быть немного больше, чем ваш радиус поиска.
  2. Сделайте быстрый запрос с присоединением к продукту и пользователю и просто проверьте, находится ли местоположение пользователя внутри этого конверта.
  3. Для каждого элемента результирующего списка вычислите точное расстояние Хаверсина с PHP (а не SQL), удалите строки, которые находятся за пределами радиуса, и отсортируйте список соответственно.

Это код, который я использую:

  $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();