今日は、MySQLのGISを使って検索を試してみるの日。
初めに
この記事は、RDBMS-GIS(MySQL,PostgreSQLなど) Advent Calendar 2021のカレンダー | Advent Calendar 2021 - Qiitaのエントリーです。
検証環境
MySQLで検証しています。
root@6438500bc2d7:/# mysql --version mysql Ver 8.0.24 for Linux on x86_64 (MySQL Community Server - GPL) root@6438500bc2d7:/# uname -a Linux 6438500bc2d7 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Oct 28 23:40:43 UTC 2020 x86_64 GNU/Linux
dockerにMySQL構築。
目次
とある日
この記事は、GISで使うテーブル設計ってどうするんだろうと言う疑問を記事にしたものです。
GIS初心者的に気になった内容を調べてみようと思います。
一通り、設計からSQLの発行までやってみようと思います。
検証
上記の記事にあったISUCON10の問題を少しだけ変更してやっていきたいと思います。
設計編
MySQLだとPOINT
で緯度と経度の情報を保持します。
CREATE TABLE advents ( id int PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, description VARCHAR(4096) NOT NULL, address VARCHAR(128) NOT NULL, latlon POINT not null );
テストデータを生成してみる。
INSERT INTO advents (name, description, address, latlon) VALUES ( "茶禅花", "三ツ星", "東京都 港区 南麻布 4-7-5", ST_GeomFromText('POINT(35.64980785547675 139.72841580761832)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "ヘレタ", "ハイセンス", "東京都 中央区 銀座 1-21-14", ST_GeomFromText('POINT(35.672680675384115 139.7706892178345)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "RhinoiR", "いくら", "東京都 港区 白金台 4-2-7", ST_GeomFromText('POINT(35.63955098605981 139.72810047607342)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "256 Chinese Restaurant", "肉", "東京都港区南青山7-10-10", ST_GeomFromText('POINT(35.659105441114136 139.7187464797685)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "エノキ飯店", "熱そう", "東京都 目黒区 三田 2-10-30", ST_GeomFromText('POINT(35.63924488847674 139.71247107704005)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "中国飯店 山麗川", "辛そう", "東京都 港区 東麻布 3-7-5", ST_GeomFromText('POINT(35.656449510639035 139.73851674670985)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "グンゲ", "フカヒレ", "東京都 中央区 銀座 7-4-5", ST_GeomFromText('POINT(35.670336627726805 139.76115233409124)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "相厨房 無", "アワビ", " 東京都 港区 白金 6-5-5", ST_GeomFromText('POINT(35.6445676673225 139.72518049284164)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "翡翠", "すごそう", "東京都 港区 六本木 4-4-2", ST_GeomFromText('POINT(35.66527837632412 139.7323430295452)') ); INSERT INTO advents (name, description, address, latlon) VALUES ( "猫峰", "上から", "東京都 港区 六本木 3-8-7", ST_GeomFromText('POINT(35.66344164600342 139.73458798966266)') );
id | name | description | address | latlon |
---|---|---|---|---|
1 | 茶禅花 | 三ツ星 | 東京都 港区 南麻布 4-7-5 | POINT (35.64980785547675 139.72841580761832) |
2 | ヘレタ | ハイセンス | 東京都 中央区 銀座 1-21-14 | POINT (35.672680675384115 139.7706892178345) |
3 | RhinoiR | いくら | 東京都 港区 白金台 4-2-7 | POINT (35.63955098605981 139.72810047607342) |
4 | 256 Chinese Restaurant | 肉 | 東京都港区南青山7-10-10 | POINT (35.659105441114136 139.7187464797685) |
5 | エノキ飯店 | 熱そう | 東京都 目黒区 三田 2-10-30 | POINT (35.63924488847674 139.71247107704005) |
6 | 中国飯店 山麗川 | 辛そう | 東京都 港区 東麻布 3-7-5 | POINT (35.656449510639035 139.73851674670985 |
7 | グンゲ | フカヒレ | 東京都 中央区 銀座 7-4-5 | POINT (35.670336627726805 139.76115233409124) |
8 | 相厨房 無 | アワビ | 東京都 港区 白金 6-5-5 | POINT (35.6445676673225 139.72518049284164) |
9 | 翡翠 | すごそう | 東京都 港区 六本木 4-4-2 | POINT (35.66527837632412 139.7323430295452) |
10 | 猫峰 | 上から | 東京都 港区 六本木 3-8-7 | POINT (35.66344164600342 139.73458798966266) |
ST_GeomFromText('POINT(35.64980785547675 139.72841580761832)')
は、文字列からGeometryに変換するための関数です。
検索編
近場のお店を検索
SELECT * from advents a order by ST_Distance(ST_GeomFromText('POINT(35.66344164600342 139.73458798966266)'),latlon) ;
id | name | description | address | latlon |
---|---|---|---|---|
10 | 猫峰 | 上から | 東京都 港区 六本木 3-8-7 | POINT (35.66344164600342 139.73458798966266) |
9 | 翡翠 | すごそう | 東京都 港区 六本木 4-4-2 | POINT (35.66527837632412 139.7323430295452) |
6 | 中国飯店 山麗川 | 辛そう | 東京都 港区 東麻布 3-7-5 | POINT (35.656449510639035 139.73851674670985) |
1 | 茶禅花 | 三ツ星 | 東京都 港区 南麻布 4-7-5 | POINT (35.64980785547675 139.72841580761832) |
4 | 256 Chinese Restaurant | 肉 | 東京都港区南青山7-10-10 | POINT (35.659105441114136 139.7187464797685) |
8 | 相厨房 無 | アワビ | 東京都 港区 白金 6-5-5 | POINT (35.6445676673225 139.72518049284164) |
3 | RhinoiR | いくら | 東京都 港区 白金台 4-2-7 | POINT (35.63955098605981 139.72810047607342) |
7 | グンゲ | フカヒレ | 東京都 中央区 銀座 7-4-5 | POINT (35.670336627726805 139.76115233409124) |
5 | エノキ飯店 | 熱そう | 東京都 目黒区 三田 2-10-30 | POINT (35.63924488847674 139.71247107704005) |
2 | ヘレタ | ハイセンス | 東京都 中央区 銀座 1-21-14 | POINT (35.672680675384115 139.7706892178345) |
ST_Distance
二点間の距離を図ります。
比較対象は適当に東京タワーの位置情報を使ってます。
東京タワー:ST_GeomFromText('POINT(35.66344164600342 139.73458798966266)'
ST_Distance(*`g1`*, *`g2`* [, *`unit`*])
g1
とg2
の間の距離を戻します。ジオメトリ引数の空間参照システム (SRS) の長さ単位で測定されるか、オプションのunit
引数 (指定されている場合) の単位で測定されます。
特定の区にあるお店を検索
港区にあるお見せを検索します。
港区の座標を適当に取ってきてます。
去年初めて色々とGISを触ったときPolygonは出発点と最終点が同じにしないと怒られることを思い出した。
範囲を設定するためにPolygon
を定義してます。
SET @json = '{ "type": "Polygon", "coordinates": [ [ [ 139.70856889128842,35.66437406359891], [ 139.76220227537425,35.66633039323733], [ 139.73594533214896,35.623490121041364], [ 139.71939832682938,35.67824257816885], [ 139.70856889128842,35.66437406359891] ] ], "crs": { "type":"name", "properties": { "name":"EPSG:4326" } } }'; SET @area = ST_GeomFromGeoJSON(@json); SELECT * FROM advents a where 1 = ST_Contains(@area, latlon);
id | name | description | address | latlon |
---|---|---|---|---|
1 | 茶禅花 | 三ツ星 | 東京都 港区 南麻布 4-7-5 | POINT (139.72841580761832 35.64980785547675) |
6 | 中国飯店 山麗川 | 辛そう | 東京都 港区 東麻布 3-7-5 | POINT (139.73851674670985 35.656449510639035) |
10 | 猫峰 | 上から | 東京都 港区 六本木 3-8-7 | POINT (139.73458798966266 35.66344164600342) |
ST_GeomFromGeoJSON
ST_GeomFromGeoJSON(*`str`* [, *`options`* [, *`srid`*]])
GeoJSON オブジェクトを表す文字列
str
を解析し、ジオメトリを戻します。
srids: 4326
はじめのINSERT文のままだと下記のエラーが出る。
Binary geometry function st_contains given two geometries of different srids: 4326 and 0, which should have been identical.
なのでINSERT文を修正。
insert into advents (name,description,address,latlon) values ( "茶禅花","三ツ星","東京都 港区 南麻布 4-7-5",ST_GeomFromText('POINT(35.64980785547675 139.72841580761832)', 4326) );
見た目は何も変わらない。
ちなみに、addressで住所も保持しているので下記のようにも取得可能。
select * from advents where address like "%港区%";
id | name | description | address | latlon |
---|---|---|---|---|
1 | 茶禅花 | 三ツ星 | 東京都 港区 南麻布 4-7-5 | POINT (139.72841580761832 35.64980785547675) |
3 | RhinoiR | いくら | 東京都 港区 白金台 4-2-7 | POINT (139.72810047607342 35.63955098605981) |
4 | 256 Chinese Restaurant | 肉 | 東京都港区南青山7-10-10 | POINT (139.7187464797685 35.659105441114136) |
6 | 中国飯店 山麗川 | 辛そう | 東京都 港区 東麻布 3-7-5 | POINT (139.73851674670985 35.656449510639035 |
8 | 相厨房 無 | アワビ | 東京都 港区 白金 6-5-5 | POINT (139.72518049284164 35.6445676673225) |
9 | 翡翠 | すごそう | 東京都 港区 六本木 4-4-2 | POINT (139.7323430295452 35.66527837632412) |
10 | 猫峰 | 上から | 東京都 港区 六本木 3-8-7 | POINT (139.73458798966266 35.66344164600342) |
適当な範囲から港区の範囲を抽出したのでその部分が弾かれている。
参考記事
MySQLに超・雑に大量のPOINTデータ(位置データ)を作成する方法 - sakaikの日々雑感~(T)編
mysql:mysql8_gis_bug [あにねこ電脳覚書]
〆
一年に1回ぐらいしかGISを触らないがなかなかに難しい。
順番とか気にするところが多くて気軽に触るのが大変。