Chapter 6. Anwendung der PostGIS Geometrie: Applikationsentwicklung

Table of Contents
6.1. Verwendung von MapServer
6.1.1. Grundlegende Handhabung
6.1.2. Häufig gestellte Fragen
6.1.3. Erweiterte Verwendung
6.1.4. Beispiele
6.2. Java Clients (JDBC)
6.3. C Clients (libpq)
6.3.1. Text Cursor
6.3.2. Binäre Cursor

6.1. Verwendung von MapServer

Der Minnesota MapServer ist ein Kartendienstserver für das Internet, der die "OpenGIS Web Map Service (WMS) Implementation Specification" erfüllt.

6.1.1. Grundlegende Handhabung

Um PostGIS mit MapServer zu verwenden müssen Sie wissen, wie Sie MapServer konfigurieren, da dies den Rahmens dieser Dokumentation sprengen würde. Dieser Abschnitt deckt PostGIS-spezifische Themen und Konfigurationsdetails ab.

Um PostGIS mit MapServer zu verwenden, benötigen Sie:

  • Die PostGIS Version 0.6, oder höher.

  • Die MapServer Version 3.5, oder höher.

MapServer greift auf die PostGIS/PostgreSQL-Daten, so wie jeder andere PostgreSQL-Client, über die libpq Schnittstelle zu. Dies bedeutet, dass MapServer auf jedem Server, der Netzwerkzugriff auf den PostgreSQL Server hat, installiert werden kann und PostGIS als Datenquelle nutzen kann. Je schneller die Verbindung zwischen den beiden Systemen, desto besser.

  1. Es spielt keine Rolle, mit welchen Optionen Sie MapServer kompilieren, solange sie bei der Konfiguration die "--with-postgis"-Option angeben.

  2. Fügen Sie einen PostGIS Layer zu der MapServer *.map Datei hinzu. Zum Beispiel:

    LAYER
      CONNECTIONTYPE postgis
      NAME "widehighways"
      # Verbindung zu einer remote Geodatenbank
      CONNECTION "user=dbuser dbname=gisdatabase host=bigserver"
      PROCESSING "CLOSE_CONNECTION=DEFER"
      # Um die Zeilen der 'geom'-Spalte aus der 'roads'-Tabelle zu erhalten
      DATA "geom from roads using srid=4326 using unique gid"
      STATUS ON
      TYPE LINE
      # Von den im Ausschnitt vorhandenen Linien nur die breiten Hauptstraßen/Highways
      FILTER "type = 'highway' and numlanes >= 4"
      CLASS
        # Autobahnen heller und 2Pixel stark machen
        EXPRESSION ([numlanes] >= 6)
        STYLE
          COLOR 255 22 22
          WIDTH 2
        END
      END
      CLASS
        # Der ganze Rest ist dunkler und nur 1 Pixel stark
        EXPRESSION ([numlanes] < 6)
        STYLE
          COLOR 205 92 82
        END
      END
    END

    Im oberen Beispiel werden folgende PostGIS-spezifische Anweisungen verwendet:

    CONNECTIONTYPE

    Für PostGIS Layer ist dies immer "postgis".

    CONNECTION

    Die Datenbankverbindung wird durch einen "Connection String" bestimmt, welcher aus einer standardisierten Menge von Schlüsseln und Werten zusammengesetzt ist (Standardwerte zwischen <>):

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

    Ein leerer "Connection String" ist ebenfalls gültig, sodass jedes Key/Value Paar weggelassen werden kann. Üblicherweise wird man zumindest den Datenbanknamen und den Benutzernamen, mit dem man sich verbinden will, angeben.

    DATA

    Dieser Parameter hat die Form "<geocolumn> from <tablename> using srid=<srid> using unique <primary key>", wobei "geocolumn" dem räumlichen Attribut entspricht, mit dem die Bildsynthese/rendern durchgeführt werden soll. "srid" entspricht der SRID des räumlichen Attributs und "primary key" ist der Primärschlüssel der Tabelle (oder ein anderes eindeutiges Attribut mit einem Index).

    Sie können sowohl "using srid" als auch "using unique" weglassen. Wenn möglich, bestimmt MapServer die korrekten Werte dann automatisch, allerdings zu den Kosten einiger zusätzlichen serverseitigen Abfragen, die bei jedem Kartenaufruf ausgeführt werden.

    PROCESSING

    Wenn Sie mehrere Layer darstellen wollen, fügen Sie CLOSE_CONNECTION=DEFER ein, dadurch wird eine bestehende Verbindung wiederverwendet anstatt geschlossen. Dies erhöht die Geschwindigkeit. Unter MapServer PostGIS Performance Tips findet sich eine detailierte Erklärung.

    FILTER

    Der Filter muss ein gültiger SQL-Text sein, welcher der Logik, die normalerweise dem "WHERE" Schlüsselwort in der SQL-Abfrage folgt, entspricht. Z.B.: um nur die Straßen mit 6 oder mehr Spuren zu rendern, können Sie den Filter "num_lanes >= 6" verwenden.

  3. Stellen Sie bitte sicher, das für alle zu zeichnenden Layer, ein räumlicher Index (GIST) in der Geodatenbank angelegt ist.

    CREATE INDEX [indexname] ON [tabellenname] USING GIST ( [geometry_spalte] );
  4. Wenn Sie Ihre Layer über MapServer abfragen wollen, benötigen Sie auch die "using unique" Klausel in Ihrer "DATA" Anweisung.

    MapServer benötigt für jeden räumlichen Datensatz, der abgefragt werden soll, eindeutige Identifikatoren. Das PostGIS Modul von MapServer benützt den von Ihnen festgelegten, eindeutigen Wert, um diese eindeutige Identifikatoren zur Verfügung zu stellen. Den Primärschlüssel zu verwenden gilt als Erfolgsrezept.

