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

More than 1 year has passed since last update.

はじめに

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

(2016/01/13追記)
この記事ではWith句を使用した方法を検討しましたが、SpatiaLite4.2.1以降のバージョンでは、ElementaryGeometries()関数が実装されており、これを利用することで難なく分解できることが判明しました。
参考:SpatiaLite: 4.2.1 functions #2 - dot macros replacement

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

サンプルデータ

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

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
    )
)
;

このデータは以下のようなデータです。
オリジナルデータ

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

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()関数を使用して線を構成するノード毎に切断し、セグメントに分解してみます。

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

このジオメトリは、見た目は同じですが、ノード毎に切断され、複数の直線からなるマルチジオメトリになっています。
(以下の画像はノードを明示していますが、実際には線のデータ。)
マルチラインストリング

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

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()関数で確認することができます。

select
    ID,
    name,
    NumGeometries(Geometry) as NumGeometry
from sample_dis
;
ID name NumGeometry
1 A 9
2 B 5
3 C 4

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

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)
SpatiaLiteでは、version4.2.1からElementaryGeometries()関数が実装されました。
この関数を使用することで、一発で要素地物に分解することができるようになっています。
orz

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

-- 新しいテーブルを作成する
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
;

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

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

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

めでたしめでたし。