第9章 トポロジー

目次

PostGIS トポロジー型と関数は、フェイス、エッジ、ノード等のトロポジオブジェクトを管理するために使います。

PostGIS Day Paris 2011における Sandro Santilli さんの講演が、PostGIS トポロジーの概略説明として良いです。Topology with PostGIS 2.0 slide deck にあります。

Vincent Picavet さんはトポロジーとは何か、どのように使われるか、および、対応する FOSS4G ツールに関する良い概略説明を PostGIS Topology PGConf EU 2012 で出しています。

トポロジーベースの GIS データベースの例に US Census Topologically Integrated Geographic Encoding and Referencing System (TIGER) データベースがあります。PostGIS トポロジーの実験でデータが必要となる場合に使って下さい。

PostGIS トポロジーモジュールは長い間存在していましたが、公式ドキュメントには常に存在しているわけではありませんでした。広範なクリーンアップで非推奨関数を削除し、既知の使い勝手に関する問題の修正、機能と関数の文書化、新機能追加、SQL-MM 適合性の改善がなされました。

本章では、リミティブテーブル、TopoGeometry タイプ、トポロジー管理関数、関数の編集、関数の評価、変換補助などのトポロジーモジュールの参照を維持しています。

このモジュールに関する全ての関数とテーブルは、topology スキーマにインストールされます。

SQL/MM 標準で定義される関数は ST_ プリフィクスを持ち、PostGIS 特有の関数はこのプリフィクスを持ちません。

トポロジー機能はデフォルトでビルドされます。2章PostGIS インストール で説明されている通り、ビルド時のコンフィギュアオプション --without-topology を指定することで、無効にできます。

9.1. ラインからフェイスを構築する

この例では、ノードを与えたラインからトポロジーのフェイスを構築し、結果フェイスにポイント属性を関連付けます。トポロジーに投入するために ST_CreateTopoGeo を使い、結果の確認に ValidateTopology を使い、各属性が指すフェイスの特定に GetFaceByPoint を使い、通常のポリゴンジオメトリーを作るのに ST_GetFaceGeometry を使います。

からトポロジーを生成し、二つのフェイスをラインを取り囲むラインを投入します:

コード
CREATE EXTENSION IF NOT EXISTS postgis_topology;

SELECT topology.CreateTopology('topo_example', 3857);

WITH lines(geom) AS (VALUES
    ('SRID=3857;LINESTRING(0 0, 2 0)'::geometry),
    ('SRID=3857;LINESTRING(0 1, 2 1)'::geometry),
    ('SRID=3857;LINESTRING(0 0, 0 1)'::geometry),
    ('SRID=3857;LINESTRING(1 0, 1 1)'::geometry),
    ('SRID=3857;LINESTRING(2 0, 2 1)'::geometry)
)
SELECT topology.ST_CreateTopoGeo(
    'topo_example',
    ST_Collect(geom)
)
FROM lines;

SELECT * FROM topology.ValidateTopology('topo_example');

ValidateTopology からの返り値が空の場合には、トポロジーのエラーがありません。属性のポイントはフェイスに合致させることができます。ID が 0 のフェイスはユニバーサルフェイスで、ポイントが全てのフェイスの外側にあります。

コード
WITH labels(name, geom) AS (VALUES
    ('west', 'SRID=3857;POINT(0.5 0.5)'::geometry),
    ('east', 'SRID=3857;POINT(1.5 0.5)'::geometry),
    ('outside', 'SRID=3857;POINT(3 0.5)'::geometry)
),
located AS (
    SELECT name,
           topology.GetFaceByPoint('topo_example', geom, 0) AS face_id
    FROM labels
)
SELECT name,
       face_id,
       CASE WHEN face_id = 0 THEN NULL
            ELSE topology.ST_GetFaceGeometry('topo_example', face_id)
       END AS geom
FROM located
ORDER BY name;

属性をフェイスのテーブルに複製する前に、ポイントがフェイスの外側に無いかを確認し、同じフェイスに複数のポイントが割り当てられないかを確認します。