6.1.2. Häufig gestellte Fragen

6.1.2.1. Wenn Ich EXPRESSION in meiner *.map Datei verwende, gibt die WHERE Bedingung niemals TRUE zurück, obwohl Ich weiß, dass sich die Werte in meiner Tabelle befinden.
6.1.2.2. Der FILTER, den ich bei meinen Shapefiles verwende, funktioniert nicht für meine PostGIS Tabelle, obwohl diese die gleichen Daten aufweist.
6.1.2.3. Mein PostGIS Layer wird viel langsamer dargestellt als mein Shapefile Layer. Ist das normal?
6.1.2.4. Mein PostGIS Layer wird ausgezeichnet dargestellt, aber die Abfragen sind sehr langsam. Was läuft falsch?
6.1.2.5. Kann Ich "Geography" Spalten (neu in PostGIS 1.5) als Quelle für MapServer Layer verwenden?

6.1.2.1.

Wenn Ich EXPRESSION in meiner *.map Datei verwende, gibt die WHERE Bedingung niemals TRUE zurück, obwohl Ich weiß, dass sich die Werte in meiner Tabelle befinden.

Anders als bei Shapefiles müssen die PostGIS-Feldnamen in EXPRESSION mit Kleinbuchstaben eingetragen werden.

EXPRESSION ([numlanes] >= 6)

6.1.2.2.

Der FILTER, den ich bei meinen Shapefiles verwende, funktioniert nicht für meine PostGIS Tabelle, obwohl diese die gleichen Daten aufweist.

Anders als bei Shapefiles, nutzen die Filter bei PostGIS-Layern die SQL Syntax (sie werden an die SQL-Anweisung, die vom PostGIS Konnektor für die Darstellung der Layer im Mapserver erzeugt wird, angehängt).

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

6.1.2.3.

Mein PostGIS Layer wird viel langsamer dargestellt als mein Shapefile Layer. Ist das normal?

Je mehr Features eine bestimmte Karte aufweist, umso wahrscheinlicher ist es, dass PostGIS langsamer ist als Shapefiles. Bei Karten mit relativ wenigen Features (100te) ist PostGIS meist schneller. Bei Karten mit einer hohen Feature Dichte (1000e) wird PostGIS immer langsamer sein.

Falls erhebliche Probleme mit der Zeichenperformance auftreten, haben Sie eventuell keinen räumlichen Index auf die Tabelle gelegt.

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

6.1.2.4.

Mein PostGIS Layer wird ausgezeichnet dargestellt, aber die Abfragen sind sehr langsam. Was läuft falsch?

Damit Abfragen schnell gehen, müssen Sie einen eindeutigen Schlüssel in Ihrer Tabelle haben und einen Index auf diesen eindeutigen Schlüssell legen.

Sie können den von MapServer zu verwendenden eindeutigen Schlüssel mit der USING UNIQUE Klausel in Ihrer DATA Zeile angeben:

DATA "geom FROM geotable USING UNIQUE gid"

6.1.2.5.

Kann Ich "Geography" Spalten (neu in PostGIS 1.5) als Quelle für MapServer Layer verwenden?

Ja! Für MapServer sind "Geography" Attribute und "Geometry" Attriute dasselbe. Es kann allerdings nur die SRID 4325 verwendet werden. Stellen Sie bitte sicher, dass sich eine "using srid=4326" Klausel in Ihrer DATA Anweisung befindet. Alles andere funktioniert genau so wie bei "Geometry".

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

6.1.3. Erweiterte Verwendung

Die SQL-Pseudoklausel USING wird verwendet, um MapServer zusätzliche Information über komplexere Abfragen zukommen zu lassen. Genauer gesagt, wenn entweder ein View oder ein Subselect als Ursprungstabelle verwendet wird (der Ausdruck rechts von "FROM" bei einer DATA Definition) ist es für MapServer schwieriger einen eindeutigen Identifikator für jede Zeile und die SRID der Tabelle automatisch zu bestimmen. Die USINGKlausel kann MapServer die Information über diese beiden Teile wie folgt zukommen lassen:

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 benötigt eine eindeutige ID für jede Zeile um die Zeile bei Kartenabfragen identifizieren zu können. Normalerweise wird der Primärschlüssel aus den Systemtabellen ermittelt. Views und Subselects haben jedoch nicht automatisch eine bekannte eindeutige Spalte. Wenn Sie MapServer's Abfragefunktionalität nutzen wollen, müssen Sie sicherstellen, dass Ihr View oder Subselect eine mit eindeutigen Werten versehene Spalte enthält und diese mit USING UNIQUE gekennzeichnet ist. Zum Beispiel können Sie hierfür die Werte des Primärschlüssels verwenden, oder irgendeine andere Spalte bei der sichergestellt ist dass sie eindeutige Werte für die Ergebnismenge aufweist.

