OSC2021 Online Hokkaido 参画

オープンソースカンファレンス北海道(オンライン)に参加してきました。

event.ospn.jp


オンラインイベントの良いところは、興味のある時間だけセミナーを聞いて、あとは日常生活を送れる点。
オフラインイベントの良いところは、会場にある意味物理的に拘束されてしまっているので、空いた時間に「少しだけ興味のある」セミナーを覗いたり、ブースで色々教えてもらえたりと、丸丸その時間全体をOSCでの吸収に当てられること。
個人的には、自分からご指名する程には興味はないのだけど何か面白そうなものに出会える場というのがOSCの本質だと思っているので、オンライン化の良い面は、私にとってのOSCとしては少し残念な変化でもあります。

 私、ラーメンが大好きなので、放っておくとラーメンばっかり食べるわけですが、やっぱり、自分が狙って食べるものだけでなく、他人に選んでもらったりその場の空気の中で違うものを選択したりすることで、いままで知らなかったものに出会えると思うのです。


MySQLユーザ会の発表

 日本MySQLユーザ会でひと枠をいただきいて、セミナーを開催しました。MySQLそのものの機能や使い方の最新情報は、比較的MySQLユーザ会のイベントなどでもよく聞けるものなので、考えた結果、やはりせっかくのOSC、つまりMySQLだけに興味があるわけじゃない人たちがいっぱい集まるイベント、ということで、『しばらくMySQLから離れていた人が最近のMySQL情報に触れて分かった気分になるための45分』と題して、MySQLの進化の歴史、最近のMySQLを理解するためのキーワードなどの紹介を含めて、ふんわりとしたお話をさせていただきました。  

発表資料を公開しています。
この資料をもとにしての「お話」をメインにしていますので、資料だけで理解できることは多くはないと思いますが、様々なキーワードとの「出会い」のきっかけとして活用していただけたらと思います。

www.slideshare.net

オンライン時代の発表ネタの難しさ

 オフラインイベントでOSCが開催されていた際には、同じテーマで少しだけ切り口を変化させながら全国のOSCで発表をしていたのですが、オンライン時代になって、主催は各地幹事でありながらも参加者自体は全国から、おそらく同じような人が、興味を持って参加してくれるようになりました。ネタの使い回しがしにくくなってきた感があります。つまり、毎回異なるテーマの発表を用意する必要があり、その結果、私自身のOSCへの参加頻度(発表頻度)も以前ほどではなくなってしまいました。(まぁもともと私は発表よりもブースでの交流を楽しみにOSCに参加していたので、オンライン化によりその楽しみの大部分を失ってしまったという面もあるのですが)
 実際のところ、運営の皆さん、参加される皆さんの感覚って、どうなんですかね。「これOSC北海道で聞いたよ。なんだよー」みたいな感じ(常に新ネタを期待している)のか、実のところ再演であってもあまり気にされていないのか。私としては今回発表したネタは、OSC的な雰囲気の中でなかなか良い感じに構成できたと感じているので、別の地域のOSCでの再演してもいいかもなぁと、少し考え始めているところです。

OSC北海道らしい北海道的なわけわからなさ

 自分の発表枠以外の時間は、自分の作業があり(スマホの機種変&設定だったんですけど)、あまり今回のOSCの全体像を追えていなかったのですが、なんか、実行委員長以下数人の方が、開催時間のあいだに道内を走り回っていたようです。いや、もう、率直に言ってわけわかんない。わけわからないのだけど、「椅子に縛り付けられるオンラインイベント」からの開放という試みとして、なんだかすごく面白そうなことをやっているような気もしました。地域名が冠してあっても、参加する側から見たら「タイムテーブルが公開されていて、時間になったら動画が流れてくる」だけの、変化のない勉強会。そんなオンラインイベントの弱い部分に対する挑戦、と捉えたら良いのかな。もう少し事前に意図を察知して、もう少しリアルタイムで追いかけていたらもっと私も盛り上がれたかなぁと、少々悔しい気分。

ということで

 タイミング(日程)が色々重なってしまって、自分の発表以外はあまり「参加感」を得られなかった今回のOSCですが(私が懇親会に顔も出さないなんて...)、事前に北海道のお菓子セットを取り寄せたりして、少しだけ北海道気分が盛り上がりました。せっかくの、地域名を冠したイベントに参加するのだから、自分の目の前で関心のある狭い領域の情報に触れるだけでなく、その地域に少しでも思いを寄せて、地域の文化や食や観光地(食、大事!!)に触れてみると、折角の「全国各地で」開催されているOSを一層楽しめるんじゃないかなと思います。
 あと、懇親会(YouTube Liveで少し拝見していました)でお話されていた事ですが、オンライン化によって、運営事務作業は別の地域のスタッフ経験者たちで回すことができるので、現地幹事の皆さんはコンテンツ作り(現地ならではの!)のほうに注力できるのでは、という考え方、良いと思いました。地域性が見えないオンラインイベントだからこそ、地域性の演出に楽しさが拡がりそうです。


 次回の北海道開催時には、現地にてまた皆さんと会い、ブースをうろうろして、おいしいもの食べて、無駄に寄り道をして、楽しみたいものです。



f:id:sakaik:20210624195824j:plain

MySQLをWindows(WSL)上でデバッグする環境を手に入れた(せじまさんの記事がすごい)

 WindowsのWSL上でMySQLをビルドし、VSCodeを使ってデバッグする方法を、せじまさんが公開してくださいました。
labs.gree.jp

私のこのエントリは、せじまさんの記事を読みながら実際に試してみた際に、考えたこと、苦労したこと、ちょっと変えてみたことなどの記録です。一番言いたいことは「せじまさんの記事すごい!リンク先ぜひ見て、ぜひ試してみて!」ですので、実際にやられる方はリンク先をご覧ください。
 リンクを貼った記事だけでなくその前後の記事も、少し異なる環境への対応がまとめられているので、参考になると思います。

私のスペック

  • Windows 10 Pro/20H2
  • i7-7770K
  • Memory: 32GB
  • WSL1 後に WSL2
  • WSL1 上に Ubuntu 29.94 環境インストール済
  • Visual Studio インストール済

WSL 環境(WSL2にしましょう)

 せじまさんの記事では WSL2 でということでしたが、私は WSL1 の環境で作業を始めました。大部分の工程をWSL1上で実施できましたが、最後の、VSCodeが起動された後に、どうしてもブレークポイントで止まらない、という事象に遭遇しました。そこで WSL2 にしたらブレークポイントで止まるようになったので、たぶん最初から WSL2 にしておくのが吉だと思います。


