Я работаю над применением 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, это не может быть действительно результативным, и запрос, возможно, станет трудно поддерживать, на мой взгляд. Это то, что я сделал бы вместо этого:
Это код, который я использую:
$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();