第4章 PostGISを使う

目次

4.1. GISオブジェクト
4.1.1. OpenGIS WKBとWKT
4.1.2. PostGIS EWKB, EWKTと標準形式
4.1.3. SQL-MM第3部
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コンソーシアム (OGC)で定義されている「シンプルフィーチャー」のスーパーセットです。0.9版からは、PostGISはOGCの「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 geotable ( 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 geotable ( the_geom, the_name ) 
  VALUES ( GeomFromEWKT('SRID=312;POINTM(-126.4 45.32 15)'), 'A Place' )

PostgreSQLの「標準的な形式」は単純なクエリ (全く関数呼び出しが無い)で得る表現であり、単純なINSERT, UPDATE, COPYで受け付けられることが保障されるものです。PostGISの"geometory"型の場合は次の通りです。

- 出力
  - バイナリ: EWKB 
    ascii: HEXEWKB (EWKBのHEX表現) 
- 入力 
  - バイナリ: EWKB 
    ascii: HEXEWKB|EWKT  

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

=# SELECT 'SRID=4;POINT(0 0)'::geometry;

geometry 
----------------------------------------------------
01010000200400000000000000000000000000000000000000 
(1 row)

4.1.3. SQL-MM第3部

SQLマルチメディア・アプリケーション空間仕様は、円弧補完曲線を定義したSQL仕様の拡張です。

SQL-MMの定義では、3DM、3DZと4Dの座標を含みますが、SRID情報の埋め込みはできません。

Well-Known Text拡張はまだ完全にはサポートされていません。単純な曲線ジオメトリの例を次に示します。

  • CIRCULARSTRING(0 0, 1 1, 1 0)

  • COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 1 0),(1 0, 0 1))

  • CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1))

  • MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4))

  • MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11)))

注記

現在、PostGISは曲線ポリゴンでの複合曲線に対応していません。

注記

SQL-MM実装での全ての浮動小数点数の比較では、所定の丸め誤差があります。現在は1E-8です。

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    VARRCHAR(256) NOT NULL, 
  f_table_schema     VARCHAR(256) NOT NULL,
  f_table_nam        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"関数によって空間カラムをテーブルに追加します。

    文法は次の通りです。

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

    現在のスキーマを使う場合には次のようにします。

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

    例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    INTEGER, 
  park_name  VARCHAR,
  park_date  DATE,
  park_type  VARCHAR
);
SELECT AddGeometryColumn('parks', 'park_geom', 128, 'MULTIPOLYGON', 2 );

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

CREATE TABLE roads ( 
  road_id INTEGER,
  road_name VARCHAR
);
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() threw an error!"または"JTS Intersection() threw an error!"のような、変なエラー・メッセージに遭遇するなら、たぶんPostGISか使用ライブラリでエラーが発生しています。PostGIS開発者と連絡をとるべきです。PostGIS関数が妥当な入力から無効なジオメトリーを返すならば、同じことがいえます。

注記

厳格にOGCジオメトリに準拠すると、Z値やM値を持てません。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 (road_id, roads_geom, road_name) 
  VALUES (1,GeomFromText('LINESTRING(191232 243118,191108 243242)',-1),'Jeff Rd'); 
INSERT INTO roads (road_id, roads_geom, road_name) 
  VALUES (2,GeomFromText('LINESTRING(189141 244158,189265 244817)',-1),'Geordie Rd'); 
INSERT INTO roads (road_id, roads_geom, road_name) 
  VALUES (3,GeomFromText('LINESTRING(192783 228138,192612 229814)',-1),'Paul St'); 
INSERT INTO roads (road_id, roads_geom, road_name) 
  VALUES (4,GeomFromText('LINESTRING(189412 252431,189631 259122)',-1),'Graeme Ave'); 
INSERT INTO roads (road_id, roads_geom, road_name) 
  VALUES (5,GeomFromText('LINESTRING(190131 224148,190871 228134)',-1),'Phil Tce'); 
