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 (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 
== 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
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).
Abbildung 4: 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
Die Daten der Tabelle „fridastreets“ haben folgende Struktur (siehe Abbildung 6):
Abbildung 6: 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
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
== 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