コード
WITH labels(name, geom) AS (VALUES
    ('west', 'SRID=3857;POINT(0.5 0.5)'::geometry),
    ('east', 'SRID=3857;POINT(1.5 0.5)'::geometry),
    ('outside', 'SRID=3857;POINT(3 0.5)'::geometry)
),
located AS (
    SELECT name,
           topology.GetFaceByPoint('topo_example', geom, 0) AS face_id
    FROM labels
)
SELECT 'outside face' AS problem,
       array_agg(name ORDER BY name) AS labels
FROM located
WHERE face_id = 0
HAVING count(*) > 0
UNION ALL
SELECT 'duplicate face ' || face_id,
       array_agg(name ORDER BY name)
FROM located
WHERE face_id != 0
GROUP BY face_id
HAVING count(*) > 1;

例で使ったトポロジーが不要であって削除するには DropTopology を使います:

コード
SELECT topology.DropTopology('topo_example');

9.2. トポロジープリミティブテーブル

あらゆるトポロジーの中核的なプリミティブは edge_data、 node、face テーブルに格納されます。これらのテーブルは CreateTopology で生成されたスキーマ内にあります。edge_data の各タプルは有向エッジを表現しています。start_node から end_node への有向曲線と、その曲線の進行方向左側にあるフェイスの識別番号 (left_face) と右側のもの (right_face) とを併せて記録しています。このため、ジオメトリー線分が 2個のフェイスに属するとき、同じ線分が 2度 (方向ごとに 1度ずつ) 出現することがあります。

next_left_edge カラムと next_right_edge カラムはフェイスを辿る方法をエンコードしてこの方向情報を完全にしています。正負符号付整数を格納していて、絶対値は進行方向を向いた時の次のエッジを示し、正負符号で次のエッジが順方向化逆方向かを決定しています。正式には、次の規則があります (エッジを e とします):

  • abs(next_left_edge) は e の左にあるフェイスを辿る場合に次に到達するエッジの識別番号です。値が正数の場合には e の終端ノードから次のエッジの格納されている通りの方向に進みます。負数の場合には次のエッジは逆向きに辿ることになるので、共有するフェイスは辿っている者から見たら左側にあります。

  • abs(next_right_edge) は e の右にあるフェイスを辿る場合に次に到達するエッジの識別番号です。値が正数の場合には次のエッジが e の終端ノードから始まり次のエッジの格納されている通りの方向に進みます。負数の場合には次のエッジは逆向きにたどることになるので、共有するフェイスは辿っている者から見たら左側にあります。負数の場合には次のエッジを逆方向で辿る、すなわちエッジの終端から始めることになり、右側のフェイスが保存されます。

  • ゼロは対応する側でエッジがぶら下がっていることを示します (たとえば接続するフェイスが 0 のユニバーサルフェイスになっている孤立エッジ)。edge ビューにある abs_next_left_edge カラムと abs_next_right_edge カラムは絶対値にして扱いすくしたものです。

この表現は二重連結辺リストの異種で、多数のトポロジールーチンで利用されます。 GetRingEdges や ValidateTopology といった関数によるフェイス境界の再構築や矛盾の診断に使われます。したがって評価時に「不正な next_left_edge」と「不正な next_right_edge」診断が報告されます。 AddEdge のようなコンストラクターは next_* 属性を普通の自己参照で初期化します。ST_AddEdgeModFace と ST_RemEdgeModFace を含む編集ルーチンは、エッジが挿入されるか削除されると同時にリンクを更新します。他の大量処理 (たとえば Polygonize) は意図的にカラムに値を設定しないことがあり、文書では振舞いに関する注意を明示的に出しています。

9.3. トポロジー型

概要

本節では、PostGIS トポロジーでインストールされる PostgreSQL データ型の一覧を挙げます。独自に関数をデザインする際に特に重要となる、キャストでの挙動を記述していることにご注意ください。

  • getfaceedges_returntype — 順序番号とエッジ番号で構成される複合型。
  • TopoGeometry — トポロジーとして定義されたジオメトリーを表現する型です。
  • validatetopology_returntype — エラーメッセージとエラーの場所を示す id1 と id2 からなる複合型です。これは ValidateTopology が返す型です。

9.4. トポロジードメイン

概要

本節では、PostGIS トポロジーでインストールされる PostgreSQL ドメインの一覧を挙げます。ドメインは、オブジェクト型のように扱え、関数やテーブルカラムのオブジェクトを返します。ドメインは存在するチェック制約を持つ既存の型である点で、型とは違います。

  • TopoElement — 二つの整数の配列で、通常 TopoGeometry 要素を識別するために使われます。
  • TopoElementArray — TopoElement オブジェクトの配列。

9.5. トポロジー管理と TopoGeometry 管理

概要

本節では、新しいトポロジースキーマの構築、トポロジーの評価、TopoGeometry カラムの管理のためのトポロジー関数の一覧を挙げます。

  • AddTopoGeometryColumn — 既存のテーブルに TopoGeometry カラムを追加し、topology.layer 内に新しいレイヤとして新しいカラムを登録して、新しい layer_id を返します。
  • RenameTopoGeometryColumn — TopoGeometry カラムの名前を変更します
  • DropTopology — 使用上の注意: この関数によって、トポロジースキーマが削除され、topology.topology テーブルからの参照が削除され、geometry_columns テーブルから削除対象スキーマ内のテーブルへの参照が削除されます。
  • RenameTopology — トポロジー名を変更します
  • DropTopoGeometryColumn — schema_name で指定されたスキーマ内にある table_name で指定されたテーブルから TopoGeometry カラムを削除し、topology.layer テーブルにある登録を解除します。
  • FixCorruptTopoGeometryColumn — postgis_topology を 3.6.0以上にアップグレードする際に発生するポジオメトリーの間違いを訂正します。
  • Populate_Topology_Layer — テーブルからメタデータを読み、topology.layer テーブルに不足しているものを追加します。
  • TopologySummary — トポロジー名を取り、トポロジー内のオブジェクトの型に関する概要の全体を提供します。
  • ValidateTopology — トポロジーの問題についての詳細を示す validatetopology_returntype の集合を返します。
  • ValidateTopologyRelation — 不正なトポロジー関係の行に関する情報を返します。
  • ValidateTopologyPrecision — トポロジー内の低精度の頂点を返します。
  • MakeTopologyPrecise — トポロジーの頂点を精度グリッドにスナップします。
  • FindTopology — 異なる方法でトポロジー行を返します。
  • FindLayer — 様々な方法で topology.layer 行を返します。
  • TotalTopologySize — 指定したトポロジーが使用するディスク容量の総量を得ます。インデックスや TOAST データも含みます。
  • UpgradeTopology — 指定したトポロジーに対して、トポロジー ID とプリミティブ ID が大きな数 (int8) に対応するようアップグレードします。

9.6. トポロジー統計管理

概要

本節では、トポロジー構築時のデータベース統計の管理について説明します。

トポロジーに要素を追加すると、そのトリガとして、分割されることになる既存のエッジを探索し、ノードを追加し、新しいラインでノードを作成するエッジを更新するために多数のデータベースクエリーが発生します。このため、トポロジーテーブル内のデータに関する統計情報が最新の状態になっているなら、統計情報を使うと便利です。

PostGIS トポロジーの追加や編集の関数は、自動的に統計情報を更新することはありません。トポロジーにおいて逐次変更していては、統計情報の更新が過剰になるためです。処理は呼び出し元の義務となっています。

[注記]

autovacuum で更新された統計情報は、autovacuum プロセス完了前に始まったトランザクションからは見えないので、更新した統計情報を使うには、実行時間の長いトランザクションでは ANALYZE 自体を実行する必要があります。

9.7. トポロジーコンストラクタ

概要

本節では、新しいトポロジーを生成するトポロジー関数を挙げます。

  • CreateTopology — 新しいトポロジースキーマを生成し、topology.topology テーブルに登録します。
  • CopyTopology — トポロジー (ノード、エッジ、フェイス、レイヤと TopoGeometry) の新しいスキーマに複写します。
  • ST_InitTopoGeo — 新しいトポロジースキーマを生成し、topology.topology テーブルに登録します。
  • ST_CreateTopoGeo — 空のトポロジーにジオメトリーのコレクションを追加し、成否を示すメッセージを返します。
  • TopoGeo_AddPoint — 許容差を使って既存のトポロジーにポイントを追加し、可能ならエッジを分割します。
  • TopoGeo_AddLineString — 許容差を使って既存のトポロジーにラインストリングを追加し、可能ならエッジ/フェイスを分割します。
  • TopoGeo_AddPolygon — 許容差を使って既存のトポロジーにラインストリングを追加し、可能ならエッジ/フェイスを分割します。エッジ識別番号を返します。
  • TopoGeo_LoadGeometry — ジオメトリーを既存のトポロジーにロードし、必要に応じてスナップと分割を実行します。

9.8. トポロジーエディタ

概要

本節では、エッジ、フェイス、ノードの追加、移動、削除、分割に関する関数を挙げます。本節の関数はすべて ISO SQL/MM で定義されています。

  • ST_AddIsoNode — フェイスに孤立ノードを追加し、新しいノードの識別番号を返します。フェイスが NULL の場合でもノードは生成されます。
  • ST_AddIsoEdge — anode と anothernode で指定される二つの既存孤立ノードを接続するトポロジーに、ジオメトリー alinestring で定義される孤立エッジを追加し、新しいエッジの識別番号を返します。
  • ST_AddEdgeNewFaces — 新しいエッジを追加します。新しいエッジがフェイスを分割する場合には、もとのフェイスを削除して、分割した二つのフェイスに置き換えます。
  • ST_AddEdgeModFace — 新しいエッジを追加します。新しいエッジがフェイスを分割する場合には、もとのフェイスを編集し、一つのフェイスを追加します。
  • ST_RemEdgeNewFace — エッジを削除し、消去対象エッジでフェイスが二つに分割されているなら元の二つのフェイスを削除し、一つの新しいフェイスに置き換えます。
  • ST_RemEdgeModFace — エッジを削除します。削除されるエッジが二つのフェイスを分割していたなら、一方のフェイスを削除し、もう一方のフェイスを両方の空間を覆うように編集します。
  • ST_ChangeEdgeGeom — トポロジー構造に影響を与えることなくエッジの形状を変更します。
  • ST_ModEdgeSplit — 既存のエッジに沿って新しいノードを生成してエッジを分割します。もとのエッジは変更され、新しいエッジが一つ追加されます。
  • ST_ModEdgeHeal — 二つのエッジについて、接続しているノードを削除して修復します。1番目のエッジを編集して、2番目のエッジを削除します。削除されたノードの識別番号を返します。
  • ST_NewEdgeHeal — 二つのエッジについて、接続しているノードを削除して修復します。両方のエッジを削除し、1番目のエッジと同じ方向のエッジに置き換えます。
  • ST_MoveIsoNode — トポロジー内の孤立ノードを別の位置に移動させます。新しい apoint ジオメトリーがノードとして存在しているなら、エラーが投げられます。移動に関する説明を返します。
  • ST_NewEdgesSplit — 新しいノードを既存のエッジに沿って作成して、エッジを分割します。もとのエッジは削除され、二つのエッジに置き換えられます。二つの新しいエッジに接続する新しいノードの識別番号を返します。
  • ST_RemoveIsoNode — 孤立ノードを削除し、実行結果が返されます。ノードが孤立していない (エッジの始端または終端である) 場合には、例外が投げられます。
  • ST_RemoveIsoEdge — 孤立エッジを削除し、実行結果の記述を返します。エッジが孤立していない場合には、例外が投げられます。