WSL1 から WSL2 への変更方法

 私がやったのは、以下の手順です。

  • PowerShellを 管理者として実行
  • PowerShell上で以下のコマンドを順次実行
    • wsl --set-default-version 2
    • wsl --list --verbose
      • で作成済みイメージのWSLバージョンを確認。私はubuntu-20.04を2にしたいので以下
    • wsl --set-version Ubuntu-20.04 2

gdb が必要

 私の環境にはまだ入れていなかったので、インストールしました。
記録取っていないのだけど、たぶん普通に

$ sudo apt-get install gdb 

とかやったのだと思う。

make オプションを少し変えてみた&メモリの使われ方

 せじまさんの記事では、make時にかなりメモリが使われるので、-j 4 のように絞ったほうがいいよ、とのことでしたが、私はとりあえずチャレンジングな心で "4" 指定なし(-jのみ)でmake実行して、無事最後まで通りました。
 タスクマネージャでメモリ使用量を目視していたところ、使用量の増減は結構あり、特に、進行82%くらいで20GB、84%付近で26GBくらい使用されたのがピークでした。(makeが終わったとき=常時=の使用量は9GB程度)

VSCode関連

 WSL[12]上の Ubuntu から、VSCodeを立ち上げることができるという事を知りませんでした。これすごい。指定したフォルダの情報を以て、VSCodeが立ち上がってくれるのですね。
 VSCode起動前に、launch.json と c_cpp_properties.json の2つのファイルを作成(せじまさんの記事からコピペ)する必要があるところ、私のポカで1つしか作らずにハマりました。ちゃんと読んで2つ作りましょう。

ブレークポイント

 とりあえずブレークポイントのお試しをするには、do_command() 関数(sql_parse.cc)が便利です。
F5を押してサーバを立ち上げた後、私は VSCode内の「ターミナル」で、mysqlコマンドを実行しました。

$ cd ~/mysql/mysql-8.0.25\bld
$ ./bin/mysql -uroot -p

 MySQLサーバに接続した「ターミナル」内で 適当なSQLコマンドを打つと、ブレークポイントで止まりました。あとは F10/F11 などを駆使して楽しめます。

まとまらないまとめ

 素晴らしい記事を公開してくださったせじまさんに、大大感謝です!
Windows上での MySQLビルドの際には、事前にソースコードにBOMを付ける作業が必要など、日本語環境では一手間ハードルがあったのですが、WSL2上での実施は、驚くほどスムーズでした(せじまさんが設定ファイル類を公開してくださっているおかげなのですが)。
 これで、私も久々に手元でのMySQLビルド&デバッグ環境が手に入ったので、さしあたって、@yoku0825 さんが作ってくれた PLEASE句パッチでも当てて遊んでみることにしますかね。



f:id:sakaik:20210524012009p:plain

MySQL8.0.24の新しいGIS関数(4)~CAST()を試す

 MySQL 8.0.24 では、Spatial機能(GIS機能)にもう一つの大きな進化がありました。CAST() 関数と CONVERT() 関数の Spatial型対応です。

CAST() のジオメトリ対応

 CAST()関数は、MySQL リファレンスマニュアルの以下の場所に記述があります。
MySQL :: MySQL 8.0 Reference Manual :: 12.11 Cast Functions and Operators

 様々なデータ型の変換(cast)を行う関数ですが、今回、ここに空間情報型(ジオメトリ型)の変換機能が追加されました。
 MySQL 8.0.24 以降の CAST() 関数で変換可能なジオメトリ型は、以下の図のとおりです。点線は、変換のための条件が結構厳しいもの(主観)、また、GEOMETRYCOLLECTION型へはすべての型からキャスト可能なので、* で記しました。

f:id:sakaik:20210515112345p:plain

 以下、それぞれの変換を試してみた結果を紹介します。


テーブルとデータの用意

 このエントリでは、以下のデータを使用します。(このエントリの途中で更に増えます)

CREATE TABLE t2 (id integer, g GEOMETRY);
INSERT INTO t2 VALUES (1, ST_GeomFromText('POINT(1 1)'));
INSERT INTO t2 VALUES (2, ST_GeomFromText('POINT(3 3)'));
INSERT INTO t2 VALUES (3, ST_GeomFromText('POINT(2 2)'));
INSERT INTO t2 VALUES (4, ST_GeomFromText('POINT(4 4)'));
INSERT INTO t2 VALUES (5, ST_GeomFromText('LINESTRING(1 2, 2 3, 3 4)'));
INSERT INTO t2 VALUES (6, ST_GeomFromText('LINESTRING(1 3, 1 4, 2 5, 1 3)'));
INSERT INTO t2 VALUES (7, ST_GeomFromText('LINESTRING(3 4, 4 2, 5 4)'));
INSERT INTO t2 VALUES (8, ST_GeomFromText('POLYGON((5 2, 7 3, 8 6, 6 5, 5 2))'));
INSERT INTO t2 VALUES (9, ST_GeomFromText('POLYGON((5 3, 7 4, 8 7, 6 7, 5 3),(6 4, 7 5, 6 5, 6 4))'));

 登録したデータを、ジオメトリ型とともに表示、確認してみます。

mysql> SELECT id, ST_AsText(g), ST_GeometryType(g) FROM t2;
+------+--------------------------------------------------+--------------------+
| id   | ST_AsText(g)                                     | ST_GeometryType(g) |
+------+--------------------------------------------------+--------------------+
|    1 | POINT(1 1)                                       | POINT              |
|    2 | POINT(3 3)                                       | POINT              |
|    3 | POINT(2 2)                                       | POINT              |
|    4 | POINT(4 4)                                       | POINT              |
|    5 | LINESTRING(1 2,2 3,3 4)                          | LINESTRING         |
|    6 | LINESTRING(1 3,1 4,2 5,1 3)                      | LINESTRING         |
|    7 | LINESTRING(3 4,4 2,5 4)                          | LINESTRING         |
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | POLYGON            |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | POLYGON            |
+------+--------------------------------------------------+--------------------+

POLYGON型からのMULTIPOLYGON

 POLYGON型からMULTIPOLYGON型への最もシンプルな変換は、要素をひとつだけ持つ MULTPOLYGON型への変換です。

