3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[MySQL]緯度経度による距離計算を高精度で行う[Rails]

Last updated at Posted at 2020-07-16

目的

レコードにある緯度経度と入力値の緯度経度から距離を計算し
一定距離内のレコードのみを取得したかった

計算方法とそれぞれへの所感

ざっとググってみると主に3つの方法が見つかりました

    1. 三角関数を駆使する方法
    1. LINESTRING()GLENGTH()を使う方法
    1. ST_Distance_Sphere()を使う方法

最終的に採用したのは3番のST_Distance_Sphere()を使う方法でした

1. 三角関数を駆使する方法について

ぶっちゃけめんどい
他にいい方法がなければ仕方なく使う最終手段

2. LINESTRING()GLENGTH()を使う方法

試してみたけどちょっと精度が悪い
580m程度の距離を計算した時に120m程度の誤差が出て許容できなかった
主に1km前後の距離を精度よく計算することが求められていたので不採用

3. ST_Distance_Sphere()を使う方法(採用)

MySQLにある関数を使うだけなので非常に使い勝手が良く
高精度の距離計算ができたのでこれを採用!!

試してないけどST_Distance_Spheroid()っていう速度は落ちるがさらに高精度な関数もあるらしい

ちなみに計算結果の単位はメートル
インチとかヤードとかじゃなくて本当に良かった

実際の実装では

Railsを使っていたため緯度経度のカラムの型にgeometry型が扱えず
代わりにdouble型のカラムで緯度経度を保持していました
geometry型の代わりにdouble型のカラムで十分だと判断した理由については別の記事でまた書きます

計算する時はPOINT()を使って緯度経度をキャストしたものをST_Distance_Sphere()の引数に渡して計算しました

Railsでの実装

前提として、店舗(Store)と住所(Address)という1:1のモデルがあり、
Addressモデルが持つ緯度経度と入力値の緯度経度から距離を計算して
一定距離内の店舗一覧を取得する処理を実装した

app/models/store.rb
  DISPLAYABLE_METER = 1000
  DISTANCE_METER_COLUMN = 'distance_meter'

  scope :select_with_distance_meter, lambda { |lat:, lng:|
    joins(:address).select(
      "
      #{self.table_name}.*,
      ST_Distance_Sphere(
        POINT(lng, lat),
        POINT(#{lng}, #{lat})
      ) AS #{DISTANCE_METER_COLUMN}
      ",
    )
  }
店舗の一覧取得
Store
  .select_with_distance_meter(lat: lat, lng: lng)
  .having("#{Store::DISTANCE_METER_COLUMN} < #{Store::DISPLAYABLE_METER}")

AS でエイリアスにしているカラムに対してはwhere使えないのでhaving使ってます

参考資料

MySQLのgeometry型で○km以内の場所を取得してみました
ST_Distance_Sphere

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?