MySQLで学ぶGIS入門 第28回

MySQLの「ST_Equals()」や「ST_Overlaps()」など8つの関数でポリゴンどうしの位置関係を見比べてみよう

第28回の今回は、前回に引き続きポリゴンどうしの位置関係で8つの判定関数の結果を一気に比較し、「ST_Equals()」関数の同一判定の仕組みや「ST_Touches()」関数の意外な挙動について解説します。

坂井 恵 (さかい けい)

6:25

はじめに

前回は、1つの基準ポリゴンにいろいろな位置関係にある5本のLINESTRINGを用意し、8つの判定関数の結果を見比べてみました。今回はその続きとして、LINESTRINGの代わりに相手をPOLYGONに変えて同じことをやってみます。基準ポリゴンは前回と同じです。

前回のおさらいと基準ポリゴン

舞台は前回と同じ、兵庫県西脇市の日本へそ公園付近です。北緯35度・東経135度の点を左下のカドとして、北と東にそれぞれ0.01度ずつ広げた長方形を「基準ポリゴン」として @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)

比較に使う8つの関数も前回と同じです。忘れてしまった方のために再掲しておきます。

 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を含むか

比較する相手を用意する(POLYGON)

今回用の比較相手のジオメトリデータとして、前回作成した g27 テーブルに、5つのPOLYGONを追加登録します。

mysql> INSERT INTO g27 VALUES
    -> (11, 'poly1_inside', ST_GeomFromText('POLYGON((35.008 135.0085, 35.008 135.009, 35.009 135.009, 35.009 135.0085, 35.008 135.0085))', 6668)),
    -> (12, 'poly2_outside', ST_GeomFromText('POLYGON((35.008 135.011, 35.008 135.015, 35.012 135.015, 35.012 135.011, 35.008 135.011))', 6668)),
    -> (13, 'poly3_cross', ST_GeomFromText('POLYGON((34.998 134.997, 34.998 135.003, 35.003 135.003, 35.003 134.997, 34.998 134.997))', 6668)),
    -> (14, 'poly4_touch', ST_GeomFromText('POLYGON((34.996 135.006, 34.996 135.011, 35 135.011, 35 135.006, 34.996 135.006))', 6668)),
    -> (15, 'poly5_same', ST_GeomFromText('POLYGON((35 135.01, 35.01 135.01, 35.01 135, 35 135, 35 135.01))', 6668));
Query OK, 5 rows affected (0.051 sec)
Records: 5  Duplicates: 0  Warnings: 0

前回と同様に、登録結果のデータ概要を確認しておきましょう。今回は g27 テーブルに前回のLINESTRINGと今回のPOLYGONが同居しているので、ST_GeometryType()関数を使ってPOLYGONで絞り込んでみます。

mysql> SELECT id, name, ST_SRID(g) SRID, ST_GeometryType(g) type
    ->   FROM g27 WHERE ST_GeometryType(g) = 'POLYGON' ORDER BY id;
+----+---------------+------+---------+
| id | name          | SRID | type    |
+----+---------------+------+---------+
| 11 | poly1_inside  | 6668 | POLYGON | 完全に中
| 12 | poly2_outside | 6668 | POLYGON | 完全に外
| 13 | poly3_cross   | 6668 | POLYGON | 一部重なる
| 14 | poly4_touch   | 6668 | POLYGON | 辺で接する
| 15 | poly5_same    | 6668 | POLYGON | 同じ形
+----+---------------+------+---------+
5 rows in set (0.000 sec)

今回新たに用意した比較対象のポリゴンは、以下の5つです。

  • 基準ポリゴンの中に完全に収まっているもの
  • 完全に外にあるもの
  • 左下の角のあたりで一部が重なっているもの
  • 下の辺の一部を共有して接しているもの
  • 基準ポリゴンとまったく同じ形のもの

最後の1つには少しだけ仕掛けがしてあるのですが、それは後ほど。

登録した各ポリゴンと基準ポリゴンの関係は下図のようになります。真ん中の大きい四角が基準ポリゴンです。

ポリゴンとポリゴンの関係の把握

それでは、前回とまったく同じ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(@poly, g)    Crs,
    ->        ST_Overlaps(@poly, g)   Ovl,
    ->        ST_Within(g, @poly)     Wtn,
    ->        ST_Contains(@poly, g)   Con
    ->   FROM g27 WHERE name LIKE 'poly%' ORDER BY id;
+----+--------------+-----+-----+-----+-----+------+-----+-----+-----+
| id | name         | Eql | Dsj | Its | Tch | Crs  | Ovl | Wtn | Con |
+----+--------------+-----+-----+-----+-----+------+-----+-----+-----+
| 11 | poly1_inside |   0 |   0 |   1 |   0 | NULL |   0 |   1 |   1 |
| 12 | poly2_outside|   0 |   1 |   0 |   0 | NULL |   0 |   0 |   0 |
| 13 | poly3_cross  |   0 |   0 |   1 |   0 | NULL |   1 |   0 |   0 |
| 14 | poly4_touch  |   0 |   0 |   1 |   0 | NULL |   1 |   0 |   0 |
| 15 | poly5_same   |   1 |   0 |   1 |   0 | NULL |   0 |   1 |   1 |
+----+--------------+-----+-----+-----+-----+------+-----+-----+-----+
5 rows in set (0.003 sec)

前回の表と見比べながら、1つ1つ確認していきましょう。

ST_Equals()

