28. 3次元

28.1. 3次元ジオメトリ

ここまでX,Y座標だけを持つ2次元ジオメトリで作業をしてきていました。しかしPostGISは全てのジオメトリタイプでさらなる次元に対応しています。"Z"座標で高さ情報を追加できますし、"M"座標値で付加的な次元情報 (一般的には時間、道路距離、河川距離)を追加できます。

3次元と4次元のジオメトリでは、座標次元は頂点の座標値として追加され、追加次元をどう解釈するかを示すようにジオメトリタイプが拡張されます。追加次元によってプリミティブなジオメトリから三つずつの拡張ジオメトリタイプが作られます。

  • Point (2次元)はPointZ, PointM, PointZMタイプに変化します。

  • Linestring (2次元)はLinestringZ, LinestringM, LinestringZMタイプに変化します。

  • Polygon (2次元)はPolygonZ, PolygonM, PolygonZMタイプに変化します。

  • 他も同じようになります。

Well-Known Text (WKT)では、高次元ジオメトリのフォーマットはISO SQL/MM仕様で与えられています。高次元情報は、タイプ名の後に文字列を追加するだけです。高次元座標値は X/Y 情報の後に追加されます。次のようになります。

  • POINT ZM (1 2 3 4)

  • LINESTRING M (1 1 0, 1 2 0, 1 3 1, 2 2 0)

  • POLYGON Z ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0))

ST_AsText()関数は3次元や4次元を扱う時には上のような表現を返します。

Well-Known Binary (WKB)では、高次元ジオメトリのフォーマットはISO SQL/MM仕様で与えられています。BNF形式のフォーマットについては https://git.osgeo.org/gitea/postgis/postgis/src/branch/master/doc/bnf-wkb.txt にあります。

標準タイプの高次元形式だけでなく、PostGISには3次元空間で意味を持つ新タイプは次の通りです。

  • TINはデータベース内の行を三角網を作る時に使います。

  • POLYHEDRALSURFACEはデータベースで立体オブジェクトを作りたい時に使います。

これらのタイプは3次元オブジェクトのモデルのためのものですのでZ形式を使うことで意味が出ます。POLYHEDRALSURFACE Zの例として1単位の立方体を次に示します。

POLYHEDRALSURFACE Z (
  ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
  ((0 0 0, 0 1 0, 0 1 1, 0 0 1, 0 0 0)),
  ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
  ((1 1 1, 1 0 1, 0 0 1, 0 1 1, 1 1 1)),
  ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),
  ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1))
)

28.2. 3次元関数

3次元オブジェクト間の関係を計算するために作られた関数が多数あります。次の通りです。

  • ST_3DClosestPoint — g1上のg2に最も近い3次元ポイントを返します。これは3次元最短線の始点です。

  • ST_3DDistance — ジオメトリ型に対しては、二つのジオメトリの3次元デカルト距離 (空間参照系に基づく)の最小値を投影参照系の単位で返します。

  • ST_3DDWithin — 3次元 (XYZ)ジオメトリタイプに対して、二つのジオメトリが3次元距離が指定した距離以内ならtrueを返します。

  • ST_3DDFullyWithin — 全ての3次元ジオメトリの互いの距離が指定した距離以内ならtrueを返します。

  • ST_3DIntersects — ジオメトリが3次元空間で「空間的にインタセクトする」場合にはTRUEを返します。ポイントとラインストリングのみ有効です。

  • ST_3DLongestLine — 二つのジオメトリの間の3次元最長距離を返します。

  • ST_3DMaxDistance — ジオメトリ型に対しては、二つのジオメトリの3次元デカルト距離 (空間参照系に基づく)の最大値を投影参照系の単位で返します。

  • ST_3DShortestLine — 二つのジオメトリの3次元最短ラインを返します。

ST_3DDistance関数を使って単位立方体とポイントとの間の距離の計算は次のようにできます。

-- This is really the distance between the top corner
-- and the point.
SELECT ST_3DDistance(
  'POLYHEDRALSURFACE Z (
    ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
    ((0 0 0, 0 1 0, 0 1 1, 0 0 1, 0 0 0)),
    ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
    ((1 1 1, 1 0 1, 0 0 1, 0 1 1, 1 1 1)),
    ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),
    ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1))
  )'::geometry,
  'POINT Z (2 2 2)'::geometry
);

-- So here's a shorter form.
SELECT ST_3DDistance(
  'POINT Z (1 1 1)'::geometry,
  'POINT Z (2 2 2)'::geometry
);

-- Both return 1.73205080756888 == sqrt(3) as expected

28.3. N次元インデックス

高次元データを持つと、インデックスを有効にすることになるかも知れません。しかしながら、多次元インデックスを適用する前に全ての次元内でのデータの分布について十分に検討するべきです。

インデックスはデータベースがWHERE条件の結果として返す行を大幅に削減する場合に限って使えます。高次元インデックスを有効に使用するには、ユーザが作ろうとするクエリに対してデータが次元の広い範囲にわたっていなければなりません。

  • DEMポイント集合は3次元インデックスの「不十分な」候補になるかも知れません。クエリがポイントの2次元ボックスを抽出することになり、ポイント集合をZ値で薄切りにしようとすることはほぼありません。

  • X/Y/T空間でのGPSトラックは、GPSトラックが互いに周期的に全ての次元に及ぶ (たとえば同じ道程を違う時刻に何度も通る)場合には、3次元インデックスの「良い」候補になるかも知れません。データセットの全ての次元で大きなばらつきが出るためです。

多次元インデックスを任意次元のデータに作ることができます (混合次元であっても)。たとえば、``nyc_streets``テーブル上に多次元インデックスを作るには次のようにします。

CREATE INDEX nyc_streets_gix_nd ON nyc_streets
USING GIST (geom gist_geometry_ops_nd);

``gist_geometry_ops_nd``パラメータで、PostGISに標準の2次元インデックスでなくN次元インデックスを使うようになります。

インデックスを構築すると、クエリ内でインデックス演算子``&&&``によってインデックスを使えるようになります。``&&&``は「バウンディングボックスの相互作用」としては``&&``と同じ意味ですが、入力ジオメトリの次元の全てを使うという意味が適用されます。次元が合致しないジオメトリは相互作用しません。

-- Returns true (both 3-D on the zero plane)
SELECT 'POINT Z (1 1 0)'::geometry &&&
       'POLYGON ((0 0 0, 0 2 0, 2 2 0, 2 0 0, 0 0 0))'::geometry;

-- Returns false (one 2-D one 3-D)
SELECT 'POINT Z (3 3 3)'::geometry &&&
       'POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'::geometry;

-- Returns true (the volume around the linestring interacts with the point)
SELECT 'LINESTRING Z(0 0 0, 1 1 1)'::geometry &&&
       'POINT(0 1 1)'::geometry;

N次元インデックスを使って``nyc_streets``テーブル内を探索するには、2次元インデックス演算子``&&``から``&&&``演算子に変更するだけです。

-- N-D index operator
SELECT gid, name
FROM nyc_streets
WHERE geom &&&
      ST_SetSRID('LINESTRING(586785 4492901,587561 4493037)'::geometry,26918);

-- 2-D index operator
SELECT gid, name
FROM nyc_streets
WHERE geom &&
      ST_SetSRID('LINESTRING(586785 4492901,587561 4493037)'::geometry,26918);

結果は同じなはずです。一般にN次元インデックスは2次元インデックスと比べて少し遅いです。N次元インデックスの使用は、N次元クエリで実行するクエリの選択性が向上すると確信できる場合だけにしてください。