トポロジに手を染めてみる

PostGIS 2.0 の新機能のひとつにトポロジがあります。…いやどうも 1.5 でもあったけれども文書を書いてなかったらしい。それはともかく、トポロジの扱い方が分からないので、自分で手を動かして確認しているところです。

トポロジサポートの導入

% psql topo -f /.../postgis.sql
% psql topo -f /.../spatial_ref_sys.sql
% psql topo -f /.../topology.sql

spatial_ref_sys.sql は、空間参照系を使わない (SRID = 0 または -1 にする) 場合には不要です。

スキーマに注意

トポロジサポートのインストール先のスキーマは topology です。
スキーマのデフォルトは public ですので、トポロジ関数を扱うなら topology.CreateTopology 等と、スキーマ名を指定します。

また、作成するトポロジデータも、スキーマ単位で管理します。

トポロジの作成

トポロジを作成して、TopologySummary と pg_tables で確認してみましょう。

topo=# SELECT topology.CreateTopology('topo1',0);
... (なんかいっぱい出力される) ...
 createtopology 
                              • -
1 (1 row) topo=# SELECT topology.TopologySummary('topo1'); topologysummary
                                                                                                      • -
Topology topo1 (1), SRID 0, precision 0 + 0 nodes, 0 edges, 0 faces, 0 topogeoms in 0 layers+ (1 row) topo=# SELECT * from pg_tables; schemaname | tablename | ...
                                                                                                    • -
... topo1 | node | topo1 | face | topo1 | edge_data | topo1 | relation | ...

孤立ノードを追加して三角形の頂点を作る

(0,0) (100,0) (200,0) を頂点とする三角形を作ります。

まずは、頂点の追加です。

topo=# SELECT topology.ST_AddIsoNode('topo1', 0, 'POINT(0 0)');
 st_addisonode 
                            • -
1 (1 row) topo=# SELECT topology.ST_AddIsoNode('topo1', 0, 'POINT(100 0)'); st_addisonode
                            • -
2 (1 row) topo=# SELECT topology.ST_AddIsoNode('topo1', 0, 'POINT(0 200)'); st_addisonode
                            • -
3 (1 row)

ST_AddIsoNode の返り値は、ノードのID です。

孤立ノードを繋いでエッジを作りノードも作る

繋げていきましょう。この際、単純にノードIDだけでなく GEOMETRY (LINESTRING) も必要です。始点と終点は開始ノードと終了ノードに一致しないといけません。

topo=# SELECT ST_AddEdgeModFace('topo1', 1, 2, 'LINESTRING(0 0, 100 0)');
 st_addedgemodface 
                                    • -
1 (1 row) topo=# SELECT ST_AddEdgeModFace('topo1', 2, 3, 'LINESTRING(100 0, 0 200)'); st_addedgemodface
                                    • -
2 (1 row) topo=# SELECT ST_AddEdgeModFace('topo1', 3, 1, 'LINESTRING(0 200, 0 0)'); WARNING: Not updating next_{left,right}_face fields of face boundary edges CONTEXT: SQL statement "SELECT topology.AddFace(atopology, rec.geom, true)" PL/pgSQL function "st_addedgemodface" line 614 at SQL statement st_addedgemodface
                                    • -
3 (1 row)

三つ目のクエリで自動的にフェイスができあがりました。あと、警告が出ましたが、無視しときます。

現状を確認しておきましょう。

topo=# SELECT topology.TopologySummary('topo1');
                  topologysummary                   
                                                                                                      • -
Topology topo1 (1), SRID 0, precision 0 + 3 nodes, 3 edges, 1 faces, 0 topogeoms in 0 layers+ (1 row)

ノードが3つ、エッジが3つ、フェイスが1つ、となっていますね。

TopoGeometryレイヤを作る

TopoGeometryレイヤは、トポロジとジオメトリとの間に入るものです。とりあえずテーブルを作って、TopoGeometryカラムを追加してみましょう。なお、これは Typmod になっていません。

topo=# CREATE TABLE topo1.area ( id SERIAL PRIMARY KEY );
...
topo=# SELECT topology.AddTopoGeometryColumn('topo1', 'topo1', 'area', 'topogeom', 'POLYGON');
 addtopogeometrycolumn 
                                            • -
1 (1 row)

AddTopoGeometryColumn の返り値は、レイヤIDです。

登録状況を確認してみましょう。

topo=# SELECT * FROM topology.layer;
 topology_id | layer_id | schema_name | table_name | feature_column | feature_type | level | child_id 
                                                                                                                                                                                                          • -
