PostGISとMySQL8のGIS機能の違い

本記事は[PostgreSQL Advent Calendar 2019](https://qiita.com/advent-calendar/2019/postgresql)の7日目となります。

# はじめに

私は普段PostGISを使っていますが、最近MySQL 8.0.xのGIS機能について調査する機会がありました。
本記事は筆者が調査の中で気づいた両者の違いをまとめたものです。

PostGISは2.x〜3.x、MySQLは8.0.17を対象としています。

両者は共にOpenGIS[^1]やSQL/MMなどの標準に基づく実装を進めているため共通している部分も多いものの、独自拡張や実装の違いによりいくつかの違いもあります。

[^1]: Open Geospatial Consortium, Inc.（OGC）が策定した技術仕様

本記事の目的は両者の優劣をつけることではありません。
両者の違いを理解して、場面に応じた適切な使い方を選択するための一助となることが目的です。

# 所感

本文に入る前に、私の所感を述べておきたいと思います。

まず、**普段PostGISを使っている人**が、MySQLのGIS機能に乗り換えるメリットは現時点では少ないかもしれません。それは現時点では機能面、安定性、速度等の面で、PostGISに一日の長があると言わざるを得ないからです。

しかし、**MySQLのGIS機能を使う上でPostGISで培った知識は役に立ちます**。それは先に述べたとおり両者が同じ標準に基づく実装を進めていることもその理由の一つです。**PostGISユーザにとっても**MySQLのGIS機能が進化することによって**GISデータを扱う手法の選択肢が増える**のは喜ばしいことだと思います。(少なくとも私は嬉しい)
そして、PostGISユーザはMySQLのGIS機能を試してみて機能追加リクエストやバグ報告をすることで、**MySQLのGIS機能の発展に寄与することができます**。

次に、**MySQLのユーザにとっては、より自在に地理空間情報が扱えるようになった**ことは喜ばしいことでしょう。
もし、更に強力なGIS機能が必要であればPostGISを使ってみて(あるいはこの記事を読んでみて)、MySQLの開発チームに機能追加をリクエストすることもできます。

# 両者の違い

ざっと並べてみました。以降はこの項目毎に詳しく解説します。

| 項目 | PostGISのGIS機能 | MySQL8のGIS機能 |
|:--- |:--- | :---|
| 利用方法 | PostgreSQL本体とは別に、拡張機能としてインストール| MySQL本体に含まれているのでそのまま利用可能 |
| 集約関数 | 対応 | 非対応 |
| データのインポート| 同梱ツール(shp2pgsql)を利用、あるいはogr2ogr、osm2pgsql等の外部ツールを利用 | ogr2ogr等の外部ツールを利用 |
| ドキュメント | [PostGISのドキュメント](https://postgis.net/docs/manual-3.0/postgis-ja.html) | [MySQL本体のドキュメント内](https://dev.mysql.com/doc/refman/8.0/en/spatial-analysis-functions.html) |
| WKTの座標指定順(axis order) | 経度 - 緯度 | 緯度 - 経度 |
| 関数の数 | 200強 | 100弱 |
| 周辺ツール | QGIS、pgRouting | MySQL Workbench |
| その他 | Materialized Viewとの組み合わせが強力 | 一部の関数でインデックスが使われないというバグが存在する |

## 利用方法

* PostGIS
    * PostgreSQL本体とは別に、拡張機能としてインストール
* MySQL
    * MySQL本体に含まれているのでそのまま利用可能

MySQL8利用者であれば**誰でもGIS機能が使える**ということです。
利用者にとって、非常にメリットが大きい特徴です。

PostGISはPostgreSQL本体には取り込まれておらず、拡張機能として実装されています。これは本体のリリースサイクルに左右されないなどの開発者にとってはメリットでもありますが、利用者にとってハードルが高いことは否めません。

また、**今後、MySQL8が使えるレンタルサーバではGIS機能が使える**という状況が増えることは非常にワクワクします。
これは、PostgreSQLが使えるレンタルサーバで必ずしもPostGIS拡張が有効になっているわけではない、という状況とは違います。ただ、そもそもPostgreSQLが使えるレンタルサーバそのものが少ないのですが。

ちなみに、PostgreSQLの主要なマネージドサービスであるAmazon RDS、Azure Database、Google Cloud SQLでは、いずれもPostGIS拡張が有効になっています。

## 集約関数

* PostGIS
    * 対応
* MySQL
    * 非対応

これは非常に重要な機能です。MySQLにもぜひ対応して欲しい。

例えばST_Union関数をみてみましょう。
ST_Union関数はPostGISとMySQLそれぞれに存在し、その基本的な機能はジオメトリ(図形)の結合です。

PostGISとMySQLのST_Unionは共通して2つのジオメトリを結合する機能を提供します。

```SQL:MySQLのST_Union関数の例
mysql> SET @g1 = ST_GeomFromText('LineString(1 1, 3 1)');
mysql> SET @g2 = ST_GeomFromText('LineString(1 3, 3 3)');
mysql> SELECT ST_AsText(ST_Union(@g1, @g2));
+--------------------------------------+
| ST_AsText(ST_Union(@g1, @g2))        |
+--------------------------------------+
| MULTILINESTRING((1 1,3 1),(1 3,3 3)) |
+--------------------------------------+
1 row in set (0.00 sec)
```

```SQL:PostGISのST_Union関数の例(非集約関数版)
opendata=# \set g1 'ST_GeomFromText(''LineString(1 1, 3 1)'')'
opendata=# \set g2 'ST_GeomFromText(''LineString(1 3, 3 3)'')'
opendata=# SELECT ST_AsText(ST_Union(:g1, :g2));
              st_astext
--------------------------------------
 MULTILINESTRING((1 1,3 1),(1 3,3 3))
(1 row)
```

PostGISのみで提供されるST_Unionの機能として、いわゆる**集約関数としても動作する**点が挙げられます。
つまり、2つだけではなく**複数のジオメトリを結合することができる**のです。

```SQL:PostGISのST_Union関数の例(集約関数版)
opendata=# CREATE TABLE test(geom GEOMETRY);
CREATE TABLE
opendata=# INSERT INTO test(geom) VALUES(ST_GeomFromText('LineString(1 1, 3 1)'));
INSERT 0 1
opendata=# INSERT INTO test(geom) VALUES(ST_GeomFromText('LineString(1 3, 3 3)'));
INSERT 0 1
opendata=# INSERT INTO test(geom) VALUES(ST_GeomFromText('LineString(1 5, 3 5)'));
INSERT 0 1
opendata=# SELECT ST_AsText(geom) FROM test;
      st_astext
---------------------
 LINESTRING(1 1,3 1)
 LINESTRING(1 3,3 3)
 LINESTRING(1 5,3 5)
(3 rows)

opendata=# SELECT ST_AsText(ST_UNION(geom)) FROM test;
                   st_astext
------------------------------------------------
 MULTILINESTRING((1 1,3 1),(1 3,3 3),(1 5,3 5))
(1 row)
```

これは非常に強力な機能です。

例えば、市区町村のポリゴンが格納されたテーブルから、県のポリゴンを生成することができたりします。

## データのインポート

* PostGIS
    * 同梱ツール(shp2pgsql)を利用、あるいはogr2ogr、osm2pgsql等の外部ツールを利用
* MySQL
    * ogr2ogr等の外部ツールを利用

PostGISに同梱されるshp2pgsqlは非常に強力です。

取り込むシェープファイルの文字コード指定や、空間参照系(SRS)を変換しながらのインポートにも対応しています。
また、何よりPostGISに同梱されているという点で、利用のハードルが低いという点も優れています。

それに対して、MySQLは現状、GISデータを取り込むためのツールを同梱していないため、別途用意する必要があります。
せっかくGIS機能が標準機能として取り込まれているのに勿体ない。

MySQLにシェープファイルをインポートする方法については下記の記事で解説しています。

https://qiita.com/miyauchi/items/c8349e1e6339bdf26a20

また、OpenStreetMapのデータを取り込むosm2pgsqlを使えば、道路データを取り込むことが可能です。
さらにその道路データを地図タイルにレンダリングするなどの活用例も事例が豊富です。

## ドキュメント

* PostGIS
    * [PostGISのドキュメント](https://postgis.net/docs/manual-3.0/postgis-ja.html)
* MySQL
    * [MySQL本体のドキュメント内](https://dev.mysql.com/doc/refman/8.0/en/spatial-analysis-functions.html)

PostGISは拡張機能であるため専用のドキュメントが整備され、MySQLのGIS機能は本体のドキュメント内の一部として整備されています。

## WKTの座標指定順(axis-order)

* PostGIS
    * 経度 - 緯度
* MySQL
    * 緯度 - 経度

単純に言うと、座標(緯度経度)の指定順が異なる場合があります。地理空間参照系(SRS)によります。
少なくとも、よく使われるWGS84(SRID=4326)では逆になっています。

PostGISのWKTでは経度緯度の指定順(axis order)は経度(lon)、緯度(lat)です。

```
opendata=# SELECT ST_AsText(ST_GeomFromText('POINT(139.708056 35.628611)', 4326));
```

対するMySQLのWKTでは緯度経度の指定順は緯度(lat)、経度(lon)です。

```
mysql> SELECT ST_AsText(ST_GeomFromText('POINT(35.628611 139.708056)', 4326));
```

MySQLではaxis orderを明示的に指定することができます。この機能を使えばPostGISと同じaxis orderにすることが可能です。

```
mysql> SELECT ST_AsText(ST_GeomFromText('POINT(139.708056 35.628611)', 4326, 'axis-order=long-lat'));
```

axis orderの違いは「一律緯度、経度の順とする」か、「[EPSG](http://www.epsg.org/)の定義順に準拠する」かのポリシーの違いです。
利用者としてはどちらかに統一してほしいのですが、現状では異なっていることを認識した上で使用する必要があります。

参考: MySQL8.0のaxis orderについて - PC関係のメモ
https://blog.mylab.jp/posts/2018102801/

## 関数の数

* PostGIS
    * 200強
* MySQL
    * 100弱

2019/12/1時点のドキュメントの下記項で数えました。

14.11. PostGIS Function Support Matrix - PostGIS 3.0.0 マニュアル
https://postgis.net/docs/manual-3.0/postgis-ja.html#PostGIS_TypeFunctionMatrix
12.16.1 Spatial Function Reference - MySQL 8.0 Reference Manual 
https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html

多ければ良いというものではありませんが、重要なファクタです。
個人的にはやはり集約関数は早く実装してほしいところです。

集約関数以外だと、個人的にPostGISの下記の関数はMySQLでも早期に実装して欲しい。

* ST_SimplifyPreserveTopology
* ST_PointOnSurface

## 周辺ツール

* PostGIS
    * QGIS、pgRouting
* MySQL
    * MySQL Workbench

QGISはPostGISのデータを可視化したり、PostGISのデータと他の情報を重ねることもできて非常に強力です。
また、pgRoutingを使うことで経路探索もできます。

MySQLはまだ周辺ツールが少ないものの、公式ツールであるMySQL Workbenchはいち早くSpatialデータの可視化に対応しています。

## その他

* PostGIS
    * Materialized Viewとの組み合わせが強力
* MySQL
    * 一部の関数でインデックスが使われないというバグが存在する

処理が重たくなりがちなGISデータとPostgreSQLのMaterialized Viewの組み合わせは非常に強力です。

MySQLのGIS機能はまだバグも多く、特に致命的なのは一部の関数でインデックスが効いていないことです。

Bug #96311 ST_Intersects() is very slow on MySQL 8.0
Submitted: 24 Jul 9:02
https://bugs.mysql.com/bug.php?id=96311

Bug #96269 st_intersects mysql 8 does not use index
Submitted: 22 Jul 13:10
https://bugs.mysql.com/bug.php?id=96269

Bug #94655 Some GIS function do not use spatial index anymore
Submitted: 14 Mar 11:04
https://bugs.mysql.com/bug.php?id=94655

# まとめ

PostGISは成熟期で安定感がありますし、MySQLのGIS機能は導入期から成長期といった感じで日進月歩で進化するのを見ているのは非常に楽しいです。

ぜひ両者を使ってみてそれぞれの良さを体感してみて下さい！