mysql> SELECT id, ST_AsText(g), 
    ->        ST_AsText( CAST(g AS MULTIPOLYGON)) MP
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+------+--------------------------------------------------+---------------------------------------------------------+
| id   | ST_AsText(g)                                     | MP                                                      |
+------+--------------------------------------------------+---------------------------------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | MULTIPOLYGON(((5 2,7 3,8 6,6 5,5 2)))                   |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | MULTIPOLYGON(((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4))) |
+------+--------------------------------------------------+---------------------------------------------------------+

 カッコが多くなってやや見にくいですが、確かに、元のPOLYGONデータ値をひとつだけ含む MULTIPOLYGON 型が生成されていることがわかります。

 ST_Collect() を使って集約して、MULTIPOLYGON 型にすることもできます。

mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTIPOLYGON)) MP
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+---------------------------------------------------------------------------------+
| MP                                                                              |
+---------------------------------------------------------------------------------+
| MULTIPOLYGON(((5 2,7 3,8 6,6 5,5 2)),((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4))) |
+---------------------------------------------------------------------------------+
1 row in set (0.00 sec)

POLYGON型からMULTILINESTRING型へ

 これもイメージどおりの動作です。POLYGONとして記述したWKTの始点から終点までのLINESTRINGへと変換されます。中をくり抜く記述がされているPOLYGONは、外の要素、中の要素それぞれがの線で2要素の MULTILINESTRING になります。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+------+--------------------------------------------------+----------------------------------------------------------+
| id   | ORIGINAL                                         | CASTED                                                   |
+------+--------------------------------------------------+----------------------------------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | MULTILINESTRING((5 2,7 3,8 6,6 5,5 2))                   |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | MULTILINESTRING((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4)) |
+------+--------------------------------------------------+----------------------------------------------------------+

POLYGON型からLINESTRING型へ

 これは、変換可能な条件がやや厳しいです。中をくりぬく記述のあるPOLYGONは2要素以上から構成されるため、シングルの LINESTRINGにはへ感できません。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS LINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
ERROR 4032 (22S01): Invalid cast from POLYGON to LINESTRING.

 これは、id=9 のほうの POLYGON が2要素に変換されるため、LINESTRING へと変換できずにエラーとなったものです。
 内側をくり抜く指定のない id=8 のほうだけを指定すると、以下のとおり、変換できます。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS LINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON' AND id=8;
+------+--------------------------------+---------------------------------+
| id   | ORIGINAL                       | CASTED                          |
+------+--------------------------------+---------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2)) | LINESTRING(5 2,7 3,8 6,6 5,5 2) |
+------+--------------------------------+---------------------------------+

LINESTRING型からMULTILINESTRING型へ

 これは非常にシンプルです。単一の LINESTRING を、要素をひとつだけ持つ MULTILINESTRIN Gへ変換するのですから、何の心配事もありません。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+------+-----------------------------+------------------------------------+
| id   | ORIGINAL                    | CASTED                             |
+------+-----------------------------+------------------------------------+
|    5 | LINESTRING(1 2,2 3,3 4)     | MULTILINESTRING((1 2,2 3,3 4))     |
|    6 | LINESTRING(1 3,1 4,2 5,1 3) | MULTILINESTRING((1 3,1 4,2 5,1 3)) |
|    7 | LINESTRING(3 4,4 2,5 4)     | MULTILINESTRING((3 4,4 2,5 4))     |
+------+-----------------------------+------------------------------------+

 もちろん、ST_Collect() を使って LINESTRING を集約したものを、MULTILINESTRIGN にすることもできます。

mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+----------------------------------------------------------------------------------+
| CASTED                                                                           |
+----------------------------------------------------------------------------------+
| MULTILINESTRING((1 2,2 3,3 4),(1 3,1 4,2 5,1 3),(3 4,4 2,5 4),(1 3,2 5,1 4,1 3)) |
+----------------------------------------------------------------------------------+

 というかまぁこの例は、ST_Collect() 自体が集約結果を MULTI* 型にしてくれるので、明示的な CAST() なしでも同じ結果を得られるんですけどね。

mysql> SELECT ST_AsText( ST_Collect(g) ) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+----------------------------------------------------------------------------------+
| CASTED                                                                           |
+----------------------------------------------------------------------------------+
| MULTILINESTRING((1 2,2 3,3 4),(1 3,1 4,2 5,1 3),(3 4,4 2,5 4),(1 3,2 5,1 4,1 3)) |
+----------------------------------------------------------------------------------+

LINESTRING型からMULTIPOINT型へ

 これもシンプルですね。LINESTRINGを構成する各点をバラバラにして、MULTIPOINTに入れてくれるものです。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+------+-----------------------------+-------------------------------------+
| id   | ORIGINAL                    | CASTED                              |
+------+-----------------------------+-------------------------------------+
|    5 | LINESTRING(1 2,2 3,3 4)     | MULTIPOINT((1 2),(2 3),(3 4))       |
|    6 | LINESTRING(1 3,1 4,2 5,1 3) | MULTIPOINT((1 3),(1 4),(2 5),(1 3)) |
|    7 | LINESTRING(3 4,4 2,5 4)     | MULTIPOINT((3 4),(4 2),(5 4))       |
|   61 | LINESTRING(1 3,2 5,1 4,1 3) | MULTIPOINT((1 3),(2 5),(1 4),(1 3)) |
+------+-----------------------------+-------------------------------------+

LINESTRING型からPOLYGON型へ

 これが結構くせ者です。
まず、よくあるLINESTRING(始点と終点が異なる点)の場合を見てみましょう。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=5;
ERROR 4032 (22S01): Invalid cast from LINESTRING to POLYGON.

 この LINESTRING から POLYGON への変換はできない(輪になっていないのだからPOLYGONにはできない)というエラーが出ています。
 では次に id=6 として用意した、始点と終点が同じ点となっている LINESTRING を POLYGON に変換してみましょう。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=6;
ERROR 4033 (22S04): Invalid cast from LINESTRING to POLYGON. A polygon ring is in the wrong direction.

 先ほどとは異なるエラーが表示されました。これね、知らないとここで詰む話なのですが、POLYGONのリングには「向き」があるんです。私も聞きかじり(読みかじり)の知識なのですけど(たしか shapefile の仕様あたりに書いてあった気が)、POLYGONの線を辿ったときに、線の「右側」のほうが内側にならなければならないのです。なので最初のPOLYGONは右回り、最初に刳り抜く指定は左回り、、、のようになっている必要があります。たぶんこのルールに抵触したのでしょう。たぶん。
 ・・・・おや。
でも、id=6の LINESTRING を見てみると、右回りですね。。まぁとりあえず逆回りになるようにして、試してみましょう。

 逆回りとなる LINESTRING データを1件追加してみます(id=61)。