9.9. トポロジーアクセサー

  • GetEdgeByPoint — 与えられたポイントにインターセクトするエッジの識別番号を探索します。
  • GetFaceByPoint — 与えたポイントとインタセクトするフェイスを見つけます。
  • GetFaceContainingPoint — ポイントを含むフェイスを見つけます。
  • GetNodeByPoint — ポイント位置にあるノードの識別番号を探索します。
  • GetTopologyID — トポロジー名から topology.topology テーブル内にあるトポロジーの識別番号を返します。
  • GetTopologySRID — トポロジー名から topology.topology テーブル内にあるトポロジーの SRID を返します。
  • GetTopologyName — 識別番号からトポロジー (スキーマ) の名前を返します。
  • ST_GetFaceEdges — 順序番号を含む、aface の境界となる、整列したエッジの集合を返します。
  • ST_GetFaceGeometry — 指定されたトポロジーの中の、フェイス識別番号で指定されたポリゴンを返します。
  • GetRingEdges — 与えられた側を歩いて得られた、正負符号付きエッジ識別番号の集合を、順序通りに返します。
  • GetNodeEdges — 与えられたノードに付随するエッジの集合を整列して返します。
  • FindVertexSegmentPairsBelowDistance — 許容距離内にあるトポロジーの頂点/線分の組を検索します。

9.10. トポロジー処理

概要

本節では、非標準の手法でのトポロジー処理の関数を挙げます。

  • Polygonize — トポロジーエッジで定義される全てのフェイスを探索し、追加します。
  • AddNode — 指定したトポロジースキーマのノードテーブルにポイントノードを追加し、新しいノードの識別番号を返します。指定したポイントに既にノードがある場合は既存のノード識別番号を返します。
  • AddEdge — 指定したラインストリングジオメトリーを使って、ラインストリングエッジをエッジテーブルに追加し、指定したトポロジースキーマの始点終点をポイントノードテーブルに追加し、新しい (または既存の) エッジの識別番号を返します。
  • AddFace — フェイスプリミティブをトポロジーに登録し、その識別番号を得ます。
  • ST_Simplify — 与えた TopoGeometry を「シンプル化した」ジオメトリーを返します。ダグラス-ポーカーのアルゴリズムを使います。
  • RemoveUnusedPrimitives — 存在する TopoGeometry オブジェクトを定義するのに必要でないトポロジープリミティブを削除します。

9.11. TopoGeometry コンストラクタ

概要

本節では、新しい TopoGeometry を生成するトポロジー関数を挙げます。

  • CreateTopoGeom — 新しい TopoGeometry オブジェクトを topo エレメント配列から生成します - tg_type: 1:[multi]point, 2:[multi]line, 3:[multi]poly, 4:collection
  • toTopoGeom — 単純なジオメトリーから TopoGeometry を生成します。
  • TopoElementArray_Agg — element_id とタイプの配列 (topoelements) からなる topoelementarray を返します。
  • TopoElement — TopoGeometry を TopoElement に変換します。

9.12. TopoGeometry エディタ

概要

本節では、既存の TopoGeometry を編集する関数を挙げます。

  • clearTopoGeom — TopoGeometry の中身を消去します。
  • TopoGeom_addElement — TopoGeometry の定義に要素を追加します。
  • TopoGeom_remElement — TopoGeometry の定義から要素を削除します。
  • TopoGeom_addTopoGeom — TopoGeometry の要素を他の TopoGeometry の定義に追加します。
  • toTopoGeom — ジオメトリーの形状を既存の TopoGeometry に追加します。

9.13. TopoGeometry アクセサー

  • GetTopoGeomElementArray — 与えられた TopoGeometry (プリミティブ要素) のトポロジー要素とタイプを含む topoelementarray (topoelement の配列) を返します。
  • GetTopoGeomElements — 与えられた TopoGeometry (プリミティブ要素) の、トポロジーの element_id と element_type を含む topoelement オブジェクトの集合を返します。
  • ST_SRID — TopoGeometry の空間参照識別子を返します。

9.14. TopoGeometry 出力

  • AsGML — TopoGeometry の GML 表現を返します。
  • AsTopoJSON — opoGeometry の TopoJSON 表現を返します。

9.15. トポロジー空間関係関数

概要

本節では TopoGeometry とトポロジープリミティブとの間の関係を見るトポロジー関数の一覧を挙げます。

  • Equals — 二つの TopoGeometry が同じトポロジープリミティブで成っている場合に true を返します。
  • Intersects — 二つの TopoGeometry からのプリミティブの組がインタセクトする場合に true を返します。

