はじめに
こんにちは。サーバサイドエンジニアの中川@tkdev0728です。
最近距離計算を行いたい場面があり、色々と調べていて実装はできたんですがGoで実装しているかつ日本語の情報が多くなかったので今の記事を書いています。
使うもの
算出方法
算出するにあたってorbというとても便利なライブラリがあるのでそちらを使っていきます。 基本的にはこちらのReadmeに沿って進めれば大丈夫です。
検算
上記ライブラリで求めた距離の検算に国土地理院のサイトを使います。
緯度経度の取得
距離計算に緯度経度を使うので、今回はMapFanにて特定の地点の緯度経度を取得します。
実装
距離計算を行う地点
今回はヤプリの会社情報から東京オフィス、大阪オフィス、福岡オフィスとの距離を算出してみます。
まずそれぞれのオフィスの緯度経度を取得します。
緯度 | 経度 | |
---|---|---|
東京オフィス | 35.6646848 | 139.737763 |
大阪オフィス | 34.6646966 | 135.501151 |
福岡オフィス | 33.586418 | 130.3957948 |
国土地理院のサイトでそれぞれのオフィス間の距離を計算するとこんな感じです
東京オフィス-大阪オフィス | 401,564.146(m) |
東京オフィス-福岡オフィス | 886,751.814(m) |
大阪オフィス-福岡オフィス | 485,849.326(m) |
実際のコードにするとこんな感じです。
コード
package main import ( "fmt" "strconv" "github.com/paulmach/orb" "github.com/paulmach/orb/geo" ) func main() { tokyo := orb.Point{139.737763, 35.6646848} osaka := orb.Point{135.501151, 34.6646966} fukuoka := orb.Point{130.3957948, 33.586418} res1 := geo.Distance(tokyo, osaka) res2 := geo.Distance(tokyo, fukuoka) res3 := geo.Distance(osaka, fukuoka) fmt.Println("東京オフィス-大阪オフィス間の距離は" + strconv.FormatFloat(res1, 'f', -1, 64) + "(m)") fmt.Println("東京オフィス-福岡オフィス間の距離は" + strconv.FormatFloat(res2, 'f', -1, 64) + "(m)") fmt.Println("大阪オフィス-福岡オフィス間の距離は" + strconv.FormatFloat(res3, 'f', -1, 64) + "(m)") }
東京オフィス-大阪オフィス間の距離は401295.96027763194(m) 東京オフィス-福岡オフィス間の距離は886472.9806886005(m) 大阪オフィス-福岡オフィス間の距離は485536.8697726517(m)
大体近い結果になっていますが、百メートル単位で誤差が生じています。これは地球は真球として考えるか回転楕円体として考えるかによる違いです。
地球は回転楕円体に近いのでより高い精度を求めるならば回転楕円体による計算をする必要がありますが、回転楕円体とする計算式は複雑であり、それをコードに落としこむのが大変であることからある程度の誤差を許容して地球を真球として計算していると思われます。
一応国土地理院のサイトとpaulmach/orbがそれぞれどのような計算式で計算しているかみてみます。
計算式
国土地理院のサイト
まずはより精度の高い国土地理院のサイトからです。サイト上に計算式のリンクがあるのでそこから参照できます。
正直言ってさっぱりわからないのですが、何やら複雑そうです。
paulmach/orb
次にライブラリのコードを読んでみます。
上記のリンクから飛べますが、関係ある箇所のみ抜き出すと下記です。
// EarthRadius is the radius of the earth in meters. It is used in geo distance calculations. // To keep things consistent, this value matches WGS84 Web Mercator (EPSG:3857). const EarthRadius = 6378137.0 // meters // Distance returns the distance between two points on the earth. func Distance(p1, p2 orb.Point) float64 { dLat := deg2rad(p1[1] - p2[1]) dLon := deg2rad(p1[0] - p2[0]) dLon = math.Abs(dLon) if dLon > math.Pi { dLon = 2*math.Pi - dLon } // fast way using pythagorean theorem on an equirectangular projection x := dLon * math.Cos(deg2rad((p1[1]+p2[1])/2.0)) return math.Sqrt(dLat*dLat+x*x) * orb.EarthRadius } func deg2rad(d float64) float64 { return d * math.Pi / 180.0 }
距離を測定したい2点の緯度経度の差をラジアンに直し、ピタゴラスの定理(三平方の定理)を使って算出しています。つまり地球上の2点間の距離は厳密には円周上の距離となりますが、ある程度の誤差を許容して平面として計算することでコードをシンプルにしていると思われます。
国土地理院のサイトとの違いはこの辺にあると理解できました。
まとめ
緯度経度を使った2点間の距離計算をGoで行う方法をまとめました。地球は平面ではないため計算方法が複雑になります。実装の複雑さと計算結果の正確さはトレードオフだと思うので、用途に応じて最適な方法を考えるのが必要ですね。
ヤプリは普段の業務からちょっと足を踏み出したこのような内容についても気軽にテックブログで発信できるような雰囲気になっています。
ヤプリに興味をもったという方はぜひカジュアル面談にお越しください。