2つのジオメトリが一致することを確認する関数です。前回はPOLYGON対LINESTRINGだったため、一致することはなく全行が 0 でしたが、今回ついに 1 が出ました。poly5_sameだけが1(TRUE)です。完全に重なっているのだから当然だと感じる方も多いと思いますが、実はこの関数の挙動には面白い点があるので、後ほど解説します。

ST_Disjoint() と ST_Intersects()

重ならないことを判定する関数と重なることを判定する関数です。この2つの関数は前回同様、きれいに反対の結果になっています。完全に外にあるpoly2_outsideだけがST_Disjoint()が1、ST_Intersects()が0となり、その他のポリゴンとの判定はすべてその逆となりました。相手が線でもポリゴンでも同じように内外判定に使えます。

ST_Touches()

下の辺の一部を共有しているpoly4_touchだけが1(TRUE)になってほしいところでしたが、、、、なんと、これも含めてすべてが 0(FALSE)判定になりました。実はこの挙動は直感に反しますが、理由がある動きであり、MySQLの独特なクセとして注意すべきポイントでもあります。じっくり解説したいので、今後の回で改めて紹介していきたいと思います。

ちなみに、今回の基準ポリゴンは別の部分、例えば東端の縦線部分に接するようなポリゴンであればタッチ判定されます。

mysql> SET @east_touch = ST_GeomFromText(
    ->       'POLYGON((35.003 135.01, 35.003 135.015, 35.006 135.015, 35.006 135.01, 35.003 135.01))', 6668);

mysql> SELECT ST_Touches(@poly, @east_touch)   Tch,
    ->        ST_Intersects(@poly, @east_touch) Its;
+------+------+
| Tch  | Its  |
+------+------+
|    1 |    1 |
+------+------+
1 row in set (0.000 sec)

ST_Crosses()

全行NULLになりました。前回、この関数だけ引数の順序を入れ替えたことを覚えているでしょうか。「第1引数がポリゴンだとNULLを返す」という仕様を回避するためでした。今回はどちらもポリゴンなので、どのように書いてもNULLが返ることになります。

つまり、ST_Crosses()はポリゴンどうしの比較には使えない関数だということです。

ST_Overlaps()

こちらは逆に、前回は全行NULLだったのが、今回は値を得ています。「2つの引数の次元が異なる場合はNULL」という仕様なので、両方ポリゴンである今回のケースでは意味を持つことになります。

ST_Overlaps()は一部が重なっていることを判定する関数です。完全に内部に含まれてしまったものや、完全に一致するものについては 0(FALSE)となります。それらを判定するには、ST_Contains()やST_Equals()を使うと良いでしょう。

ST_Within() と ST_Contains()

内部に含まれているかを判定する関数です。「AがBを含むか」「AがBに含まれるか」という、引数の順序が違うだけで同じ判定をする関数でしたね。完全に中に入っているpoly1_insideが1(TRUE)です。そしてpoly5_sameも1になっています。「まったく同じ形」も「含まれる(含む)」判定となることが分かりました。

「同じ図形」とは何か

さて、ST_Equals()のところで「同じ」と判定された図形について、追加で解説します。この2つのポリゴンは形としては同じものですが、実はあえて与え方に少し変化を加えていました。2つのポリゴンを ST_AsText()関数を使って確認してみましょう。

mysql> SELECT ST_AsText(@poly);
+-----------------------------------------------------------+
| ST_AsText(@poly)                                          |
+-----------------------------------------------------------+
| POLYGON((35 135,35 135.01,35.01 135.01,35.01 135,35 135)) |
+-----------------------------------------------------------+

mysql> SELECT ST_AsText(g) FROM g27 WHERE name='poly5_same';
+--------------------------------------------------------------+
| ST_AsText(g)                                                 |
+--------------------------------------------------------------+
| POLYGON((35 135.01,35.01 135.01,35.01 135,35 135,35 135.01)) |
+--------------------------------------------------------------+

座標部分だけを抜き出して、同じ頂点となる部分を縦に並ぶように整形してみます。

35 135,35 135.01,35.01 135.01,35.01 135,35 135
       35 135.01,35.01 135.01,35.01 135,35 135,35 135.01

1つめは、(35 135)から始まるひとまわりの図形、2つめは (35 135.01)からはじまるひとまわりの図形となっています。結果として同じポリゴン図形になっていますが、それぞれ異なる始点を採用しているのです。

このような2つのポリゴンどうしの場合でも、図形として同じであれば ST_Equals()は同一だと判定してくれるのが素晴らしいところです。

ST_AsText()関数を使ってテキスト化したポリゴンどうしでは一致を判定できないことを、以下のクエリで確認してみましょう。並べて見ると一目瞭然ですね。

mysql> SELECT ST_AsText(@poly) = ST_AsText(g) text_same,
    ->        ST_Equals(@poly, g)             geom_same
    ->   FROM g27 WHERE name = 'poly5_same';
+-----------+-----------+
| text_same | geom_same |
+-----------+-----------+
|         0 |         1 |
+-----------+-----------+
1 row in set (0.000 sec)

おわりに

前回と今回の2回にわたって、8つのジオメトリ判定関数を一気に確認する手法と、その結果について解説をしてきました。1つ1つの関数を見ているだけでは理解しにくかった違いなども、並べて見るとそれぞれの関数のクセや特徴などが浮かび上がってきたのではないでしょうか。

次回は、触れていそうな2つのポリゴンがなぜST_Touches()で FALSEと判定されたのか、その謎に迫ります。

今回も、表示用の地図データはOpenStreetMapデータを使用しました。

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored