第4章 PostGISを使う

目次

4.1. GISオブジェクト
4.1.1. OpenGIS WKBとWKT
4.1.2. PostGIS EWKB, EWKTと標準形式
4.2. OpenGIS標準を使う
4.2.1. SPATIAL_REF_SYSテーブル
4.2.2. GEOMETRY_COLUMNSテーブル
4.2.3. 空間テーブルを作る
4.2.4. ジオメトリのOpenGIS準拠を確実にする
4.3. GISデータのロード
4.3.1. SQLを使う
4.3.2. ローダを使う
4.4. GISデータを検索する
4.4.1. SQLを使う
4.4.2. ダンパを使う
4.5. インデックスを構築する
4.5.1. GiSTインデックス
4.5.2. インデックスを使う
4.6. 複雑なクエリ
4.6.1. インデックスの利点を使う
4.6.2. 空間SQLの例
4.7. MapServerを使う
4.7.1. 基本的な使い方
4.7.2. よくある質問
4.7.3. 踏み込んだ使用法
4.7.4. 例
4.8. Javaクライアント (JDBC)
4.9. Cクライアント (libpq)
4.9.1. テキストカーソル
4.9.2. バイナリカーソル

4.1. GISオブジェクト

PostGISでサポートされるGISオブジェクトは、OpenGIS Consortium (OGC)が定義する"Simple Features" のスーパーセットです。 PostGIS 0.9版からOGCの"Simple Features for SQL"仕様で定められた全てのオブジェクトと関数をサポートしています。

PostGISは標準から拡張して 3DZ, 3DM, 4D 座標 (訳注: それぞれXYZ, XYM, XYZM)をサポートしています。

4.1.1. OpenGIS WKBとWKT

OpenGIS仕様は空間オブジェクトの表現について二つの標準を定義しています。Well-Knownテキスト (WKT)形式とWell-Knownバイナリ (WKB)形式です。WKTもWKBも、オブジェクトの型とオブジェクトを形成する座標に関する情報を持っています。

フィーチャーの空間オブジェクトのテキスト表現 (WKT)の例は、次の通りです。

  • POINT(0 0)

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

  • POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))

  • MULTIPOINT(0 0,1 2)

  • MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))

  • MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))

  • GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4)))

OpenGIS仕様では、空間オブジェクトの内部保存書式は空間参照系識別子 (Spatial Referencing System IDentifier, SRID)を含むことも求められます。SRIDはデータベースへの挿入のために空間オブジェクトが生成される時に求められます。

これらの書式の入出力は次のインタフェースを用いて実現できます。

bytea WKB = asBinary(geometry);
        text WKT = asText(geometry);
        geometry = GeomFromWKB(bytea WKB, SRID); 
        geometry = GeometryFromText(text WKT, SRID);
        

たとえば、OGC空間オブジェクトを生成して挿入する妥当なINSERTステートメントは次の通りです。

INSERT INTO SPATIALTABLE ( 
                  THE_GEOM, 
                  THE_NAME 
        ) 
        VALUES ( 
                  GeomFromText('POINT(-126.4 45.32)', 312), 
                  'A Place' 
        )

4.1.2. PostGIS EWKB, EWKTと標準形式

OGC書式は2次元ジオメトリしかサポートされておらず、また、入出力の表現においてSRIDは*決して*埋め込まれません。

PostGIS拡張書式は現在のところOGC書式のスーパーセットとなっています (全ての妥当なWKB/WKTは妥当なEWKB/EWKTです)。しかし、特にもしOGCがPostGIS拡張と矛盾する新しい書式を出すことがあるなら、これは将来変更されるかも知れません。ゆえにこの機能に頼るべきではありません。

PostGIS EWKB/EWKT では 3dm, 3dz, 4d の座標サポートが追加され、SRID情報が埋め込まれます。