[Note]

"eine Karte abfragen" ist jene Aktion, bei der man auf die Karte klickt und nach Information über Kartenfeatures an dieser Stelle fragt. Verwechseln Sie bitte nicht "Kartenabfragen" mit der SQL Abfrage in der DATA Definition.

USING SRID=<srid>

PostGIS muss wissen, welches Koordinatenreferenzsystem von der Geometrie verwendet wird, um korrekte Daten an MapServer zurückzugeben. Üblicherweise kann man diese Information in der Tabelle "geometry_columns" in der PostGIS Datenbank finden. Dies ist jedoch nicht möglich bei Tabellen die On-the-fly erzeugt wurden, wo wie bei Subselects oder Views. Hierfür erlaubt die Option USING SRID= die Festlegung der richtigen SRID in der DATA Definition.

6.1.4. Beispiele

Beginnen wir mit einem einfachen Beispiel und arbeiten uns dann langsam vor. Betrachten Sie die nachfolgende MapServer Layerdefinition:

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

Dieser Layer stellt alle Straßengeometrien der "roads"-Tabelle schwarz dar.

Angenommen, wir wollen bis zu einem Maßstab von 1:100000 nur die Autobahnen anzeigen - die nächsten zwei Layer erreichen diesen Effekt:

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

Der erste Layer wird verwendet, wenn der Maßstab größer als 1:100000 ist und es werden nur die Straßen vom Typ "highway"/Autobahn als schwarze Linien dargestellt. Die Option FILTER bedingt, dass nur Straßen vom Typ "highway" angezeigt werden.

Der zweite Layer wird angezeigt, wenn der Maßstab kleiner als 1:100000 ist. Er zeigt die Autobahnen als doppelt so dicke rote Linien an, die anderen Straßen als normale schwarze Linien.

Wir haben eine Reihe von interessanten Aufgaben lediglich mit der von MapServer zur Verfügung gestellten Funktionalität durchgeführt, und unsere SQL-Anweisung unter DATA ist trotzdem einfach geblieben. Angenommen, die Namen der Straßen sind in einer anderen Tabelle gespeichert (wieso auch immer) und wir müssen einen Join ausführen, um sie für die Straßenbeschriftung verwenden zu können.

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

Dieser Beschriftungslayer fügt grüne Beschriftungen zu allen Straßen hinzu, wenn der Maßstab 1:20000 oder weniger wird. Es zeigt auch wie man einen SQL-Join in einer DATA Definition verwenden kann.

6.2. Java Clients (JDBC)

Java Clients können auf die PostGIS Geoobjekte in der PostgreSQL Datenbank entweder direkt über die Textdarstellung zugreifen oder über die Objekte der JDBC Erweiterung, die mit PostGIS gebündelt sind. Um die Objekte der Erweiterung zu nutzen, muss sich die Datei "postgis.jar" zusammen mit dem JDBC Treiberpaket "postgresql.jar" in Ihrem CLASSPATH befinden.

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 {
    /*
    * Den JDBC Treiber laden und eine Verbindung herstellen.
    */
    Class.forName("org.postgresql.Driver");
    String url = "jdbc:postgresql://localhost:5432/database";
    conn = DriverManager.getConnection(url, "postgres", "");
    /*
    * Die geometrischen Datentypen zu der Verbindung hinzufügen. Beachten Sie bitte,
    * dass Sie die Verbindung in eine pgsql-specifische Verbindung umwandeln
    * bevor Sie die Methode addDataType() aufrufen.
    */
    ((org.postgresql.PGConnection)conn).addDataType("geometry",Class.forName("org.postgis.PGgeometry"));
    ((org.postgresql.PGConnection)conn).addDataType("box3d",Class.forName("org.postgis.PGbox3d"));
    /*
    * Eine Anweisung erzeugen und eine Select Abfrage ausführen.
    */
    Statement s = conn.createStatement();
    ResultSet r = s.executeQuery("select geom,id from geomtable");
    while( r.next() ) {
      /*
      * Die Geometrie als Objekt abrufen und es in einen geometrischen Datentyp umwandeln.
      * Ausdrucken.
      */
      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();
  }
}
}

Das Objekt "PGgeometry" ist ein Adapter, der abhängig vom Datentyp ein bestimmtes topologisches Geoobjekt (Unterklassen der abstrakten Klasse "Geometry") enthält: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon.

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());
    }
  }
}

Das JavaDoc der Erweiterung liefert eine Referenz für die verschiedenen Zugriffsfunktionen auf die Geoobjekte.

6.3. C Clients (libpq)

...

6.3.1. Text Cursor

...

6.3.2. Binäre Cursor

...