マルチジオメトリをシングルジオメトリに分解する（SpatiaLite）

# はじめに

SpatiaLiteでは、ひとつの属性データに対して複数のジオメトリを紐付けることができます。（マルチジオメトリ）
このジオメトリタイプが便利な場合もありますが、個々のジオメトリを個別に扱いたい場合は不都合なので、これを分解する必要があります。
ここでは、単一のマルチジオメトリを、これを構成する複数のシングルジオメトリに分解する方法についてやってみたので書き残しておきます。

（2016/01/13追記）
この記事ではWith句を使用した方法を検討しましたが、SpatiaLite4.2.1以降のバージョンでは、**`ElementaryGeometries()関数`**が実装されており、これを利用することで難なく分解できることが判明しました。
参考：[SpatiaLite: 4.2.1 functions #2 - dot macros replacement](https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.1+functions#2)

これ以前のバージョンを使用する場合などの参考として、残しておきます。^^;




# サンプルデータ

サンプルとするラインストリングを生成します。

```sql
create table sample
(
	'ID' INTEGER PRIMARY KEY,
	'name' TEXT,
	'Geometry' LINESTRING
)
;
insert into sample
values(
	1,
	'A',
	GeomFromText(
		'LINESTRING(
			142.66876 44.35546,142.66768 44.35485,142.66652 44.35374,
			142.66654 44.35340,142.66854 44.35274,142.67097 44.35306,
			142.67128 44.35292,142.67134 44.35267,142.67127 44.35252,
			142.66738 44.35022
		)',
		4612
	)
)
;
insert into sample
values(
	2,
	'B',
	GeomFromText(
		'LINESTRING(
			142.67322 44.35473,142.67192 44.35375,142.67208 44.35362,
			142.67284 44.35375,142.67267 44.35336,142.67127 44.35252
		)',
		4612
	)
)
;
insert into sample
values(
	3,
	'C',
	GeomFromText(
		'LINESTRING(
			142.67127 44.35252,142.67086 44.35251,142.66836 44.35213,
			142.66368 44.35314,142.66270 44.35427
		)',
		4612
	)
)
;
```

このデータは以下のようなデータです。
![オリジナルデータ](https://qiita-image-store.s3.amazonaws.com/0/101923/63066bcc-eb8a-271d-a71f-f8c79b5db93f.png)


データの内容とジオメトリのタイプを確認してみます。

```sql
select
	*,
	GeometryType(Geometry) as GeometryType
from sample
;
```

|ID|name|GEOMETRY|GeometryType|
|:--:|:--:|:--:|:--:|
|1|A|BLOB sz=208 GEOMETRY|LINESTRING|
|2|B|BLOB sz=144 GEOMETRY|LINESTRING|
|3|C|BLOB sz=128 GEOMETRY|LINESTRING|


上記の通り、地物数は3、これを構成する属性データの数も3で、地物と属性が一対一のシングルジオメトリです。


# マルチラインストリングの生成

このジオメトリを、`DissolveSegments()`関数を使用して線を構成するノード毎に切断し、セグメントに分解してみます。

```sql
create table sample_dis as
select
	ID,
	name,
	DissolveSegments(Geometry) as Geometry
from sample
;
```

このジオメトリは、見た目は同じですが、ノード毎に切断され、複数の直線からなるマルチジオメトリになっています。
（以下の画像はノードを明示していますが、実際には線のデータ。）
![マルチラインストリング](https://qiita-image-store.s3.amazonaws.com/0/101923/de8b8923-4511-e37e-99e4-052121ce90d5.png)

`GeometryType()`関数を使用して確認してみます。

```sql
select
	*,
	GeometryType(Geometry) as GeometryType
from sample_dis
;
```

|ID|name|GEOMETRY|GeometryType|
|:--:|:--:|:--:|:--:|
|1|A|BLOB sz=417 GEOMETRY|MULTILINESTRING|
|2|B|BLOB sz=253 GEOMETRY|MULTILINESTRING|
|3|C|BLOB sz=212 GEOMETRY|MULTILINESTRING|

マルチジオメトリの特定のジオメトリがいくつの要素地物を持っているかは、`NumGeometries()`関数で確認することができます。

```sql
select
	ID,
	name,
	NumGeometries(Geometry) as NumGeometry
from sample_dis
;
```

|ID|name|NumGeometry|
|:--:|:--:|:--:|
|1|A|9|
|2|B|5|
|3|C|4|

また、`GeometryN()`関数を用いてマルチジオメトリに含まれる特定の地物を抽出することができます。

```sql
select
	name,
	AsWKT(GeometryN(Geometry,1)) as WKT -- ジオメトリに含まれる最初の要素をWKTで抽出
from sample_dis
;
```

|name|WKT|
|:--:|:--:|
|A|LINESTRING(142.66876 44.35546,142.66768 44.35485)|
|B|LINESTRING(142.67322 44.35473,142.67192 44.35375)|
|C|LINESTRING(142.67127 44.35252,142.67086 44.35251)|


# マルチジオメトリのシングルジオメトリへの分解

上記のマルチラインストリングは分解前と属性データの数は変わらないため、地物と属性が多対一の関係になっています。
このままで便利な場合もありますが、個別の地物として扱いたい場合にはそれぞれに属性データを与える必要があります。

~~空間関数を利用してお手軽に分割できれば良いのですが、どうやらSpatiaLiteにはそのような関数がありません。（というより、ひとつのレコードを複数のレコードに分割するという発想はSQLになじまないということなのかもしれないです。知識不足が否めないorz）~~
<font color="red">SpatiaLiteでは、version4.2.1から**`ElementaryGeometries()関数`**が実装されました。
この関数を使用することで、一発で要素地物に分解することができるようになっています。</font>orz

そこでどうにかして（SQLで）これを実現する方法を考えたのが以下。
前述のとおり、`GeometryN()`関数を使用すればマルチジオメトリに含まれる特定の地物を抽出することができるので、これを応用して新たなデータを作成してみました。


```sql:
-- 新しいテーブルを作成する
create table sample_dis_single
(
	ID INTEGER PRIMARY KEY,
	ID_multi INTEGER, -- 元のマルチジオメトリのID
	name TEXT, -- 元の地物名称
	GeomN INTEGER, -- マルチジオメトリの何番目の要素か
	Geometry	 BLOB
)
;
-- with句を使用して（含まれる最大ジオメトリ数までの）連番を生成する。
with recursive
cnt(x) as (
		values (1)
	union all
		select x+1
		from cnt
	limit (
		select max(
			NumGeometries(Geometry)
		)
		from sample_dis
	)
)
-- この連番に対応するジオメトリを抽出し、用意したテーブルに挿入する
insert into sample_dis_single
select
	NULL,
	a.ID,
	a.name,
	b.x, -- マルチジオメトリ中のうち抽出するジオメトリ要素の番号
	GeometryN(
		a.Geometry,
		b.x
	) -- シングルジオメトリの抽出
from -- 自己結合で全ての組み合わせを得る
	sample_dis as a,
	cnt as b
where -- 各マルチラインストリング地物に含まれる要素数までを条件とする
	x <= NumGeometries(a.Geometry)
order by ID,x
;
-- IDを更新する
update sample_dis_single
set ID = rowid
;
```

新しく得られたテーブルのデータを確認してみます。


```sql:
select
	ID,
	ID_multi,
	name,
	GeomN,
	GeometryType(Geometry) as GeomType
from sample_dis_single
;
```

|ID|ID_multi|name|GeomN|GeomType|
|:--:|:--:|:--:|:--:|:--:|
|1|1|A|1|LINESTRING|
|2|1|A|2|LINESTRING|
|3|1|A|3|LINESTRING|
|4|1|A|4|LINESTRING|
|5|1|A|5|LINESTRING|
|6|1|A|6|LINESTRING|
|7|1|A|7|LINESTRING|
|8|1|A|8|LINESTRING|
|9|1|A|9|LINESTRING|
|10|2|B|1|LINESTRING|
|11|2|B|2|LINESTRING|
|12|2|B|3|LINESTRING|
|13|2|B|4|LINESTRING|
|14|2|B|5|LINESTRING|
|15|3|C|1|LINESTRING|
|16|3|C|2|LINESTRING|
|17|3|C|3|LINESTRING|
|18|3|C|4|LINESTRING|


シングルラインストリング（シングルジオメトリ）でデータが構成されています。
これを、GISで確認してみます。

![03.png](https://qiita-image-store.s3.amazonaws.com/0/101923/9fceeba1-df6b-4e8d-4227-4e59c0a9876b.png)

このように、分割した地物がそれぞれ別のシングルジオメトリとして登録されていることがわかりました。

めでたしめでたし。