第3章 よくある質問

3.1. どの種類のジオメトリオブジェクトを格納できますか?
3.2. GISオブジェクトをデータベースに挿入するにはどうしますか?
3.3. 空間クエリを作成するにはどうするのですか?
3.4. 大きなテーブルでの空間クエリの速度向上はどうするのですか?
3.5. なぜPostgreSQLのR木インデックス機能を持たないのですか?
3.6. なぜ AddGeometryColumn()関数と他のOpsnGIS関数を使うべきなのですか?
3.7. 半径内にあるオブジェクトを全て検索する最善の方法は何ですか?
3.8. クエリの一部として投影変換を実現するにはどうしますか?

3.1.

どの種類のジオメトリオブジェクトを格納できますか?

ポイント、ライン、ポリゴン、マルチポイント、マルチライン、マルチポリゴン、ジオメトリコレクションを格納できます。これらは Open GIS Well Known Text Formatで規定されています (XYZ,XYM,XYZM拡張付き)。

3.2.

GISオブジェクトをデータベースに挿入するにはどうしますか?

まず、GISデータを保持するためにジオメトリ型のカラムをテーブルに作成する必要があります。psqlでデータベースに接続して、次のSQLを実行します。

CREATE TABLE gtest ( ID int4, NAME varchar(20) );
SELECT AddGeometryColumn('', 'gtest','geom',-1,'LINESTRING',2);

ジオメトリカラムの追加に失敗したなら、PostGIS関数とオブジェクトをそのデータベースにロードしていない可能性があります。インストール方法をご覧ください。

これで、SQLのINSERTステートメントを使って、ジオメトリをテーブルに挿入することができます。GISオブジェクト自体は、OpenGISコンソーシアムの"well-known text"形式を使っています。

INSERT INTO gtest (ID, NAME, GEOM) 
VALUES (
  1, 
  'First Geometry', 
  GeomFromText('LINESTRING(2 3,4 5,6 5,7 8)', -1)
);

GISオブジェクトの詳細については、オブジェクトリファレンスをご覧下さい。

テーブルの中にあるGISデータを閲覧するには、次のようにします。

SELECT id, name, AsText(geom) AS geom FROM gtest;

返り値は次のようなかんじになります。

id | name           | geom
----+----------------+-----------------------------
  1 | First Geometry | LINESTRING(2 3,4 5,6 5,7 8) 
(1 row)

3.3.

空間クエリを作成するにはどうするのですか?

他のデータベースクエリを作るのと同じで、返り値、関数、テストのSQLの組み合わせです。

空間クエリでは、クエリを作成する際に心を平静に保つための重要な二つの問題があります。 一つは、使用することができる空間インデックスがあるか、です。もう一つは、多数のジオメトリを相手に計算量の多い計算を行っているか、です。

一般的に、フィーチャーのバウンディングボックスがインタセクト (交差)しているかをテストするインタセクト演算子 (&&)を使います。&&演算子が便利な理由は、速度向上のために空間インデックスが付けられているなら、&&演算子は空間インデックスを使うからです。これによって、クエリの速度はとてもとても速くなります。

また、検索結果をより狭めるために、Distance(), ST_Intersects(), ST_Contains(), ST_Within() などといった空間関数を使うことでしょう。ほとんどの空間クエリは、インデクスのテストと空間関数のテストを含みます。インデクスのテストで返ってくるタプルを、求める条件に合致するかもしれないタプルのみとして、タプルの数を制限します。それから、空間関数で確実な条件のテストを行います。

SELECT id, the_geom 
FROM thetable 
WHERE 
  the_geom && 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))' 
AND
  _ST_Contains(the_geom,'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))');

3.4.

大きなテーブルでの空間クエリの速度向上はどうするのですか?

大きなテーブルの速いクエリは、空間データベースのレゾンデートル (トランザクションサポートもそうですが)で、良いインデックスは重要です。

geometryカラムを持つテーブルでの空間インデックスの構築は、"CREATE INDEX"を使って、次のようにします。

