Anleitung für Routing (Pgrouting) und UMN MapServer mit den Freien Geodaten aus Osnabrück (Frida)

From OSGeo
Revision as of 10:35, 12 November 2007 by Wiki-Kai (talk | contribs) (→‎Daten mittels PHP-Skript einlesen)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Einführung

Mittels dieser Anleitung soll Ihnen etwas Hilfestellung beim Thema pgRouting und UMN MapServer gegeben werden. Die Anleitung basiert im Wesentlichen auf Know-how, welches auf der Homepage http://pgrouting.postlbs.org vermittelt wird.

Weitere hilfreiche Quellen:

Mailinglist von umn-mapserver.de (mit 14 Antworten)

Englischsprachige Mailinglist des UMN MapServers (mit 8 Antworten)

Forum auf umn-mapserver-community.de (mit 25 Antworten)


Diese Anleitung ist für Windows XP geschrieben, funktioniert (mit den entsprechenden Änderungen) natürlich auch auf Linux-Systemen. Für diese Anwendung sollten Sie Grundkenntnisse im Umgang mit dem UMN MapServer, PostgreSQL/PostGIS sowie PHP/Mapscript besitzen.

Folgende Umgebung wurde installiert:

  • Das ms4w-Paket (2.2.3)
  • PostgreSQL 8.2.4 mit PostGIS-Aufsatz 1.1


Wie gehen wir nun vor? Zunächst einmal laden Sie von der Seite http://pgrouting.postlbs.org den pgRouting 1.0.0a-win32-installer herunter. Anschließen ein Doppelklick auf das Paket. Die Installation läuft quasi von alleine.

Installieren Sie pgrouting am Besten in das Verzeichnis C:\Programme\PostgreSQL\8.2 (siehe Abbildung 1).

Abbildung 1: Installation von PgRouting

Installationsroutine von pgRouting

Daten