拡張された空間オブジェクトのテキスト表現 (EWKT)の例は、次の通りです。

  • POINT(0 0 0) -- XYZ

  • SRID=32632;POINT(0 0) -- XY with SRID

  • POINTM(0 0 0) -- XYM

  • POINT(0 0 0 0) -- XYZM

  • SRID=4326;MULTIPOINTM(0 0 0,1 2 1) -- SRID付きXYM

  • MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))

  • POLYGON((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0))

  • MULTIPOLYGON(((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))

  • GEOMETRYCOLLECTIONM(POINTM(2 3 9),LINESTRINGM((2 3 4,3 4 5)))

これらの書式の入出力は次のインタフェースを用いて実現できます。

bytea EWKB = asEWKB(geometry);
        text EWKT = asEWKT(geometry);
        geometry = GeomFromEWKB(bytea EWKB);
        geometry = GeomFromEWKT(text EWKT);
        

たとえば、PostGISの空間オブジェクトを作成し挿入する妥当なINSERTステートメントは次の通りです。

INSERT INTO SPATIALTABLE ( 
                  THE_GEOM, 
                  THE_NAME 
        ) 
        VALUES ( 
                  GeomFromEWKT('SRID=312;POINTM(-126.4 45.32 15)'), 
                  'A Place' 
        )

The "canonical forms" of a PostgreSQL type are the representations you get with a simple query (without any function call) and the one which is guaranteed to be accepted with a simple insert, update or copy. For the postgis 'geometry' type these are:

- Output -
        binary: EWKB
         ascii: HEXEWKB (EWKB in hex form)

        - Input -
        binary: EWKB
         ascii: HEXEWKB|EWKT
        

たとえば、次のステートメントは、標準的なASCII文字列による入出力の処理でEWKTを読み、HEXEWKBを返すものです。

=# SELECT 'SRID=4;POINT(0 0)'::geometry;
                              geometry
        ----------------------------------------------------
         01010000200400000000000000000000000000000000000000
        (1 row)
        

4.2. OpenGIS標準を使う

OpenGISの「SQL用シンプルフィーチャー仕様」では、標準GISオブジェクト型とこれらを操作するために必要な関数、メタデータテーブルのセットが定義されています。メタデータが一貫性を維持していることを保証するために、空間カラムの生成、消去といった操作はOpenGISで定義されている空間プロシージャを通して実行されます。

OpenGISメタデータテーブルにはSPATIAL_REF_SYSGEOMETRY_COLUMNSの二つがあります。SPATIAL_REF_SYSテーブルは空間データベースで用いられる座標系の、数字によるIDと文字による説明を持っています。

4.2.1. SPATIAL_REF_SYSテーブル

SPATIAL_REF_SYSテーブル定義は次の通りです。

CREATE TABLE SPATIAL_REF_SYS ( 
  SRID INTEGER NOT NULL PRIMARY KEY, 
  AUTH_NAME VARCHAR(256), 
  AUTH_SRID INTEGER, 
  SRTEXT VARCHAR(2048), 
  PROJ4TEXT VARCHAR(2048)
)

SPATIAL_REF_SYSのカラムは次の通りです。

SRID

一意に定められた整数値で、データベースで空間参照系 (SRS)を識別するものです。

AUTH_NAME

その参照系の引用元である標準の名前です。たとえば「EPSG」は妥当なAUTH_NAMEです。

AUTH_SRID

AUTH_NAMEで引用される団体によって定義された空間参照系のIDです。EPSGの場合、EPSG投影コードが入ります。

SRTEXT

空間参照系のWell-Knownテキスト表現です。たとえば、WKT SRSの表現は、次のようになります。

PROJCS["NAD83 / UTM Zone 10N", 
  GEOGCS["NAD83",
    DATUM["North_American_Datum_1983", 
      SPHEROID["GRS 1980",6378137,298.257222101]
    ], 
    PRIMEM["Greenwich",0], 
    UNIT["degree",0.0174532925199433] 
  ],
  PROJECTION["Transverse_Mercator"], 
  PARAMETER["latitude_of_origin",0],
  PARAMETER["central_meridian",-123], 
  PARAMETER["scale_factor",0.9996],
  PARAMETER["false_easting",500000], 
  PARAMETER["false_northing",0],
  UNIT["metre",1] 
]

EPSG投影コードと対応するWKT表現の一覧についてはhttp://www.opengis.org/techno/interop/EPSG2WKT.TXTをご覧下さい。WKTの一般的な議論については、OpenGISの「座標変換サービス実装仕様」http://www.opengis.org/techno/specs.htmをご覧下さい。欧州石油調査グループ (European Petroleum Survey Group, EPSG)と EPSG空間参照系のデータベースに関する情報は、http://epsg.orgをご覧下さい。

PROJ4TEXT

PostGISは座標変換機能を提供するためにProj4ライブラリを用いています。 PROJ4TEXTカラムには、特定のSRIDを示すProj4座標定義文字列が入ります。たとえば次のようになります。

+proj=utm +zone=10 +ellps=clrk66 +datum=NAD27 +units=m

詳細情報については、Proj4ウェブサイhttp://www.remotesensing.org/projをご覧下さい。spatial_ref_sys.sqlは、全てのEPSG投影のためのSRTEXTPROJ4TEXTを持っています。

4.2.2. GEOMETRY_COLUMNSテーブル

GEOMETRY_COLUMNSテーブルは、次のように定義されています。

CREATE TABLE GEOMETRY_COLUMNS ( 
  F_TABLE_CATALOG VARCHAR(256) NOT NULL, 
  F_TABLE_SCHEMA VARCHAR(256) NOT NULL, 
  F_TABLE_NAME VARCHAR(256) NOT NULL, 
  F_GEOMETRY_COLUMN VARCHAR(256) NOT NULL,
  COORD_DIMENSION INTEGER NOT NULL, 
  SRID INTEGER NOT NULL, 
  TYPE VARCHAR(30) NOT NULL 
)

カラムは次のとおりです。

F_TABLE_CATALOG, F_TABLE_SCHEMA, F_TABLE_NAME

ジオメトリカラムを持つフィーチャーテーブルの完全修飾名。"catalog"および"schema"の語はOracle風であることに注意して下さい。"catalog"に類似するものはPostgreSQLになく、このカラムは空白にされます。"schema"についてはPostgreSQLスキーマ名が使われています (publicがデフォルトです)。

F_GEOMETRY_COLUMN

フィーチャーテーブル内のジオメトリカラムの名前。

COORD_DIMENSION

そのカラムの空間の次元 (2, 3 または 4)。

SRID

このテーブルの座標ジオメトリのために使われる空間参照系のID。SPATIAL_REF_SYSへの外部キーになっています。

TYPE

空間オブジェクトのタイプ。 空間カラムを単一型に制限するには、 POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION のうちのいずれかを、また、XYMで使う場合には LINESTRINGM, POLYGONM, MULTIPOINTM, MULTILINESTRINGM, MULTIPOLYGONM, GEOMETRYCOLLECTIONM のうちのいずれかを使います。複数の型が混合するコレクションの場合は "GEOMETRY"タイプとすることができます。

注記

この属性は (おそらく)OpenGIS仕様に入っていませんが、型の同一性を保証するために必要です。

4.2.3. 空間テーブルを作る

空間データを持つテーブルを生成するには、次の通り二段階で行います。

  • 通常の非空間テーブルを生成します。

    たとえば、次のようにします。CREATE TABLE ROADS_GEOM ( ID int4, NAME varchar(25) )

  • OpenGISの"AddGeometryColumn"関数によって空間カラムをテーブルに追加します。

    The syntax is:

    AddGeometryColumn(<schema_name>, <table_name>,
                <column_name>, <srid>, <type>,
                <dimension>)

    Or, using current schema:

    AddGeometryColumn(<テーブル名>,
                <カラム名>, <SRID>, <タイプ>,
                <次元>)

    例1: SELECT AddGeometryColumn('public', 'roads_geom', 'geom', 423, 'LINESTRING', 2)

    例2: SELECT AddGeometryColumn( 'roads_geom', 'geom', 423, 'LINESTRING', 2)

次はテーブルを作成して空間カラムを作る例です (128というSRIDがあると仮定します)。

CREATE TABLE parks ( PARK_ID int4, PARK_NAME varchar(128), PARK_DATE date, PARK_TYPE varchar(2) );
SELECT AddGeometryColumn('parks', 'park_geom', 128, 'MULTIPOLYGON', 2 );

もうひとつ、ジェネリックな「ジオメトリ」型とSRID不明を示す-1を使った例を挙げます。

CREATE TABLE roads ( ROAD_ID int4, ROAD_NAME varchar(128) ); 
SELECT AddGeometryColumn( 'roads', 'roads_geom', -1, 'GEOMETRY', 3 );

4.2.4. ジオメトリのOpenGIS準拠を確実にする

GEOSライブラリで実装される関数のほとんどが、ジオメトリがOpenGIS単純フィーチャー仕様の定義から見て妥当であることが仮定されています。ジオメトリの妥当性のチェックには、IsValid()関数を使います。例を次に示します。

gisdb=# select isvalid('LINESTRING(0 0, 1 1)'), isvalid('LINESTRING(0 0,0 0)');
 isvalid | isvalid
---------+---------
 t       | f

デフォルトでは、PostGISはジオメトリ入力に関するこの妥当性チェックを適用しません。複雑なジオメトリの妥当性のチェックはCPU時間を多く必要とするためです。データソースが信用できない場合は、手動でこのチェックを強制するための制約を付けることができます。

ALTER TABLE mytable ADD CONSTRAINT geometry_valid_check CHECK (isvalid(the_geom));

妥当な入力ジオメトリでPostGIS関数を呼んだのに「GEOS Intersection()がエラーを投げました!」や「JTS Intersection()がエラーを投げました!」というようなメッセージに遭遇したら、それはたぶん、PostGISまたは使用しているライブラリの中のエラーを発見しました。PostGIS開発者に連絡するべきです。PostGIS関数が妥当である入力ジオメトリから妥当でないジオメトリが返る場合も同じです。

注記

厳格にOGCジオメトリに準拠すると、Z値やM値を持てません。ST_IsValid()は高次を考慮に入れません。AddGeometryColumn()を実行するとジオメトリの次元をチェックする制約が加わるので、そこで2を指定すれば十分です。

4.3. GISデータのロード

空間テーブルを作成したら、これでGISデータをデータベースにアップロードする準備ができたことになります。現在、PostGIS/PostgreSQLデータベースにデータをロードするには、SQLステートメントを使う、またはシェープファイルのローダ/ダンパを使う、二つの方法があります。

4.3.1. SQLを使う

データをテキスト表現に変換できるなら、フォーマットされたSQLを使うのがデータをPostGISに入れる最も簡単な方法です。Oracleや他のSQLデータベースを使うように、SQL端末モニタにSQLの"INSERT"ステートメントで一杯になった大きなテキストファイルをパイプで送ることで、大量のデータをロードできます。

データアップロードファイル (たとえばroads.sql)は次のようになるでしょう。

BEGIN;
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (1,GeomFromText('LINESTRING(191232 243118,191108 243242)',-1),'Jeff Rd'); 
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (2,GeomFromText('LINESTRING(189141 244158,189265 244817)',-1),'Geordie Rd'); 
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (3,GeomFromText('LINESTRING(192783 228138,192612 229814)',-1),'Paul St'); 
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (4,GeomFromText('LINESTRING(189412 252431,189631 259122)',-1),'Graeme Ave'); 
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (5,GeomFromText('LINESTRING(190131 224148,190871 228134)',-1),'Phil Tce'); 
INSERT INTO ROADS_GEOM (ID,GEOM,NAME ) VALUES (6,GeomFromText('LINESTRING(198231 263418,198213 268322)',-1),'Dave Cres');
COMMIT;

データファイルは、次に示す"psql"というSQL端末モニタを使って、簡単にPostgreSQLにパイプで送ることができます。

psql -d [データベース名] -f roads.sql

4.3.2. ローダを使う

shp2pgsqlデータローダは、ESRIシェープファイルをPostGIS/PostgreSQLデータベースに挿入するための適切なSQLに変換します。ローダには、次に示すコマンドラインフラグによって識別される、いくつかの操作モードがあります。

-d

シェープファイルにあるデータを持つ新しいテーブルを作成する前にデータベーステーブルを削除します。

-a

シェープファイルからデータベーステーブルにデータを追加します。複数のファイルをロードするためにこのオプションを使う場合は、これらのファイルは同じ属性と同じデータ型を持つ必要があります。

-c

新しいテーブルの作成とシェープファイルからのデータの読み込みを行います。これがデフォルトモードです

-p

テーブル作成のSQLコードを生成するだけで、実際のデータは追加しません。このモードは、テーブル作成とデータロードとを完全に分けたい場合に使用します。

-D

出力データにPostgreSQLのダンプ書式を用います。このモードは-a, -c, -dと組み合わせて利用します。デフォルトの"insert"によるSQL書式よりも、大変早くロードできます。大きなデータセットではこちらを使用して下さい。

-s <SRID>

指定したSRIDでジオメトリデーブルの作成とデータの読み込みを行います。

-k

識別子 (カラム、スキーマおよび属性)の大文字小文字を保持します。シェープファイルの属性は全て大文字であることに注意して下さい。

-i

全ての整数を標準の32ビット整数に強制します。DBFヘッダではそれが正当であったとしても、64ビットのbigintを生成しません。

-w

古い版 (0.x版)のPostGISのためにWKT書式を出力します。このオプションを使うと、座標変動が発生したり、M値が削除されることに注意して下さい。

-a, -c, -dおよび-pは互いに排他的であることに注意して下さい。

ローダを使って入力ファイルを生成してアップロードするセッション例は次の通りです。

# shp2pgsql shaperoads myschema.roadstable > roads.sql 
# psql -d roadsdb -f roads.sql

変換とアップロードはUNIXのパイプを使うと一回で実行できます。

# shp2pgsql shaperoads myschema.roadstable | psql -d roadsdb

4.4. GISデータを検索する

データは、SQLまたはシェープファイルローダ/ダンパを使ってデータベースから抜き出すことができます。SQLに関する節において、空間テーブルでの比較とクエリを行うために用いることができる演算子のいくつかを議論します。

4.4.1. SQLを使う

データベースからデータを引き出す最もストレートな手段は、次のように、SQLのSELECTクエリを使ってカラムを可読なテキストファイルとして出力することです。

db=# SELECT id, AsText(geom) AS geom, name FROM ROADS_GEOM; 
id | geom                                    | name 
---+-----------------------------------------+-----------
 1 | LINESTRING(191232 243118,191108 243242) | Jeff Rd  
 2 | LINESTRING(189141 244158,189265 244817) | Geordie Rd 
 3 | LINESTRING(192783 228138,192612 229814) | Paul St 
 4 | LINESTRING(189412 252431,189631 259122) | Graeme Ave 
 5 | LINESTRING(190131 224148,190871 228134) | Phil Tce 
 6 | LINESTRING(198231 263418,198213 268322) | Dave Cres 
 7 | LINESTRING(218421 284121,224123 241231) | Chris Way 
(6 rows)

しかし、返ってくる結果の数を削るために、なんらかの制限をかけることが重要となるときがあるでしょう。属性ベースの制限の場合、非空間テーブルで使う通常の文法と同じSQLを使うだけです。空間ベースの制限の場合、次の演算子が使用可能であり、便利です。

&&

この演算子で、一つのジオメトリのバウンディングボックスが他のバウンディングボックスとインタセクトするかを問い合わせることができます。

~=

この演算子で、二つのジオメトリが幾何的に同一であるかを見ることができます。 たとえば、'POLYGON((0 0,1 1,1 0,0 0))' は 'POLYGON((0 0,1 1,1 0,0 0))' と同じかを見ることができます (これは同じとなります)。

=

この演算子は他より若干素朴なもので、二つのジオメトリのバウンディングボックスが同じかを見るだけです。

次に、これらの演算子をクエリで使うことができます。SQLコマンドラインからジオメトリとボックスの特定を行うときは、"GeomFromText()"関数で、明示的に文字列表現をジオメトリに変換しなければならないことに注意して下さい。たとえば、次のようになります。

SELECT 
  ID, NAME 
FROM ROADS_GEOM 
WHERE 
  GEOM ~= GeomFromText('LINESTRING(191232 243118,191108 243242)',-1);

上のクエリは"ROADS_GEOM"テーブルから、その値と等価である単一のレコードを返します。

"&&"演算子を使うとき、比較フィーチャーをBOX3DかGEOMETRYかに指定することができます。ただし、GEOMETRYを指定すると、それのバウンディングボックスが比較に使われます。

SELECT 
  ID, NAME 
FROM ROADS_GEOM 
WHERE 
  GEOM && GeomFromText('POLYGON((191232 243117,191232 243119,191234 243117,191232 243117))',-1);

上のクエリでは、比較するためにポリゴンのバウンディングボックスを用いています。

最も一般的な空間クエリは「フレームベース」のクエリでしょう。これは、表示するためのデータの価値のある「マップフレーム」を取得するために、データブラウザやウェブマッパのようなクライアントソフトウェアに使われます。このフレームで"BOX3D"オブジェクトを使う場合は、次のようなクエリになります。

SELECT 
  AsText(GEOM) AS GEOM 
FROM ROADS_GEOM 
WHERE 
  GEOM && GeomFromText('BOX3D(191232 243117,191232 243119)'::box3d,-1);

ここで、BOX3Dの投影を指定するためにSRIDを使っていることに注意して下さい。SRIDを-1に設定しているのは、SRIDを指定しないということを示しています。

4.4.2. ダンパを使う

pgsql2shpテーブルダンパは、データベースに直接接続して、テーブル (あるいはクエリによって定義されたもの)をシェープファイルに変換するものです。基本的な文法は次の通りです。

pgsql2shp [<オプション>] <データベース> [<スキーマ>.]<テーブル>
pgsql2shp [<オプション>] <データベース> <クエリ>

コマンドラインオプションは次の通りです。

-f <ファイル名>

特定のファイル名に出力を書きこみます。

-h <ホスト>

接続先データベースのホスト名。

-p <ポート>

接続先データベースのポート。

-P <パスワード>

データベースに接続するためのパスワード。

-u <ユーザ名>

データベースに接続する際のユーザ名。

-g <ジオメトリカラム>

複数のジオメトリカラムを持つテーブルの場合の、シェープファイルの出力に使用するジオメトリカラム。

-b

バイナリカーソルを使います。これは、実行時間を短くしますが、テーブルの非ジオメトリ属性がテキストへのキャストを持っていない場合には、動作しません。

-r

Rawモード。gidフィールドを落としたり、カラム名をエスケープしてはいけません。

-d

後方互換: 古い (1.0.0より前)のPostGISデータベースからダンプする際に3次元のシェープファイルを出力します (デフォルトでは2次元になります)。 PostGIS 1.0.0以上では、次元は完全に反映されます。

4.5. インデックスを構築する

インデックスは大きなデータセットを持つ空間データベースの利用を可能にするものです。インデックスなしでは、フィーチャーの検索でデータベースの全レコードを「シーケンシャルスキャン」する必要があります。インデックスをつけることで、データを検索木に組織化して、特定のレコードを発見するための検索をより早くすることができます。 PostgreSQLは、B木、R木、GiSTの3種類のインデックスをデフォルトでサポートしています。

  • B木は、数字、文字、日付といった、一つの軸に沿ってソートできるデータに使用します。 GISデータは合理的に一つの軸に沿ったソートはできません ((0,0)と(0,1)と(1,0)で大きいのはどれでしょう?)ので、B木インデックスは、ここでは使えません。

  • R木はデータを長方形に分割して、さらにその長方形を小さい長方形に分割していったものです。R木はいくつかの空間データベースでGISデータのインデックスに使われますが、PostgreSQLのR木実装は、GiST実装ほどにロバストではありません。

  • GiST (Generalized Search Trees)インデックスはデータを「一方へのもの」 (訳注: 「左側にあるもの」「上側にあるもの」など)、「オーバラップするもの」、「中にあるもの」に分割して、GISデータを含む幅広いデータ型で使えるようにしたものです。PostGISではGISデータにインデックスを付けるためにGiSTの上でR木インデックス実装を使用しています。

4.5.1. GiSTインデックス

GiSTは「汎用的な検索木 (Generalized Search Tree)」の意味で、インデックスの一般化された形式です。GISインデックスに加えて、GiSTは通常のB木インデックスに従わない全ての種類の不規則なデータ構造 (整数配列, スペクトラルデータ等)の検索速度を向上させるために使います。

ひとたびGISデータテーブルが数千行を超えたら、空間検索の速度向上のためインデックスを構築したくなるでしょう (これは属性検索でない場合です。属性でしたら通常のインデックスを属性フィールドに追加します)。

GiSTインデックスをジオメトリカラムに追加するための文は次の通りです。

CREATE INDEX [indexname] ON [tablename] 
  USING GIST ( [geometryfield] GIST_GEOMETRY_OPS ); 

空間インデックスの構築は、計算量を集中させて行われます。100万行のテーブルで、300MHzのSolaris機ではGiSTインデックスの構築に概ね1時間かかりました。インデックスを構築したあとは、クエリプランの最適化に使うため、次のようにPostgreSQLにテーブル統計情報の収集をさせることが重要です。

VACUUM ANALYZE [テーブル名] [カラム名];
-- 次のクエリはPostgreSQL 7.4以前でのみ必要です
SELECT UPDATE_GEOMETRY_STATS( [テーブル名], [カラム名] );

GiSTインデックスはPostgreSQLのR木インデックスと比べて二つの利点を持っています。まず、GiSTインデックスは「NULLセーフ」、すなわちNULL値を含むインデックスカラムで利用できることです。次に、GiSTインデックスはGISオブジェクトがPostgreSQLで8Kのページサイズを超えるサイズを扱う際に重要な「不可逆」の概念を持っていることです。不可逆にすることによって、PostgreSQLは、インデックスにおけるオブジェクトの「重要な」部分、GISオブジェクトの場合にはバウンディングボックスになりますが、これのみを納めることができます。 R木インデックスで8Kを超えるGISオブジェクトのインデックスを構築しようとすると、失敗します。

4.5.2. インデックスを使う

通常、インデックスは見えないところでデータアクセスの速度向上を行います。すなわち、ひとたびインデックスが構築されたら、クエリプランナは透過的に、クエリプランの速度を向上させるためにインデックス情報を使うべき時を判断します。残念なことに、PostgreSQLクエリプランナは、GiSTインデックスの使用について十分に最適化できず、時々、検索で空間インデックスを使用すべきなのに、テーブル全体を順に走査することがあります。

空間インデックスが使用されていない (または属性インデックスがその問題のために使用されていない)場合、次の二つのことができます。

  • まず、クエリプランナにインデックス使用まわりの判断に利用するためのより良い情報を提供するために、値の数量と分散に関する統計情報が収集されたかを確認してください。PostgreSQL 7.4以前では、update_geometry_stats([テーブル名], [カラム名]) (分散計算)とVACUUM ANALYZE [テーブル名] [カラム名] (値の数量の計算)とを実行します。PostgreSQL 8.0については、VACUUM ANALYZEを実行することで同じ動作になります。常に定期的なデータベースへのvacuumを実行すべきです。多くのPostgreSQLのデータベースエージェントは、閑散時のcronジョブとして定期的にVACUUMを実行します。

  • vacuumが働かないなら、SET ENABLE_SEQSCAN=OFFコマンドで、プランナにインデックス情報を強制的に使わせることができます。このコマンドは控え目に実行すべきで、かつ、空間インデックスがあるクエリ上でのみ使うべきです。一般的に言うと、通常のB木インデックスを使うべき時に関してあなたが知っていることよりも、プランナはより良く知っています。クエリを実行したら、ENABLE_SEQSCAN設定を戻して、他のクエリでは通常通りプランナを使用することを考えるべきです。

    注記

    0.6版では、ENABLE_SEQSCANでプランナにインデックスを強制的に使わせることは重要ではありません。

  • 順に走査する際のコストとインデックスを使う際のコストとを比較してプランナが間違っていることに気付いたら、postgresql.confでrandom_page_costの値を減らしてみるか"SET random_page_cost=#"を使ってみてください。このパラメータのデフォルト値は4ですが、1か2にしてみて下さい。値を減らすことでプランナがよりインデックス検索を行う傾向になります。

4.6. 複雑なクエリ

空間データベース機能のレゾンデートルは、通常はデスクトップGISに求める機能を、データベース内部のクエリで実現することです。PostGISを効果的に使用するには、どの空間機能が有効かを知り、また、良い性能を提供する所に適切にインデックスがあることが保証されていることが求められます。

4.6.1. インデックスの利点を使う

クエリを作成するとき、 &&のようなバウンディングボックスを基準とした演算子によってのみGiST空間インデックスの利点が出てくることだけは覚えておくことが重要です。distance()のような関数では演算の最適化を行うためにインデックスを使うことができません。 たとえば、次のクエリでは、大きなテーブルでは本当に遅くなります。

SELECT the_geom FROM geom_table
WHERE distance( the_geom, GeomFromText( 'POINT(100000 200000)', -1 ) ) < 100

このクエリは、geom_tableにおける (100000, 200000) の点から距離が100単位以内にある全てのジオメトリを選択します。これは、テーブル内にあるそれぞれの点と指定した点との距離を計算する、すなわち、それぞれの行でひとつのdistance()計算を行うため、遅くなるのです。&&演算子を使うと、求められる距離計算の量を減らすことで回避できます。次のようにします。

SELECT the_geom FROM geom_table
WHERE the_geom && 'BOX3D(90900 190900, 100100 200100)'::box3d
  AND distance( the_geom, GeomFromText( 'POINT(100000 200000)', -1 ) ) < 100

このクエリは、同じジオメトリを選択しますが、より効果的な方法で行われます。the_geomにGiSTインデックスがあると仮定すると、クエリプランナは、distance()関数の結果を計算する前に行を減らすためにインデックスを使うことができると認識します。&&演算子で使われているBOX3Dジオメトリは、指定位置を中心とした一辺200単位の正方形です。これが「クエリボックス」です。&&演算子は 結果セットを「クエリボックス」にオーバラップするバウンディングボックスを持つジオメトリだけに素早く減らすためにインデックスを使います。「クエリボックス」がジオメトリテーブル全体の範囲より十分に小さいと仮定すると、行われなければならない距離計算の量は劇的に減少します。

4.6.2. 空間SQLの例

本節の例では、線型の道、ポリゴンの自治体境界、の二つのテーブルを使います。テーブルの定義をしまします。bc_roadsについては次の通りです。

Column    |       Type        |   Description
------------+-------------------+-------------------
 gid        | integer           | Unique ID
 name       | character varying | Road Name
 the_geom   | geometry          | Location Geometry (Linestring)

bc_municipalityテーブルの定義については次の通りです。

Column   |       Type        |   Description
-----------+-------------------+-------------------
 gid       | integer           | Unique ID
 code      | integer           | Unique ID
 name      | character varying | City / Town Name
 the_geom  | geometry          | Location Geometry (Polygon)
4.6.2.1.1. 道路の総延長はkm表記でいくらになるでしょう?
4.6.2.1.2. プリンスジョージ市の大きさはha表記でいくらになるでしょう?
4.6.2.1.3. 県内で最も大きな面積となる自治体はどこでしょう?
4.6.2.1.4. 各自治体内に含まれる道路の総延長はいくらでしょう?
4.6.2.1.5. プリンスジョージ市内の全ての道路からなるテーブルを作る
4.6.2.1.6. ビクトリア州の「ダグラス通り」の長さはkm表記でいくらになるでしょう?
4.6.2.1.7. 穴を持つ自治体ポリゴンのうち最も大きいのはどれでしょう?
4.6.2.1.1. 道路の総延長はkm表記でいくらになるでしょう?
4.6.2.1.2. プリンスジョージ市の大きさはha表記でいくらになるでしょう?
4.6.2.1.3. 県内で最も大きな面積となる自治体はどこでしょう?
4.6.2.1.4. 各自治体内に含まれる道路の総延長はいくらでしょう?
4.6.2.1.5. プリンスジョージ市内の全ての道路からなるテーブルを作る
4.6.2.1.6. ビクトリア州の「ダグラス通り」の長さはkm表記でいくらになるでしょう?
4.6.2.1.7. 穴を持つ自治体ポリゴンのうち最も大きいのはどれでしょう?

4.6.2.1.1.

道路の総延長はkm表記でいくらになるでしょう?

この問題は、次のようなとても単純なSQLで答を得ることができます。

postgis=# SELECT sum(length(the_geom))/1000 AS km_roads FROM bc_roads;
     km_roads
------------------
 70842.1243039643
(1 row)

4.6.2.1.2.

プリンスジョージ市の大きさはha表記でいくらになるでしょう?

このクエリでは、属性条件 (municipality name, 自治体名)に空間計算 (面積)を併用しています。

postgis=# SELECT area(the_geom)/10000 AS hectares FROM bc_municipality 
          WHERE name = 'PRINCE GEORGE';
     hectares
------------------
 32657.9103824927
(1 row) 

4.6.2.1.3.

県内で最も大きな面積となる自治体はどこでしょう?

このクエリは、空間計測をクエリ条件に持ってきています。この問題へのアプローチの方法はいくつかありますが、最も効率的なのは次の通りです。

postgis=# SELECT name, area(the_geom)/10000 AS hectares 
          FROM bc_municipality 
          ORDER BY hectares DESC 
          LIMIT 1;
     name      |    hectares
---------------+-----------------
 TUMBLER RIDGE | 155020.02556131
(1 row)

このクエリの答を出すためには、全てのポリゴンの面積を求める必要があることに注意して下さい。このクエリを多く実行する場合、性能向上のためにテーブルにareaカラムを追加して、別のインデックスを追加することができるようにするのは、意義のあることです。結果を距離について降順に並べ替え、PostgreSQLの"LIMIT"コマンドを用いることで、max()のような集約関数を使わずに、簡単に最も大きい値を得ることができます。

4.6.2.1.4.

各自治体内に含まれる道路の総延長はいくらでしょう?

これは、二つのテーブルからデータを持ち込んで (結合して)いるので「空間結合」の例です。しかし、結合の条件として共通キーの上で接続するという普通のリレーションのやり方でなく空間インタラクション条件 (「含む」)を使っています。

postgis=# SELECT m.name, sum(length(r.the_geom))/1000 as roads_km 
          FROM bc_roads AS r,bc_municipality AS m 
          WHERE r.the_geom && m.the_geom 
          AND contains(m.the_geom,r.the_geom) 
          GROUP BY m.name 
          ORDER BY roads_km;

            name            |     roads_km
----------------------------+------------------
 SURREY                     | 1539.47553551242
 VANCOUVER                  | 1450.33093486576
 LANGLEY DISTRICT           | 833.793392535662
 BURNABY                    | 773.769091404338
 PRINCE GEORGE              |  694.37554369147
 ...

このクエリは、テーブル内の全ての道路の合計を最終結果 (この例での話ですが約250Kmの道です)にまとめられるので、少し時間がかかります。より小さいオーバレイ (数百の道路で数千のレコード)の場合、応答はもっと早くなりえます。

4.6.2.1.5.

プリンスジョージ市内の全ての道路からなるテーブルを作る

これは「オーバレイ」の例です。つまり、二つのテーブルを取得して、空間的に切り取られた結果からなる新しいテーブルを出力します。上で示した「空間結合」と違い、このクエリは実際に新しいジオメトリを生成します。生成されたオーバレイはターボのかかった空間結合みたいなもので、より確かな解析作業に便利です。

postgis=# CREATE TABLE pg_roads as
          SELECT intersection(r.the_geom, m.the_geom) AS intersection_geom, 
                 length(r.the_geom) AS rd_orig_length, 
                 r.* 
          FROM bc_roads AS r, bc_municipality AS m 
          WHERE r.the_geom && m.the_geom 
          AND intersects(r.the_geom, m.the_geom) 
          AND m.name = 'PRINCE GEORGE';

4.6.2.1.6.

ビクトリア州の「ダグラス通り」の長さはkm表記でいくらになるでしょう?

postgis=# SELECT sum(length(r.the_geom))/1000 AS kilometers 
          FROM bc_roads r, bc_municipality m 
          WHERE r.the_geom && m.the_geom 
          AND r.name = 'Douglas St' 
          AND m.name = 'VICTORIA';
    kilometers
------------------
 4.89151904172838
(1 row)

4.6.2.1.7.

穴を持つ自治体ポリゴンのうち最も大きいのはどれでしょう?

postgis=# SELECT gid, name, area(the_geom) AS area 
          FROM bc_municipality 
          WHERE nrings(the_geom) > 1 
          ORDER BY area DESC LIMIT 1;
 gid |     name     |       area
-----+--------------+------------------
  12 | SPALLUMCHEEN | 257374619.430216
(1 row)

4.7. MapServerを使う

Minnesota MapServerはOpenGIS Web Mapping Server仕様を満たすウェブマッピングサーバです。

4.7.1. 基本的な使い方

MapServerでPostGISを使うには、MapServerのコンフィギュレーション方法についての知識が必要ですが、この文書の範囲外です。この節では、PostGIS特有の問題とコンフィギュレーション詳細について記載します。

MapServerでPostGISを使うには、次のものが必要です。

  • PostGIS 0.6以上

  • MapServer 3.5以上

MapServerは、他のPostgreSQLクライアントのように、libpqを使ってPostGIS/PostgreSQLデータにアクセスします。これは、システムがPostgreSQLクライアントライブラリであるlibpqを持っている限りは、PostGISサーバへのネットワークアクセスを持つあらゆる機械にMapServerをインストールできるということを示しています。

  1. "--with-postgis"と好きなconfigureオプションを付けてMpaserverのコンパイルとインストールを行います。

  2. MapServerのmapファイルの中に、PostGISレイヤを追加します。たとえば次のようになります。

    LAYER
      CONNECTIONTYPE postgis
      NAME "widehighways"
      # リモートの空間データベースに接続します
      CONNECTION "user=dbuser dbname=gisdatabase host=bigserver"
      # 'roads'テーブルの'geom'カラムからラインを取得します
      DATA "geom from roads"
      STATUS ON
      TYPE LINE
      # 範囲内のラインのうち、広い高速道路のみ描画します
      FILTER "type = 'highway' and numlanes >= 4"
      CLASS
        # 非常に広い高速道路はより明るい色かつ2ピクセル幅にします
        EXPRESSION ([numlanes] >= 6)
        COLOR 255 22 22      
        SYMBOL "solid"
        SIZE 2
      END
      CLASS
        # 残りは、暗い色かつ1ピクセル幅です
        EXPRESSION ([numlanes] < 6)
        COLOR 205 92 82      
      END
    END

    上の例におけるPostGIS特有のディレクティブは次の通りです。

    CONNECTIONTYPE

    PostGISレイヤでは常に"postgis"とします。

    CONNECTION

    データベース接続は「接続文字列」によって制御されます。接続文字列は、次に示すような標準的なキーと値からなります(<>内はデフォルト値)。

    user=<ユーザ名> password=<パスワード> dbname=<ユーザ名> hostname=<サーバ> port=<5432>

    空の接続文字列も妥当とされますし、あらゆるキーと値のペアは省略できます。接続するためには一般的にはdbnameとusernameとが最少で与えるものとなります。

    DATA

    このパラメータの形式は "<カラム名> from <テーブル名>"となります。ここで、カラム名は地図に描画したい空間カラムを指します。

    FILTER

    フィルタは、妥当なSQL文字列でなければなりません。この文字列は、通常はSQLクエリにおける"WHERE"に続く論理式に対応します。たとえば、6レーン以上の道路だけを描画する場合には、"num_lanes >= 6"というフィルタを使います。

  3. 空間データベースにおいては、空間 (GiST)インデックスを、マップに描かれるレイヤ全てに構築していることを保証して下さい。

    CREATE INDEX [インデックス名]
      ON [テーブル名] 
      USING GIST ( [ジオメトリカラム] GIST_GEOMETRY_OPS );
  4. MapServerを使ってレイヤにクエリを発行する場合は、「oidインデックス」も必要です (訳注: PostgreSQL 8.1以降は、oidはデフォルトでは追加されなくなりました。替わりにSERIAL型フィールドを生成して使うべきです。テーブル生成時に"WITH OID"を付けるとoid付きテーブルが生成されます)。

    MapServerからは、クエリを実行するときに、それぞれの空間レコードを識別する一意な識別子が求められ、MapServerのPostGISモジュールはPostgreSQLのoid値を一意な識別子に使います。これの副作用はクエリ内のレコードのランダムアクセスを早く行うのにoidインデックスが必要となることです。

    「oidインデックス」を構築するには、次のようなSQLを実行します。

    CREATE INDEX [インデックス名] ON [テーブル名] ( oid );

4.7.2. よくある質問

4.7.2.1.1. EXPRESSIONをマップファイルで使う時に、値がテーブルにあるのを確認しているのに条件がtrueになりません。
4.7.2.1.2. シェープファイルで使っているFILTERが、同じデータを持つPostGISテーブルでは動作しません。
4.7.2.1.3. PostGISレイヤの描画がシェープファイルより遅くなりますが、これが普通なのでしょうか?
4.7.2.1.4. PostGISレイヤはちゃんと描けましたが、クエリが本当に遅いです。何が問題なのですか?
4.7.2.1.1. EXPRESSIONをマップファイルで使う時に、値がテーブルにあるのを確認しているのに条件がtrueになりません。
4.7.2.1.2. シェープファイルで使っているFILTERが、同じデータを持つPostGISテーブルでは動作しません。
4.7.2.1.3. PostGISレイヤの描画がシェープファイルより遅くなりますが、これが普通なのでしょうか?
4.7.2.1.4. PostGISレイヤはちゃんと描けましたが、クエリが本当に遅いです。何が問題なのですか?

4.7.2.1.1.

EXPRESSIONをマップファイルで使う時に、値がテーブルにあるのを確認しているのに条件がtrueになりません。

EXPRESIONで使うフィールド名は、シェープファイルと違ってPostGISの場合小文字になります。

EXPRESSION ([numlanes] >= 6)

4.7.2.1.2.

シェープファイルで使っているFILTERが、同じデータを持つPostGISテーブルでは動作しません。

シェープファイルと違い、PostGISレイヤのフィルタはSQL構文を使います (PostGISコネクタがMapServerでレイヤを描画するために生成するSQLステートメントに追加されます)。

FILTER "type = 'highway' and numlanes >= 4"

4.7.2.1.3.

PostGISレイヤの描画がシェープファイルより遅くなりますが、これが普通なのでしょうか?

一般的に、PostGISレイヤは同等のシェープファイルレイヤより10%遅いと考えて下さい。 データベースとMapServerとの間で発生するデータベース接続、データの変換と転送によってオーバヘッドが増えるためです。

重大な描画性能の問題があるようでしたら、テーブルにある空間インデックスを構築していないというのがありそうです。

postgis# CREATE INDEX geotable_gix ON geotable USING GIST ( geocolumn );
postgis# SELECT update_geometry_stats();  -- PostgreSQL 8.0より後
postgis# VACUUM ANALYZE;                  -- PostgreSQL 8.0以前

4.7.2.1.4.

PostGISレイヤはちゃんと描けましたが、クエリが本当に遅いです。何が問題なのですか?

クエリを早くするには、空間テーブルに一意なキーを持たせ、そのキーにインデックスを持たせなければなりません。

DATA行のUSING UNIQUE節で、MapServerで使用する一意なキーをどれにするか指定することができます。

DATA "the_geom FROM geotable USING UNIQUE gid"

テーブルに明示的に一意なカラムが無い場合は、PostgreSQLの行"oid"を用いて一意なカラムを「模造する」ことができます。"oid"は、宣言していないなら、デフォルトの一意なカラムです。ですから、クエリ速度を強化するには、空間テーブルのoid値にインデックスを構築することです (訳注: PostgreSQL 8.1以降は、oidはデフォルトでは追加されなくなりました)。

postgis# CREATE INDEX geotable_oid_idx ON geotable (oid);

4.7.3. 踏み込んだ使用法

USING疑似SQL節を使ってMapServerがより複雑なクエリの結果を理解できるようにするための情報を追加します。より詳しく言うと、ビューまたは副問い合わせが元テーブル (DATA定義で"FROM"の右にあるもの)として使われる時、MapServerが自動的に一意な識別子がそれぞれの行にあるか、また、SRIDがテーブルにあるかを判別するのは困難です。USING節によって、MapServerがこれらの情報を得ることができます。例を次に挙げます。

DATA "the_geom FROM (SELECT table1.the_geom AS the_geom, table1.oid AS oid, table2.data AS data
 FROM table1 LEFT JOIN table2 ON table1.id = table2.id) AS new_table USING UNIQUE oid USING SRID=-1"