CREATE INDEX [インデックス名] ON [テーブル名] USING GIST ( [ジオメトリカラム] );

"USING GIST"オプションによって、サーバにGiST (Generalized Search Tree)インデックスを作るよう指示が渡ります。

注記

GiSTインデックスは、不可逆であると仮定します。不可逆インデックスの構築には、代理オブジェクト (空間インデックスの場合はバウンディングボックス)を使います。

PostgreSQLのクエリプランナがインデックスを作るべきかについて合理的な決定を行うよう、十分な情報を確実に持てるようにすべきです。そのために、ジオメトリテーブル上で"gather statistics"を実行しなければなりません。

PostgreSQL 8.0.x以上では、VACUUM ANALYZEコマンドを実行するだけです。

PostgreSQL 7.4.x以下では、SELECT UPDATE_GEOMETRY_STATS()を実行します。

3.5.

なぜPostgreSQLのR木インデックス機能を持たないのですか?

PostGISの、かつての版では、PostgreSQLのR木インデックスを使っていましたが、0.6版でPostgreSQLのR木は完全に捨てて、R-Tree-over-GiSTスキームによる空間インデックスを提供しています。

私たちの試験では、R木とGiSTの検索速度は同程度であることが示されています。PostgreSQLのR木には、GISフィーチャーで使うためには好ましくない二つの制限があります (これらの制限は現在のPostgreSQLネイティブのR木実装についてであって、R木一般の話ではありません)。

  • PostgreSQLのR木インデックスは、8K以上のサイズのフィーチャーは扱えません。GiSTインデックスはフィーチャー自体の代わりにバウンディングボックスを用いる「不可逆」トリックを使っているので扱うことができます。

  • PostgreSQLのR木インデックスは「NULLセーフ」ではなく、NULLジオメトリを含むジオメトリカラムではインデックス作成に失敗します。

3.6.

なぜ AddGeometryColumn()関数と他のOpsnGIS関数を使うべきなのですか?

OpenGIS関数を使いたくないのでしたら、使う必要はありません。単純にジオメトリカラムをCREATEステートメントで定義する古いやり方で作成して下さい。全てのジオメトリはSRIDが-1になり、OpenGISメタデータテーブルは適切に書き込まれません。これによって、ほとんどのPostGISベースのアプリケーションでは失敗します。一般的にはAddGeometryColumn()を用いることをお勧めします。

MapServerはgeometry_columnsメタデータを使うアプリケーションの一つです。踏み込んでいえば、MpaServerはジオメトリカラムのSRIDを使って、正しい地図投影へのフィーチャーの自動投影変換を行います。

3.7.

半径内にあるオブジェクトを全て検索する最善の方法は何ですか?

データベースを最も効果的に使うには、半径検索とバウンディングボックス検索を組み合わせた半径検索を行うのが最も良いです。バウンディングボックス検索で空間インデックスを使用するので、半径検索が適用されるサブセットへのアクセスが早くなります。

ST_DWithin(geometry, geometry, distance)関数は、インデックス付きの距離検索を実行する手軽な方法です。この関数は、距離半径を十分に含む大きさの検索矩形を作成して、 インデックス付きの結果サブセットに対して確実な距離検索を行います。

たとえば、POINT(1000 1000)から100メートル内の全てのオブジェクトを見つけるためには、次のクエリで動作します。

SELECT * FROM geotable 
  WHERE ST_DWithin(geocolumn, 'POINT(1000 1000)', 100.0);

3.8.

クエリの一部として投影変換を実現するにはどうしますか?

投影変換を行うには、変換元と変換先双方の座標系がSPATIAL_REF_SYSテーブルに定義されていて、かつ投影変換されるジオメトリがそのSRIDを持っている必要があります。これが行われていると、投影変換は求める変換先SRIDを参照するのと同じぐらい簡単です。

SELECT ST_Transform(the_geom,4269) FROM geotable;