1 | 1 | topo1 | area | topogeom | 3 | 0 | (1 row)

TopoGeometryを作る

topo=# INSERT INTO topo1.area (topogeom) SELECT CreateTopoGeom('topo1', 3, 1, '{{1,3}}');
INSERT 0 1

インサートされたものを見てみましょう。

topo=# SELECT * FROM topo1.area;
 id | topogeom  
                              • -
1 | (1,1,3,3) (1 row)

topogeomカラムのプロパティは、順に トポロジID, レイヤID, TopoGeometry ID, レイヤタイプ です。

TopoGeometryはGEOMETRYに変換できます。

topo=# SELECT ST_AsText(topogeom::GEOMETRY) FROM topo1.area;
                st_astext                
                                                                                • -
MULTIPOLYGON(((100 0,0 0,0 200,100 0))) (1 row)

三角形を横に切る

ここまで (0,0) (100,0) (0,200) の三頂点からなる三角形を作ってきました。

Y=50 の直線でぶった切ってみましょう。辺との交点はとりあえず自前で出して、そこでエッジを分割して、あらためて繋ぎます。交点は、エッジ3番とは(0,50)、 エッジ2番とは(75,50) です。

topo=# SELECT ST_ModEdgeSplit('topo1',2,'POINT(75 50)');
 st_modedgesplit 
                                • -
4 (1 row) topo=# SELECT ST_ModEdgeSplit('topo1',3,'POINT(0 50)'); st_modedgesplit
                                • -
5 (1 row)

さきほどと同じようにノード番号4番と5番をつなげてみます。

topo=# SELECT ST_AddEdgeModFace('topo1', 4, 5, 'LINESTRING(75 50, 0 50)');
WARNING:  Not updating next_{left,right}_face fields of face boundary edges
CONTEXT:  SQL statement "SELECT topology.AddFace(atopology, rec.geom, true)"
PL/pgSQL function "st_addedgemodface" line 614 at SQL statement
 st_addedgemodface 
                                    • -
6 (1 row)

ST_AddEdgeModFace の返り値は新たに追加されたエッジのIDです。生成されるフェイスのIDではありません。

フェイスのIDが分からないので、とりあえずテーブルを見てみましょう。

topo=# SELECT * FROM topo1.face;
 face_id | mbd
                                      • -
0 | 2 | 0103... 1 | 0103... (3 rows)

たぶん2番ですね。

なお、これだと見えにくいのですが、ST_AddEdgeModFace でフェイスの1番を分割した場合、フェイスの1番は分割の片一方として残留し、もう一方が新規登録されます。

フェイスを編集しても TopoGeometry まで更新されない

フェイスの2番をTopoGeometryにします。

topo=# INSERT INTO topo1.area (topogeom) SELECT CreateTopoGeom('topo1', 3, 1, '{{2,3}}');
INSERT 0 1

topo=# SELECT id, ST_AsText(topogeom::GEOMETRY) FROM topo1.area;
 id |                     st_astext                      
                                                                                                                • -
1 | MULTIPOLYGON(((100 0,0 0,0 50,0 200,75 50,100 0))) 2 | MULTIPOLYGON(((100 0,0 0,0 50,75 50,100 0))) (2 rows)

えーっと、id=1 の行がちょっと不思議か。…ていうか順を追っていくと、(100 0) (0 0) (0 200) を頂点とする三角形に分割ノード (0 50) と (75 50) がくっついているだじゃないか。

じゃあ改めてフェイスの1番をTopoGeometryにしてやる。

topo=# INSERT INTO topo1.area (topogeom) SELECT CreateTopoGeom('topo1', 3, 1, '{{1,3}}');
INSERT 0 1
topo=# SELECT id, ST_AsText(topogeom::GEOMETRY) FROM topo1.area;
 id |                     st_astext                      
                                                                                                                • -
1 | MULTIPOLYGON(((100 0,0 0,0 50,0 200,75 50,100 0))) 2 | MULTIPOLYGON(((100 0,0 0,0 50,75 50,100 0))) 3 | MULTIPOLYGON(((0 200,75 50,0 50,0 200))) (3 rows)

id=3 の行が追加されました。id=1は消えません。

まとめ

  • トポロジの各種関数・テーブルは topology スキーマにある
  • トポロジごとにスキーマを作る
  • ノード追加、ノードをもとにエッジ追加ができる
  • エッジ追加時にフェイスを分割できる場合は自動的に分割する
  • TopoGeometryとトポロジとは連動していない