環境
Laravel 7.x
MySQL 8
やりたいこと
緯度経度情報から店舗を近い順に並べ替えたい
精度は10mくらいでいい
データ
DB の shops テーブルに緯度 latitude, 経度 longitude カラムがある
id | name | latitude | longitude |
---|---|---|---|
1 | 店舗A | 35.1111 | 137.1111 |
2 | 店舗B | 35.2222 | 137.2222 |
3 | 店舗C | 35.3333 | 137.3333 |
4 | 店舗D | 35.4444 | 137.4444 |
... | ... | ... | ... |
緯度経度から距離を出す計算式
地点P(緯度φ1,経度λ1) から 地点Q(緯度φ2,経度λ2)までの距離を x [km] とすると
x = \arccos(\cos\phi_1\cos\phi_2\cos(\lambda_1-\lambda_2) + \sin\phi_1\sin\phi_2) \times 6370
出典: http://www.orsj.or.jp/archive2/or60-12/or60_12_701.pdf
6370は地球の半径[km]。
OrderByRawを使う
上記計算式を orderByRaw を使って実装する。
ソートするだけなので、定数である半径 6370 はいらない。
$latitude = $request->get('latitude'); // 起点の緯度 queryに入っているテイ
$longitude = $request->get('longitude'); // 起点の経度 queryに入っているテイ
$shops = \App\Models\Shop::orderByRaw(
'ACOS(COS(RADIANS('.$latitude.')) * COS(RADIANS(latitude)) * COS(RADIANS(longitude) - RADIANS('.$longitude.'))
+ SIN(RADIANS('.$latitude.')) * SIN(RADIANS(latitude)))'
)
->get();
距離を値として取りたい場合
上記だとソートするだけになるが、距離も値として欲しいときはこちら。
$latitude = $request->get('latitude'); // 起点の緯度 queryに入っているテイ
$longitude = $request->get('longitude'); // 起点の経度 queryに入っているテイ
$shops = \App\Models\Shop::select('*',
DB::raw('6370 * ACOS(COS(RADIANS('.$latitude.')) * COS(RADIANS(latitude)) * COS(RADIANS(longitude) - RADIANS('.$longitude.'))
+ SIN(RADIANS('.$latitude.')) * SIN(RADIANS(latitude))) as distance'))
->orderBy('distance')
->get();