USING UNIQUE <uniqueid>

MapServerは、マップクエリを実行する際、行識別のために、それぞれの行に一意な識別子を求めます。 通常なら、oidを一意な識別子として使えますが、ビューや副問い合わせでは、自動的にoidを持つことができません。MapServerのクエリ機能を使いたいなら、一意性のあるカラムをビューまたは副問い合わせに追加する必要があり、USING UNIQUE宣言を付ける必要があります。たとえば、この目的のための主キー値のテーブルでのカラム名や、結果セットで一意性が保障されたカラムを明示的にSELECTに入れることができます。

マップクエリを実行する場合には、USINGステートメントは単純なDATAステートメントに対しても便利なものになります。マップクエリの速度性能向上のために、クエリ実行可能なレイヤで使われるテーブルのoidカラムにインデックスを追加することを、前に推奨しました。しかしながら、USINGを使うと、MapServerがテーブルのプライマリキーをマップクエリの識別子に使うことができ、インデックスを追加する必要はもうありません。

注記

「マップクエリ」はマップ上でクリックして、その場所におけるフィーチャーに関する情報を問い合わせる動作です。「マップクエリ」とDATA定義におけるSQLクエリと混同しないで下さい。

USING SRID=<srid>

PostGISは、MapServerに正しいデータを返すために、ジオメトリがどの空間参照系を使っているかを知る必要があります。通常は、この情報はPostGISデータベースの"geometry_columns"テーブルから得ることができます。しかし、副問い合わせやビューのような一時テーブルでは、この方法は不可能です。そこで、USING SRID=オプションを使って、正しいSRIDがDATA定義で使われるように指定します。