INSERT INTO t2 VALUES (61, ST_GeomFromText('LINESTRING(1 3, 2 5, 1 4, 1 3)'));
mysql> SELECT id, ST_AsText(g) FROM t2 WHERE id=61;
+------+-----------------------------+
| id   | ST_AsText(g)                |
+------+-----------------------------+
|   61 | LINESTRING(1 3,2 5,1 4,1 3) |
+------+-----------------------------+


 このLINESTRINGについて、POLYGONにCASTを試みます。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=61;
+------+-----------------------------+----------------------------+
| id   | ORIGINAL                    | CASTED                     |
+------+-----------------------------+----------------------------+
|   61 | LINESTRING(1 3,2 5,1 4,1 3) | POLYGON((1 3,2 5,1 4,1 3)) |
+------+-----------------------------+----------------------------+

 お。POLYGONになった!!
とりあえず直感と異なるけど、今日の所は3つの仮説を立てて、詳細後日追試してみることにします。
仮説(1)「右側が内側」という私の記憶が誤り。実は逆。
仮説(2)SRID=0 の時、x軸y軸は入れ替えた状態(横軸がy)で評価するものである、そういう仕様なのである
仮説(3)MySQLのバグ(そもそも逆の動作をしている説/SRIDによって動作がおかしい説)

POINT型からMULTIPOINT型

 これも、LINESTRING型から MULTILINESTRING型への変換と同様に、単体のものを1要素だけを持つMULTI*型に変換するもの、そしてグルーピングにより単体の集合を MULTIPOINTにまとめるものがあります。後者は ST_Collect 自体がMULTIPOINTに変更してくれるので実はCAST不要です。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POINT';
+------+------------+-------------------+
| id   | ORIGINAL   | CASTED            |
+------+------------+-------------------+
|    1 | POINT(1 1) | MULTIPOINT((1 1)) |
|    2 | POINT(3 3) | MULTIPOINT((3 3)) |
|    3 | POINT(2 2) | MULTIPOINT((2 2)) |
|    4 | POINT(4 4) | MULTIPOINT((4 4)) |
+------+------------+-------------------+
mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POINT';
+-------------------------------------+
| CASTED                              |
+-------------------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |
+-------------------------------------+

MULTIPOINT型からPOINT型

 MULTIPOINT型からの変換を確認する元データを格納するために、テーブル t21 を作成します。

CREATE TABLE t21 AS 
SELECT CAST(ST_Collect(g) AS MULTIPOINT) g
  FROM t2
 WHERE ST_GeometryType(g)='POINT';
mysql> SELECT ST_AsText(g) FROM t21;
+-------------------------------------+
| ST_AsText(g)                        |
+-------------------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |
+-------------------------------------+

 この MULTIPOINT型データからの CAST() を試しましょう。

mysql> SELECT ST_AsText( CAST(g AS POINT) ) P2LS
    ->   FROM t21;
ERROR 4032 (22S01): Invalid cast from MULTIPOINT to POINT.

 複数要素を持つ MULTIPOINT 型は 単一要素であるPOINT型へ変換できなことがわかります。
同じMULTIPOINTでも、要素がひとつしかない場合は POINT型にキャスト可能です。以下の例は、FROMサブクエリ内で 元のデータ(t2テーブル)のPOINTを一旦 1要素のみを持つMULTIPOINT型にした後、POINT型へのCAST()を試したものです。

mysql> SELECT id, ST_AsText(CASTED) ORG, ST_AsText( CAST(CASTED AS POINT)) BTP
    ->  FROM (
    ->     SELECT id, g , 
    ->            CAST(g AS MULTIPOINT) CASTED
    ->       FROM t2
    ->      WHERE ST_GeometryType(g)='POINT'
    ->       ) t;
+------+-------------------+------------+
| id   | ORG                      | BTP        |
+------+-------------------+------------+
|    1 | MULTIPOINT((1 1)) | POINT(1 1) |
|    2 | MULTIPOINT((3 3)) | POINT(3 3) |
|    3 | MULTIPOINT((2 2)) | POINT(2 2) |
|    4 | MULTIPOINT((4 4)) | POINT(4 4) |
+------+-------------------+------------+


 なお、要素数がひとつだけの場合に MULTI* → 単体型 への変換ができるので、予め ST_NumGeometries() 関数を使って要素数を確認し、要素数が1 のデータのみを変換対象にするなどの扱いをすることになろうかと思います。

mysql> SELECT ST_AsText(g), ST_NumGeometries(g) FROM t21;
+-------------------------------------+---------------------+
| ST_AsText(g)                        | ST_NumGeometries(g) |
+-------------------------------------+---------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |                   4 |
+-------------------------------------+---------------------+

MULTIPOINT型からLINESTRING型

 まさにこの変換が欲しかった!という機能。
なんらかのルールに基づき点の集合を MULTIPOINTにまとめた後、それらをつないで線にするといった活用法が考えられます。

mysql> SELECT ST_AsText(g) ORG, ST_AsText( CAST(g AS LINESTRING) ) P2LS
    ->   FROM t21;
+-------------------------------------+-----------------------------+
| ORG                                 | P2LS                        |
+-------------------------------------+-----------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) | LINESTRING(1 1,3 3,2 2,4 4) |
+-------------------------------------+-----------------------------+

 ただし、この、MULTIPOINTを作る際の順序指定がなかなか難しそうで、手元で軽く試した範囲では、順序指定するに至りませんでした。継続調査が必要です(できないはずはないので、私のSQL力の問題だとは思いますが)。例えば上記では、MULTIPOINTを作る時(このテーブルt21を作った時の話です)に ST_X による順序指定ができたら良いのにな、と考えています。あるいは上の実行結果で見るMULTIPOINT型を、順序を並べ替えながら LINESTRINGにCASTできたら、、という考え方もありそうですかね。


その他のキャスト

 MULTILINESTRINGからの変換(MULTIPOLYGON/POLYGON/LINESTRINGへ変換可能)と、MULTIPOLYGONからの変換(POLYGON/MULTILINESTRINGへ変換可能)は今回は割愛します。このうち、

  • MULTILINESTRING→LINESTRING は、例の、1要素のみの場合は簡単そうです
  • MULTIPOLYGON → POLYGONも同様
  • MULTIPOLYGON → MULTILINESTRING も、淡々と要素を分解して入れるだけかな

ということで、あとは 線の集合からPOLYGON系へ変換する場合ですね。これはやや複雑そうな気がします。集合内にある閉じた線が、ひとつの親POLYGONになりたいのか、刳り抜くPOLYGON要素なのかを判断する情報が、MULTILINESTRING内に含まれていないですからね。

