第6章 PostGISを使う: アプリケーションを構築する

目次

6.1. MapServerを使う
6.1.1. 基本的な使い方
6.1.2. よくある質問
6.1.3. 踏み込んだ使用法
6.1.4. 例
6.2. Javaクライアント(JDBC)
6.3. Cクライアント(libpq)
6.3.1. テキストカーソル
6.3.2. バイナリカーソル

6.1. MapServerを使う

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

6.1.1. 基本的な使い方

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

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

  • PostGIS 0.6以上

  • MapServer 3.5以上

MapServerは、他のPostgreSQLクライアントのように、libpqインタフェースを使って、PostGIS/PostgreSQLデータにアクセスします。よってMapServerはPostGISサーバにアクセスするネットワークを持つ計算機にインストールでき、PostGISをデータソースとして使用することができます。システム間の接続は速いほど良いです。

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

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

    LAYER 
      CONNECTIONTYPE postgis 
      NAME "widehighways" 
      # リモートの空間データベースに接続
      CONNECTION "user=dbuser dbname=gisdatabase host=bigserver"
      PROCESSING "CLOSE_CONNECTION=DEFER"
      # 'road'テーブルの'geom'カラムから線を取得
      DATA "geom from roads using srid=4326 using unique gid" 
      STATUS ON
      TYPE LINE 
      # 範囲内の線のうち広い高速道路のみ描画
      FILTER "type = 'highway' and numlanes >= 4" 
      CLASS 
        # スーパー高速道路は、明るくし、2ピクセル幅にする
        EXPRESSION ([numlanes] >= 6) 
        STYLE
          COLOR 255 22 22 
          WIDTH 2 
        END
      END 
      CLASS 
        # 残りの道路は、暗くし、1ピクセル幅にする
        EXPRESSION ([numlanes] < 6) 
        STYLE
          COLOR 205 92 82
        END
      END 
    END

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

    CONNECTIONTYPE

    PostGISレイヤにするには、ここは常に"postgis"になります。

    CONNECTION

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

    user=<username> password=<password> dbname=<username> hostname=<server> port=<5432>

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

    DATA

    このパラメータの形式は"<カラム> from <テーブル名> using srid=<srid> using unique <主キー>"です。ここで、カラムは地図に描画する空間カラムで、SRIDは、カラムが使用するSRIDで、主キーはテーブルの主キー(または他の、インデクスを持つユニークな値のカラム)です。

    "using srid"と"using unique"節は省略可能です。MapServerは可能なら自動的に正しい値を決定しますが、マップを描画するたびにいくつかの追加クエリを実行するコストがかかります。

    PROCESSING

    接続を閉じずに、複数のレイヤで再利用する場合にCLOSE_CONNECTION=DEFERを設定します。これで速度が改善します。より詳しい説明についてはMapServer PostGIS Performance Tipsを参照して下さい。

    FILTER

    フィルタは、妥当なSQL文字列でなければなりません。普通はSQLクエリ内で"WHERE"キーワードに続く論理式に沿います。たとえば、6レーン以上の道路のみをレンダリングするには、フィルタを"num_lanes >= 6"とします。

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

    CREATE INDEX [インデクス名] ON [テーブル名] USING GIST ( [ジオメトリカラム名] );
  4. MapServerを使用するレイヤのクエリを実行する場合には、"using unique"節もDATAステートメントに追加しなければなりません。

    MapServerでは、クエリ実行の際には、それぞれの空間レコードを識別するための一意な識別子が必要です。MapServerのPostGISモジュールは、一意な識別子を提供するために、ユーザ指定の一意な値を使います。テーブルの主キーを使うのが最も良い方法です。

6.1.2. よくある質問

6.1.2.1. EXPRESSIONをmapファイルで使う時に、値がテーブルにあるのを確認しているのに条件がtrueになりません。
6.1.2.2. シェープファイルで使っているFILTERが、同じデータを持つPostGISテーブルでは動作しません。
6.1.2.3. PostGISレイヤの描画がシェープファイルより遅くなりますが、これが普通なのでしょうか?
6.1.2.4. PostGISレイヤはちゃんと描けましたが、クエリが本当に遅いです。何が問題なのですか?
6.1.2.5. ジオグラフィカラム(PostGIS 1.5で機能追加)をMapServerのレイヤのソースとして使用できますか?

6.1.2.1.

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

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

EXPRESSION ([numlanes] >= 6)

6.1.2.2.

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

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

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

6.1.2.3.

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

一般的に、地図に描画されるフィーチャーが多くなると、PostGISはシェープファイルより遅くなります。比較的少ないフィーチャー(100台)ではPostGISの方が早く、フィーチャー密度が高くなる(1000台)と、PostGISの方が遅くなります。

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

postgis# CREATE INDEX geotable_gix ON geotable USING GIST ( geocolumn ); 
postgis# VACUUM ANALYZE;

6.1.2.4.

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

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

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

DATA "geom FROM geotable USING UNIQUE gid"

6.1.2.5.

ジオグラフィカラム(PostGIS 1.5で機能追加)をMapServerのレイヤのソースとして使用できますか?

できます!MapServerはジオグラフィカラムをジオメトリカラムと同じに認識します。しかし、常にSRIDを4326とします。"using srid=43426"節をDATAステートメントに入れて下さい。他の部分はジオメトリの場合と同じです。

DATA "geog FROM geogtable USING SRID=4326 USING UNIQUE gid"

6.1.3. 踏み込んだ使用法

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

DATA "geom FROM (
  SELECT 
    table1.geom AS geom, 
    table1.gid AS gid, 
    table2.data AS data 
  FROM table1 
  LEFT JOIN table2 
  ON table1.id = table2.id
) AS new_table USING UNIQUE gid USING SRID=4326"
USING UNIQUE <uniqueid>

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

[注記]

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

USING SRID=<srid>

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

6.1.4. 例

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

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

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

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

LAYER 
  CONNECTIONTYPE postgis 
  CONNECTION "user=theuser password=thepass dbname=thedb host=theserver" 
  PROCESSING "CLOSE_CONNECTION=DEFER"
  DATA "geom from roads"
  MINSCALE 100000 
  STATUS ON 
  TYPE LINE 
  FILTER "road_type = 'highway'" 
  CLASS 
    COLOR 0 0 0 
  END 
END 
LAYER 
  CONNECTIONTYPE postgis 
  CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
  PROCESSING "CLOSE_CONNECTION=DEFER"
  DATA "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  
    STYLE
      COLOR 0 0 0 
    END
  END 
END

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

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

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

LAYER 
  CONNECTIONTYPE postgis
  CONNECTION "user=theuser password=thepass dbname=thedb host=theserver" 
  DATA "geom FROM (SELECT roads.gid AS gid, roads.geom AS 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 gid USING SRID=4326" 
  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を使用する方法も示しています。

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

Javaクライアントは、直接的にテキスト表現として、またはPostGISにバンドルされているJDBC拡張オブジェクトを使用して、PostgreSQLデータベース内にある、PostGISの"geometry"オブジェクトにアクセスできます。拡張オブジェクトを使うためには、"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.PGConnection)conn).addDataType("geometry",Class.forName("org.postgis.PGgeometry"));
    ((org.postgresql.PGConnection)conn).addDataType("box3d",Class.forName("org.postgis.PGbox3d"));
    /* 
    * ステートメントの生成とSELECTクエリの実行を行います。
    */ 
    Statement s = conn.createStatement(); 
    ResultSet r = s.executeQuery("select 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をご覧下さい。

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

...

6.3.1. テキストカーソル

...

6.3.2. バイナリカーソル

...