警告

MapserverのPostGISレイヤのパーサは、かなり原始的で、大文字小文字を区別するところが2,3あります。全てのSQLキーワードと全てのUSING節が大文字であることを保証し、USING UNIQUE節がUSING SRID節より前に来るようにして下さい。

4.7.4. 例

簡単な例から始めて、ステップアップしていきましょう。次のMapServerレイヤ定義を考えて下さい。

LAYER
 CONNECTIONTYPE postgis
 NAME "roads"
 CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
 DATA "the_geom FROM roads"
 STATUS ON
 TYPE LINE
 CLASS
  COLOR 0 0 0
 END
END

このレイヤは"roads"テーブルにある道路ジオメトリの全部を黒線で表示するものです。

では、少なくとも1:100000にズームするまでは高速道路だけを表示したい、としましょう。次の二つのレイヤで、その効果が実現できます。

LAYER
 CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
 DATA "the_geom FROM roads"
 MINSCALE 100000
 STATUS ON
 TYPE LINE
 FILTER "road_type = 'highway'"
 CLASS
  COLOR 0 0 0
 END
END

LAYER
 CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
 DATA "the_geom FROM roads"
 MAXSCALE 100000
 STATUS ON
 TYPE LINE
 CLASSITEM road_type
 CLASS
  EXPRESSION "highway"
  SIZE 2
  COLOR 255 0 0
 END
 CLASS
  COLOR 0 0 0
 END