まとめ

 MySQL 8.0.24 での CAST() 関数のジオメトリ対応を試してみました。これまでは、「点や線やポリゴンのデータをMySQLに入れて」「判定しながら取り出す」という使い方止まりだったところ、この対応により、「まとめたり」「型を変換したり」ができるようになり、使えるシーンが少し広がったのではないでしょうか。
 今後は、複数のPOLYGONを1個にまとめる(外周を辿ってくれるみたいな)とか、複数のLINESTRINGを一本のLINESTRINGにまとめる(これは CAST() を駆使するとできそうな気がする。あとで試そう)などの関数群も増えていくといいですね。

 MySQLリファレンスマニュアルのSpatial関数のページを見た範囲では、今のところ MySQL 8.0.27 (通常どおりリリースされれば、2021年10月頃リリースされるもの)までの間にジオ関係の新たな関数が追加されるという情報はありませんが、少しずつ機能が増えて、様々な利用シーンに対応できるようになることを楽しみにしています。
 今回の MySQL 8.0.24 は、ジオ的には非常に「大漁」でした!(なお、今回の動作確認は、その後2021年5月に緊急リリースされた MySQL 8.0.25を使用しました)

MySQL、"UDF" の名称を "Loadable Function" に変更

 MySQLのリファレンスマニュアルは日々更新されているのですが、本日、ちょっと大きめの用語の変更が加えられました。

 その変更とは、今まで User-Defined Function (UDF) と呼ばれていたものがすべて、Loadable Function になった、というものです。本日時点ではとりあえずマニュアル上の表記のみの変更であり、最新の MySQL 8.0.25 であっても特に動作が変更されたというものではありませんが、今後徐々に変化していくものと思われます。

 なお自画自賛ですが、この日記は、おそらくこの用語に関する世界で一番早い情報です。:-)

マニュアルの変更

 リファレンスマニュアルで、膨大な量の "UDF", "User-Defined Function" の表記が廃止され、ほぼすべてが本日の変更で "Loadable Function" に置き換えられました。一部、performance_schema の名前やモジュールの名前等に udf の表記は残っていますが、表題や説明文などはすべて置き換えが完了している模様です。確認をしながら結構大変な作業だと思いますが、こういう運営をきちんとしてくれるドキュメントチームが機能していることが、MySQLの魅力でもあると感じました。

変更の背景(想像)

 呼び方が変更された理由については想像するしかありませんが、UDF(ユーザ定義関数) という呼び方が、いかにも一般ユーザが作るものという印象が強いからかと想像しました。もちろん一般ユーザも自由に作って、自分のMySQLに機能を追加できるものなのですが、今は、(Enterpriseの) asymmetric_encrypt() 関数などの asymmetric_* 関数群やkeyring関係の関数など、公式のUDFもあるので、「ユーザ定義」という名称よりは「読み込み可能な」と称するのが適切と判断したのでしょう。

関連情報の追加

 マニュアルには、
9.6 Query Attributes に "Query Attribute Loadable Functions" の記述が追加されています。
https://dev.mysql.com/doc/refman/8.0/en/query-attributes.html


 Loadable Function の説明(元 UDF の説明)は、こちらです。
https://dev.mysql.com/doc/refman/8.0/en/function-loading.html


用語の整理

 User-Defined Function (UDF) が Loadable Function へと変更されたのに合わせて、今まで単純に SQL Function と称されることもあった関数たちは、built-in function または native function と明示的に呼ばれることになったようです。もちろん元々より built-in (native) function という名称はあったものなので、こちらは、Loadable Function に対する用語の確認といったところです。

performance_shcema は

MySQL 8.0.25時点ではもちろん、performance_schema のテーブル名や列名は変わっていません。今後どのように変更していくのか(MySQL 8.0 の中で付け替えが発生するのか、並行するのか、このままでいくのか等)注目です。

mysql> use performance_schema;
mysql> SELECT * FROM user_defined_functions;
+-------------------------------------------------+-----------------+----------+-------------+-----------------+
| UDF_NAME                                        | UDF_RETURN_TYPE | UDF_TYPE | UDF_LIBRARY | UDF_USAGE_COUNT |
+-------------------------------------------------+-----------------+----------+-------------+-----------------+
| asynchronous_connection_failover_delete_source  | char            | function | NULL        |               1 |
| asynchronous_connection_failover_add_source     | char            | function | NULL        |               1 |
| mysqlx_get_prepared_statement_id                | integer         | function | NULL        |               1 |
| mysqlx_generate_document_id                     | char            | function | NULL        |               1 |
| asynchronous_connection_failover_delete_managed | char            | function | NULL        |               1 |
| mysqlx_error                                    | char            | function | NULL        |               1 |
| innodb_redo_log_archive_flush                   | integer         | function | NULL        |               1 |
| innodb_redo_log_archive_stop                    | integer         | function | NULL        |               1 |
| asynchronous_connection_failover_add_managed    | char            | function | NULL        |               1 |
| innodb_redo_log_archive_start                   | integer         | function | NULL        |               1 |
+-------------------------------------------------+-----------------+----------+-------------+-----------------+
10 rows in set (0.01 sec)


f:id:sakaik:20210513150648p:plain

MySQL8.0.24の新しいGIS関数(3)~ST_Collect()を試す

 MySQL 8.0.24 では ST_Collect() 関数が実装されました。ジオメトリのデータを collect してくれそうな、見たままの機能を持った関数です。
 とりあえずどんな動きをするのか試してみたので、紹介します。なお、今回は 5/11に緊急リリースされた MySQL 8.0.25 にて動作確認をしています。

データ準備

 id、カテゴリ番号、そしてジオメトリの3つのカラムを持つテーブル t1 を作成し、データをつっこみます。

CREATE TABLE t1 (id integer, cat integer, g GEOMETRY);
INSERT INTO t1 VALUES (1, 1, ST_GeomFromText('POINT(1 1)'));
INSERT INTO t1 VALUES (2, 2, ST_GeomFromText('POINT(2 1)'));
INSERT INTO t1 VALUES (3, 1, ST_GeomFromText('POINT(3 1)'));
INSERT INTO t1 VALUES (4, 1, ST_GeomFromText('POINT(3 2)'));
INSERT INTO t1 VALUES (5, 2, ST_GeomFromText('POINT(2 1)'));
INSERT INTO t1 VALUES (6, 3, ST_GeomFromText('POINT(3 3)'));


内容確認。内部的にはジオメトリのデータはバイナリになっているので、人間が目視できる形に「変換」するために、ST_AsText() を使うのでしたね。