9.16. トポロジーのインポートとエクスポート

トポロジーを生成したり、場合によってはトポロジーレイヤに関連付けると、バックアップや他のデータベースへの転送のために、ファイルに出力するフォーマットでエクスポートした方がいいでしょう。

トポロジーはテーブルの集合 (プリミティブで 4テーブル、レイヤで任意数) と、メタデータテーブルのレコード (topology.topology と topology.layer) になっているため、PostgreSQL の標準的なダンプ/レストアのツールでは問題があります。さらに、トポロジーの識別子はデータベース間で同じではないため、トポロジーのパラメーターはレストア時に変更する必要があります。

トポロジーのエクスポート/レストアを簡単化するために、二つの実行可能ファイルが提供されています。pgtopo_export と pgtopo_import です。使用例は次の通りです。

コード
pgtopo_export dev_db topo1 | pgtopo_import topo1 | psql staging_db

9.16.1. トポロジーエクスポータの使用

pgtopo_export スクリプトはデータベース名とトポロジー名を取り、トポロジー (と関連するレイヤ) を新しいデータベースにインポートするために使うことができるダンプファイルを出力します。

デフォルトでは pgtopo_export はダンプファイルを標準出力に書くので、pgtopo_import にパイプで繋げたり、ファイルにリダイレクトする (端末への出力を止める) ことができます。出力ファイル名をコマンドラインスイッチ -f で指定することもできます。

デフォルトでは pgtopo_export は、指定されたトポロジーに対して定義されたレイヤの全てのダンプが含まれます。これは、必要なデータより多い場合もありますし、動作しない場合もあり (レイヤテーブルが複雑な依存関係を持っている場合)、この場合には --skip-layers スイッチでレイヤをスキップし、別々に処理することができます。

pgtopo_export を --help (または短縮の -h) スイッチで実行すると、常に短い使用法文字列を印字します。

ダンプファイルフォーマットは、少なくともフォーマットバージョン情報を持つ pgtopo_dump_version ファイルを含む pgtopo_export ディレクトリーの圧縮された tar アーカイブです。バージョン 1 では、ディレクトリーにはトポロジーのプリミティブテーブル (node, edge_data, face, relation) と、プリミティブと関連付けられたトポロジーとレイヤとを持つタブ区切り CSV ファイルが含まれ、(--skip-layers が与えられない場合) 与えられたトポロジーのレイヤとして報告されたテーブルの PostgrfeSQL ダンプのカスタムフォーマットも含まれます。

9.16.2. トポロジーインポータの使用

pgtopo_import スクリプトは pgtopo_export 書式のトポロジーのダンプと、生成するトポロジーに与える名前とを取り、そこからトポロジーと関連するレイヤを再構築する SQL スクリプトを出力します。

生成された SQL には、与えられた名前でトポロジーを生成し、プリミティブなデータをロードし、格納して、TopoGeometry 値を確実に正しいトポロジーに関連付けることで全てのトポロジーレイヤを登録する手続きが含まれます。

デフォルトでは pgtopo_import は標準入力からダンプを読むので、pgtopo_export をパイプで繋げて併用することができます。入力ファイル名をコマンドラインスイッチ -f で指定することもできます。

デフォルトでは pgtopo_import の SQL ファイル出力には、ダンプで発見された全てのレイヤを格納するためのコードが含まれます。

これらは、ダンプ内にあるテーブル名が既に対象データベース内のテーブル名で使われている場合には、望ましくないようになるか動作しない可能性があります。この場合には、レイヤをスキップする --skip-layers スイッチを使い、別に (または後で) 処理することができます。

ロードして、レイヤを名前付きトポロジーにリンクする SQL は --only-layers スイッチで生成できます。これは、名前の競合を解決した*後に*レイヤをロードしたり、異なるトポロジーのレイヤ (開始トポロジーで空間的に簡略化されたもの等が挙げられます) にリンクするのに使えます。

対象トポロジーが既に存在していて事前に削除したいなら --drop-topology スイッチを渡すことができます (PostGIS 3.6.0以降)。