END

一つ目のレイヤはスケールが1:100000以上であるときに使われ、道路タイプが"highway"である道路のみ黒線で表示されます。FILTERオプションによって、道路タイプが"highway"の場合のみ表示することになります。

二つ目のレイヤはスケールが1:100000未満である時に使われ、"highway"は赤い二重細線で表示され、他の道路は黒線で表示されます。

さて、MapServerの機能を使うだけで、二つのおもしろいことを実行しました。しかし、DATAのSQLステートメントは、単純なままです。道路名が (どういう理由かは知りませんが)他のテーブルに収められていて、それのデータを取得するためにテーブルを連結して、道路のラベルを取る必要がある、とします。

LAYER
 CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
 DATA "the_geom FROM (SELECT roads.oid AS oid, roads.the_geom AS the_geom, road_names.name as name
   FROM roads LEFT JOIN road_names ON roads.road_name_id = road_names.road_name_id) AS named_roads
   USING UNIQUE oid USING SRID=-1"
 MAXSCALE 20000
 STATUS ON
 TYPE ANNOTATION
 LABELITEM name
 CLASS
  LABEL
   ANGLE auto
   SIZE 8
   COLOR 0 192 0
   TYPE truetype
   FONT arial
  END
 END
END

このANNOTAIONレイヤでは、縮尺が1:20000以下のときに、全ての道路に緑色のラベルを表示します。また、この例は、DATA定義で、SQLのJOINを使用する方法も示しています。

