MySQLの「ST_Touches()」や「ST_Crosses()」などの8つの関数でジオメトリの位置関係を見比べてみよう
第27回の今回は、MySQLの「ST_Touches()」や「ST_Crosses()」など8つのジオメトリ判定関数を一気に比較し、ポリゴンと線の位置関係による挙動の違いについて解説します。
6:30
はじめに
第25回ではST_Within()とST_Contains()で「含まれる」関係を、第26回ではST_Intersects()で「交わる」関係を判定する方法を紹介しました。実はMySQLには、この手の「2つのジオメトリがどういう位置関係にあるか」を判定する関数がたくさん用意されています。
今回と次回の2回にわけて、まだ紹介していない関数も含めて全部で8つの判定関数を、一気に見比べてみます。個々の関数を見ているだけでは気づきにくい特徴などを浮き上がらせることができればと思います。
舞台は日本へそ公園
サンプルデータは、みんな大好き「北緯35度、東経135度付近」の数値を使うことにしました。第5回で紹介した兵庫県西脇市の「日本のへそ」がある場所です。
この場所には測地系の違いにより「大正のへそ」と「平成のへそ」の2つの35-135ポイントがありますが、今回はSRID 6668(JGD2011 現JGD2024)を使いますので、POINT(35 135) は平成のへそのほうを指していることになります。
今回の比較の基準とするポリゴン
さて、この点を左下の角として、北東方向それぞれに0.01度ずつ広げた長方形を「基準ポリゴン」とします。北緯35度付近では緯度0.01度が約1.1km、経度0.01度が約0.9kmになります。今回のテーマである様々な関数の動作を試す、というだけの話ではここまで意識する必要はありませんが、このように日頃から「地図感覚」を高めておくと、きっと何かの時に役に立ちます。
mysql> SELECT ST_Length(ST_GeomFromText('LINESTRING(35 135, 35.01 135)',6668)) lat001;
+--------------------+
| lat001 |
+--------------------+
| 1109.4148613032885 |
+--------------------+
1 row in set (0.000 sec)
mysql> SELECT ST_Length(ST_GeomFromText('LINESTRING(35 135, 35 135.01)',6668)) lon001;
+-------------------+
| lon001 |
+-------------------+
| 912.8817248239576 |
+-------------------+
1 row in set (0.000 sec)今回の基準ポリゴンは、以下のようにして @poly 変数に入れておくことにしましょう。
mysql> SET @poly = ST_GeomFromText(
-> 'POLYGON((35 135, 35 135.01, 35.01 135.01, 35.01 135, 35 135))', 6668);
Query OK, 0 rows affected (0.00 sec)この基準ポリゴンに、いろいろな位置関係にあるLINESTRINGを用意して、さまざまな判定関数の結果を見ていきます。
比較する相手を用意する(LINESTRING)
この基準ポリゴンに、いろいろな位置関係にあるLINESTRING図形を5個用意しました。
まずは登録するテーブルを作成して、LINESTRINGデータを登録します。
mysql> CREATE TABLE g27 (
-> id INTEGER PRIMARY KEY,
-> name VARCHAR(20) ,
-> g GEOMETRY SRID 6668 NOT NULL
-> );
Query OK, 0 rows affected (0.224 sec)
mysql> INSERT INTO g27 VALUES
-> (1, 'line1_out', ST_GeomFromText('LINESTRING(35.011 135.002, 35.011 135.004)', 6668)),
-> (2, 'line2_stab',ST_GeomFromText('LINESTRING(35.009 134.995, 35.007 135.003)', 6668)),
-> (3, 'line3_through', ST_GeomFromText('LINESTRING(35.003 134.995, 35.007 135.012)', 6668)),
-> (4, 'line4_inside', ST_GeomFromText('LINESTRING(35.001 135.004, 35.003 135.007)', 6668)),
-> (5, 'line5_online', ST_GeomFromText('LINESTRING(35.002 135.01, 35.005 135.01)', 6668));
Query OK, 5 rows affected (0.043 sec)
Records: 5 Duplicates: 0 Warnings: 05つのLINESTRING図形は、基準ポリゴンの外にあるもの、外から入ってきて中で止まるもの(突き刺し)、突き抜けるもの(貫通)、完全に中に収まるもの、基準ポリゴンの辺の上にぴったり乗っている線を用意しました。
登録した各LINESTRINGと基準ポリゴンの関係は下図のようになります。
登録結果をデータでも確認してみましょう。ST_GeometryType()や ST_SRID() の関数でジオメトリの型やSRIDを確認できます。基準ポリゴンに対してどのような位置関係にあるかの日本語説明を、クエリ結果の右側に追記しました。
mysql> SELECT id, name, ST_SRID(g) SRID, ST_GeometryType(g) type FROM g27;
+----+---------------+------+------------+
| id | name | SRID | type |
+----+---------------+------+------------+
| 1 | line1_out | 6668 | LINESTRING | 完全に外
| 2 | line2_stab | 6668 | LINESTRING | 突き刺し
| 3 | line3_through | 6668 | LINESTRING | 貫通
| 4 | line4_inside | 6668 | LINESTRING | 完全に中
| 5 | line5_online | 6668 | LINESTRING | 境界線上
+----+---------------+------+------------+
5 rows in set (0.000 sec)8つの判定関数
登録したこれらのデータに、以下の8つのジオメトリ判定関数を並べて実施してみることにします。
戻り値はどれも 1(TRUE)か 0(FALSE)、またはNULLです。
ST_Equals(A, B) 図形として同じか
ST_Disjoint(A, B) まったく共有点がないか
ST_Intersects(A, B) 共有点がひとつでもあるか
ST_Touches(A, B) 境界だけで接しているか
ST_Crosses(A, B) 突き抜けているか
ST_Overlaps(A, B) 部分的に重なっているか
ST_Within(A, B) AがBに含まれるか
ST_Contains(A, B) AがBを含むか線とポリゴンの関係の把握
まずは線5本から。8つの関数を横に並べて、5本の線それぞれについて結果を出します。
mysql> SELECT id, name,
-> ST_Equals(@poly, g) Eql,
-> ST_Disjoint(@poly, g) Dsj,
-> ST_Intersects(@poly, g) Its,
-> ST_Touches(@poly, g) Tch,
-> ST_Crosses(g, @poly) Crs,
-> ST_Overlaps(@poly, g) Ovl,
-> ST_Within(g, @poly) Wtn,
-> ST_Contains(@poly, g) Con
-> FROM g27 WHERE name LIKE 'line%' ORDER BY id;
+----+---------------+------+------+------+------+------+------+------+------+
| id | name | Eql | Dsj | Its | Tch | Crs | Ovl | Wtn | Con |
+----+---------------+------+------+------+------+------+------+------+------+
| 1 | line1_out | 0 | 1 | 0 | 0 | 0 | NULL | 0 | 0 |
| 2 | line2_stab | 0 | 0 | 1 | 0 | 1 | NULL | 0 | 0 |
| 3 | line3_through | 0 | 0 | 1 | 0 | 1 | NULL | 0 | 0 |
| 4 | line4_inside | 0 | 0 | 1 | 0 | 0 | NULL | 1 | 1 |
| 5 | line5_online | 0 | 0 | 1 | 1 | 0 | NULL | 0 | 0 |
+----+---------------+------+------+------+------+------+------+------+------+
5 rows in set (0.001 sec)1つ1つ確認していきましょう。必要に応じて MySQL Reference Manual の Spatial Functionのページ、およびそのリンク先を参照します。
ST_Equals()
2つのジオメトリの一致を判定します。今回はPOLYGON vs LINESTRINGなので絶対に一致することはありませんから、すべて 0 (FALSE)判定となったことは納得ができます。
ST_Disjoint()
「交わらないこと」の判定を行う関数です。まったくPOLYGONの外側にある線は1(=交わらない)と判定されました。完全にPOLYGONの中に入っているline4_insideを含め、少しでも中に入っていれば 0(=交わらなくない)と判定されていることも読み取れます。また、POLYGONの境界線上にぴったり重なる線も同様に0と判定されています。
ST_Intersects()
前回紹介した「交わる」判定です。ST_Disjoint()の反対(NOT)なので、結果もそのようになっています。POLYGONの線上にある line5_onlineは「交わる」と判定されていることに注目です。
ST_Touches()
POLYGONの線上に重なるLINESTRINGだけが「1(TRUE)」と評価されました。ST_Touchesは文字通り「触れている」ことを判定する関数で、突き抜けてしまったり中に入っているものは対象になりません。
追加で確認してみましょう。基準ポリゴンの東側にぴたっと「触れる」線(@tch_sample1)と、微妙に触れない線(@tch_sample2)で試してみます。基準ポリゴンの東端の線は東経 135.01度なので、@tch_sample1はこの経度から東に向かって伸びる線、@tch_sample2はここより少し東側となる 135.0105度から東側に延びる線を用意しました。