mysql> SELECT id, cat, ST_AsText(g) FROM t1 ORDER BY id;
+------+------+--------------+
| id   | cat  | ST_AsText(g) |
+------+------+--------------+
|    1 |    1 | POINT(1 1)   |
|    2 |    2 | POINT(2 1)   |
|    3 |    1 | POINT(3 1)   |
|    4 |    1 | POINT(3 2)   |
|    5 |    2 | POINT(2 1)   |
|    6 |    3 | POINT(3 3)   |
+------+------+--------------+

単純に全部まとめる

 いわゆる SELECT SUM(col1) FROM t1; みたいな、GROUP BY を指定しないグルーピングです。

mysql> SELECT ST_AsText( ST_Collect(g) ) FROM t1;
+-------------------------------------------------+
| ST_AsText( ST_Collect(g) )                      |
+-------------------------------------------------+
| MULTIPOINT((1 1),(2 1),(3 1),(3 2),(2 1),(3 3)) |
+-------------------------------------------------+

 6件すべての POINT が、ひとつの MULTIPOINT に集約されました。
ST_Collect() の集約では、DISTINCT をかけることもできます。重複除去です。

mysql> SELECT ST_AsText( ST_Collect(DISTINCT g) ) FROM t1; 
+-------------------------------------------+
| ST_AsText( ST_Collect(DISTINCT g) )       |
+-------------------------------------------+
| MULTIPOINT((1 1),(2 1),(3 1),(3 2),(3 3)) |
+-------------------------------------------+

 2件あった (2 1) の重複が排除された結果が得られました。

普通にグルーピング

 今度は、GROUP BY を明記する、普通のグルーピングを試してみます。

mysql> SELECT cat, ST_AsText( ST_Collect(g) ) FROM t1 GROUP BY cat;
+------+-------------------------------+
| cat  | ST_AsText( ST_Collect(g) )    |
+------+-------------------------------+
|    1 | MULTIPOINT((1 1),(3 1),(3 2)) |
|    2 | MULTIPOINT((2 1),(2 1))       |
|    3 | MULTIPOINT((3 3))             |
+------+-------------------------------+

 カテゴリ(cat)ごとに、グルーピングしてみました。カテゴリが等しいPOINT値ごとに集約されていることがわかります。 ST_Collect() は集約を行う関数ですから、結果として点がひとつだけになった場合でも、集合型である MULTIPOINT 型を返すのですね(cat=3)。

POINT 型以外も混在する場合

 ここで、新たに LINESTRING 型の値を持つ行を追加してみます。

INSERT INTO t1 VALUES (7, 3, ST_GeomFromText('LINESTRING(3 2, 1 0, 4 3)'));


 この行が加わった状態で、先ほどと同様に、カテゴリ(cat)ごとに ST_Collect() を使ってグルーピングしてみると、、、

mysql> SELECT cat, ST_AsText( ST_Collect(g) ) FROM t1 GROUP BY cat;
+------+--------------------------------------------------------+
| cat  | ST_AsText( ST_Collect(g) )                             |
+------+--------------------------------------------------------+
|    1 | MULTIPOINT((1 1),(3 1),(3 2))                          |
|    2 | MULTIPOINT((2 1),(2 1))                                |
|    3 | GEOMETRYCOLLECTION(POINT(3 3),LINESTRING(3 2,1 0,4 3)) |
+------+--------------------------------------------------------+

 カテゴリ3のものは、POINT と LINESTRING が混在した GEOMETRYCOLLECTION型になっていることがわかります。
 長くなるので実行例はひとつだけにしますが、すべてが LINESTRING型の時には MULTILINESTRINGに、すべてが POLYGON型の場合は MULTIPOLYGON型に、混在の時には GEOMETRYCOLLECTION型になります。

INSERT INTO t1 VALUES (8, 4, ST_GeomFromText('LINESTRING(1 2, 3 4, 5 6)'));
INSERT INTO t1 VALUES (9, 4, ST_GeomFromText('LINESTRING(3 5, 6 8)'));
mysql> SELECT cat, ST_AsText( ST_Collect(g) ) FROM t1 GROUP BY cat;
+------+--------------------------------------------------------+
| cat  | ST_AsText( ST_Collect(g) )                             |
+------+--------------------------------------------------------+
|    1 | MULTIPOINT((1 1),(3 1),(3 2))                          |
|    2 | MULTIPOINT((2 1),(2 1))                                |
|    3 | GEOMETRYCOLLECTION(POINT(3 3),LINESTRING(3 2,1 0,4 3)) |
|    4 | MULTILINESTRING((1 2,3 4,5 6),(3 5,6 8))               |
+------+--------------------------------------------------------+

Window関数でも使える ST_Collect()

 ST_Collect() は集約関数のひとつですから、SUM() などと同じように Window関数でも使うことができます。 
 ST_AsText() があるぶんだけ少し複雑に見えますが、本質は Window関数の 「集約関数(カラム) OVER (PARTITION BY カラム) であり、この結果を我々人間にも分かるように変換してもらうために ST_AsText() をカマしているという形です。

 mysql> SELECT id, cat, ST_AsText(g), 
    ->        ST_AsText( ST_Collect(g) OVER (PARTITION BY cat) ) gw
    ->  FROM t1
    ->  ORDER BY id;
+------+------+-------------------------+--------------------------------------------------------+
| id   | cat  | ST_AsText(g)            | gw                                                     |
+------+------+-------------------------+--------------------------------------------------------+
|    1 |    1 | POINT(1 1)              | MULTIPOINT((1 1),(3 1),(3 2))                          |
|    2 |    2 | POINT(2 1)              | MULTIPOINT((2 1),(2 1))                                |
|    3 |    1 | POINT(3 1)              | MULTIPOINT((1 1),(3 1),(3 2))                          |
|    4 |    1 | POINT(3 2)              | MULTIPOINT((1 1),(3 1),(3 2))                          |
|    5 |    2 | POINT(2 1)              | MULTIPOINT((2 1),(2 1))                                |
|    6 |    3 | POINT(3 3)              | GEOMETRYCOLLECTION(POINT(3 3),LINESTRING(3 2,1 0,4 3)) |
|    7 |    3 | LINESTRING(3 2,1 0,4 3) | GEOMETRYCOLLECTION(POINT(3 3),LINESTRING(3 2,1 0,4 3)) |
|    8 |    4 | LINESTRING(1 2,3 4,5 6) | MULTILINESTRING((1 2,3 4,5 6),(3 5,6 8))               |
|    9 |    4 | LINESTRING(3 5,6 8)     | MULTILINESTRING((1 2,3 4,5 6),(3 5,6 8))               |
+------+------+-------------------------+--------------------------------------------------------+


 ということで、「データを格納し、検索して取り出す」というジオメトリデータ(空間データ、spatialデータ、GISデータ)の基本的な取り扱いから一歩進んで、データベースへの格納済みデータを MULTI* 型へとまとめることができるようになりました。

 相変わらず応用事例や実際の案件に関わっていない私ですので、正直なところ具体的な用途は今ひとつピンと来ていないのですが、ST_Collect() により行えるデータ操作の幅が一気に広がったような気がします。実際の案件で使ってみた方はぜひブログとかでお話を聞かせてください! あるいはお手伝いさせてください(笑)

