😀

いくつかのシステムでポリゴンを左回りにするか右回りにするかを見て回る

2020/10/27に公開

はじめに

するどいコメント

PostGISユーザがSQL Server 2017の空間機能を少し触ってみた では、TSQLの空間機能についてざらっと触った結果を報告したわけですが、そこで、ozo360さんから次のようなコメントを頂きました。

STUnionの結果が「うん、これは同じ結果になりましたね」とありますが順番が異なっているように思います。

これは、PostGISでは右回り、TSQLでは左回り、と違っているので、本当にこれでいいの?というご指摘。

これについては、しっかりと教え諭してやらないといけないと思い、ありのままに書きました。

TSQLは左回りでPostGISが右回りだ、という話ですね。
正直言います、見落としてました。
もうこれは穴が合ったら入って啓蟄になってから出てきたいレベル。

事実です、恥ずかしい。

システム内でそろってればOKなので訂正はしません

でも、結果的に訂正をする必要は無い、と判断しました。

これまで、右手系と左手系でさんざんそろえろよとかなんとか言ってたので、ポリゴンが右回りと左回りとで違っても訂正しないのが分かりにくいかも知れません。が、右手系左手系の話でも、右回り左回りの話でもそうですが、システム内で統一されていれば、どちらでもいいです。システム内で統一されてないなら、まずい(あとあと泣きますよ)、ということです。

以上です

以上で終わってもいいかなと思うのですが、余談的に、いくつかのシステムでどちらに回しているかを見ていきましょう。

試験内容

ポリゴンが右回りか左回りかに敏感でなければならない関数(ここではST_Union)の引数に左回りポリゴンと右回りポリゴンを与え、返り値が左回りか右回りかを見ます。
関数内部では(たぶん)右回りか左回りかのどちらかにそろえてくる(はず)なので、返り値で判定できる(はず)。

さらに、左回りポリゴンを右回りに、右回りポリゴンを左回りに、それぞれ変更して引数として与えた結果も見てみます。2種の引数で返り値の方向が同じなら、それはそろえている、と言ってもいいのではないかと思います。

余談:ポリゴンが右回り左回りがどうでもいい関数もある

たとえば、ST_Area()。これは、外積(のZ成分の値)の合計を2で割ると求まりますが、右回りか左回りかで正負符号が変わります(これは、右回りか左回りかを判定する方法そのものです)。ただ、面積はその絶対値を取ればいいので、正負がどうであろうとどうでもいいので、右回りであろうが左回りであろうがどうでもいいです。

SQLServerの場合

左回り+右回り

SELECT (
  geometry::STGeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))',0)
).STUnion(
  geometry::STGeomFromText('POLYGON((1 0, 1 1, 2 1, 2 0, 1 0))',0)
).STAsText();
--------
POLYGON ((1 0, 2 0, 2 1, 2 2, 1 2, 1 1, 1 0)) 

右回り+左回り

SELECT (
  geometry::STGeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))',0)
).STUnion(
  geometry::STGeomFromText('POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))',0)
).STAsText();
--------
POLYGON ((1 0, 2 0, 2 1, 2 2, 1 2, 1 1, 1 0))

SQLServerのまとめ

左回りにしています。

PostGISの場合

左回り+右回り

SELECT ST_AsText(
  ST_Union(
    'POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))'::GEOMETRY,
    'POLYGON((1 0, 1 1, 2 1, 2 0, 1 0))'::GEOMETRY
  )
);
--------
POLYGON((1 1,1 2,2 2,2 1,2 0,1 0,1 1))

右回り+左回り

SELECT ST_AsText(
  ST_Union(
    'POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))'::GEOMETRY,
    'POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))'::GEOMETRY
  )
);
--------
POLYGON((1 1,1 2,2 2,2 1,2 0,1 0,1 1))

PostGISのまとめ

右回りにしています。

MySQLの場合

左回り+右回り

SELECT ST_AsText(
  ST_Union(
    ST_GeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))'),
    ST_GeomFromText('POLYGON((1 0, 1 1, 2 1, 2 0, 1 0))')
  )
);
--------
POLYGON((2 0,2 2,1 2,1 1,1 0,2 0))

右回り+左回り

SELECT ST_AsText(
  ST_Union(
    ST_GeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))'),
    ST_GeomFromText('POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))')
  )
);
--------
POLYGON((2 0,2 2,1 2,1 1,1 0,2 0))

MySQLのまとめ

左回りにしています。

SpatiaLiteの場合

左回り+右回り

SELECT ST_AsText(
  ST_Union(
    ST_GeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))'),
    ST_GeomFromText('POLYGON((1 0, 1 1, 2 1, 2 0, 1 0))')
  )
);
--------
POLYGON((1 1, 1 2, 2 2, 2 1, 2 0, 1 0, 1 1))

右回り+左回り

SELECT ST_AsText(
  ST_Union(
    ST_GeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))'),
    ST_GeomFromText('POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))')
  )
);
--------
POLYGON((1 1, 1 2, 2 2, 2 1, 2 0, 1 0, 1 1))

SpatiaLiteのまとめ

右回りにしています。

内部表現生成時に方向を強制するようにはなっていない

2019年1月8日になって、内部表現は左回りまたは右回りを強制しているかもしれない、と思いました。

WKTからパースした時点でどちらか一方に強制されても、上記のような結果が出るはずです。

WKTからパースした時点なのかST_Union()関数にかけた時点なのか、ちょっと気になったので確かめました。

右回り左回り両方のWKTから内部表現を生成し、この時点で方向を強制されているなら、ST_AsText()の出力に影響を与えるはずですので、観察してみます。

SQLServerの場合

左回り

SELECT (
  geometry::STGeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))',0)
).STAsText();
--------
POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))

右回り

SELECT (
  geometry::STGeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))',0)
).STAsText();
--------
POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))

PostgreGIS

左回り

SELECT ST_AsText(
  'POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))'::GEOMETRY
);
--------
POLYGON((1 1,2 1,2 2,1 2,1 1))

右回り

SELECT ST_AsText(
  'POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))'::GEOMETRY
);
--------
POLYGON((1 1,1 2,2 2,2 1,1 1))

## MySQLの場合

### 左回り

```sql
SELECT ST_AsText(
  ST_GeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))')
);
--------
POLYGON((1 1,2 1,2 2,1 2,1 1))

右回り

SELECT ST_AsText(
  ST_GeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))')
);
--------
POLYGON((1 1,1 2,2 2,2 1,1 1))

SpatiaLiteの場合

左回り

SELECT ST_AsText(
  ST_GeomFromText('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))')
);
--------
POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))

右回り

SELECT ST_AsText(
  ST_GeomFromText('POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))')
);
--------
POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))

このセクションでのまとめ

内部表現生成時に方向を強制するようにはなっていません。

全体のまとめ

右回り+左回りであっても、左回り+右回りであっても、MySQLとSQLServerは左回りになり、PostGISとSpatiaLiteは右回りになりました。

右回りの結果が出るか左回りの結果が出るかは、システム毎に違っていて、システム内では統一されている、と言えそうです。

また、左回り右回りの強制は、方向の強制を必要とする関数に渡った時点で強制されるものであり、内部表現生成時においては、まだ方向の強制はされていないようです。

ポリゴンを右回りにするか左回りにするかは、(たぶん)システム毎に決まってい(ると思いま)す(自信は無い)。

本記事のライセンス

クリエイティブ・コモンズ・ライセンス
この記事は クリエイティブ・コモンズ 表示 4.0 国際 ライセンス の下に提供されています。

Discussion