INSERT INTO roads (road_id, roads_geom, road_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の"dump"書式を用います。このモードは-a, -cと-dが含まれます。デフォルトの"insert"によるSQL書式よりも、とても早くロードできます。大きなデータセットではこちらを使用して下さい。

-s <SRID>

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

-k

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

-i

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

-I

ジオメトリカラムにGiSTインデックスを生成します。

-w

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

-W <エンコーディング>

入力データ (dbfファイル)のエンコーディングを指定します。全てのdbfの属性は指定されたエンコーディングからUTF8に変換されます。SQL出力結果には SET CLIENT_ENCODING to UTF8が含まれるようになり、バックエンドはUTF-8からデータベースが内部利用のために設定したエンコーディングに再変換できます。

-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 road_id, AsText(road_geom) AS geom, road_name FROM roads; 

road_id | geom                                    | road_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 road_id, road_name 
  FROM roads 
  WHERE roads_geom ~= GeomFromText('LINESTRING(191232 243118,191108 243242)',-1);

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

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

SELECT road_id, road_name 
FROM roads 
WHERE roads_geom && GeomFromText('POLYGON((...))',-1);

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

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

SELECT AsText(roads_geom) AS geom 
FROM roads 
WHERE 
  roads_geom && SetSRID('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 [インデクス名] ON [テーブル名] USING GIST ( [ジオメトリカラム名] ); 

空間インデックスの構築は、計算量を集中させて行われます。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空間インデックスの利点が出てくることだけは覚えておくことが重要です。ST_Distance()のような関数では演算の最適化を行うためにインデックスを使うことができません。たとえば、次のクエリでは、大きなテーブルでは本当に遅くなります。

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

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

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

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

挙動の変更

PostGIS 1.3.0では、ST_DisjointとST_Relateの注目すべき例外がありますが、ほとんどのジオメトリ関係関数は暗黙的なバウンディングボックスオーバラップ演算子を含んでいます。

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. 道路の総延長はkm表記でいくらになるでしょう?
4.6.2.2. プリンスジョージ市の大きさはha表記でいくらになるでしょう?
4.6.2.3. 県内で最も大きな面積となる自治体はどこでしょう?
4.6.2.4. 各自治体内に含まれる道路の総延長はいくらでしょう?
4.6.2.5. プリンスジョージ市内の全ての道路からなるテーブルを作る
4.6.2.6. ビクトリア州の「ダグラス通り」の長さはkm表記でいくらになるでしょう?
4.6.2.7. 穴を持つ自治体ポリゴンのうち最も大きいのはどれでしょう?

4.6.2.1.

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

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

SELECT sum(ST_Length(the_geom))/1000 AS km_roads FROM bc_roads; 

km_roads 
------------------
70842.1243039643 
(1 row)

4.6.2.2.

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

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

SELECT 
  ST_Area(the_geom)/10000 AS hectares 
FROM bc_municipality 
WHERE name = 'PRINCE GEORGE'; 

hectares 
------------------ 
32657.9103824927 
(1 row)

4.6.2.3.

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

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

SELECT 
  name, 
  ST_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.4.

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

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

SELECT 
  m.name, 
  sum(ST_Length(r.the_geom))/1000 as roads_km 
FROM 
  bc_roads AS r,  
  bc_municipality AS m 
WHERE
  ST_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.5.

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

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

CREATE TABLE pg_roads as 
SELECT 
  ST_Intersection(r.the_geom, m.the_geom) AS intersection_geom,
  ST_Length(r.the_geom) AS rd_orig_length, 
  r.* 
FROM 
  bc_roads AS r, 
  bc_municipality AS m 
WHERE ST_Intersects(r.the_geom, m.the_geom)
  AND m.name = 'PRINCE GEORGE';

4.6.2.6.

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

SELECT 
  sum(ST_Length(r.the_geom))/1000 AS kilometers 
FROM 
  bc_roads r, 
  bc_municipality m 
WHERE ST_Contains(m.the_geom, r.the_geom) 
  AND r.name = 'Douglas St' 
  AND m.name = 'VICTORIA'; 

kilometers 
------------------
4.89151904172838 
(1 row)

4.6.2.7.

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

SELECT gid, name, ST_Area(the_geom) AS area 
FROM bc_municipality 
WHERE ST_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" 
      # Connect to a remote spatial database
      CONNECTION "user=dbuser dbname=gisdatabase host=bigserver"
      # Get the lines from the 'geom' column of the 'roads' table 
      DATA "geom from roads" 
      STATUS ON
      TYPE LINE 
      # Of the lines in the extents, only render the wide highways 
      FILTER "type = 'highway' and numlanes >= 4" 
      CLASS 
        # Make the superhighways brighter and 2 pixels wide
        EXPRESSION ([numlanes] >= 6) 
        STYLE
          COLOR 255 22 22 
          WIDTH 2 
        END
      END 
      CLASS 
        # All the rest are darker and only 1 pixel wide 
        EXPRESSION ([numlanes] < 6) 
        STYLE
          COLOR 205 92 82
        END
      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 ( [ジオメトリカラム] );
  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. EXPRESSIONをマップファイルで使う時に、値がテーブルにあるのを確認しているのに条件がtrueになりません。
4.7.2.2. シェープファイルで使っているFILTERが、同じデータを持つPostGISテーブルでは動作しません。
4.7.2.3. PostGISレイヤの描画がシェープファイルより遅くなりますが、これが普通なのでしょうか?
4.7.2.4. PostGISレイヤはちゃんと描けましたが、クエリが本当に遅いです。何が問題なのですか?

4.7.2.1.

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

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

EXPRESSION ([numlanes] >= 6)

4.7.2.2.

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

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

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

4.7.2.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.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" 
    STYLE
      WIDTH 2 
      COLOR 255 0 0  
    END
  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");
    /* 
    * Create a statement and execute a select query. 
    */ 
    Statement s = conn.createStatement(); 
    ResultSet r = s.executeQuery("select AsText(geom) as geom,id from geomtable"); 
    while( r.next() ) { 
      /* 
      * ジオメトリをオブジェクトとして取得して、geometry型にキャストします
      * オブジェクトを印字します
      */ 
      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. バイナリカーソル

...