現在地から半径○メートル以内の○○を抽出するSQL

システム部の鈴木です。

ポケモンGOの日本リリースからはや2週間、皆様は引き続き楽しまれてますでしょうか?

自分は既に飽き出しておりギャラドスへの進化を見る事無く、
わざわざ買った携帯バッテリーを使う事も無く引退が危ぶまれております。

さて、ポケモンGOでまた脚光を浴びた「位置ゲー」ですが、
ポケモンGOの場合は現在地点にプレーヤーのキャラがいて
周囲の○メートルくらいにあるポケストップではアイテムがもらえたりしますよね?

こういった「現在地から半径○メートル以内にある○○を探す」系の事をやるときの
データをどうやって探すのか?ってのをやってみたいと思います。

やりたいこと

map0

弊社のあるアークヒルズフロントタワーを中心としたとき、
色々ある座標データまでの直線距離を計算し、それが○メートルに収まるかどうか?
ということがやりたいことです。

そして今回は、様々な座標が登録されたMySQLのDBから一気に該当する座標を取りたいと思います。

データベース

今回は例としてこのようなテーブルを作成しました。

<br />
CREATE TABLE IF NOT EXISTS `test` (<br />
  `id` int(11) NOT NULL AUTO_INCREMENT,<br />
  `place` varchar(255) NOT NULL DEFAULT &#8221;,<br />
  `lat` double NOT NULL DEFAULT &#8216;0&#8217;,<br />
  `lng` double NOT NULL DEFAULT &#8216;0&#8217;,<br />
  PRIMARY KEY (`id`),<br />
  KEY `lat` (`lat`,`lng`)<br />
) ENGINE=InnoDB;<br />

<br />
INSERT INTO `test` (`id`, `place`, `lat`, `lng`) VALUES(1, &#8216;東京駅&#8217;, 35.68121, 139.765751);<br />
INSERT INTO `test` (`id`, `place`, `lat`, `lng`) VALUES(2, &#8216;品川駅&#8217;, 35.62857, 139.738809);<br />
INSERT INTO `test` (`id`, `place`, `lat`, `lng`) VALUES(3, &#8216;横浜駅&#8217;, 35.465365, 139.62209);<br />
INSERT INTO `test` (`id`, `place`, `lat`, `lng`) VALUES(4, &#8216;二子玉川駅&#8217;, 35.611237, 139.626182);&lt;/pre&gt;<br />
&lt;pre&gt;<br />

以下のような感じのテーブルから、弊社から半径10km以内にある駅を探してみたいと思います。

<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />
| id | place      | lat       | lng        |<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />
|  1 | 東京駅     |  35.68121 | 139.765751 |<br />
|  2 | 品川駅     |  35.62857 | 139.738809 |<br />
|  3 | 横浜駅     | 35.465365 |  139.62209 |<br />
|  4 | 二子玉川駅 | 35.611237 | 139.626182 |<br />
+&#8212;-+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />

ではこの中から半径10kmに収まる座標を取りだすにはどうすればよいでしょうか?

中学で習ったアレを使います

2地点間の距離を出すには3平方の定理を使いましょう

map02

3平方の定理は aの二乗+bの二乗 = cの二乗 というものです。
この公式は文系出身の自分でも中学の時に聞いた覚えがあります。

現在地であるアークヒルズの座標を lat = x1, lng = y1 (35.668185, 139.739487 )
そして目標となるATTビルの座標を lat = x2, lng = y2

とすると、緯度の差が x1 – x2 = a
経度の差が y1 – y2 = b
ということです。

上の図の例だと、例えば赤い線の半径に収まる座標がどれかを探したいわけですが
要は斜辺の長さがその半径の長さに収まっているかどうかが分かればよいという事ですね。

さらに地球の丸みを考慮して正確に距離を出すためには
弧度法のラジアン(弧度)に変換し、3平方の定理に当てはめればよさそうです。
そして地球の一週の距離は約 6378km らしいので、それを使えばこんな感じで出来るようです。

 

 

<br />
SELECT id, place, lng, lat, (6378 * acos(cos(radians(35.668185)) * cos(radians(lat)) * cos(radians(lng) &#8211; radians(139.739487)) + sin(radians(35.668185)) * sin(radians(lat)))) AS distance<br />
FROM test HAVING distance &lt; 10 ORDER BY distance<br />

このSQLを実行した結果がこちらです。

<br />
+&#8212;-+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
| id | place  | lng        | lat      | distance          |<br />
+&#8212;-+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
|  1 | 東京駅 | 139.765751 | 35.68121 | 2.782586467919596 |<br />
|  2 | 品川駅 | 139.738809 | 35.62857 | 4.410253359933821 |<br />
+&#8212;-+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />

見事に近い順で絞り込むことに成功しました。
直線距離なので実際の道路で進んだ距離とは異なりますが、便利に使えそうですね。