4.8. Javaクライアント (JDBC)

Javaクライアントは、直接的にテキスト表現として、またはPostGISに同梱されているJDBC拡張オブジェクトを使用して、PostgreSQLデータベース内にある、PostGISの"geometry"オブジェクトにアクセスできます。JDBC拡張オブジェクトを使うためには、"postgis.jar"ファイルを、JDBCドライバパッケージの"postgresql.jar"とともに、 CLASSPATHに置く必要があります。

import java.sql.*; 
import java.util.*; 
import java.lang.*; 
import org.postgis.*; 

public class JavaGIS { 
  public static void main(String[] args) 
  { 
    java.sql.Connection conn; 
    try 
    { 
      /* 
      * JDBCドライバをロードして接続を確立します。
      */  
      Class.forName("org.postgresql.Driver"); 
      String url = "jdbc:postgresql://localhost:5432/database"; 
      conn = DriverManager.getConnection(url, "postgres", ""); 
    
      /*
      * ジオメトリ型を接続に追加します。
      * ご注意 : addDateType()を呼ぶ前に
      *   接続をpgsql特有の接続実装にキャストしなければなりません。
      */
      ((org.postgresql.Connection)conn).addDataType("geometry","org.postgis.PGgeometry");
      ((org.postgresql.Connection)conn).addDataType("box3d","org.postgis.PGbox3d");

      /*
      * ステートメントの生成とSELECTクエリの実行を行います。
      */
      Statement s = conn.createStatement(); 
      ResultSet r = s.executeQuery("select AsText(geom) as geom,id from geomtable"); 
      while( r.next() ) 
      { 
        /*
        * ジオメトリをオブジェクトとして検索してジオメトリ型にキャストします。
        * オブジェクトを印字します
        */
        PGgeometry geom = (PGgeometry)r.getObject(1); 
        int id = r.getInt(2);
        System.out.println("Row " + id + ":"); 
        System.out.println(geom.toString()); 
      }
      s.close(); 
      conn.close(); 
    } 
    catch( Exception e ) 
    { 
      e.printStackTrace(); 
    }  
  }
}

"PGeometry"オブジェクトは、Point、LineString、Polygon、MultiPoint、MultiLineString、MultiPolygonの各型に依存する、特定のトポロジカルジオメトリオブジェクト ("Geometory"抽象クラスの子クラス)を持つラッパオブジェクトです。

PGgeometry geom = (PGgeometry)r.getObject(1); 
if( geom.getType() = Geometry.POLYGON ) 
{ 
  Polygon pl = (Polygon)geom.getGeometry();
  for( int r = 0; r < pl.numRings(); r++ ) 
  { 
    LinearRing rng = pl.getRing(r);
    System.out.println("Ring: " + r); 
    for( int p = 0; p < rng.numPoints(); p++ ) 
    { 
      Point pt = rng.getPoint(p); 
      System.out.println("Point: " + p);
      System.out.println(pt.toString()); 
    } 
  } 
}

幾何オブジェクトのさまざまなデータアクセサ関数に関する参照情報については、拡張オブジェクトのJavaDocをご覧下さい。

4.9. Cクライアント (libpq)

...

4.9.1. テキストカーソル

...

4.9.2. バイナリカーソル

...