Anschließend brauchen wir natürlich Geodaten. Wir verwenden dafür die Freien Geodaten aus dem von der Intevation GmbH initiierten Projekt „Frida“ (http://frida.intevation.org/) (siehe Abbildung 2).

Abbildung 2: Homepage der Frida-Daten

Homepage der Frida-Daten


Laden Sie sich hier folgende Daten herunter: frida-1.0.1-shp-joined.tar.gz und entpacken Sie diese. Im ersten Schritt brauchen wir die „strassen-joined.shp“-Daten. Diese Daten benötigen wir allerdings im SQL-Format um diese in die anzulegende PostgreSQL/PostGIS-Datenbank zu lesen. Also geben wir auf der Kommandozeile z.B. folgendes ein (siehe auch Abbildung 3):

Shp2pgsql D:\frida\strassen-joined.shp fridastreets routingdb > D:\frida\strassen-joined.sql

Abbildung 3: Shape in SQL-Format umwandeln

Shape in SQL-Format umwandeln

Datenbank anlegen

Anschließend legen wir eine Datenbank mit PostGIS-Unterstützung an (z.B. mit dem Tool pgAdmin III). Diese Datenbank nennen wir hier mal „routingdb“ (Abbildung 4).

Abbildung 4: Anlegen einer Datenbank mit PgAdminIII

Anlegen einer Datenbank mit PgAdminIII

Anschließend dann muss die Datei strassen-joined.sql in die Datenbank eingelesen werden. Gegen Sie also auf Kommandozeile folgendes ein:

psql -U postgres -f D:/frida/strassen-joined.sql routingdb

Abbildung 5: Befehl zum Einlesen der SQL-Datei in die Datenbank

Befehl zum Einlesen der SQL-Datei in die Datenbank

Die Daten der Tabelle „fridastreets“ haben folgende Struktur (siehe Abbildung 6):

Abbildung 6: Ursprüngliche Struktur der Frida-Daten

Ursprüngliche Struktur der Frida-Daten

Die Datenbank ist zu diesem Zeitpunkt allerdings noch nicht in der Lage Routen zu berechnen. Das wollen wir ändern. Dafür führen wir folgende Befehle aus:

psql -U postgres -f C:\Programme\PostgreSQL\8.2\share\contrib\routing.sql routingdb

sowie anschließend:

psql -U postgres -f C:\Programme\PostgreSQL\8.2\share\contrib\routing_postgis.sql routingdb

Abbildung 7: Routingfunktionen werden in Datenbank gebracht

Routingfunktionen werden in Datenbank gebracht

Ok, die Datenbank ist für Routing im Grunde präpariert. Das bedeutet aber noch lange nicht, dass sie nun auch diesbezüglich funktioniert. Für verschiedene Funktionen von pgRouting muss eine bestimmte Tabellenstruktur vorliegen. Neben der gid und der Geometrie (the_geom) müssen auch die Anfangskoordinaten (x1, y1 jeweils als eigene Spalte (Datentyp numerisch)) bzw Endkoordinaten (x2,y2 ebenso jeweils als eigene Spalte) vorliegen. Zudem muss die Tabellenspalte „length“ (numeric) sowie source und target (bigint) vorliegen (siehe Abbildung 8).

Abbildung 8: Tabellenstruktur für verschiedene Funktionen von pgRouting

Tabellenstruktur für verschiedene Funktionen von pgRouting

Daten mittels PHP-Skript einlesen

Nachdem diese Spalten angelegt worden sind geht es darum automatisiert die Werte von x1,y1,x2,y2 einzulesen. Dafür wurde folgendes PHP-Skript geschrieben:

<?php
$host = "localhost";
$port = "5432";
$dbname = "routingdb";
$user = "postgres";
$password = "postgres";
$con_string = "host=$host port=$port dbname=$dbname user=$user password=$password";
$con = pg_connect ($con_string);
//Hier der Code für das Ermitteln von x1 und y1
$id_check = "SELECT max(gid)as gid from roads";
$res_id_check = pg_query($con,$id_check);
$count = pg_result($res_id_check,"gid");
echo "Anzahl der Eintraegege in der DB: ".$count;
echo "
"; for ($x=1;$x<=$count;$x++) { $start = "SELECT astext(StartPoint(the_geom))as startpoint from roads where gid='$x'"; $res_start= pg_query($con,$start); $start_ergebnis = pg_result($res_start,"startpoint"); echo "Geometrie $x
"; echo "Anfangspunkte (x1,y1): ".$start_ergebnis; echo "
"; $array_01=array("POINT(",")"); $array_02=array("",""); for($r=0;$r<sizeof($array_01);$r++) { $start_ergebnis=str_replace($array_01[$r],$array_02[$r],$start_ergebnis); } $explode=explode(" ",$start_ergebnis); $x1=$explode[0]; $y1=$explode[1]; echo "
"; //Hier der Code für das Ermitteln von x2 und y2 $end = "SELECT astext(EndPoint(the_geom))as endpoint from roads where gid='$x'"; $res_end= pg_query($con,$end); $end_ergebnis = pg_result($res_end,"endpoint"); echo "Endpunkte (x2,y2): ".$end_ergebnis; echo "
"; echo "--------------"; echo "
"; $array_01=array("POINT(",")"); $array_02=array("",""); for($r=0;$r<sizeof($array_01);$r++) { $end_ergebnis=str_replace($array_01[$r],$array_02[$r],$end_ergebnis); } $explode=explode(" ",$end_ergebnis); $x2=$explode[0]; $y2=$explode[1]; //Hier werden dann die Werte in die Spalten geschrieben $werte_in_tabelle_schreiben="UPDATE roads SET x1='$x1',y1='$y1',x2='$x2',y2='$y2' where gid='$x'"; $res = pg_query($werte_in_tabelle_schreiben); } ?>

Das Skript funktioniert eigentlich ganz einfach. Es stellt eine Verbindung zur PostgreSQL/PostGIS-Datenbank her. Dann wird ermittelt wie viele Geometrie-Einträge insgesamt vorliegen und in einer Schleife werden Rechtswerte und Hochwerte der Vertices in die Tabelle gelesen. Das Skript dann einfach über einen Webserver (z.B. innerhalb des ms4w-Paketes) abschicken.

Wichtig: Es kann eine Weile dauern, bis alle Einträge in die Datenbenk geschrieben
sind.
Falls Sie mit dem ms4w-Paket arbeiten sollten Sie unbedingt die Werte in der
PHP-Konfigurationsdatei php.ini (C:\ms4w\Apache\cgi-bin) ändern.
In Zeile 255 sollte die execution-time hochgesetzt werden, z.B.:
max_execution_time=300;
.........damit auch die kompletten Datensätze eingelesen werden.

Als Bestätigung erscheint beim Aufruf des Skriptes etwa folgendes Fenster (Abb. 9), in welchem in diesem Falle alle 12323 Einträge bestätigt werden.

Abbildung 9: Start- und Endpunkte der Strassengeometrien erstellen

Start- und Endpunkte der Strassengeometrien erstellen

Neben der oben beschrieben Möglichkeit die Daten mit PHP einzulesen, befüllt auch das folgende SQL-Kommando die Tabelle:

update roads set x1=X(StartPoint(the_geom)), y1=Y(StartPoint(the_geom)), x2=X(EndPoint(the_geom)), y2=Y(EndPoint(the_geom)),   
length=length(the_geom);
Wichtig: In neuen Versionen von PostGIS (ab 1.3.x) muss allen Funktionen ein 'ST_' vorangestellt werden.

Weitere Routingspezifische Werte berechnen

Anschließend sollen die length-Werte berechnet werden. Das geht ganz einfach mit folgendem SQL-Befehl in der routingdb-Datenbank (Abbildung 10):

UPDATE fridastreets set length=length(the_geom);


Abbildung 10: length-Berechnung

Start- und Endpunkte der Strassengeometrien erstellen

Jetzt fehlt aber noch etwas....... Um die Werte für source und target zu errechnen benutzen wir eine vorgefertige Funktion:

SELECT assign_vertex_id('fridastreets', 5);

Die Zahl ist letztlich variabel. Die Zahl 5 steht für einen Distanzraum, in welchem Knoten die selbe Vertexid erhalten. Die Funktion erwartet allerdings, dass die Spaltennamen nicht source bzw. target sondern source_id & target_id heissen. Natürlich könnten wir die Funktion jetzt modifizieren. Schneller geht’s aber, wenn wir die Spalten mal eben umbenennen, anschließend dann die Funktion absenden. Das ganze dauert dann ein Weilchen, irgendwann sind die Einträge dann aber getätigt


Abbildung 11: Source/target-Werte berechnen

Source/target-Werte berechnen

Routingvisualisierung

Anschließend ändern Sie source_id zu „source“ und „target_id“ zu „target“. Ok, dann brauchen wir noch ein PHP/Mapscript-Skript sowie ein passendes Mapfile. Unter http://files.orkney.jp/pgrouting/sample/pgRouting-sampleapp.tar.bz kann man sich diesbezügliche Dateien herunterladen. Diese Dateien wurden etwas verändert und können hier als routing.map bzw. phtmls/routing_os_frida.phtml heruntergeladen werden.

Die Datei routing.map ist eigentlich ganz einfach. Defaultmäßig werden die Frida-Daten über folgenden Eintrag visualisiert:

LAYER
 NAME "roads"
 TYPE LINE
 CONNECTION "user=postgres password=postgres dbname=routingdb host=localhost port=5432"
 CONNECTIONTYPE postgis
 DATA "the_geom from fridastreets"
 STATUS DEFAULT
#LABELITEM 'strname'
CLASSITEM 'strtypid'
   CLASS
        EXPRESSION '1'
        STYLE
      COLOR       255 0 0
      END
   END
 CLASS
        EXPRESSION '3'
        STYLE
              COLOR       255 255 0
        END
 END
 CLASS
        EXPRESSION /./
        STYLE
              COLOR       200 200 200
        END
 END
END


Abbildung 12: Darstellung der Frida-Geometrien im UMN MapServer

Source/Darstellung der Frida-Geometrien

Der Layer über welchen letztlich die Ausgabe der Route dargestellt wird nennt sich „path“.

LAYER
  NAME "path"
  CONNECTION "user=postgres password=postgres dbname=frida host=localhost port=5432"
  CONNECTIONTYPE postgis
 STATUS ON
  TYPE LINE
  CLASS
   NAME "path"
      STYLE
      SYMBOL 'circle'
COLOR 255 0 0
SIZE 8
      END
  END
END

Dieser wird dann über PHP/Mapscript aktiviert. Schauen Sich sich mal den Quellcode von routing_os_frida.phtml mal an. Mittels dieses Codes wird die Angabe zum Map-Objekt getätigt und ein statischer Extent definiert, dieser kann über die Variable $delta leicht verändert werden:

$delta=0;
$map_file=MAPFILE;
$map=ms_newMapObj($map_file);
$l=$map->getLayerByName("path");
if($l) {
 if($l && $start!=0 && $end!=0) {
 $cx1=3429000;
 $cy1=5787000;
 $cx2=3444000;
 $cy2=5800000;
   if($cx1!=0 && $cy1!=0 && $cx2!=0 && $cy2!=0 &&
      $cx1!=$cx2 && $cy1!=$cy2) {
     $minx = min($cx1,$cx2)-$delta;
     $miny = min($cy1,$cy2)-$delta;
     $maxx = max($cx1,$cx2)+$delta;
     $maxy = max($cy1,$cy2)+$delta;
     $map->setextent($minx,$miny,$maxx,$maxy);

Entscheidend ist der Aufruf der Funktion „shortest_path_astar2_as_geometry_internal_id“ (welcher nur dann gelingt, wenn die Tabelle die entsprechend angelegte Struktur besitzt).

$ll_x = $rectobj->minx;
   $ll_y = $rectobj->miny;
   $ur_x = $rectobj->maxx;
   $ur_y = $rectobj->maxy;
   $sql="the_geom from (select gid, the_geom from ".
       "shortest_path_astar2_as_geometry_internal_id('fridastreets', ".
       $start.", ".$end.", ".$ll_x.", ".$ll_y.", ".$ur_x.", ".
       $ur_y.")) as g using unique gid using SRID=-1";
   $l->set('data', $sql);
   $l->set('status', MS_ON);

Die Funktion selber ist definiert in der Datei routing_postgis.sql und wurde ja von uns in die Datenbank eingelesen. Wichtig ist dann auch noch das Definieren der Start- bzw. Endpunkte. Dieses geht über numerische Werte in einem Formular:

<select name=start>
<option value=0 >Wähle....</option>
<option value=7649 >Dom</option>
<option value=291 >Im Hone</option>
<option value=7750 >Kolpingstrasse</option>
<option value=7313 >Martinistr.</option>

Achtung: Die Zahlen stehen aber nicht für die gid in der Tabelle sondern für den Wert der source- oder aber target-spalte (siehe Werte der Kolpingstr. in Abb. 13).


Abbildung 13: Source/target-Werte der Kolpingstr.


Source/target-Werte der Kolpingstr.


Anschließend in der Anwendung dann einfach mal 2 Punkte auswählen. Über die Funktion shortest_path_astar2_as_geometry_internal_id wird dann „on_the_fly“ die entsprechende Route erstellt und über den Layer „path“ im Mapfile ausgegeben (Abb. 14).

Abbildung 14: Route mit pgRouting erstellt


Route mit pgRouting erstellt

Fragen zu dieser Thematik bitte an die Mailinglist von http://www.umn-mapserver.de, oder in die Foren auf: http://www.umn-mapserver-community.de bzw. http://pgrouting.postlbs.org

Diese Anleitung wurde von Kai Behncke und Florian Thürkow erstellt. Ergänzung von Nicol Hermann