「MySQL8.0.24リリースノートでわいわい言う勉強会」開催しました

MySQLリースートでわいわい言う強会 8.0.24(Myリノベ8.0.24) を開催しました。

mysql.connpass.com


 MySQL 8.0.24 はMySQL 8.0シリーズの「メンテナンスリリースらしいメンテナンスリリース」で、新機能や機能の変更等は比較的おとなしめだったのですが、それでも参加者それぞれの視点から興味深い変更点を聞かせてもらうことができました。

MySQL 8.0.24 の Change log:
MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.24 (2021-04-20, General Availability)

MySQL 8.0.24 でのパラメタ類の変更点(サンプル公開):
MySQL Parameters Dashboard (8.0.24)


エラーメッセージが増えた

 @mita2 さんからの発表。MySQLの接続が切れてしまった時の原因は様々ですが、そのエラーメッセージが今までよりもきめ細かく表示されるようになったというお話です(エラーコード 04031 の「アクティブじゃないから切られちゃったよ」の新設など。 wait_timeout や interactive_timeout あたりの設定が影響)。
speakerdeck.com


 即座に @tom__bo さんが、本当に 4031 が返ってくることを、パケットを覗いて確認! このインタラクティブさが、いいですね。



DBUG_ASSERT が assert に

 「リリースノートの話ではないのですが・・」と前置きしてお話された @NayutaYanagisaw さん。一瞬「ん??リリースノートではないとは何事?」と思ったのですが、ソースコードの話でした。もっと深い!(笑)
 MySQL 8.0.24 では、今までソースコード中で使われていた DBUG_ASSERT マクロが、すべて 標準の assert に置き換えられたとのこと。へぇぇぇ。少し前の話ですが MySQL 8.0.18 の時には、DBUG_ENTER と DBUG_RETURN (関数の入り口と出口に書かれていた)が、DBUG_TRACE(関数の入り口だけに書けば良い)に置き換わったとか。 特に 8.0.24 のほうの変更はリリースノートにも書かれていない、ソースコードを追っている人しか知らないような貴重な情報でした。
 詳しくは以下のブログに。
nayuta-yanagisawa.hatenablog.com

Spatial(GIS)関数の追加

 私から。
ST_LineInterpolatePoint(), ST_LineInterpolatePoints(), ST_PointAtDistance() の各関数、ST_Collect() 関数、そして、CAST() や CONVERT()関数の spatilal 対応について紹介しました。最初の3つの関数は私の以下のブログで紹介しています。それ以外の関数は追々、書こうと思います。
MySQL8.0.24の新しいGIS関数(1)~LINESTRINGの経路点を求める~ - sakaikの日々雑感~(T)編
MySQL8.0.24の新しいGIS関数(2)~ST_LineInterpolatePoints()を試す - sakaikの日々雑感~(T)編

 個人的印象として、今回 CAST() 関数に色々加わっていて、このへん、問題動作の巣窟になりそうな予感がしています。大丈夫かな。。

MySQL 8.0.25

 私から。次のバージョンナンバーである MySQL 8.0.25。緊急リリースとして定期(次回7月)よりも前に出るんじゃないの?という予測について紹介しました。
f:id:sakaik:20210508135826p:plain
 MySQLのマニュアル中には「この機能は 8.0.xxで追加されました」のようにバージョン番号が入っている部分がいくつもあります。将来リリースされるものについても(おそらく内部での開発が確定されたタイミングで)マニュアルに掲載されているのですが、先週のあるタイミングで、8.0.25についての言及がすべてひとつずつ後ろにずれる修正がなされたのです。その結果、現在のマニュアル中に MySQL 8.0.25 についての言及はゼロ。というのが左側の表で示していること。
 そして、このイベントの当日に加えられた修正が右のもの。MySQL 8.0.27 に関する言及の多くが 8.0.24 に変更されたものです。これは何かというと、そのバージョンで加えられた機能のほかに、実行例やファイル名などにもバージョン番号が含まれています。一昨日までのマニュアルではこれらのバージョンは「マニュアル中の最先端」のもの、つまり 8.0.27 となっていました。これが、一気に「現在リリースされている最新バージョン」に変更されたという事です。 おそらくマニュアルの元ファイルでは {CURRENT_VERSION} みたいな記述になっていて、その根拠となる数字(のポリシー)が変更になったのかなと想像しています。



 その他、いろいろな話を皆さんに教えていただきました。今回も勉強になり、皆さんありがとうございました!

今後について

 MySQL 8.0.20 から5回開催してきた 「リリースノートでわいわい言う勉強会」ですが、少し大きめに変更をしてみようと考えています。

(1)幹事を持ち回りに: 初回からずっと私が企画/進行役をやらせてもらっていましたが、率直に言って自分の「場作り」の限界を感じています。もっと自由に発言しあう場にしたいのに、「コンテンツ作り」を意識してか、どうしても場をカタくしてしまっている認識です。そこで、皆さんにお願いです。「次回以降、誰か幹事やってください!」
 いろんな人の、いろんな進行方法があって良いと思っていて、必ずしも初回から「大成功」する必要もないですから、あなたの思う「リリースノート勉強会の空気」を目指して、トライしてみていただけたらと思います。幹事というと少し固そうに感じる人もいるかもしれませんが、私の感覚としては「日直」または「班長さん」くらいの感覚です。リノベの場、リノベの雰囲気、リノベで得られる情報とかが好きな人は、ぜひ協力していただけたらと願います。ぐるぐる回るようになってきたら、私も「ときどきやる班長さんのひとり」として進行に手を挙げたいと思います。
 細かいことは個別相談ですが、最低限「日程決定+当日進行」をしていただければ。 connpassのベース準備、会場(Zoom)用意は、こちらでやります。 今回の会の中では唐突だったこともあってか、次回幹事は決まりませんでしたので、またリリースが近くなった頃にTwitter の #mysql_jp で呼びかける予定です。