mysql> SET @tch_sample1 = ST_GeomFromText('LINESTRING(35.005 135.01, 35.008 135.015)',6668);
Query OK, 0 rows affected (0.000 sec)
mysql> SET @tch_sample2 = ST_GeomFromText('LINESTRING(35.004 135.0105, 35.007 135.014)',6668);
Query OK, 0 rows affected (0.000 sec)
mysql> SELECT ST_Touches(@poly, @tch_sample1) Tch1,
-> ST_Touches(@poly, @tch_sample2) Tch2;
+------+------+
| Tch1 | Tch2 |
+------+------+
| 1 | 0 |
+------+------+
1 row in set (0.000 sec)触れている @tch_sample1だけが 1(TRUE)と判定されることが確認できました。
言葉から得られる印象で「ポリゴンに触れる」というのは、そのポリゴンの内側にタッチしても良さそうな気がするので、他のものも 1 になりそうなものですが、実験からはポリゴンが相手の時にはその「境界線にタッチし、内側には入り込んでいない」ことを判定する関数であることが分かります。(ここでは例を示しませんが、「内側から」境界線に触れるようなものは 0(FALSE)になります。興味がある方は試してみてください)。
ST_Crosses()
ポリゴンの境界線を横切る2つのLINESTRINGが 1(TRUE)と判定されています。実はこの関数だけ判定のSQLの中で、引数の順序を入れ替えています。
ST_Crosses(g, @poly) Crs,これは「第一引数がポリゴンの場合はNULLを返す」という関数の仕様を回避するために、引数を入れ替えたものです。
https://dev.mysql.com/doc/refman/9.7/en/spatial-relation-functions-object-shapes.html#function_st-crosses
ST_Crosses() handles its arguments as described in the introduction to this section except that the return value is NULL for these additional conditions:
g1 is of dimension 2 (Polygon or MultiPolygon).
g2 is of dimension 1 (Point or MultiPoint). 【要約】ST_Crosses()は以下の時に戻り値をNULLにする:g1がPOLYGONのとき。g2がPOINTのとき(注:Pointは本来 dimension 0 なので、マニュアルの誤記の可能性があります)。
このように「やってみなければ気づかない」という細かい仕様もたくさんあるので、ぜひ色々なケースで関数を試していただきたいところです。
ST_Overlaps()
部分的に重なる部分があるかを判定する関数です。リファレンスマニュアルによると「2つの引数の次元が異なる場合、NULLを返す」ということで、今回の POLYGON vs LINESTRING ではその位置関係によらず、常にNULLが返されます。
ST_Within()
第1引数が第2引数に含まれるかを判定する関数です。今回は「線がポリゴンに含まれるか」を判定したいので、第1引数に LINESTRING であるg列を与えています。
ST_Within(g, @poly) Wtn,完全にポリゴンの中に含まれている line4_inside だけが 1(TRUE)になります。結果を見ると、境界線上の line5_onlineは 0 となっています。境界線上はまだ中に入っていないということなのでしょう。
ST_Contains()
第1引数が第2引数を含むかを判定する関数です。今回は「ポリゴンが線を含むか」を判定したいので、以下のように引数を与えています。
ST_Contains(@poly, g) ConST_Within()とST_Contains()は引数が逆の関係にありますが、英語感のない私はいつも引数の順序が分からなくなり、リファレンスマニュアルを引くはめになります。
おわりに
今回は、POLYGONとLINESTRINGの位置関係を判定する関数をまとめて紹介しました。実際に動かしてみると「この関数はあの関数の逆(NOT)」だとか「境界値で突き抜けるか触れているだけかで結果が変わる」など、さまざまな気づきがあるものです。関数の動作を知りたくなったら、ぜひ「実際にサンプルを作って動かしてみて、そしてマニュアルも確認する」という習慣をつけてほしいところです。一気に理解が進むこと間違いなしです(あるいは更に分からない部分が出てきて、自分の理解度合いを把握できるかもしれません)。
次回は、POLYGON vs POLYGONの関係判定を紹介します。今回も、表示用の地図データはOpenStreetMapデータを使用しました。
この記事をシェアしてください
