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

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: http://freegis.org/pipermail/mapserver-de/2006-August/002433.html (mit 14 Antworten)

Englischsprachige Mailinglist des UMN MapServers: http://lists.umn.edu/cgi-bin/wa?A2=ind0612&L=mapserver-users&T=0&F=&S=&P=30653 (mit 8 Antworten)

Forum auf umn-mapserver-community.de: http://www.selbstverwaltungbundesweit.de/mapserver/modules.php?name=Forums&file=viewtopic&t=331

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

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). 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

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).

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

Die Daten der Tabelle „fridastreets“ haben folgende Struktur: Abbildung 6: Ursprüngliche Struktur der Frida-Daten

So weit so gut. 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

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).

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 (liegt im heruntegeladenen Ordner als x_y_einlesen.php).

<?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 " "; 6 $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 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 Entpunkte der Strassengeometrien erstellen

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

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

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. Sie liegen im Ordner als routing.map bzw. phtmls/routing_os_frida.phtml 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 CLASSITEM 'strtypid' CLASS EXPRESSION '1' STYLE 9      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
 * 1) LABELITEM 'strname'

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 10 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: W&auml;hle.... Dom Im Hone Kolpingstrasse Martinistr. 11 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.

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

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