(2)動画の公開をやめます: ごく少数の方から公開動画に感謝の言葉もいただいていますが、絶対数として手間に見合う閲覧数を獲得できない状態で、分かりやすく言えばコスパが悪い、あるいは、苦労が報われないない状態。ということで、今回を最後に、リリースノート勉強会の動画公開は行わないことにします。(今回ぶん、撮れていた動画のサイズがなにかおかしいのですが、まぁ一応公開することにします)
 動画の公開をしないことで、幹事(班長さん、日直さん)をやりやすくなるのかなという面も期待しているので、(1)(2)併せて、何卒よろしくお願い申し上げる次第です。



当日動画:
www.youtube.com

MySQL8.0.24の新しいGIS関数(2)~ST_LineInterpolatePoints()を試す

 ひとつ前のエントリ「MySQL8.0.24の新しいGIS関数(1)~LINESTRINGの経路点を求める~」で、LINESTRINGを 指定した割合ごとに区切ってPOINT集合を返す、ST_LineInterpolatePoints()関数を紹介しました。
 MySQL 8.0 のGIS機能(spatial機能)のウリは「測地系に対応したこと」ですので、この ST_LineInterpolatePoints()関数も測地系に対応しているのだと思います。平面座標と地球上の座標では「直線」の捉え方に差が出てきますので、このエントリではそのあたりを試してみたいと思います。

題材とするLINESTRING

 LINESTRING は2個以上の点(POINT)をつないでできる線です。2個以上なので、3個、4個、、、の点を順次つないだものを扱えるのですが、今回は2点間のシンプルなLINESTRINGで試してみることにします。扱う2点は、成田空港(日本)と シャルル・ド・ゴール空港(フランス)とします。測地系WGS84 (4326)です。

空港名 北緯 東経
成田空港 35.793537 度 140.386548 度
シャルルドゴール空港 48.997026 度 2.281060 度

単純にLINESTRINGを表示してみる

 MySQL Workbench を使って、まずこの2点を結ぶLINESTRINGを表示してみます。

SELECT ST_GeomFromText(
  'LINESTRING(35.793537 140.386548, 48.997026 2.281060)', 4326
)

f:id:sakaik:20210503114225p:plain
 少しわかりにくいのですが、東京(右のほう、北緯36度くらい)から、パリ(真ん中あたり、北緯49度くらい)に向けて直線が表示されています。実際の地球上の最短経路が表示されているわけではないようです。

経路点で区切って表示してみる

 そこで、経路点の集合を返してくれる ST_LineInterpolatePoints()関数を使って、このLINESTRINGを100分割したPOINTを表示してみましょう。 先ほどの ST_GeomFromText() を使って作成した SRID 4326 のLINESTRINGに対して、ST_LineInterpolatePoints()関数をカマしてみます。100分割なので第二引数には 0.01 を与えます。

SELECT ST_LineInterpolatePoints(
   ST_GeomFromText(
     'LINESTRING(35.793537 140.386548, 48.997026 2.281060 )', 4326
  ), 0.01
)

f:id:sakaik:20210503114630p:plain

 美しいですね。ST_LineInterpolatePoints() は(そしておそらくは ST_LineInterpolatePoint()も)、きちんと地球が丸いことを知った上で経路上の点を求めてくれていることがわかりました。


ほんとに測地系を考慮した結果なのか?

 こういった実験は、予測した結果が「得られた」ケースとともに「得られなかった」ケースについても確認することで、より内容に確信が持てるようになるものです。ということで、平面座標である SRID 0 について、同じ2点間の座標の Interpolate を求めてみることにします。
 SQLは以下の通り。測地系の指定がなくなったことと、SRID 0 は Axis が SRID 4326 の場合とは異なるので x, y を入れ替えている点が先ほどとの違いです。
 

SELECT ST_LineInterpolatePoints(
   ST_GeomFromText(
     'LINESTRING(140.386548 35.793537, 2.281060 48.997026)'
  ), 0.01
)

 結果は・・・・
f:id:sakaik:20210503115443p:plain
 直線になりました! SRID 4326 でのあの曲線は、ちゃんと測地系を考慮してくれた結果だということが確認できました。

余談:日付変更線を越える場合

 MySQLの中での(SRIDを指定しての)地球は「丸い」ので、東経 180度と 西経180度はつながっています。それを確認してみましょう。サンフランシスコ国際空港(北緯 37.621438 度、西経 -122.376194 度)と成田空港との経路点を表示させてみます。

SELECT ST_LineInterpolatePoints(
   ST_GeomFromText(
     'LINESTRING(35.793537 140.386548, 37.621438 -122.376194)', 4326
  ), 0.01
)

 MySQL Workbench の Spatial View 機能がちょっとイケてなくて、全体を一画面中に表示することができない(縮尺変更が期待通りに動作しない)のですが、以下のように 東京(本当は千葉)を出発して、(緯線に対してやや北よりの進路を取り)日付変更線を越えて、サンフランシスコに到着していることが確認できます。
f:id:sakaik:20210503120043p:plain
f:id:sakaik:20210503120105p:plain




 得られた MULTIPOINT を CAST()関数を使って LINESTRING に戻してあげれば、元のLINESTRINGを刻んだLINESTRINGを生成することができ、例えば フレシェ値(あるいはハウスドルフ値)のより精度の高い結果を得るのに役に立つのかもしれません。
CAST()の使用例については私もまだ試すことができていないので、おいおい挑戦してみたいと思います。


追記

 折角なので CAST() で MULTIPOINT を LINESTRING に変換するのもやってみました。
分割数が多すぎると「きれいに」見えてしまうので、敢えて成田からシャゴールまでを 0.2 ごとに分割する例で試しました。

SELECT CAST(ST_LineInterpolatePoints(
   ST_GeomFromText(
     'LINESTRING(35.793537 140.386548, 48.997026 2.281060 )', 4326
  ), 0.2)
  AS LINESTRING )

f:id:sakaik:20210503122328p:plain

SQL入力エリアに赤バッテンがついていますが、実行自体は正しくできます(MySQL Workbench が、まだこの構文、または LINESTRING という型を認識していないということでしょうか)。
 結果は、カクッカクとした点を経由したLINESTRINGとなったことが見てわかります。
ただ、ひとつ重大な欠点に気づいてしまいまして、、、、、このLINESTRINGは、というかその元となったMULTIPOINTは、「始点を含まない」ということです。0.2ずつに区切る場合は、始点から 0.2 の位置、0.4の位置、、、、を MULTIPOINT として返しますから、0.0 の位置(始点)がここには含まれないのです。
 この用途で使用するときには一工夫が必要となりそうです。(元のLINESTRINGの始点を、InterpolatePoints で得られた MULTIPOINT の先頭にくっつけた上で CASTする等)