- Fachgebiet Datenbanken und Informationssysteme

Werbung
Fakultät für Elektrotechnik und Informatik
Institut für Praktische Informatik
Fachgebiet Datenbanken und Informationssysteme
Routenberechnung mit partitionierten
Straßendaten
Bachelorarbeit
im Studiengang Informatik
David Bormann
Matrikelnummer: 2739920
Prüfer: Prof. Dr. Udo Lipeck
Zweitprüfer: Dr. Hans Hermann Brüggemann
Betreuer: M. Sc. Hendrik Warneke
Juli 2012
Inhaltsverzeichnis
1 Einleitung
4
1.1
Motivation und Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . .
4
1.2
Gliederung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2 Grundlagen
6
2.1
Die Open-Streetmap-Daten . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.2
Das Simple-Feature-Modell . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.2.1
Geometrieschema . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.2.2
Topologische Prädikate und Geometriefunktionen . . . . . . . . .
9
Graphen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.3.1
Graphentheorie . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.3.2
Der abstrakte Datentyp Graph . . . . . . . . . . . . . . . . . . .
11
Kürzeste-Wege-Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.4.1
Der Bellman-Ford-Algorithmus . . . . . . . . . . . . . . . . . . .
13
2.4.2
Der Dijkstra-Algorithmus . . . . . . . . . . . . . . . . . . . . . .
14
2.4.3
Der A*-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . .
16
Schlussfolgerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
2.3
2.4
2.5
3 Konzepte
19
3.1
Partitionierungsarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
3.2
Ablauf der partitionierten Routenberechnung . . . . . . . . . . . . . . . .
21
3.3
Routensuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.3.1
Heuristiken zur Findung von Zielknoten . . . . . . . . . . . . . .
23
3.3.2
Metriken zur Gewichtung der Kanten . . . . . . . . . . . . . . . .
24
2
INHALTSVERZEICHNIS
3.3.3
3.4
3
Vorgehen beim Misserfolg . . . . . . . . . . . . . . . . . . . . . .
24
Komposition der Routen . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
4 Implementierung
27
4.1
Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.2
OpenJUMP und seine Schnittstelle . . . . . . . . . . . . . . . . . . . . .
27
4.3
Beschreibung der Benutzerschnittstelle . . . . . . . . . . . . . . . . . . .
29
4.4
Finden von Koordinaten über Adressangabe . . . . . . . . . . . . . . . .
31
4.5
Umgang mit den WGS84-Koordinaten . . . . . . . . . . . . . . . . . . .
31
4.6
Aufbau des Graphen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
4.6.1
Ermittlung von Kanten aus den OSM-Daten . . . . . . . . . . . .
32
4.6.2
Clipping der Kanten in einer Partition . . . . . . . . . . . . . . .
33
4.7
Klassenbeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
4.8
Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
5 Experimente
48
5.1
Die Experimentierumgebung . . . . . . . . . . . . . . . . . . . . . . . . .
48
5.2
Das Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
5.3
A* versus Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
5.4
Große versus kleine Partitionen . . . . . . . . . . . . . . . . . . . . . . .
53
5.5
Unterschiede der Metriken . . . . . . . . . . . . . . . . . . . . . . . . . .
55
5.6
Unterschiede der Heuristiken . . . . . . . . . . . . . . . . . . . . . . . . .
56
5.7
Abhängige versus unabhängige Partitionierung . . . . . . . . . . . . . . .
59
5.8
Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
6 Ausblick
64
A Das simple Osmosis-PostGIS-Schema
65
B PL/pgSQL-Funktionen
66
B.1 Funktionen zum Erstellen der Edges-Tabelle . . . . . . . . . . . . . . . .
66
B.2 Funktionen zum Finden von Start- und Zielknoten . . . . . . . . . . . . .
69
B.3 Funktionen zum Graphaufbau . . . . . . . . . . . . . . . . . . . . . . . .
72
Kapitel 1
Einleitung
Dieses Kapitel dient dazu, einen Überblick über das Thema dieser Arbeit zu geben.
Es wird die Aufgabenstellung beschrieben und eine Gliederung der weiteren Kapitel
angegeben.
1.1
Motivation und Aufgabenstellung
Die für Fahrzeug- oder Fußgängernavigation benötigten Wegenetze werden üblicherweise aus geographischen Daten gewonnen. Diese werden in einen gewichteten Graphen
transformiert, in dem Kanten Wege darstellen und Knoten Kreuzungen dieser. Mit Hilfe verschiedener Algorithmen kann in diesem Graphen der kürzeste Pfad zwischen zwei
Knoten ermittelt werden. Dies basiert auf einer Gewichtung der Kanten, beispielsweise
aus der Entfernung zwischen zwei Knoten. Nun können diese Wegenetze allerdings sehr
groß werden und den kompletten Graph zu speichern, könnte die Arbeitsspeicherkapazitäten eines Rechners sprengen, zum Beispiel die von mobilen Kommunikationsgeräten
oder mobilen Servicerobotern. In diesem Fall kann der Graph, beziehungsweise dessen
Ausgangsmaterial aus geographischen Daten, in kleinere Stücke zerlegt werden, um diese
dann vollständig im Arbeitsspeicher abzulegen.
In dieser Arbeit geht es um die Routenplanung mit Straßendaten, die vorab partitioniert worden sind. Für die Berechnung einer Route über mehrere Partitionen, werden
die einzelnen Ergebnisse einer Wegfindung auf einer Partition zusammengesetzt. Durch
Experimente soll bestimmt werden, inwiefern ein solches Ergebnis von der optimalen
Route abweicht. Dabei sollen verschiedene Algorithmen, sowie Partitionierungsgrade
und -arten getestet werden. Die erforderlichen geographischen Daten stammen aus dem
Open-Streetmap-Projekt und werden in einer PostGIS-Datenbank abgelegt.
4
KAPITEL 1. EINLEITUNG
1.2
5
Gliederung
In Kapitel 2 werden zuerst Open-Streetmap und die Struktur der von ihr bereitgestellten Daten skizziert. Es werden graphentheoretische Begriffe diskutiert und verschiedene
Algorithmen zur Wegfindung vorgestellt.
In Kapitel 3 wird der Ablauf der partitionierten Routenberechnung erläutert. Dazu gehört das Laden von Graphpartitionen, dann wird auf diesen die Wegesuche ausgeführt
und anschließend werden die Routen der einzelnen Partitionen zusammengeführt.
Das Kapitel 4 beschäftigt sich mit der Implementierung des Graphaufbaus, verschiedenen
weiteren Implementierungsdetails und einer Beschreibung der Benutzerschnittstelle des
Programms.
Das nachfolgende Kapitel 5 beschreibt die ausgeführten Experimente und ihre Ergebnisse.
Das Kapitel 6 bietet einen Ausblick, was man bei der partitionierten Routensuche noch
ändern oder verbessern könnte.
Im Anhang A ist das verwendete Datenbankschema zur Verwendung der Open-StreetMap-Daten dargestellt.
Im Anhang B finden sich entwickelte PL/pgSQL-Funktionen.
Kapitel 2
Grundlagen
In diesem Kapitel wird ein Überblick über das Open-Streetmap-Projekt gegeben, sowie
über den Aufbau der Daten, die von Open-Streetmap zur Verfügung gestellt werden.
Es folgen notwendige Begriffserklärungen der Graphentheorie und der Aufbau eines Abstrakten Datentyps eines Graphen. Anschließend werden mehrere Algorithmen vorgestellt, die kürzeste Wege von einem Startknoten aus finden.
2.1
Die Open-Streetmap-Daten
Open-Streetmap (im Folgendem mit OSM abgekürzt) ist eine freie Weltkarte, an der
jeder mitarbeiten kann, sie zu komplettieren.1 Sie wurde 2004 in Großbritannien ins Leben gerufen. Mitglieder der OSM-Community zeichnen ihre Bewegung mit einem GPSTracker auf und fügen den geographischen Daten anschließend weitere Informationen
hinzu. Dies können Straßenart und -namen und angrenzende Points of Interests, beispielsweise Gebäude oder Parkplätze, aber auch Briefkästen und Bushaltestellen sein.
Eine andere Möglichkeit OSM-Karten zu erstellen, ist das Abzeichnen von Luftbildern.
Weitere Information zu OSM kann man in [RT09] finden.
Das Datenmodell von OSM enthält zwei für das Routing wichtige Typen Ways und
Nodes. Des Weiteren gibt es noch Relations, die aus Ways und Nodes bestehen und
so komplexere Zusammenhänge darstellen können, zum Beispiel den Grundriss eines
Gebäudes, Straßen sind allerdings keine Relationen. Ein Node besteht aus seiner geographischen Länge und Breite und, wie die anderen Objekte auch, aus beliebig vielen
Attributen, sogenannte Tags. Das sind Schlüssel-Wert-Paare, die dabei helfen, Objekte
genauer zu klassifizieren und so zum Beispiel eine Autobahn und einen Fußweg auf einer
Karte verschieden darzustellen. Alle Straßen erhalten den Schlüssel highway, ein Fußweg
1
http://www.openstreetmap.org, steht unter der CC-BY-SA 2.0-Lizenz
6
KAPITEL 2. GRUNDLAGEN
7
erhält dann den Tag highway=footway, eine Autobahn highway=motorway. Es gibt aber
auch Tags wie oneway=yes, an denen man Einbahnstraßen erkennen kann. Ein Way
besteht zusätzlich noch aus mehreren Nodes, wobei es auf die Reihenfolge ankommt, in
der die Nodes abgelegt sind. Seit 2009 darf ein Way- oder Nodeobjekt keine zwei Tags
mit dem selben Schlüssel besitzen.
Abbildung 2.1: EER-Diagramm zu Node, Way und deren Tags
In Abbildung 2.1 sind diese Zusammenhänge als EER-Diagramm dargestellt. Für das
Routing irrelevante Attribute sind nicht aufgenommen. Zum Beispiel welcher User eine
Kante oder einen Knoten zu welchem Zeitpunkt hinzugefügt oder verändert hat. Die
Attribute lat und lon bestimmen die geographische Lage eines Nodes, die geographische
Breite lat und Länge lon. Die Koordinaten sind in der Form des WGS84 (World Geodetic
System 1984) gespeichert.
Die OSM-Daten von Deutschland, Stand 06.06.2012, wurden mit Hilfe von Osmosis2 ,
einem Bearbeitungswerkzeug für OSM-Daten, in eine PostGIS-Datenbank mit dem simplen OSM-PostGIS -Schema, siehe Anhang A überführt. Wichtig für den Aufbau des
Graphen sind dabei folgende Relationen und Attribute:
NODES
NODE TAGS
WAYS
WAY TAGS
WAY NODES
2
(Id, Geom)
(Node Id → NODES, K, V)
(Id, Linestring)
(Way Id → WAYS, K, V)
(Way Id → WAYS, Sequence Id, Node Id → NODES)
http://wiki.openstreetmap.org/wiki/DE:Osmosis, Version 0.40.1
KAPITEL 2. GRUNDLAGEN
8
Die geographische Lage wird zusammengefasst in dem geographischen Attribut Geom
Das Attribut Linestring stellt einen Linienzug über eine geordnete Menge von Koordinaten dar. Es gilt die Integritätsbedingung, dass die sequence id von 0 beginnend,
fortlaufend für jeden Way ist. Mit Hilfe der sequence id lässt sich ein Linienzug über die
Nodes eines Ways konstruieren. Die Relation bilden aus dem EER-Diagram wird durch
WAY NODES umgesetzt.
Dass die Karten vornehmlich von Hobbykartographen erstellt werden, bedeutet allerdings auch, dass ein OSM-Datensatz nicht unbedingt vollständig sein muss, beziehungsweise Details fehlen können, die für ein Routing benötigt würden. Dies kann der Fall
sein, wenn man eine Route von einem Startpunkt, von dem nur die Adresse bekannt ist,
aus sucht, diese Adresse aber nicht in den OSM-Daten verzeichnet ist. Im Normalfall
sollte allerdings die Postleitzahl und der Straßenname ausreichen, um einen Start- oder
Endpunkt fest zu legen.
2.2
2.2.1
Das Simple-Feature-Modell
Geometrieschema
Das Modell, in dem die OSM-Daten vorliegen, ist das Simple-Feature-Modell 3 . In diesem werden Datentypen und räumliche Operationen für zweidimensionale Geometrien
definiert[Bri08]. Die Geometrien sind deswegen simpel, da ihre Stützpunkte nur gradlinig verbunden sind. Es gibt vier Geometrieformen des Typs Geometry in dem Modell,
nämlich Punkte, Linien, Flächen und Geometriesammlungen.
Punkte werden durch ein Point-Objekt beschrieben, dem eine Koordinate zugeordnet
ist, auf deren Werte man mittels getX(), beziehungsweisse getY() zugreifen kann. Ein
OSM-Node wird so dargestellt.
Linien sind Streckenzüge, die durch eine Folge von Stützpunkten definiert sind. Man
kann auf den Anfangs- und Endpunkt, die Länge und den n-ten Stützpunkt zugreifen.
Durch diese LineStrings werden OSM-Ways und somit unter anderem Straßen in OSM
dargestellt.
Flächen werden durch Polygone beschrieben. Ein Polygon besteht aus einem geschlossenen äußeren Streckenzug und beliebig vielen inneren, die Löcher beschreiben. In OSM
werden Polygone durch Relationen dargestellt, zum Beispiel durch das Relationentag
area und mit Verweis auf eine Menge von OSM-Ways, die den Tag outer besitzen, wird
der äußere Ring eines Polygons beschrieben.
3
ISO 19125
KAPITEL 2. GRUNDLAGEN
9
Eine Geometriesammlung ist eine Sammlung verschiedener Geometrien. Man kann bei
einer GeometryCollection auf die verschiedenen Geometrien zugreifen und falls alle Geometrien vom gleichen Typ sind, kann man eine der Unterklassen MultiPoint, MultiLineString oder MultiPolygon verwenden.
Abgespeichert werden die Geometrien als well-known Binary (WKB) und können für
Menschen lesbar gemacht werden als well-known Text (WKT). Beispiele für WKT sind:
POINT(6.2 19.90) oder LINESTRING(1 3, 3 7, 4 2).
2.2.2
Topologische Prädikate und Geometriefunktionen
Topologische Eigenschaften beschreiben die räumliche Beziehung von Geometry-Objekten
zueinander. Ob eine Geometrie vollständig oder nur zum Teil in einer anderen enthalten
ist oder diese berührt wird, wird mit Hilfe von topologischen Prädikaten ausgedückt.
• contains(Geometry other) - ist true, wenn die Geometrie other komplett in dieser
Geometrie enthalten ist
• crosses(Geometry other) - ist true, wenn die beiden Geometrien eine Schnittmenge
haben, aber eine Geometrie nicht komplett in der anderen enthalten ist
• touches(Geometry other) - ist true, wenn die beiden Geometrien sich nur an ihren
Rändern schneiden
Weitere geometrische Funktionen einer Geometrie sind:
• Envelope(): Geometry - Rückgabe ist das minimal umgebende Rechteck der Geometrie
• Boundary(): Geometry - zurück gegeben wird der Rand der Geometrie
• Distance(Geometry other): double - es wird der geringste Abstand zwischen zwei
Punkten der Geometrie und other berechnet und zurück gegeben
• Intersection(Geometry other): Geometry - gibt eine Geometrie zurück, die die Schnittmenge der Geometrie und other beschreibt
• Difference(Geometry other): Geometry - gibt eine Geometrie zurück, die die Geometrie ohne die Schnittmenge der Geometrie und other beschreibt
KAPITEL 2. GRUNDLAGEN
2.3
2.3.1
10
Graphen
Graphentheorie
Ein Graph besteht aus Knoten, die über Kanten verbunden sein können. Oder formal
ausgedrückt, ein Graph G ist ein Tupel (V,E), welches aus zwei endlichen Mengen V 6= ∅
(Vertices für die Knoten) und E (Edges für die Kanten) besteht. Die Kanten in einem
gerichteten Graphen sind zweistellige Relationen der Knoten, E ⊆ V × V , zum Beispiel
die Kante e=(a, b) verbindet die beiden Knoten a und b. Diese sind die Endpunkte von
e. Die Knoten a und b sind damit adjazent und diese Knoten mit e inzident. Der Grad
eines Knoten v ist die Anzahl der mit v inzidenten Kanten. Eine Schleife ist eine Kante,
die einen Knoten mit sich selbst verbindet. In Abbildung 2.2 ist ein gerichteter Graph
dargestellt. Ein Graph heißt dünn besetzt, wenn |E| = Θ(|V|).[Jun94]
d
e
b
a
c
Abbildung 2.2: Ein gerichteter Graph
Bei einem gewichteten Graphen werden alle Kanten mit einer Zahl, der Länge dieser
Kante, gewichtet. Das muss nicht unbedingt der geographische Abstand zwischen zwei
Knoten sein, sondern kann auch als Kosten, Zeiten oder Kapazitäten genutzt werden
und darf auch negativ sein.
Ein Weg besteht aus einer Menge von Knoten, die jeweils adjazent sind und ein Pfad
ist ein spezieller Weg, bei dem kein Knoten doppelt auftreten darf. Ein Zyklus in einem
Graph ist ein Weg, in dem ein Knoten Start- und Zielknoten zugleich darstellt. Die Länge
eines Weges ist dann die Summe der Gewichte der Kanten des Weges.
Der Abstand d(a,b) der Knoten a und b, oder der kürzeste Weg von a nach b, ist das
Minimum der Längen aller Pfade mit Startpunkt a und Endpunkt b, b ist dann von a
aus erreichbar. Falls es keinen Pfad von a nach b gibt, wird d(a,b) angenommen als ∞.
Ein Weg, der nur einen Knoten enthält, hat die triviale Länge 0. In Abbildung 2.3 ist
der kürzeste Weg vom Knoten e zum Knoten d markiert.
Falls es in dem Graphen einen Zyklus gibt, dessen Länge negativ ist, kann es keinen kürzesten Weg geben. Zumindest nicht, wenn man einen kürzesten Weg von a nach b sucht
und man einen solchen Zyklus von a aus erreichen kann. In den folgenden Betrachtungen
seien negative Kantengewichte ausgeschlossen.
KAPITEL 2. GRUNDLAGEN
1
11
d
b
0.5
3
e
1
1
2
a
c
4
Abbildung 2.3: Ein kürzester Weg zwischen zwei Knoten
2.3.2
Der abstrakte Datentyp Graph
Der abstrakte Datentyp, kurz ADT, Graph benutzt die ADTs Vertex und Edge. Diese
dienen lediglich dazu die Knoten und Kanten darzustellen und stellen, außer einem
Konstruktor, nur Anfrage- und Änderungsmethoden bereit, die für die Bestimmung des
kürzesten Weges hilfreich sind.
Der ADT Edge stellt folgende Methode bereit:
• getId(): String - liefert die OSM-Id dieser Kante
• getWeight(): double - liefert das Gewicht einer Kante
• getLineString(): LineString - gibt den Streckenzug der Kante als Geometrie zurück
Im ADT Vertex wird vermerkt, welchen Abstand ein Knoten zu einem Startknoten hat,
beziehungsweise welcher Abstand bisher geschätzt wurde und welcher Knoten in einem
kürzesten Weg sein Vorgänger ist:
• getId(): String - liefert die OSM-Id dieses Knotens
• setPred(Vertex v) - setzt den Vorgänger von diesem Knoten auf v
• getPred(): Vertex - gibt den Vorgänger dieses Knotens zurück
• setD(double distance) - setzt den Abstand von dem Startknoten zu diesem auf
distance
• getD(): double - gibt den Abstand zum Startknoten zurück
• getPoint(): Point - gibt die geometrische Lage diese Knotens als Geometrie zurück
• markAsTarget(): - markiert den Knoten als Ziel einer Wegesuche
• isTarget(): boolean - gibt true zurück, falls der Knoten ein Zielknoten ist
KAPITEL 2. GRUNDLAGEN
12
Der ADT Graph besitzt folgende Methoden. Eine Sequence ist eine verkettete Liste mit
Indexzugriff. Ein Großteil der Methoden entstammt [Lip12];
• size(): int - liefert die Anzahl der Knoten dieses Graphen
• vertices(): Sequence - liefert die Knoten dieses Graphen
• insertVertex(Vertex v) - fügt den Knoten v in den Graphen ein
• removeVertex(Vertex v) - entfernt den Knoten v und seine inzidenten Kanten aus
dem Graph
• edges(): Sequence - liefert die Kanten dieses Graphen
• insertEdge(Vertex v, Vertex w, Edge e) - fügt die gerichtete Kante e zwischen den
Knoten v und w ein
• removeEdge(Edge e) - entfernt die Kante e aus dem Graphen
• inDegree(Vertex v): int - liefert die Anzahl der eingehenden Kanten von v
• inIncidentEdges(Vertex v): Sequence - liefert die eingehenden Kanten vom Knoten
v
• inAdjacentVertices(Vertex v): Sequence - liefert die Knoten, die über eine eingehende Kante zum Knoten v adjazent sind
• areAdjacent(Vertex v, Vertex w): boolean - gibt true zurück, falls v über eine Kante
mit w verbunden ist
• origin(Edge e): Vertex - liefert den Knoten, von dem die Kante e ausgeht
• destination(Edge e): Vertex - liefert den Knoten, wohin die Kante e führt
• findEdge(Vertex v, Vertex w): Edge - liefert die Kante, über die die Knoten v und
w verbunden sind
• getVertexWithId(String id): Vertex - liefert die Knoten mit der übergebenen id,
sofern vorhanden
• containsVertexWithId(String id): boolean - liefert true, falls ein Knoten mit der
dieser id im Graphen existiert
Die Methoden outDegree(), outIncidentEdges() und outAdjacentVertices() werden analog definiert. Die Methode findEdge() hat für beliebige Graphen eine Laufzeit von O(|E|).
Da ein Straßennetz in der Regel dünn besetzt ist, Knoten höchstens den Grad 4 haben
und ein- und ausgehende Kanten eines Knoten mit Indizenzlisten für jeden Knoten festgehalten werden können, beträgt die Laufzeit in diesem Fall O(1).
KAPITEL 2. GRUNDLAGEN
2.4
13
Kürzeste-Wege-Algorithmen
Man unterscheidet zwischen einer kürzesten Wegfindung von einem Startknoten aus
und zwischen allen Knoten. Manche Algorithmen kann man abbrechen, falls man einen
kürzesten Weg zu einem gesuchten Zielknoten gefunden hat.
Die Initalisierung ist bei allen Algorithmen gleich. Man benötigt einen Graph G und
einen Startknoten, Vertex s, siehe Algorithmus 1. Dieser ist aus [CLRS10] entnommen,
wie auch die Algorithmen 2 und 3. Die Distanz zum Startknoten wird für alle Knoten auf
Algorithm 1
1: procedure Init-Single-Source(Graph G, Vertex s)
2:
for all Vertex v ∈ G.vertices() do
3:
v.setD(∞);
4:
v.setPred(NULL);
5:
end for
6:
s.setD(0);
7: end procedure
den maximal zulässigen Wert gesetzt, bis auf die triviale Distanz vom Startknoten s zu s
auf 0. Da jeder Knoten so initalisiert wird, hat die Methode die Laufzeit O(|V|). Wird die
Routensuche nur einmal auf einen Graphen angewandt, kann schon bei der Initalisierung
der Knoten die Distanz auf ∞ gesetzt werden, wodurch die Laufzeit konstant wäre.
Die folgenden Algorithmen berechnen alle kürzesten Wege von einem Knoten zu prinzipiell allen anderen.
2.4.1
Der Bellman-Ford-Algorithmus
Der Bellman-Ford-Algorithmus würde auch ein Ergebnis bei negativen Kanten liefern,
hat aber eine Laufzeit von O(|E||V |). Er wird im Algorithmus 2 beschrieben. Da es
nur positve Kanten gibt, wird auf die Darstellung des Programmcodes, der prüft, ob
der Graph einen Zyklus mit negativer Länge enthält, verzichtet. Die Zeilen 6-9 sind
bei allen Algorithmen ähnlich und beschreiben die Relaxation einer Kante. Dabei wird
geprüft, ob sich der geschätzte Abstand vom Startpunkt zum Endknoten einer Kante
verringert, falls man diese Kante in den kürzesten Weg mit aufnehmen würde. Wenn
dies der Fall ist, wird der neue Schätzwert vermerkt und der Startknoten der Kante wird
zu dem Vorgänger des Endknotens in diesem geschätzten kürzesten Weg. Dies wird für
alle Kanten |V|-1 mal gemacht
Der Algorithmus arbeitet korrekt, falls der Graph keinen Zyklus mit negativer Länge
enthält. Denn dann kann ein kürzester Weg höchstens aus |V|-1 Kanten bestehen, da
KAPITEL 2. GRUNDLAGEN
14
Algorithm 2
1: procedure Bellman-Ford(Graph G, Vertex s)
2:
Init-Single-Source(G,s);
3:
int newDist=0;
4:
for i=1 to G.size()-1 do
5:
for all Edge e ∈ G.edges() do
6:
newDist=G.origin(e).getD()+e.getWeight();
7:
if G.destination(e).getD()>newDist then
8:
G.destination(e).setD(newDist);
9:
G.destination(e).setPred(G.origin(e));
10:
end if
11:
end for
12:
end for
13: end procedure
ein Zyklus mit positiver Länge einen Weg nur verlängert. Wenn es einen kürzesten Weg
von s zu einem Knoten gibt, dann kann man diesen erhalten, indem man die Kanten in
der Reihenfolge dieses Weges sukzessiv relaxiert. Pro äußeren Schleifendurchlauf, werden
alle Kanten relaxiert. Da dies |V|-1 mal passiert, relaxiert man unter anderem auch die
Kanten eines kürzesten Weges mit |V|-1 Kanten. Daher die Laufzeit O(|E||V|).
2.4.2
Der Dijkstra-Algorithmus
Djikstras Algorithmus, der Algorithmus 3, kann nur auf einen Graphen mit positiven
Kantengewichten ausgeführt werden, läuft dafür aber auch schneller, bei geeigneter Implementierung der PriorityQueue Q, in O((|V|+|E|)log|V|). Die Idee hinter Djkstras Algorithmus ist, dass in einer Menge von Knoten, der cloud, vermerkt wird zu welchen
Knoten kürzeste Wege schon bekannt sind. Dies wird erreicht, in dem der Knoten mit
der kürzesten Länge zur cloud in eben diese aufgenommen wird, siehe die Zeilen 8 und 9.
Dann werden alle Kanten, die von diesem Knoten ausgehen, relaxiert. Das funktioniert,
da es nur positive Kantengewichte gibt und so ein aktuelles Minimum einer Distanz zur
cloud nicht nachträglich unterschritten werden kann. Insgesammt wird jede Kante nur
genau einmal relaxiert.
In Q werden die noch nicht in die cloud aufgenommenen Knoten nach ihrer bisher berechneten Länge des kürzesten Weges aufsteigend geordnet. Dabei muss ein Knoten nach
einer erfolgreichen Relaxation einer eingehenden Kante eventuell neu positioniert werden, siehe Zeile 15 replaceKeyAt(). Q.poll() gibt den Knoten mit kleinster Distanz zurück
und löscht diesen aus Q. Die Implementierung dieser Methoden, beeinflusst die Laufzeit
des Dijkstra-Algorithmus. Wird die Priorityqueue mit einem Min-Heap realisiert, erhält
man eine Laufzeit von O(|E|log|V|). Ein Knoten mit der geringsten Distanz zur cloud,
KAPITEL 2. GRUNDLAGEN
15
Algorithm 3
1: procedure Dijkstra(Graph G, Vertex s)
2:
Init-Single-Source(G,s);
3:
int newDist=0;
4:
Vertex u=NULL;
5:
Set cloud=∅;
6:
PriorityQueue Q=G.vertices();
. Der Schlüssel ist getD()
7:
while Q 6= ∅ do
8:
u=Q.poll();
9:
cloud.add(u);
10:
for all Vertex v ∈ G.outAdjacentVertices(u) do
11:
newDist=u.getD()+G.findEdge(u,v).getWeight();
12:
if v.getD()>newDist then
13:
v.setD(newDist);
14:
v.setPred(u);
15:
Q.replaceKeyAt(v, newDist);
16:
end if
17:
end for
18:
end while
19: end procedure
also der Aufruf Q.poll(), wird maximal |V|-mal aus Q benötigt. Die Umstrukturierung
nach dem Entfernen des Minimums läuft, bei maximal |V| Knoten im Heap, in einer
Laufzeit von O(log|V|), also insgesammt O(|V|log|V|). Die nach der Relaxation nötige
Umstrukturierung des Heaps, kann in O(log|V|) geschehen, durch schrittweises Vertauschen des betreffenden Heapknotens mit seinen Elternknoten, bis die Heapeigenschaft
wieder erfüllt ist4 . Da dies höchstens |E|-mal passieren kann ergibt sich eine Laufzeit von
O(|E|log|V|). Die Laufzeiten von replaceKeyAt() und Q.poll() ergeben also insgesammt
O((|V|+|E|)log|V|).
Falls man die Route zu einem bestimmten Zielknoten sucht, kann der Algorithmus abgebrochen werden, wenn der Zielknoten in die cloud aufgenommen wurde oder man nicht
alle Knoten erreichen kann und sich in Q nur noch Knoten mit einer unendlichen Distanz
zum Startknoten befinden.
4
Da die Java-Implementierung der PriorityQueue die replaceKeyAt()-Methde nicht anbietet, wird
der Knoten aus dem Heap entfernt und neu einsortiert. Durch das Entfernen eines Nicht-Wurzel-Knotens
verschlechtert sich die Laufzeit auf O(|V|)
KAPITEL 2. GRUNDLAGEN
2.4.3
16
Der A*-Algorithmus
Die bisher vorgestellten Algorithmen bedienen sich nur Eigenschaften eines abstrakten
Graphen. Sind zusätzlich noch andere Informationen bekannt, wie zum Beispiel die geographische Lage der Knoten, lässt sich eine Heuristik bestimmen, mit deren Hilfe man
gezielter nach einem kürzesten Weg suchen lassen kann. Im Falle des A*-Algorithmus,
siehe Algorithmus 4, wird es vermieden, Kanten zu relaxieren, die wahrscheinlich nicht
zu dem kürzesten Weg gehören. Dazu müssen aber auch eine Menge von Zielknoten T
bekannt sein.
Um zu entscheiden, welche Kante als nächstes relaxiert wird, wird eine Schätzfunktion fˆ
benutzt,[HNR68]. Die Länge des kürzesten Weges von einem Start- zu einem Zielpunkt
über einen Knoten n wird beschrieben durch f (n) = g(n) + h(n). g(n) ist die Länge des
kürzesten Weges zum Knoten n, während h(n) die Länge des kürzesten Weges von n
zum Zielpunkt bestimmt, beziehungsweise zu einem Zielpunkt t ∈ T , zum Beispiel zum
Weitentferntesten vom Startpunkt aus. Die Schätzfunktion für die Länge eines kürzesten
Weges ist dann fˆ(n) = ĝ(n) + ĥ(n). ĝ(n) ist eine Schätzung der Länge des Weges
zum Knoten n, ĥ(n) dementsprechend analog. Für ĝ(n) kann also die Länge des bisher
berechneten kürzesten Weges genutzt werden. Wird ĥ für alle Knoten auf 0 gesetzt,
erhält man dadurch Dijkstra’s Algorithmus und es gilt sogar fˆ(n) = f (n), da durch die
Aufnahme des Knotens mit dem kürzesten Abstand zur cloud, ĝ(n) = g(n) gilt.
ĥ(n) darf die wirkliche Länge nicht überschätzen, dann bestimmt der Algorithmus auch
den kürzesten Weg. Eine mögliche Schätzung für die Länge des Restwegs ist der Luftlinienabstand, da kein Weg kürzer sein kann, als der direkte. Die Schätzfunktion ĥ ist
konsistent, genau dann wenn für jeden Knoten n und jeden seiner Nachfolger m die Dreiecksungleichung h(n, m) + ĥ(m) ≥ ĥ(n) gilt, wobei h(n, m) die wirkliche Länge zwischen
n und m ist. Die Luftlinie ist eine konsistente Schätzfunktion, da die Dreiecksungleichung der euklidischen Distanz in der Ebene gilt. Die Berechnung der Schätzfunktion
unter Verwendung des euklidischen Abstandes für einen Knoten v sähe folgendermaßen
aus, wobei t ein Point sei, der die geographische Lage des Zielpunktes besitzt und tx für
die X-Koordinate von t steht.
q
ˆ
f (v) = v.getD() + (vx − tx )2 + (vy − ty )2
Der Algorithmus 4 zeigt den allgemeinen Fall, in dem ĥ(n) nicht konsistent ist. Es werden wie bei Dijkstra zwei Mengen von Knoten benutzt, die zwar disjunkt sind, aber
vereinigt nicht die Menge aller Knoten ergeben müssen. Es wird eine PriorityQueue, hier
open, benutzt, um zu ermitteln welcher Knoten als nächstes in die Liste closed kommt.
Der Schlüssel, nach dem die Priorityqueue sortiert ist, ist der Wert von fˆ für den jeweiligen Knoten. Sobald ein Weg zum Startknoten bekannt ist, wird der Knoten inopen
aufgenommen, Zeile 14 f. Wenn der geschätzte kürzeste Weg zu einem Knoten bekannt
ist, wird er in closed aufgenommen. Falls der Knoten zu T gehört, kann der Algorithmus
abgebrochen werden. Wenn ein Knoten in closed aufgenommen wird, werden alle aus-
KAPITEL 2. GRUNDLAGEN
17
Algorithm 4
1: procedure A*(Graph G, Vertex s)
2:
Init-Single-Source(G, s);
3:
int newDist=0;
4:
Vertex u=NULL;
5:
Sequence closed=∅; . Hier ist der Schlüssel fˆ(n), Pendant zu Dijkstra’s cloud
6:
PriorityQueue open.add(s);
. enhält Knoten, zu denen ein Weg bekannt ist
7:
while open 6= ∅ do
8:
u=open.poll();
9:
closed.add(u);
10:
if u.isTarget() then
11:
break
12:
end if
13:
for all Vertex v ∈ G.outAdjacentVertices(u) do
14:
if v ∈
/ closed ∧ v ∈
/ open then
15:
open.add(v);
16:
end if
17:
newDist=u.getD()+G.findEdge(u,v).getWeight();
18:
if v.getD()>newDist then
19:
v.setD(newDist);
20:
v.setPred(u);
21:
if v ∈ closed then
22:
closed.remove(v);
23:
open.add(v)
24:
else
25:
open.replaceKeyAt(v, fˆ(v));
26:
end if
27:
end if
28:
end for
29:
end while
30: end procedure
KAPITEL 2. GRUNDLAGEN
18
gehenden Kanten relaxiert. Ist ĥ konsistent, dann entspricht die Laufzeit im worst-case
der des Dijkstra Algorithmus. Denn dann wird kein Knoten mehr nachträglich aus closed
entfernt, beziehungsweise muss die Schätzung für die Länge des kürzesten Weges nicht
mehr nachträglich korrgiert werden. Ist ĥ(n) konsistent, kann Dijkstras Algorithmus
übernommen werden, nur der Schlüssel, der zum Einordnen in die PriorityQueue benutzt
wird, muss fˆ(n) sein.[Bri08] Aber unter Anwendung der gleichen Heuristik, relaxiert
kein Algorithmus weniger Kanten als der A*-Algorithmus, bis er mit einer geeigneten
Heuristik einen Zielknoten gefunden hat.
2.5
Schlussfolgerung
Die OSM-Daten sind schon als Graph organisiert, weshalb es nicht schwer ist, aus diesen
einen Graphen aufzubauen. Die OSM-Nodes werden zu Knoten in dem Graphen und
die Kanten entstehen aus den Teilabschnitten der Ways, die zwei Nodes verbinden. Da
es Einbahnstraßen gibt, bietet sich ein gerichteter Graph an. Gewichtete Kanten werden vorausgesetzt, bleibt also nur noch die Frage offen, wie man die Kanten gewichtet.
Zusätzlich zu der Länge des Weges, den eine Kante darstellt, könnten auch andere Faktoren, wie die Art der Straße, das Gewicht einer Kante bestimmen. Die vorgestellten
Algorithmen bestimmen kürzeste Wege von einem Knoten zu allen anderen. Das wird
nicht nötig sein und der Dijkstra- und A*Algorithmus können abgebrochen werden, wenn
ein kürzester Weg zum Zielknoten gefunden oder ein Zielknoten aus einer Menge von
Knoten erreicht wurde. Dies ist beim Bellman-Ford-Algorithmus nicht der Fall, daher
wird auf die Implementierung verzichtet, da er die gleiche kürzeste Route wie Dijkstra
liefert. Der A*-Algorithmus ist der einzige Algorithmus, der die geographische Lage der
Knoten beachtet, dadurch wird er durch das zielgerichtete Suchen wahrscheinlich am
Schnellsten ein Ergebnis liefern.
Kapitel 3
Konzepte
In diesem Kapitel wird der Ablauf der partitionierten Routenberechnung erläutert. Dazu
gehört zunächst das Laden von Graphpartitionen, dann wird auf diesen die Wegesuche
ausgeführt und anschließend werden die Routen der einzelnen Partitionen zusammengeführt.
3.1
Partitionierungsarten
Bevor die Routensuche beginnen kann, müssen die geographischen Daten partitioniert
werden. Es werden nun zwei Arten der Partitionierung vorgestellt.
Die Straßendaten werden partitioniert, indem sie in Gitterzellen zerlegt werden. Diese
Gitterzellen sind alle gleich breit und gleich lang und sind lückenlos aneinander gereiht.
Die geometrischen Daten in Punktform sind so disjunkt aufgeteilt. Bei Linienzügen wird
durch sogenanntes Clipping der Teil, der außerhalb einer Gitterzelle liegt, abgeschnitten,
so dass auch die Straßen disjunkt aufgeteilt sind. Einzig die Knoten auf dem Gitter sind in
den jeweiligen Graphen mehrmals vorhanden. Im Folgenden unterscheiden wir zwischen
Partitionierungsarten, die abhänig oder unabhängig von der gesuchten Route sind. Eine
unabhängige Partitionierung kann für verschiedene Routensuchen genutzt werden, nur
die Wahl der relevanten Partitionen wäre unterschiedlich.
Ein Beispiel für eine unabhängige Partitionierung ist in Abbildung 3.1(a) dargestellt.
Die Gitterlinien sind horizontal und vertikal angeordnet. Alle geographischen Daten liegen innerhalb der schwarzen Umrandung. Zusätzlich ist ein Weg von einem Punkt s zu
einem Punkt t eingezeichnet. In 3.1(b) ist eine abhängige Partitionierung dargestellt.
Die Gitterlinien sind parallel und orthogonal zur Luftlinie zwischen Start- und Endpunkt ausgerichtet. Der Start- und Endpunkt befinden sich im Zentrum einer Partition.
19
KAPITEL 3. KONZEPTE
20
t
s
t
s
(a) Eine unabhängige Partitionierung
(b) Eine abhängige Partitionierung
Abbildung 3.1: Zwei Partitionierungsarten
Dadurch soll verhindert werden, dass Straßen nicht vollends berücksichtigt werden können, falls bei der unabhängigen Partitionierung der Start- oder Endpunkt der gesuchten
Route am Rand zu einer nicht weiter betrachteten Partition liegt. Das soll die Wahrscheinlichkeit erhöhen, dass Straßen, die zu dem optimalen kürzesten Weg gehören, nicht
an einem Rand abgeschnitten werden und nicht weiter untersucht werden können. Durch
die Ausrichtung entlang der Luftlinie, kann sich die Anzahl der relevanten Partitionen
verringern, vergleiche Abbildung 3.1. Wir nehmen an, dass eine Vorgabe existiert, wie
breit und wie lang eine Gitterzelle sein soll.
Das abhängige Gitter soll also in zwei Kriterien von der gesuchten Route abghängig sein.
• Start- und Zielpunkt sollen mittig in ihrer Partition liegen
• Die Gitterlinien sollen parallel, beziehungsweise orthogonal zur Luftlinie ausgerichtet sein.
Anders als bei der unabhängien Partitionierung muss die vorgegebene Breite oder Höhe,
die eine Partitionsgitterzelle besitzen soll, wahrscheinlich noch angepasst werden, damit
die Partitionierung dem ersten Kriterium gerecht wird. Die Vorgaben der Länge und
Höhe werden dabei als Mindestlänge und -höhe interpretiert und werden gegebenenfalls
noch angepasst. Um das zweite Kriterium zu erfüllen, müssen nun die Partitionen in
Richtung der Luftlinie vom Startpunkt aus rotiert werden.
KAPITEL 3. KONZEPTE
21
Beispiel zur Erstellung einer abhängigen Partitionierung
Gegeben sei der Startpunkt s bei (0,0) und ein Endpunkt t bei (2.5,5). Die Höhe und
Breite einer Gitterpartition soll 1 betragen. Eine unabhängige Partitionierung sähe wie
im Bild von Abbildung 3.2(a) aus.
Um aus ihr eine abhängige Partitionierung zu machen, wird zuerst beispielsweise die
Höhe einer Partition neu angepasst. Der Abstand zwischen s und t beträgt 5,59. Das
bedeutet, dass bei der Höhe von 1 nach sechs Partitionen der Zielpunkt erreicht wird.
Damit beide Punkte jeweils mittig liegen, wird der Nachkommateil der Differenz auf die
Partitionshöhe von fünf Partitionen aufgeteilt. Das macht 1+(0,59/5) also etwa 1,18.
Nachdem man das Gitter um die Hälfte der neuen Höhe und der Breite verschoben hat,
erhält man eine Partitionierung, wie sie im Bild 3.2(b) dargestellt ist.
Nun muss noch das gesammte Gitter um den Winkel, der zwischen der Y-Achse und
der Luftlinie liegt, im Startpunkt gedreht werden, in diesem Beispiel sind das 26,565◦ . In
diesem Beispiel wurde die Y-Achse genommen, da der Startpunkt im Ursprung liegt und
so um den kleinsten Winkel gedreht werden konnte. Die fertige abhängige Partitionierung
ist in Bild 3.2(c) zu sehen.
t
s
(a) Erster Schritt
t
s
(b) Zweiter Schritt
t
s
(c) Dritter Schritt
Abbildung 3.2: Beispiel zur Erstellung einer abhängigen Partitionierung
3.2
Ablauf der partitionierten Routenberechnung
Ziel einer partitionierten Routenberechnung ist es, den kürzesten Weg von einem Startpunkt in einer Partition zu einem Endpunkt in einer anderen Partition über beliebig
viele Partitionen zu finden. Es ergibt sich grob der folgende Programmablauf aus Algorithmus 5.
KAPITEL 3. KONZEPTE
22
Algorithm 5
1: procedure PartitionStreetRouting()
2:
Erstelle Partitionen und
3:
Bestimme Start- und Zielpartition
. benötigt Start- und Zielpunkt
4:
Beginne in der Startpartition
5:
while aktuelle Partition ist nicht die Zielpartition do
6:
Baue den Graph auf
. benötigt Partitionspolygon
7:
Ermittle den Rand, zu dem geroutet werden soll
8:
Markiere den oder die Zielknoten nach gewählter Heuristik
9:
Suche einen Weg vom aktuellen Startknoten aus
10:
if Kein weiterer Rand erreichbar then
. siehe Algorithmus 7
11:
Breche Routensuche komplett ab
12:
end if
13:
Setze neuen Startknoten auf Zielknoten dieser Route
14:
Verarbeite das Routingergebnis
15:
Verwerfe den Graphen
16:
Wechsel in die nächste Partition
17:
end while
18:
Baue Graph auf und markiere Zielknoten
19:
Finde Route zum Endpunkt
20:
Gib das Ergebnis der Gesamroute zurück
21: end procedure
KAPITEL 3. KONZEPTE
23
Ein Graph wird dabei für eine Partition höchstens ein Mal aufgebaut, man kann also nicht zweimal die gleiche Partition untersuchen. In den nachfolgenden Abschnitten
werden ein Großteil der Zeilen erläutert. In Abschnitt 3.2 werden zwei Partitionierungsarten vorgestellt. Die while-Schleife und was genau mit Heuristik gemeint ist, wird in
Abschnitt 3.3 geklärt.
3.3
Routensuche
Nachdem die Art der Partitionierung gewählt wurde, kann man mit der Routensuche
in der Partition mit dem Startpunkt beginnen.Die nachfolgenden Abschnitten beziehen
sich auf die Schleife in Algorithmus 6.
Der Zwischenzielpunkt der Routensuche in einer Partition ist ein Randpunkt zu einer
anderen Partition. Dieser Rand wird vor jeder Routensuche durch die geringste Distanz
eines Randes zum Zielpunkt bestimmt.
3.3.1
Heuristiken zur Findung von Zielknoten
Zu Beginn der Routensuche in einer Partition hat man genau einen Startknoten. Das ist
entweder der Startpunkt der gesuchten Route in der ersten Partition oder ein Knoten,
der die gleiche geographische Lage besitzt, wie der Zielknoten, der in der vorherigen
Partition berechnet wurde. Den Zielknoten auf dem Zielrand einer Partition kann man
über folgende Heuristiken bestimmen.
a) Kürzeste Distanz zum Start: Der Randknoten mit dem berechneten kürzesten Weg
zum Startpunkt wird ausgewählt
b) Kürzeste Distanz zum Ziel: Der Randknoten mit dem geschätztesten geringsten
Abstand zum Zielpunkt wird ausgewählt
c) Gewichtung der Straßenart: Ein Randknoten einer Autobahn wird dem einer Landstraße vorgezogen
Wird die Heuristik a) gewählt, hat man eine Menge von Zielknoten, nämlich alle die auf
dem Rand zur nächsten relevanten Partition liegen.
Verwendet man b), kann man vor dem Starten der Routensuche den Knoten auf dem
Zielrand berechnen, der den geringsten Abstand zum Zielknoten der gesamten Route
besitzt, und nur diesen als Ziel markieren. Man sollte jedoch sicher stellen, dass dieser
Knoten auch erreichbar ist und man nicht die Spur einer Autobahn wählt, die in die
KAPITEL 3. KONZEPTE
24
Partition hineinführt. Denn dann kann dieser Zielknoten nicht erreicht werden und es
werden solange Kanten relaxiert, bis zu allen erreichbaren Knoten ein kürzester Weg
bestimmt worden ist. Jedoch kann man dies nicht nur über das Ausschließen von bestimmten Einbahnstraßen lösen und diese Gefahr besteht immer bei einer ungeschickten
Partitionierung.
Die Heuristik c), die die Art der Straße berücksichtigt, muss entweder mit a) oder b)
kombiniert werden. Im Falle von a) werden zum Beispiel nur Autobahnknoten und von
b) nur der Autobahnknoten mit dem geringsten Abstand zum Ziel markiert.
3.3.2
Metriken zur Gewichtung der Kanten
Zur Gewichtung der Kanten haben wir zwei verschiedene Metriken gewählt.
i) Die kürzeste Route: Das Maß ist die Länge des LineStrings einer Kante
ii) Die schnellste Route: Das Maß ist die Länge des LineStrings ÷ Durchschnittsgeschwindigkeit, bestimmt durch die Art der Straße
In der Tabelle 3.1 sind die highwaytags, ihre Bedeutung und die Wahl der Durchschnittsgeschwindigkeit dieser Straßenart in der Reihenfolge notiert, in der sie von der Heuristik
c) bevorzugt werden. Die Geschwindigkeiten entsprechen denen von OpenRouteService1 ,
ein Webservice, das eine Routensuche mit OSM-Daten anbietet.
3.3.3
Vorgehen beim Misserfolg
Stellt man während des Routings fest, dass man in einer Partition nicht den Zielknoten
oder Zielgrenzknoten erreichen kann, muss die Auswahl des Zielrandes korrigiert werden.
Wir gehen aber davon aus, dass man die Partitionen so groß wählt und das Straßennetz
von Deutschland so ausgebaut ist, dass man von einem Rand einen Weg zu jedem anderen Rand finden kann. Erreicht man trotzdem keine anderen Randknoten, weil man
zum Beispiel durch eine ungeschickte Partitionierung auf einer Halbinsel angekommen
ist, wird die Routensuche ohne Ergebnis abgebrochen. Auch falls man bereits in der
Endpartition angelangt ist und man den Zielpunkt nicht erreichen kann, wird abgebrochen, da kein Partitionsgraph mehrfach berechnet werden soll. Dieses Vorgehen ist im
Algorithmus 6 dargestellt.
Das Rand erreichbar aus Zeile 7 soll einerseits ausdrücken, dass es einen Knoten auf diesem Rand gibt, der bei der Routensuche erreicht wurde, als auch dass man die Partition
1
http://www.openrouteservice.org
KAPITEL 3. KONZEPTE
25
Highway-Tag
Bedeutung
Ø Geschwindigkeit (km/h)
motorway
Autobahn
110
motorway link
Autobahnauffahrt
90
trunk
Kraftfahrstraße
90
trunk link
Kraftfahrstraßenauffahrt
70
primary
Bundesstraße
70
primary link
Bundesstraßenauffahrt
60
secondary
Landesstraße
60
tertiary
Kreisstraße
55
unclassified
Nebenstraße
50
residential
Straße in einem Wohngebiet
40
service
Zugangsweg
30
living street
Wohn- oder Spielstraße
10
track
Feld- oder Waldweg
10
Tabelle 3.1: Die Straßenarten und ihre Gewichtungen
noch untersuchen darf. Wurde der Zielrand nicht erreicht gibt es somit nur noch zwei
mögliche Ränder, da man auch nicht mehr die Partition untersuchen kann, aus der man
in diese Partition gelangt ist.
KAPITEL 3. KONZEPTE
26
Algorithm 6
1: function Kein weiterer Rand erreichbar()
2:
if mindestens ein Knoten auf dem Zielrand erreichbar then
3:
Bestimme Zielknoten
4:
return false;
5:
else
6:
for all Ränder dieser Partition do
. sortiert nach Entfernung zum Ziel
7:
if Rand erreichbar then
8:
Bestimme Zielknoten
9:
return false;
10:
end if
11:
end for
12:
Bestimme Teilergebnis der Routensuche
13:
return true;
. Suche muss endgültig abgebrochen werden
14:
end if
15: end function
3.4
Komposition der Routen
Die Ergebnisse der Routensuche in einer Partition bestehen aus dem berechneten Weg
und dem Gewicht dieses Kantenzugs. Um Speicherplatz zu sparen wird lediglich der
LineString gespeichert, der aus den Kanten des kürzesten Weges bestimmt wird, sowie
das Gewicht. Dadurch gehen zwar Informationen verloren, die in dieser Arbeit aber
auch keine Rolle spielen, wie eine detailierte Beschreibung des Weges, beispielsweise
”nach zwanzig Kilometern auf der A2, rechts abbiegen“. Die Gewichte der einzelnen
Partitionen auf dem Gesamtweg werden aufsummiert und die Linestring-Geometrien
vereinigt.
Kapitel 4
Implementierung
Dieses Kapitel beschäftigt sich mit der Implementierung des Graphaufbaus, verschiedenen Implementierungsdetails, einer Beschreibung der Klassen und einer Beschreibung
der Benutzerschnittstelle.
4.1
Anforderungen
Das Programm soll einen Weg durch verschiedene Partitionen berechnen können. Dabei
sollen die Faktoren Partitionsgröße, Metrik zur Gewichtung der Kanten, Heuristik zur
Auswahl der Randknoten und Wahl des Kürzeste-Wege-Algorithmus, entweder Dijkstra
oder A*, beliebig kombinierbar sein. Da das alleinige Gesamtgewicht der Route wenig
Aussagekraft hat, soll die Route noch visuell dargestellt werden können. Dies geschieht
durch die Umsetzung als PlugIn für das Open-Source-Programm OpenJUMP 1 . Der Benutzer soll Ziel- und Endpunkt entweder als WGS84-Koordinaten oder als Adressen
angeben können. Da die Straßendaten partitioniert werden sollen, um den Arbeitsspeicher zu entlasten, sollte der Speicherplatz des Graphen, der in einer Partition berechnet
wurde, wieder freigegeben werden, wenn man eine andere Partition betrachtet.
4.2
OpenJUMP und seine Schnittstelle
OpenJUMP ist ein Open-Source-Programm, das in Java geschrieben ist und unter der
GNU-Lizenz steht. Es ist ein geographisches Informationssystem und bietet die Anzeige und Bearbeitung von Geometrien. Die Geometrien können zum Beispiel aus einer
1
http://www.openjump.org/
27
KAPITEL 4. IMPLEMENTIERUNG
28
PostGIS-Datenbank geladen und in Features, die aus einer Geometrie und beliebig vielen anderen Attributen bestehen, überführt werden. Die Geometrien werden mit Datentypen von der Java Topology Suite, kurz JTS, dargestellt, ein Framework, das das
Simple Feature Format unterstützt. Diese Features können in der Workbench dargestellt
werden, dem Arbeitsbereich in OpenJUMP. Die Features befinden sich in Layern, die
in ihrer Darstellung in Farbe, Transparenz oder Linienbreite angepasst werden können.
Außer Geometrien, können auch Bilder dargestellt werden und auch Karten, zum Beispiel mittels einem sogenannten Web-Map-Service, kurz WMS. In Abbildung 4.1 ist die
Oberfläche mit einem Ausschnitt eines WMS-Layers von Deutschland dargestellt.
Abbildung 4.1: Die OpenJump-Oberfläche
OpenJUMP unterstützt das PlugIn Framework. Damit OpenJUMP ein PlugIn benutzen kann, muss es sich entweder im Extension-Ordner, der von OpenJUMP spezifiziert
wird, befinden oder man teilt OpenJUMP über eine workbench-property.xml -Datei mit,
an welchem Ort im Dateisystem sich ein zu ladenes PlugIn befindet. Ein PlugIn bekommt dann ein PluginContext übergeben, über das es auf die Workbench zugreifen
und neue Layer oder Menüpunkte anlegen kann. Das Programm wird also als PlugIn für
OpenJUMP realisiert.
KAPITEL 4. IMPLEMENTIERUNG
29
Abbildung 4.2: Das Einstellungsfenster
4.3
Beschreibung der Benutzerschnittstelle
Wurde das PlugIn in OpenJUMP geladen, erscheint ein Menue-Punkt, der PRS lautet.
Betätigt man diesen kann man das Partitionierte Routensuche-PlugIn auswählen. Es
erscheint ein Fenster in dem man verschiedene Einstellungen bezüglich der Routensuche
treffen kann. In Abbildung 4.2 ist dieses Fenster zu sehen. Man kann zwischen folgenden
Einstellungen wählen.
Start/Ziel Es kann gewählt werden zwischen der Eingabe des Start- und Zielpunktes
als Adresse, bestehend aus Postleitzahl und Straßenname oder als Koordinaten im
dezimalen Gradsystem. In das jeweils linke Textfeld wird die Postleitzahl oder der
Breitengrad angegeben.
Routingalgorithmus Man kann zwischen Dijkstra und A* als Algorithmus für die
Routensuche wählen.
Heuristik Es kann zwischen den vier Heuristiken eine ausgewählt werden, die der kürzesten Distanz zum Startknoten, zum Zielknoten oder jeweils unter Berücksichti-
KAPITEL 4. IMPLEMENTIERUNG
30
gung der Straßenart.
Metrik Hier muss man die Entscheidung treffen, ob man die kürzeste oder die schnellste
Route suchen möchte.
Partitionierung In diesem Bereich müssen die Höhe und die Breite einer Gitterzelle gesetzt werden. Wird als Einteilung ausgewählt, wird die Fläche Deutschlands in ein
Gitter der Form Breite mal Höhe aufgeteilt. Wird in km gewählt werden die Angaben im Textfeld als Breite- und Längeangaben einer Partition in km interpretiert.
Zudem kann zwischen einer abhängigen und einer unabhängigen Partitionierung
gewählt werden.
Damit die Verbindung zu einer PostGIS-Datenbank hergestellt werden kann, muss sich
im OpenJUMP-Ordner einer Datei namens db.con befinden. In ihr müssen folgende
Verbindungseinstellungen befinden, wobei es nicht auf die Reihenfolge ankommt: Host,
Port, Datenbank, Benutzer, Passwort.
host:<Host>
port:<Port>
database:<Datenbank>
user:<Benutzer>
password:<Passwort>
Bestätigt man die Eingabe, wierden die Partitionen aufgebaut. Liegen bei der abhängigen Partitionierung die eingegebenen Punkte zu nah beieinander, sodass nicht beide
mittig in einer Partition liegen können, wird der Benutzer aufgefordert die Partitionsgrößen anzupassen. Ansonsten beginnt die Routensuche und es werden erst die Startund Zielpunkte angezeigt. Für jede Partition erscheint das Partitionspolygon und nach
dem Aufbau des Graphen und der Routensuche wird der Ergebnis-LineString dargestellt.
Dahinter wird ein WMS-Layer aus OSM-Daten2 angezeigt.
Am Ende erscheint ein Fenster, in dem die Länge des kürzesten Weges oder die Information, dass abgebrochen werden musste, steht. Nach der Berechnung einer Route kann man
das PlugIn erneut starten und die neue Route wird gezeichnet, während die bisherige
Route in der Workbench verbleibt. Man kann die Berechnung des PlugIns auch abbrechen, allerdings sollte dann vor einem erneuten Durchlauf, OpenJUMP neu gestartet
werden.
2
http://ows.terrestris.de/dienste.html#openstreetmap-deutschland
KAPITEL 4. IMPLEMENTIERUNG
4.4
31
Finden von Koordinaten über Adressangabe
Da sowieso nicht alle Straßennummern in den OSM-Daten verzeichnet sind, werden
Koordinaten nur über eine Postleitzahl und eine Straße gesucht.
Es gibt zwei Modellierungsmöglichkeiten für Postleitzahlen. Entweder werden OSMNodes mit Schlüssel-Wert-Paaren der Form addr:postcode und addr:street angelegt oder
es gibt Polygone, die über eine Relation mit einer Postleitzahl verknüpft sind.
Diese Polygone sind weit verbreitet, decken aber nicht ganz Deutschland ab. Es wird
zuerst geprüft, ob es ein passendes Polygon zu der gegebenen Postleitzahl gibt. Falls
kein Polygon existiert werden alle Edges und Nodes aus den OSM-Daten überprüft, ob sie
einen Tag-key besitzten mit addr:postcode und der value gleich der gesuchten Postleitzahl
ist. Um diese Geometrien wird eine BoundingBox gelegt und als Polygonersatz genutzt.
Wurde ein Polygon oder eine BoundingBox gefunden, wird in dieser Geometrie nach
der Straße mit dem angegebenen Straßennamen gesucht und die Node Id und PointGeometrie, eines Knotens zurückgegeben. Dies führt die Funktion getNodeIdForAdress(text,text)
aus, die im Anhang B.2 zu finden ist.
4.5
Umgang mit den WGS84-Koordinaten
Die OSM-Daten liegen in zweidimensionalen Koordinaten bezogen auf den WGS84Referenzellipsoiden in dezimaler Schreibweise vor. Das bedeutet unter anderem, dass die
Entfernung zwischen zwei beliebigen Koordinaten nicht über den euklidischen Abstand
der Ebene bestimmt werden kann. Zumindest nicht, wenn man den realen Abstand zweier Punkte auf der Erde in beispielsweise Kilometern wissen möchte. Dazu müsste man
den kürzesten Weg auf einer Kugel oder besser noch auf dem WGS84-Referenzellipsoiden
bestimmen.
PostGIS hat für geographische Daten und Geometrien mit geographischen Koordinaten
ab der Version 1.5 einen eigenen Typen, Geography, der bestimmte Geometrieberechnungen auf dem WGS84-Referenzellipsoiden anbietet. So kann die Distanz zwischen zwei
Koordinaten mit ST Distance(Geography,Geography) bestimmt werden und auch die
Länge eines LineStrings kann mit ST Length(Geography) in Metern ausgedrückt werden.
Die Linestrings liegen zwar als Geometry vor, können jedoch automatisch zu Geography
gecastet werden, wenn eindeutig ist, dass eine Geography benötigt wird.
Den Abstand zwischen zwei Koordinaten kann man mit dem Verfahren nach Andoyer aus
[Mee91], Seite 81, bestimmen. Alternativ, da sowieso nur Distanzen innerhalb Deutschlands bestimmt werden sollen, könnte auf trigonometrische, spärischen Berechnungen
verzichtet und angenommen werden, dass die Krümmung der Erde keine Rolle spielt. Es
KAPITEL 4. IMPLEMENTIERUNG
32
wird die Abstandsberechnung der Ebene modifiziert und um die durchschnittliche Distanz in Kilometern zwischen zwei WGS84-Koordinaten mit dem Abstand einer Längenund einer Breiteneinheit in dem Weltkoordinatensystem, erweitert. Der 1◦ -BreitenkreisAbstand variert jedoch stark innerhalb Deutschlands, so dass es nicht sehr genau ist, die
Abweichung beträgt zum Beispiel sechs Kilometer bei der Distanz zwischen Hamburg
und Dresden.
Der Durchschnittsabstand von 70,5 km für eine Längeneinheit wird dennoch benutzt,
um eine gegebene Partitionslänge von km in Gradabständen umzurechnen. Für die Bestimmung der Distanz zum Startpunkt, sei es für die Heuristik b) zum Finden eines
Randzielknotens oder die Schätzfunktion beim A*-Algorithmus, benutzen wir das Verfahren nach Andoyer. Die Länge eines LineStrings wird mit PostGIS bei der Erstellung,
beziehungsweise während des Clippings berechnet.
4.6
4.6.1
Aufbau des Graphen
Ermittlung von Kanten aus den OSM-Daten
Nach dem Import der OSM-Daten in die PostGIS-Datenbank legen wir eine Tabelle an,
in der die Straßen in Kanten aufgeteilt sind.
EDGES( Edge Id, Linestring, Length, Node Id1 → NODES,
Node Id2 → NODES, Oneway, Highway)
Die Funktion, die diese Tabelle erstellt, buildEdges() ist im Anhang B.1 zu finden. Zu
der LineString-Geometrie wird ein räumlicher Index angelgt. Dabei werden nur Kanten
übernommen, die zu Straßen gehören, auf denen ein vierrädiges Fahrzeug fahren kann,
siehe Abschnitt 3.3.2, für eine Beschreibung der hierfür notwendigen Tags. Sackgassen
werden nicht in den Graph mit aufgenommen oder Teile einer Straße, die nur einen Wendekreis bilden - unter der Annahme, dass die Straßen ‘‘vernünftig’’ modelliert wurden,
das heißt, dass Straßen nicht unnötig durch viele kleine LineStrings modelliert werden,
wenn auch einer genügen würde und dass kreuzende Straßen sich auch in dem selben
OSM-Node treffen und es nicht verschiedene OSM-Nodes mit gleichen Koordinaten sind.
Die Straßenart wird im Attribut Highway vermerkt und ob die Straße eine Einbahnstraße ist, im Attribut Oneway. Um eine Straße als Einbahnstraße zu klassifizieren, benötigt man den Tagschlüssel oneway, nur nicht bei Autobahnen, die auch ohne Tag als
Einbahnstraßen betrachtet werden. Der Tagschlüssel ist dann yes, true, 1 oder -1. Die
Einbahnstraße verläuft dann in die Richtung, in die der LineString erstellt wurde. Es sei
denn, der Tagwert ist -1, dann ist die Richtung umgekehrt zu der des LineStrings.
KAPITEL 4. IMPLEMENTIERUNG
33
In Abbildung 4.3 ist dargestellt, wie die OSM-Ways und -Nodes in den Graphen übernommen werden. Es werden nur Kreuzungen übernommen, nicht aber die OSM-Nodes,
durch die schwarzen Punkte dargestellt, die nur für die Darstellung des Linienzuges benötigt werden. Die magenta-farbene Straße wird nur bis zum Knoten c übernommen,
die Sackgasse jedoch nicht.
a
b
c
Abbildung 4.3: Transformation des OSM-Graphen
4.6.2
Clipping der Kanten in einer Partition
Um zu entscheiden welche Geometrien in welcher Partition liegen, wird jeder Partition
ein Polygon zugeordnet, das die Partitionsfläche beschreibt.
Die LineStrings von Kanten in der Datenbank, die in einer fraglichen Partition beginnen
und auch enden, werden komplett in den Graphen übernommen. Die anderen LineStrings,
die das Polygon nur kreuzen, also nur zum Teil innerhalb des Polygons liegen, werden,
sofern sie aus der Partition heraus und nicht wieder zurück führen, am Rand der Partition abgeschnitten (geclippt). Beginnt eine Kante in der Partition, aber endet dort
nicht, dann wird sie am ersten Schnittpunkt mit dem Rand abgeschnitten. Endet sie nur
in einer Partition, wird der LineString bis auf den Teil vom Start- zum ersten Schnittpunkt übernommen. Dadurch werden Kanten, die sich um den Rand zweier Partitionen
schlängeln, disjunkt und eindeutig aufgeteilt.
Das ist in Abbildung 4.4 graphisch dargestellt, wobei die Pfeile die Richtung andeuten,
in die der LineString modelliert ist. Der blaue Teil eines LineStrings wird in der unteren Partition aufgenommen, die roten nicht. Führt eine Kante durch drei Partitionen,
was selten passiert, aber wenn, dann an einer Ecke vorkommt, muss sie in der mittleren
Partition so gekürzt werden, dass die Vereinigung der drei Teil-LineStrings wieder den
Gesammten ergibt. Um dies zu erreichen müssen die vier Kombinatinen von Startpunkt
liegt/liegt nicht und Endpunkt liegt/liegt nicht in der gegebenen Partition untersucht
werden. Das wird von den zwei PL/pgSQL-Funktionen getEdges(Geometry) und getBorderEdges(Geometry) erledigt, die ebenfalls im Anhang B.3 zu finden sind.
KAPITEL 4. IMPLEMENTIERUNG
Partitionsgrenze
c
a
b
34
d
e
f
g
h
Abbildung 4.4: Clipping an der Partitionsgrenze
4.7
Klassenbeschreibung
Im Folgenden werden die implementierten Klassen kurz erläutert und wie sie zusammenhängen. Die Architektur wurde an das MVC-Muster angelehnt. Allerdings gibt es
wenig Interaktion mit dem Benutzer, wodurch es es viele Models, aber wenige Views
und Controller gibt. Alle Klassen, die mit den Knoten des Graphen arbeiten, haben eine
tidyUp()-Methode, die dafür sorgt, dass nicht mehr auf diese Knoten referenziert wird,
wodurch sie von der GarbageCollection von Java gelöscht werden können. Reine Getund Set-Methoden werden nicht weiter erklärt, es werden nur die Attribute erläutert.
Welche Get- und Set-Methoden eine Klasse anbietet, kann man den Klassendiagrammen
entnehmen.
Das Package Graph
In diesem Package befinden sich die drei Klassen Vertex, Edge und Graph, die zusammen
einen Graph darstellen. Sie implementieren die Methoden, die in Kapitel 2 vorgestellt
worden sind, sowie Methoden, die zur Verwaltung der Relationen zwischen Knoten und
Kanten benötigt werden, aber nur Package-weit sichtbar sind. In Abbildung 4.5 sind die
Klassen diese Packages als Klassendiagramm dargestellt.
Graph
Die Klasse Graph wurde als Singleton implementiert. Das bedeutet, dass es nur eine
Instanz dieser Klasse gibt. Zusätzlich zu den Knoten- und Kanten-Mengen wurde eine
Hashtabelle erstellt, die eine rasche Zuordnung von einer Vertex-Id zu ihrem Vertex
ermöglicht.
• getInstance():Graph - Liefert die Instanz des Graphen
KAPITEL 4. IMPLEMENTIERUNG
35
• tidyUp() - leert die Knoten- und Kanten-Menge und sorgt dafür, dass auch zwischen den Knoten und Kanten untereinander keine Referenzen mehr bestehen
Edge
Jede Edge, besitzt eine Referenz auf die beiden Vertices, start und end, die sie verbindet.
Die Methoden zum Setzen dieser Vertices ist nicht öffentlich, sondern nur Package-weit
zugänglich. Straßen, die keine Einbahnstraßen sind, werden nur durch eine Kante dargestellt, daher wird in einem boolean oneway gespeichert, ob es sich um eine Einbahnstraße
handelt. Die Art der Straße wird über ein Enum Highway vermerkt, das im Abschnitt
über die Model -Klassen erläutert wird. Noch nicht genannte Methoden sind folgende.
• Edge(String id, LineString line, boolean oneway) - Erstellt eine neue Kante, die,
falls oneway true ist, eine Einbahnstraße darstellt, mit der übergebenen id und
dem PostGIS-LineString line
• Edge(String id, LineString line) - Erstellt eine Kante, die keine Einbahnstraße ist
Vertex
Jeder Vertex hat eine Liste von ein- (in) und ausgehenden (out) Edges. Die Methoden, die
diese Listen verändern können nicht von außerhalb dieses Packages aufgerufen werden.
Die Klasse Vertex implementiert zusätzlich das Interface Comparable<Vertex>, wodurch
eine natürliche Ordnung der Knoten festgelegt wird, nämlich die Länge des kürzesten
Weges zu einem Startknoten.
• Vertex(String id, Point p) - erstellt einen neuen Knoten mit der übergebenen id
und dem PostGIS-Point p
Die Geometrien der Knoten- und Kanten werden jeweils als PostGIS-Geometrie abgelegt.
Um die Geometrien in OpenJUMP darstellen zu können, müssen sie erst noch JTSGeometrien transformiert werden. Da dies aber nicht für alle Kanten notwendig ist,
wird dies nur bei den Kanten auf dem kürzesten berechneten Weg vorgenommen.
Das Package Partition
Die Klassen in diesem Package dienen der Darstellung einer Partition. Die Klassen sind
auch in Abbildung 4.6 dargestellt.
KAPITEL 4. IMPLEMENTIERUNG
Abbildung 4.5: Die Klassen des Graph-Packages
36
KAPITEL 4. IMPLEMENTIERUNG
37
Partition
Diese Klasse besitzt ein JTS-Polygon, das ihre Fläche beschreibt, eine Liste von PostGISLineStrings, die den Weg in dieser Partition wiedergeben und einen double-Wert für de
Länge des kürzesten Weges. Desweiteren zwei boolsche Werte, die anzeigen, ob es sich um
die Partition mit dem Endpunkt der Route handelt, target und ob sie schon untersucht
wurde, used. Es wird ein JTS-Polygon verwendet, da die PostGIS-Geometrieklassen keine
Geometrie-Methoden anbieten. Mit JTS ist es leichter eine Partition zu drehen oder zu
prüfen, ob eine Koordinate auf dem Rand liegt.
• Partition(Polygon p) - erstellt eine neue Partition mit dem übergebenen Polygon
p
• buildResultLineString(Vertex v) - Baut die Route vom Startpunkt der Route, bis
zum Knoten v auf
Border
Über das Enum Border, das die Werte RIGHT, LEFT, ABOVE und BELOW annehmen
kann, ist eine Bezeichnung der Ränder möglich, sofern es sich um ein Gitter handelt.
GridPartition
Eine Spezialisierung der Partition-Klasse ist die GridPartition, in ihr wird vermerkt in
welcher Spalte i und Zeile j sich die Partition im Gitter befindet. Sie hat folgende
Methoden.
• GridPartition(int i, int j, Polygon p) - erstellt eine neue Gitterpartition mit dem
übergebenen Polygon p und der Postion (i,j ) im Gitter
• getBorders():Collection<Border> - gibt die Ränder, sortiert nach der geringsten
Distanz zum Startpunkt, zurück
• getBorder(Gridpartition other):Border - gibt den Rand zurück, an den die übergebene GridPartition other grenzt
Das Package Routingalgorithm
In diesem Package befindet sich ein Interface IRoutingAlgorithm, das von zwei Klassen
implementiert wird , Dijkstra und AStar. Die Klassen sind zusätzlich in Abbildung 4.7
KAPITEL 4. IMPLEMENTIERUNG
38
GridPartition
Partition
Partition
Object
double getResultWeigth()
void buildResultLineString(Vertex)
Polygon getPolygon()
String toString()
void setPolygon(Polygon)
boolean isTargetPartition()
void setTargetPartition(boolean)
boolean isUsed()
void setUsed(boolean)
void setPolygon(Polygon)
Collection getBorders()
int getJ()
int getI()
Border getBorder(GridPartition)
boolean equals(Object)
String toString()
Border
Enum
Border[] values()
Border valueOf(String)
Abbildung 4.6: Die Klassen des Partition-Packages
dargestellt. IRoutingAlgorithm verlangt folgende Methoden.
• routes(String startVertexId) - Startet die Routensuche mit dem Vertex, der die
startVertexId aufweist
• getreachedVertices():Collection<Vertex> - gibt die Knoten zurück, die erreichbar
waren, bis die Routensuche abgebrochen wurde
• tidyUp() - Leert die Container, in denen Vertices gespeichert waren
Im Gegensatz zur Dijkstra-Klasse implementiert AStar noch eine weitere Methode:
• setTargetPoint(Point target)- Setzt den Zielpunkt, zu dem die Distanz geschätzt
werden soll auf den PostGIS-Point target
Das Package View
In diesem Package befinden sich zwei Klassen. Sie sind in Abbildung 4.8(a) als Klassendiagramm dargestellt. Die eine ist ein spezialisiertes JPanel, die SettingsView und wird
benutzt um die Routing-Einstellungen, die zu Beginn vom PlugIn abgefragt werden, an
zu zeigen, siehe Abbildung 4.2. Sie implementiert einen KeyListener, um einen Teil von
fehlerhaften Eingaben nicht zu zu lassen.
KAPITEL 4. IMPLEMENTIERUNG
39
Abbildung 4.7: Die Klassen des RoutingAlgorithm-Package
OpenJumpView
Die andere Klasse ist die OpenJumpView, die als Singleton implementiert ist. Diese Klasse
dient dem Anzeigen des Start- und Zielpunktes, der Partitionen und den LineStrings der
errechneten Route.
• getInstance():OpenJumpView - gibt die Instanz der OpenJumpView zurück
• init(PlugInContext context, TaskMonitor monitor) - nimmt den context und monitor
entgegen
• addResultLineString(Collection<LineString> linestrings) - wandelt die linestrings
in eine JTS-Geometrie um und stellt sie in der Workbench dar
• addPolygon(Polygon p) - stellt das JTS-Polygon p in der Workbench dar
• setPoints(Point start, Point end) - stellt start und end in der Workbench dar
• setMonitorText(String text) - setzt die Anzeige des Monitors auf text
Das Package Controller
Die einzige Klasse in diesem Package dient dazu, die Angaben, die im SettingsPanel
getroffen wurden, an die verschiedenen Models weiterzugeben. Der SettingsController ist
KAPITEL 4. IMPLEMENTIERUNG
(a) View
40
(b) Controller
Abbildung 4.8: Die Klassen des Controller- und View-Packages
in Abbildung 4.8(b) dargestellt un nutzt dafür folgende Methoden.
• SettingsController(SettingsView settings) - erstellt einen neuen SettingsController
mit den Komponenten der übergebenen SettingsView settings
• store():boolean - übergibt den verschiedenene Models die Einstellungen, die getroffen wurden und gibt true zurück, falls keine fehlerhaften Eingaben getroffen
wurden.
Das Package Connectors
In diesem Package befinden sich Klassen, die in irgendeiner Art und Weise Verbindung
zu anderen Systemen als unsere Programm aufbauen oder selbst von anderen Systemen
benutzt werden. Ihre Klassen sind auch in Abbildung 4.9 dargestellt.
PSR OJ Plugin
Diese Klasse ist das PSR OJ Plugin, das ThreadedPlugIn implementiert, ein Interface,
das von OpenJUMP bereitgestellt wird und benutzt werden kann, falls nicht absehbar
ist, wie lange eine Aufgabe dauert. Bei einem normalen PlugIn friert die Workbench
ein, während ein PlugIn beim ThreadedPlugin die Workbench verändern kann, allerdings
kann der Benutzer nicht an der Workbench arbeiten, sondern höchstens das Plugin abbrechen. Außerdem wird ein kleines Fenster, ein TaskMonitor, geöffnet, in dem kurze
Textnachrichten erscheinen können.
• initalisize(PlugInContext context) - Fügt dem Menue einen neuen Punkt hinzu,
über das das PlugIn gestartet werden kann
• execute(PlugInContext context) - Öffnet ein Fenster, in dem die SettingsView angezeigt wird und beendet das Plugin, falls die Eingaben fehlerhaft sind
KAPITEL 4. IMPLEMENTIERUNG
41
• run(TaskMonitor monitor, PlugInContext context) - Gibt den context und den monitor an die OpenJumpView weiter und startet die Routensuche
PSR Extension
Die Klasse PRS Extension, sorgt lediglich dafür, dass das Plugin geladen wird, falls es im
Extension-Ordner von OpenJump liegt. Dazu muss es von Extension, welches ebenfalls
von OpenJUMP bereitgestellt wird, erben und folgende Methode überschreiben.
• configure(PlugInContext context) - erstellt ein neues PSR OJ Plugin-Objekt und
initalisiert es mit dem übergebenen context
DataBase
Die dritte Klasse in diesem Package ist die DataBase-Klasse. Sie ist als Singleton implementiert und verarbeitet die Anfragen an die PostGIS-Datenbank. Es werden zwei
Arten von Anfragen gestellt, die zum Finden von Start und Endpunkt am Anfang und
die zum Aufbau des Graphen, die mehrmals pro Routensuche benötigt wird.
• getInstance():DataBase - gibt die DataBase-Instanz zurück
• buildEdges(Polygon p) -stellt die Anfrage an die PostGIS-Datenbank und gibt die
Ergebnisse dieser Anfrage weiter, siehe die Beschreibung zum GraphModel
• findVertexWithAddress(String postcode, String street, boolean startPoint) - startet
die Anfrage getPointForAddress(postcode, street) und falls startpoint true ist, wird
das Ergebnis als Startpunkt gespeichert, sonst als Endpunkt der Routensuche
• findVertexWithPoint(String lat, String lon, boolean startPoint) - startet die Anfrage getPointForPoint(Point) mit einem PostGIS-Point, der aus lat und lon erstellt
wurde
Das Package Model
In diesem Package befinden sich eine Reihe von Klassen, die das Haupt-Model, das RoutingModel, unterstützen, in dem in ihnen Funktionen ausgelagert sind. Es sind alles
Singleton-Klassen, mit Ausnahme eines Enums. Sie sind in Abbildung 4.10 zusehen.
Durch die Assoziationspfeile ist angedeutet, dass jediglich das RoutingModel alle anderen Models kennt. Nur das TargetModel kennt ebenfalls ein anderes Model.
KAPITEL 4. IMPLEMENTIERUNG
42
Abbildung 4.9: Die Klassen des Connectors-Packages
Highway
Das Highway-Enum stellt die verschiedenen Straßenarten aus Tabelle 3.1 dar. Sie besitzt
nur eine nicht-Enum-spezifische Methode.
• getWeight(Highway street):int - gibt die Durchschnittsgeschwindigkeit der Straßenart street zurück
RoutingModel
Dieser Klasse kümmert sich um die Hauptaufgabe des PlugIns, eine Route zu finden. Sie
setzt den Ablauf um, der im Algorithmus 5 zusammen mit der Funktion aus 3.3.3 beschrieben ist. Also solange in Partitionen die Routensuche durch zu führen, bis die Endpartition und in ihr der Zielpunkt der Route erreicht wurde. Im RoutingModel werden
über startPoint und endPoint die Koordinaten des Start- und Zielpunktes als PostGISPoint gehalten. Das resultWeight bezeichnet das bisher errechnete Gesamtgewicht der
Routenberechnung. Es besitzt als einziges Model eine Referenz auf einen IRoutingAlgorithm und es wird vermerkt, welche Heuristik gewählt wurde, ohne die Gewichtung
der Straßen mit ein zu beziehen, daher reicht ein Boolean targetHeuristic aus. Es bietet
folgende Methoden an.
• getInstance():RoutingModel - gibt die RoutingModel-Instanz zurück
• route():boolean - startet die partitionierte Routensuche und gibt true zurück, falls
der Endpunkt gefunden wurde
KAPITEL 4. IMPLEMENTIERUNG
43
• setTargetPointForAStar(Point p)- verändert den Punkt, zu dem geroutet werden
soll auf p, wird nur benötigt, wenn der A*-Algorithmus mit der Heuristik der
niedrigsten Distanz zum Zielpunkt gewählt wird
PartitionModel
Das PartitionModel sorgt für die Einteilung in Gitterzellen und verwaltet diese. Bei der
Erstellung vermerkt sie die Partition, in der die Route beginnt in der GridPartition startPartition.
• getInstance():PartitionModel - gibt die PartitionModel-Instanz zurück
• init(Point start, Point end) - nimmt den Startpunkt start und Zielpunkt end entgegen
• buildGrid(double width, double height, boolean absolut, boolean dependent) - erstellt das Gitter, wenn dependent true ist ein abhängiges, ansonsten eine unabhängiges; ist absolut true dann werden width und height als Gitterhöhe und -breite in
km interpretiert, sonst wird ein Gitter der Form width mal height erstellt
• getNextPartition(GridPartition partition, Border next):GridPartition - gibt die GridPartition zurück die an den Rand next von partition angrenzt
GraphModel
Das GraphModel ist für den Aufbau des Graphen zuständig. Es wird auch vermerkt
welche Knoten in der aktuellen Partition auf welchem Rand liegen. Diese Knoten werden jeweils in einer Periphery-Liste für jeden Rand gehalten. Um zu entscheiden, ob
ein Randknoten prinzipiell erreichbar ist, muss es eine Referenz auf den Rand mit dem
Startpunkt geben, startBorder. Der Border targetBorder stellt den Zielrand der aktuellen
Routensuche dar. Das GraphModel verwaltet die Ids des Start- und Endknotens, startVertexId und endVertexId und welche Metrik gewählt wurde in der boolschen Variable
velocityMetric. Ist diese true, wird die schnellste Route gesucht. Es beinhaltet folgende
Methoden.
• getInstance():GraphModel - gibt die GraphModel-Instanz zurück
• buildGraph(Polygon p) - veranlasst die DataBase-Instanz die Anfrage nach den
Kanten in dem JTS-Polygon p vorzunehmen die Ergebnisse werden an die folgenden
Methoden übermittelt
KAPITEL 4. IMPLEMENTIERUNG
44
• addEdge(String id, LineString line, String vertexId1, String vertexId2, String oneway,
String highway, double weight) - Fügt dem Graph eine Kante mit den Übergebenen
Eigenschaften zwischen den Vertices mit den Ids vertexId1 und vertexId2
• addBorderEdge(String id, LineString line, String vertexId1, String vertexId2, String
oneway, String highway, double weight) - siehe addEdge(...), nur dass zusätzlich die
Randknoten auf die Ränder aufgeteilt werden
• isIrrelevantBorderVertex(Vertex v):boolean - gibt true zurück, falls der Knoten zu
einer Kante gehört, die zu einem anderen Rand führt als der auf dem v liegt, und
dieser nicht der Startrand ist; der Knoten kann folglich nicht erreicht werden
• getTargetPeripheryVertices():Collection<Vertex> - gibt die Knoten zurück, die auf
dem Zielrand liegen
• getPeripheryVertices(Border b):Collection<Vertex> - gibt die Knoten zurück, die
auf dem Rand b liegen
• tidyUp(): - leert die Collections mit den Rand-Vertices
Das TargetModel
Das TargetModel ist für das Markieren der Zielknoten zuständig und wählt auch nach
der Routensuche in einer Partition den Knoten aus, der der Startpunkt in der nächsten
Partition sein soll. Dafür benötigt es eine Menge von Randknoten oder den Knoten, der
den Zielpunkt der Routensuche beschreibt, falls in der Endpartition geroutet werden
soll. Diese werden ebenfals in einer Periphery-Sammlung von Knoten gehalten. Einzig
das TargetModel weiß über eine boolsche Variable withType, ob die Art der Straße in
die Bestimmung der Zielknoten mit ein bezogen werden soll. Es muss wissen, ob der
A*-Algorithmus verwendet wird, um nach dem Markieren des einen Zielknotens dessen
Punkt als neues Ziel fest zu legen, siehe die Klasse RoutingModel, Methode setPointForAStar(). Das wird über das Boolean aStar gelöst. Die Methoden des TargetModels
sind:
• getInstance():TargetModel - gibt die TargetModel-Instanz zurück
• markTarget() - wird benutzt wenn mit der Heuristik b, oder c in Kombination mit
b, nur ein Knoten als Ziel markiert wird
• markAllAsTargets(): - markiert alle Knoten auf dem Zielrand, Heuristik a, oder
nur die mit der höchsten Gewichtung, also c plus a
• getNextStartVertex():Vertex - gibt den Knoten zurück, der auf dem Zielrand erreicht wurde oder einen default-Vertex mit der Id deadend, wenn kein Knoten auf
dem Rand erreicht wurde
KAPITEL 4. IMPLEMENTIERUNG
45
GraphModel
Object
RoutingModel
Object
GraphModel getInstance()
void buildGraph(Polygon)
void addEdge(String,LineString,String,String,String,String,double)
void addBorderEdge(String,LineString,String,String,String,String,double)
boolean isIrrelevantBorderEdge(Vertex)
Collection getTargetPeripheryVertices()
Collection getPeripheryVertices(Border)
void tidyUp()
void setVelocityMetric (boolean)
void setTargetBorder(Border)
void setStartBorder(Border)
void setStartVertexId(String)
void setEndVertexId(String)
String getStartVertexId()
String getEndVertexId()
boolean isVelocityMetric ()
RoutingModel getInstance()
boolean route()
void setTargetPointForAStar(Point)
double getResultWeight()
void addResultWeight(double)
Point getEndPoint()
void setEndPoint(Point)
Point getStartPoint()
void setStartPoint (Point)
void setRoutingAlgorithm(IRoutingAlgorithm)
void setHeuristic (boolean)
PartitionModel
Object
Highway
Enum
int getWeight(Highway)
Highway[] values()
Highway valueOf(String)
PartitionModel getInstance()
void init(Point,Point)
GridPartition getNextPartition(GridPartition,Border)
GridPartition getStartPartition()
void buildGrid(double,double,boolean,boolean)
TargetModel
Object
TargetModel getInstance()
void markTarget()
void markAllAsTargets()
Vertex getNextStartVertex ()
void tidyUp()
void setPeriphery(Collection)
void setPeriphery(Vertex)
void setWithType(boolean)
void setTargetHeuristic(boolean)
void setAStar (boolean)
Abbildung 4.10: Die Klassen des Model-Packages
• tidyUp(): - leert die Collections mit den Rand-Vertices
Interaktion zwischen den Models
Im folgenden Sequenzdiagramm, Abbildung 4.11, ist der allgemeine Ablauf als Interaktion zwischen den Model-Klassen und einem IRoutingAlgorithm dargestellt, die Korrektur
des Weges ist der Übersicht halber nicht mit aufgenommen wurde. Dies ist nur eine
Schleife in der geprüft wird, ob man in die angrenzenden Partitionen gelangen kann,
falls sie noch nicht besucht worden sind. Falls in eine Partition gewechselt werden darf,
wählt das TargetModel den nächsten Startknoten aus.
Es ist stark vereinfacht und stellt nur die wichtigsten Methoden der Model-Klassen dar,
um zu zeigen, dass vom RoutingModel die Aktionen ausgehen und die anderen Models es
nur unterstützen.
4.8
Speicherverwaltung
In Java hat man nur einen indirekten Zugriff auf die Speicherverwaltung. Die GarbageCollection prüft in gewissen Abständen oder wenn der Speicher knapp wird, welche
KAPITEL 4. IMPLEMENTIERUNG
46
Objekte nicht mehr referenziert werden und gibt deren Speicherplatz frei. Das muss
aber nicht sofort geschehen. Das hat zur Folge, dass eine Partition, die ein Viertel des
Gebiets Deutschlands beinhaltet und dadurch 2 bis 2,5GB groß ist, noch Speicher verbraucht, obwohl es keine Referenzen auf die Knoten und Kanten des Graphen gibt und
der Speicher ausgeht, beim Versuch die nächste Partition zu laden. Bei etwas kleineren
Partitionen ab circa 1,8GB kann es zu Verzögerungen im Graphaufbau kommen, aber
die GarbageCollection schafft es meistens rechtzeitig genug Speicherplatz zu schaffen.
:PartitionModel
:GraphModel
bis die Endpartition erreicht ist
Vertex
:TargetModel
Abbildung 4.11: Der Ablauf des Routings als Sequenzdiagramm (vereinfacht)
Partition
getNextPartition()
getNextStartVertex()
routes(StartVertex)
markTarget()
buildGraph( Partition)
findTargetBorder()
Loop
Partition
getStartPartition()
:RoutingModel
route()
:PRS OJ Plugin
:IRoutingAlgorithm
KAPITEL 4. IMPLEMENTIERUNG
47
Kapitel 5
Experimente
Das folgende Kapitel beschreibt die ausgeführten Experimente und ihre Ergebnisse. Am
Ende werden diese in einem Fazit zusammengefasst.
5.1
Die Experimentierumgebung
Die Experimente wurden an einem fachgebietseigenen Rechner vorgenommen. Die benutzte PostgreSQL-Datenbank ist von der Version 9.1 und wurde unter dem LinuxBetriebssystem openSUSE kompiliert. Instaliert ist auch die PostGIS-Erweiterung in
der Version 1.5. Der Rechner ist mit 4 GB Arbeitsspeicher ausgestattet und hat einen
Quad-Core mit jeweils 3 Ghz. Der virtuellen Maschine wurden 2,5 GB Arbeitsspeicher
zugestanden.
5.2
Das Vorgehen
Es wurden drei Routen gewählt, an deren Beispiel die Laufzeit und die Güte der partitionierten Routenberechnung getestet werden soll. Diese Routen gehen von Hamburg
nach Dresden, von Dresden nach München und von Hannover nach München. Um die
Güte der errechneten Routen abzuschätzen, wurde zuerst die optimale Route berechnet.
Die Heuristiken aus Kapitel 3 werden wie folgt abgekürzt.
a) Kürzeste Distanz zum Start
b) Kürzeste Distanz zum Ziel
48
KAPITEL 5. EXPERIMENTE
49
Route
kürzeste in km schnellste in h
Dresden-München
435.73
4.22
Hamburg-Dresden
438.06
4.58
Hannover-München
549.32
5.82
Tabelle 5.1: Die optimalen kürzesten Routen
c.a) Kürzeste Distanz zum Start unter Berücksichtigung der Gewichtung der Straßenart
c.b) Kürzeste Distanz zum Ziel unter Berücksichtigung der Gewichtung der Straßenart
Als Abweichung der berechneten Route zur optimalen, wurde die Differenz zwischen
der Länge der kürzesten Wege von berechneter und optimaler Route ins Verhältnis zur
Länge der optimalen Route gesetzt. Beträgt die Länge des optimalen Weges 400 km und
wurde ein Weg der Länge 600 km errechnet, beträgt die Abweichung 50%.
Wenn in einer Tabelle die Anzahl der Knoten vermerkt sind, so ist dies die durchschnittliche Knotenanzahl der Partitionen, durch die die Route berechnet wurde. Spielt die Anzahl der untersuchten Knoten, also die Größe der cloud oder der closed-List, eine Rolle,
so wurde wieder der Durchschnitt über den Partitionsweg ermittelt. Um den Schnitt
nicht nach unten zu ziehen, wurden dabei nur die Partitionen betrachtet, in denen nach
mehr als fünfhundert untersuchten Knoten ein kürzester Weg gefunden wurde. Tritt der
Text k.E. in einer Tabelle auf, so heißt das, dass keine Route zum Zielpunkt gefunden
werden konnte. Wurde kein Grund dafür angegeben, so wurde in ein Teil einer Partition
geroutet, von der aus man keinen anderen Partitionsrand mehr erreichen konnte.
In den folgenden Experimenten soll geklärt werden, welche Unterschiede es zwischen
Dijkstra und A* gibt, wie die Güte und die Laufzeit von den Partitionsgrößen abhängt,
ob die abhängige Partitionierung bessere Ergebnisse erzeugt, als die unabhängige, welche
Unterschiede es zwischen den Metriken der schnellsten und der kürzesten Route gibt und
ob eine Heuristik den anderen vielleicht überlegen ist.
5.3
A* versus Dijkstra
Das erste Experiment befasst sich mit der Fragestellung, um wieviel ‘‘schneller’’ der
A*-Algorithmus im Vergleich zu Dijkstra ist und ob beide Algorithmen in den selben
Partitionen die gleiche Route finden. Dafür wurde die schnellste Route von Dresden nach
München und die kürzeste von Hamburg nach Dresden mit verschiedenen Partitionsgrößen gesucht.
Im ersten Experiment wurde die kürzeste Route zwischen Dresden und München mit
KAPITEL 5. EXPERIMENTE
50
einer abhängigen Partitionierung und der Heuristik c.b) gesucht. Es wird also genau
ein Zielknoten gesucht und deswegen errechnen Dijkstra und A* die gleichen optimalen
Routen für eine Partition und diesen Zielrandpunkt. Sie unterscheiden sich nur in der
Anzahl der untersuchten Knoten. In der folgenden Tabelle 5.2 ist der Zusammenhang
zwischen Knoten in einer Partition und untersuchten Knoten dargestellt. Dabei wurden
nur Partitionen betrachtet, in denen der Zielrandknoten auch erreicht wurde Die Anzahl
der Knoten der Route ist der Durchschnitt über die Partitionen auf der berechneten
Route.
P.Größe in km2
3 080
3 300
4 265
4 620
10 265
# Knoten # unters. Kn. (D) # unters. Kn. (A*) #Knoten der Route
50 162
34 249
8 571
259
54 909
39 954
9 155
242
72 546
49 298
16 188
312
60 028
40 835
17 316
299
172 812
109 396
25 650
321
Tabelle 5.2: Dresden-München, Vergleich Knotenanzahl Dijkstra und A*
Es gibt in der Tabelle eine Auffälligkeit. So verringert sich die durchschnittliche Anzahl
der Knoten bei dem Wechsel der Partitionsgröße von 4 265km2 auf 4 620km2 um fast
12 000, da verschiedene Wege eingeschlagen werden und die Route mit den größeren
Partitionen durch zwei ländlichere Gebiete führt, was die Anahl der Knoten insgesamt
verringert. Dadurch werden auch weniger Knoten von Dijkstra untersucht, während sich
der Größenunterschied beim A*-Algorithmus bemerkbar macht. Wird die Partition vergrößert, werden auch mehr Knoten untersucht.
Wie man in der Tabelle 5.2 und im Diagramm 5.1 sehen kann, untersucht Dijkstra
zwei bis vier Mal so viele Knoten wie Dijkstra. Wobei man bei der größten Partition
am besten sehen kann, wie viele kürzeste Wege zu nicht relevanten Knoten ermittelt
werden. Nur 0,2% der Knoten sind Bestandteil des kürzesten Weges, während es beim
A*-Algorithmus immerhin gut 1,5% sind. Das ist zwar auch nicht viel, aber dafür wurden
auch nur 15% der Gesamtknoten untersucht, statt 63% wie bei Dijkstra. Aber was sagt
das über die Geschwindigkeit und die Güte der beiden Algorithmen aus?
In Tabelle 5.3 wird gezeigt, wie lange die Routenberechnung ohne Graphaufbau gedauert
hat. Es ist zudem die Anzahl der untersuchten Knoten der beiden Algorithmen dargestellt und die Größe der Partitionen.
Dabei fällt auf, dass der A*-Algorithmus in den meisten Fällen weniger als 1% der Zeit
benötigt, die Dijkstra rechnet.
Im zweiten Experimente wurde die kürzeste Route gesucht, aber diesmal von Hamburg
nach Dresden und die Heuristik c.a) wurde benutzt, es wurden also mehrere Randknoten
KAPITEL 5. EXPERIMENTE
51
untersuchte Knoten
·105
1
Dijkstra
A*
0.8
0.6
0.4
0.2
0
0.4 0.6 0.8 1 1.2 1.4 1.6 1.8
Anzahl Knoten
·105
Abbildung 5.1: A* und Dijkstra, Vergleich untersuchter Knoten
P.Größe in km #unt. Kn. (D) Rout.Ber.(D) in s
3 080
34 249
31,23
3 300
39 954
40,07
4 265
49 298
68,89
4 620
40 835
53,61
10 265
109 396
638,01
#unt. Kn. (A*) Rout.Ber. (A*) in s
8 571
0,32
9 155
0,31
16 188
0,43
17 316
0,57
25 650
0,97
Tabelle 5.3: Dresden-München, Vergleich Routingdauer zwischen Dijkstra und A*
als Ziel markiert. Es wurde eine abhängige Partitionierung genutzt. Die Metriken und die
Partitionierung wurden verändert, um beispielhaft zu zeigen, dass diese keinen Einfluss
auf die Laufzeit und die Güte der berechneten Routen der beiden Algorithmen haben. In
der Tabelle 5.4 ist die Dauer der Routenberechnung dargestellt. Die Tabelle ist genauso
wie die Tabelle 5.3 aufgebaut.
Man kann die gleiche Beobachtung wie in der Tabelle 5.3 machen, nämlich dass der
Dijkstra-Algorithmus bei jeder Partitionsgröße mehr Zeit benötigt. Besonders auffällig
ist die Dauer bei großen Partitionen, wie der in der letzten Zeile. Insgesamt vier Stunden
und vierzig Minuten benötigt Dijkstra um durch zwei Partitionen von Hamburg nach
Dresden zu gelangen, während der A*-Algorithmus es in 73 Sekunden schafft. Das liegt
daran, dass zum Beispiel in der Zielpartition der A*-Algorithmus nur 45 000 Knoten
untersucht und dafür 3-8 Sekunden benötigt, während Dijkstra in der Zielpartition fast
300 000 Knoten untersuchen muss und dafür fast 110 Minuten braucht.
Dadurch dass mehrere Knoten als Zielknoten innerhalb einer Partition markiert werden,
können sich die kürzesten Wege unterscheiden, die von den beiden Algorithmen errechnet
KAPITEL 5. EXPERIMENTE
P.Größe in km #unt. Kn. (D) Rout.Ber.(D) in s
2 432
23 896
14,3
3 242
25 318
24,75
4 539
22 910
42,25
6 808
42 775
98,1
11 347
66 576
250,75
22 693
88 167
685,25
68 074
373 919
7 918,2
52
#unt. Kn. (A*) Rout.Ber. (A*) in s
10 354
0,675
14 758
0,89
17 728
1,13
22 517
2,26
38 898
3,85
36 446
4,35
150 492
36,5
Tabelle 5.4: Hamburg-Dresden, Vergleich Routingdauer zwischen Dijkstra und A*
werden. Dijkstra bestimmt wirklich den kürzesten Weg zu einem Randpunkt, während
der A*-Algorithmus den Randknoten zu finden versucht, der einen kurzen Weg aus vom
Start aufweist und am nächsten am Zielpunkt liegt. Tatsächlich ermittelt Dijkstra in dieser Testreihe nur einmal einen schnelleren Weg als A* bei gleichen Partitionsgrößen, siehe
Tabelle 5.5. In ihr ist dargestellt, welche Abweichungen die Routen der beiden Algorithmen von der optimalen Route aufweisen und wieviele Partitionen auf dem berechneten
Weg liegen.
P.Größe in km Abweichung (D) % Abweichung (A*) in % # Part. (D) # Part. (A*)
2 432
45,6
34,9
10
8
3 242
58,5
49,3
7
8
4 539
19,9
42,8
10
7
6 808
40,8
39,1
6
6
11 347
43,2
40,6
4
4
22 693
5,4
4,6
5
5
68 074
19,4
6,7
2
2
Tabelle 5.5: Hamburg-Dresden, Vergleich Güte zwischen Dijkstra und A*
Dass die Abweichungen so groß sind, meistens zwischen 40 und 60%, liegt daran, dass der
schnellste Weg zwischen Hamburg und Dresden über eine Autobahn durch Brandenburg
führt und nicht wie die Luftlinie durch Sachsen-Anhalt. Im Falle des besseren Ergebnisses von Dijkstra, routet der A*-Algorithmus schon in der ersten Partition zu einem
Randknoten, der nicht auf der optimalen Route liegt, während Dijkstra einen Umweg
von drei Partition nimmt und ein doppelt so gutes Ergebnis errechnet. Siehe Bild 5.2. Die
Route, die von Dijkstra gefunden wird ist in rot dargestellt, während der blaue Linienzug
die berechnete Route vom A*-Algorithmus darstellt. Man kann erkennen, dass Dijkstras
Weg in der Startpartition echt kürzer ist als der vom A*-Algorithmus, allerdings ist dies
auch die einzige Partitionierung, in der der vorausberechnete Rand nicht erreicht wird,
weshalb es zu diesem Umweg kommt, der trotzdem ein besseres Ergebnis liefert.
KAPITEL 5. EXPERIMENTE
53
Abbildung 5.2: Vorteil von Dijkstra gegenüber A*
An diesen zwei Beispielen kann man die Vorteile bezüglich der Laufzeit des A*-Algorithmus
im Vergleich zu dem von Dijkstra erkennen. In den nachfolgenden Vergleichen wird daher
auf Dijkstra verzichtet und alle Berechnungen mit dem A*-Algorithmus gemacht.
5.4
Große versus kleine Partitionen
Die folgenden Experimente sollen zeigen, in wie fern die Größe der Partition die Güte
der Routensuche bestimmt. Man sollte annehmen, dass große Parttionen ein besseres
Ergebnis als kleine liefern, da man über mehr Informationen in einer Partition verfügt
und man insgesamt weniger Partitionen untersuchen muss.
Als erste Route wurde die schnellste Route von Hannover nach München mit der Heuristik c.b) mit einer unabhängigen Partitionierung gesucht. Dabei wurde die Fläche
Deutschlands in Quadrate unterteilt. Die erste Tabelle 5.6, zeigt die Unterschiede verschiedener Partitionsgrößen in Gesamtrechendauer und Abweichung von der optimalen
Route. Es sind zudem die Größe der Partitionen, die Dauer, in der der Graph aufgebaut
wurde, die Anzahl der Knoten pro Partition und die Anzahl von Partitionen auf der
Route angegeben.
Man kann in dieser Tabelle erkennen, dass die Güte mit jeder Vergrößerung einer Partition besser wird. Die Zeit für die Erstellung des Graphen wächst scheinbar proportional
mit der Anzahl der Knoten. Wie erwartet verringert sich auch die Anzahl der Partitionen
mit der Vergrößerung der Partitionen. Das kann man auch an der Tabelle 5.7 ablesen.
KAPITEL 5. EXPERIMENTE
54
Aufteilung Größe (km2 ) # Knoten # Part. Aufbau (s) Dauer (s) Abweichung (%)
10x10
6 052
115 120
10
24
318
34,19
8x8
9 392
141 260
7
28
287
31,44
6x6
17 011
311 755
5
56
496
9,62
4x4
37 989
608 616
4
105
734
9,11
3x3
69 023
1 101 969
2
206
677
5,15
Tabelle 5.6: Hannover-München, Vergleich Partitionsgrößen
Sie zeigt die Ergebnisse der kürzesten Route von Hamburg nach Dresden, die mit der
Heuristik c.a und einer abhängigen Partitionierung gesucht wurde.
Aufteilung Größe (km2 ) # Knoten # Part. Aufbau (s) Dauer (s) Abweichung (%)
10x10
6 267
57 221
8
17
138
29,43
8x8
9 813
99 225
6
20
153
26,85
6x6
17 506
162 327
5
31
163
24,31
4x4
39 661
338 600
5
60
312
18,94
3x3
69 023
669 354
4
119
332
15,43
2x2
161 831
1 037 545
3
180
555
9,12
Tabelle 5.7: Hamburg-Dresden, Vergleich Partitionsgrößen
Die Tabelle 5.8 zeigt jedoch, dass eine Partitionierung mit großen Partition nicht automatisch besser ist als eine mit kleineren Partitionen. In ihr sind die Ergebnisse einer
Routensuche des schnellsten Weges von Dresden nach München mit einer unabhängigen Partitionierung und der Heuristik c.b) in dem gleichen Format wie die vorherigen
zwei Tabellen dargestellt. In dieser Tabelle kann man einen Nachteil großer Parttionen
erkennen, nämlich wenn eine Partition Straßen beinhaltet, die zwar zum kürzesten oder
schnellsten Weg gehören, aber nicht vom Startpunkt in dieser Partition aus erreichbar
sind. Da man keine Partition zweimal untersuchen kann, sind diese Straßen nicht mehr
zu erreichen und folglich kann die optimale Route nicht mehr gefunden werden. Dieses
Verhalten ist bei der Route von Dresden nach München bei einer Aufteilung von drei
mal drei Gitterzellen zu sehen. Dafür ergibt die Aufteilung von vier mal vier Gittern
eine Route, die der optimalen Route nahe kommt.
Aufteilung Größe (km2 ) # Knoten # Part. Aufbau (s) Dauer (s) Abweichung (%)
8x8
9 392
130 265
6
26
177
17,77
6x6
17 011
191 572
4
46
257
3,79
4x4
37 989
405 843
3
71
320
1,18
3x3
69 023
888 601
3
148
686
7,82
Tabelle 5.8: Dresden-München, Vergleich Partitionsgrößen
Die Ergebnisse zeigen, dass die Größe der Partitionen eine Rolle spielen, es aber auch
KAPITEL 5. EXPERIMENTE
55
immer darauf an kommt, ob die Partitionierung glücklich gewählt ist. Wurde eine Route
gesucht, deren Luftlinie nicht das Bundesgebiet verlässt, wurde durch eine Vergrößerung
der Partitionen ein besseres Ergebnis erzielt. Die Route von Dresden nach München
besitzt noch weitere Besonderheiten, wie die folgenden Abschnitte zeigen werden.
5.5
Unterschiede der Metriken
Dieser Abschnitt soll die Unterschiede der beiden Metriken bei der partitionierten Routensuche deutlich machen. Dazu wurde mit der Heuristik c.b) und einer unabhängigen
Partitionierung und verschiedenen Partitionsgrößen der schnellste und kürzeste Weg auf
den den drei Teststrecken gesucht. In den Tabellen sind die Abweichungen der kürzesten
und schnellsten berechneten Route zu den verschiedenen Aufteilungen dargestellt.
In der Tabelle 5.9 sind die Ergebnisse der Route in Form der Abweichung von der optimalen Route in % von Hannover nach München vermerkt. In den meisten Fällen liegt
die Güte der kürzesten Routen unterhalb der der schnellsten oder sie sind etwa gleich
gut..
Aufteilung schnellste
10x10
34,36
8x8
31,44
6x6
9,62
4x4
9,11
3x3
5,15
kürzeste
30,25
12,44
8,04
9,50
3,57
Tabelle 5.9: Hannover-München, Vergleich Güte bezüglich der Metriken
In der Tabelle 5.11 sind die Ergebnisse der Route von Dresden nach München dargestellt.
Bei einer Aufteilung von zehn mal zehn Gitterzellen wird keine Route gefunden, siehe
auch den Abschnitt über Heuristiken 5.6. Hier schneidet die schnellste Route etwas besser
ab, als die schnellste Route zwischen Hannover und München. Die Aufteilung in drei mal
drei Gitterzellen liefert ein ungenaueres Ergebnis als die Aufteilung von vier mal vier,
da ein Teil des kürzesten Weges in einer Partition nicht erreichbar ist und so dieser Teil
nicht mehr genutzt werden kann.
In Tabelle 5.11 sind die Ergebnisse der Route Hamburg-Dresden enthalten. Hier liegt
die Güte der kürzesten Route wieder für jede Aufteilung unterhalb der der schnellsten.
Wie schon bemerkt sind die Abweichungen der schnellsten Route sehr groß, da die optimale Route nicht mit der Luftlinie übereinstimmt. Die Abweichungen sind zum Teil
noch größer, wenn man eine abhängige Partitionierung verwendet, siehe Tabelle 5.5.
Die Methode, den Zielrand über die geringste Entfernung zum Zielpunkt der Route zu
ermitteln, ist also nicht immer optimal für das Finden der schnellsten Route.
KAPITEL 5. EXPERIMENTE
Aufteilung schnellste
10x10
k.E.
8x8
41,71
6x6
8,77
4x4
1,18
3x3
7,82
56
kürzeste
k.E.
18,40
10,17
0,39
10,16
Tabelle 5.10: Dresden-München, Vergleich Güte bezüglich der Metriken
Aufteilung schnellste
10x10
51,09
8x8
18,56
6x6
k.E.
4x4
15,50
kürzeste
20,28
9,20
4,15
1,19
Tabelle 5.11: Hamburg-Dresden, Vergleich Güte bezüglich der Metriken
Insgesamt hat sich gezeigt, dass das Finden einer kürzesten Route mit einer geringen
Abweichung zur optimalen meistens eher gelingt als mit der schnellsten Route.
5.6
Unterschiede der Heuristiken
In diesem Abschnitt geht es um die Unterschiede zwischen den Heuristiken a), c.a),
b) und c.b). Es wurden die drei bekannten Strecken bei fester Partitionsgröße einer
Aufteilung von zehn mal zehn Gitterzellen untersucht. Es wurde eine abhängige Partitionierungen gewählt und diese mit den beiden Metriken kombiniert. In der Tabelle 5.12
sind die Abweichungen in Prozent der Route Hamburg-Dresden dargestellt.
Metrik
schnellste
kürzeste
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
46,72
40,39
94,98
46,72
1,42
2,85
17,99
10,84
Tabelle 5.12: Hamburg-Dresden, Güte der verschiedenen Heuristiken
Es tritt das wieder das Problem auf, dass die Abweichung sehr groß wird, wenn die
schnellste Route nicht ungefähr mit der Luftlinie übereinstmmt. Trotzdem ist es schon
überraschend, dass die Heuristik b) eine fast doppelt so langsame Strecke ermittelt. Die
kürzesten Strecken sind gemessen an der geringen Partitionsgröße bei den Heuristiken
a und c.a überraschend nah an der optimalen Route mit jeweils weniger als 3% Abweichung.
Die Tabelle 5.13 liegt im gleichen Format vor und beschreibt die Abweichungen der
KAPITEL 5. EXPERIMENTE
Metrik
schnellste
kürzeste
57
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
17,30
10,66
k.E.
13,51
13,45
7,86
k.E.
9,25
Tabelle 5.13: Dresden-München, Güte der verschiedenen Heuristiken
Strecke Dresden-München. Bei dieser Route schneidet die Heuristik c.a) am besten ab.
Die Heuristik b) gerät mit ihrem gewählten Zielknoten in eine Sackgasse. Im Vergleich
zu der schnellsten Strecke von Hamburg nach Dresden sind die Abweichungen bei der
schnellsten Route von Dresden nach München geringer, obwohl auch hier die schnellste
Route nicht mit der Luftlinie übereinstimmt.
In tabelle 5.14 sind die Abweichungen zur optimalen Route bezüglich der Route von
Hannover nach München dargestellt. Auch hier sind die Ergebnise für diese kleine Partitionsgröße mit den Heuristiken a) und c.a) ziemlich gut mit weniger als 2%. Die Abweichung bei der Heuristik b) hingegen ist wieder sehr groß, auch auf dieser Strecke stimmt
die schnellste Route nur zum Teil mmit der Luftlinie überein.
Metrik
schnellste
kürzeste
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
6,36
6,01
89,35
5,50
1,19
1,70
10,06
5,97
Tabelle 5.14: Hannover-München, Güte der verschiedenen Heuristiken
Insgesamt liegt die Heuristik b, was die Güte betrifft zum Teil deutlich - 60% Unterschied
und mehr in der Abweichung - hinter den anderen zurück. Die Routen, die ermittelt
werden sehen auch nicht natürlich aus, sie sind sehr zackig, siehe Abbildung 5.3. Es ist
die schnellste Route von Hannover nach München mit der Heuristik b) dargestellt. Es ist
eine Aufteilung in zehn mal zehn Gitterzellen genutzt worden mit einer unabhängigen
Partitionierung. Bei der unabhängigen Partitionierung ist noch deutlicher das Verhalten
dieser Heuristik zu erkennen und liefert auch ein noch schlechteres Ergebnis, nämlich eine
Abweichung von 95%. Die kürzeste Route in Kombination mit der Heuristik a) liefern
meistens die besten Ergebnisse. Es kommt allerdings auf eine glückliche Partitionierung
an, siehe die kürzeste Strecke von Dresden nach München, in der die Heuristik c.a das
beste Ergebnis liefert.
Wird die schnellste Route gesucht, schneiden die Heuristiken c.a) und c.b) am Besten
ab. Siehe die drei Tabellen dieses Abschnittes und zum Beispiel Tabelle 5.8 in der mit
der Heuristik c.b) eine Abweichung von nur 1,18% erreicht wird.
In der Abbildung 5.4 ist allgemein die Probleme der Heuristiken zu sehen. Dargestellt ist
die kürzeste optimale Route in grün von dem oberen Punkt zum unteren. Die untere rote
Linie symbolisiert die Route, die mit der Heuristik a bestimmt wurde, während die obere
Route mit der Heuristik b bestimmt wurde. Die schwarze Linie ist ein Partitionsrand. Die
KAPITEL 5. EXPERIMENTE
Abbildung 5.3: Probleme der Heuristiken
Abbildung 5.4: Probleme der Heuristiken
58
KAPITEL 5. EXPERIMENTE
59
Heuristiken c.a und c.b sind nicht dargestellt, da sie eine Autobahn als Zielrandknoten
bestimmt, der sehr weit von dem Zielpunkt entfernt liegt.
Die Heuristiken sind eben nur Heuristiken und bestimmen nicht zwangsläufig den optimalen Zielrandknoten. Rein optisch wurde von der Heuristik a) der kürzeste Weg in der
Startpartition bestimmt, nur ist dieser gewählte Randknoten nicht Teil der optimalen
Route. Den Zielpunkt, den die Heuristik b) gewählt hat, liegt zwar am nächsten zum
Zielpunkt, aber über diesen Randknoten führt der optimale kürzeste Weg nicht. Dieses
Beispiel ist zwar konstruiert, aber es verdeutlicht das Problem, dass je mehr Partitionen genutzt werden, die Wahrscheinlichkeit steigt, dass wie in der Abbildung nicht die
optimalen Randknoten gewählt werden.
Die Ergebnisse zeigen, dass die Heuristik b) in den meisten Fällen ungeeignet ist den
kürzesten Weg zu finden. Die Heuristen c.a) und c.b) können der optimalen schnellsten
Route nahe kommen, sofern sie mit der Luftlinie übereinstimmt. Die Heuristik a) liefert
meistens gute Ergebnisse auf der Suche nach der kürzesten Route.
5.7
Abhängige versus unabhängige Partitionierung
Im letzen Abschnitt befassen wir uns mit der Frage, welchen Vorteil eine abhängige
Partitionierung bringt oder ob sie die Ergebnisse der Routensuche überhaupt verbessern
kann.
In den folgenden Tabellen sind die Ergebnisse des letzten Abschnitts noch um die Abweichungen der unabhängigen Partitionierung erweitert. Es ist also wieder die Abweichung
der verschiedenen Heuristiken bei einer Partitionierungsaufteilung von zehn mal zehn
Gitterzellen dargestellt. Die Tabelle 5.15 zeigt diese für die Route Hamburg-Dresden.
Für beide Metriken und alle Heuristiken liefert die abhängige Partitionierung ein besseres
Ergebnis. Bei der unabhängigen Partitionierung in Kombination mit der Heuristik b wird
eine Route gefunden, die in eine Sackgasse auf halber Strecke führt. Auch kann man hier
wieder sehen, dass die Auswahl des Zielrandes über die kürzeste Distanz zum Zielpunkt,
nicht geeignet ist zur Findung der schnellsten Route.
Partitionierung
unabhängig
abhängig
Metrik
schnellste
kürzeste
schnellste
kürzestee
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
52,40
51,09
k.E.
47,82
16,40
20,28
k.E.
17,60
46,72
40,39
94,98
46,72
1,42
2,85
17,99
10,84
Tabelle 5.15: Hamburg-Dresden, Güte bzgl. der Partitionierung und Heuristiken
KAPITEL 5. EXPERIMENTE
60
Abbildung 5.5: Nachteil der unabhängige Partitionierung
Die Tabelle 5.16, in der die Ergebnisse der Routensuche von Hannover nach München
dargestellt ist, bestätigt den Eindruck, dass die abhängige Partitionierung geeigneter
zur Routensuche ist. Wie bei der Route Hamburg-Dresden sind alle Ergebnisse mit einer
abhängigen Partitionierung genauer. Bei der Heuristik c.a und der kürzesten Route sogar
um 50%.
Partitionierung
unabhängig
abhängig
Metrik
schnellste
kürzeste
schnellste
kürzeste
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
8,93
30,07
95,70
34,36
k.E.
54,51
26,09
31,07
6,36
6,01
89,35
5,50
1,19
1,70
10,06
5,97
Tabelle 5.16: Hannover-München, Güte bzgl. der Partitionierung und Heuristiken
In Abbildung 5.5 kann man den Nachteil der unabhängigen Partitionierung sehen, wenn
der Startpunkt nahe der Partitionsgrenze liegt. Dargestellt sind die Routen die bei einer
Aufteilung von 8x8 Gitterzellen entstehen (blau) und bei einer Aufteilung von 6x6 (rot).
Der Zielrand der ersten Partition bei der Aufteilung 8x8 ist schwarz hervorgehoben. Es
stehen bei der Partitionierung mit den kleineren Partitionen nicht genug Informationen
in der Startpartition zur Verfügung, wodurch eine komplett andere Route gefunden wird
und ein Unterschied von 20% in der Abweichung entsteht.
Die Tabelle 5.17 zeigt die Ergebnisse der Route Dresden-München. Auffällig ist, das bei
der unabhängigen Partitionierung einzig in Kombination mit der Heuristik b eine Route
gefunden wird. Das liegt daran, dass bei den anderen Heuristiken zu früh eine Partition
KAPITEL 5. EXPERIMENTE
Partitionierung
unabhängig
abhängig
Metrik
schnellste
kürzeste
schnellste
kürzeste
61
Heuristik a Heuristik c.a Heuristik b Heuristik c.b
k.E.
k.E.
128,20
k.E.
k.E.
k.E.
11,86
k.E.
17,30
10,66
k.E.
13,51
13,45
7,86
k.E.
9,25
Tabelle 5.17: Dresden-München, Güte bzgl. der Partitionierung und Heuristiken
verlassen wird und in einen Teil einer Partition gerät, den man nicht wieder verlassen
kann. Gleichzeitig findet man mit der Heuristik b) keine Route bei einer abhängigen
Partitionierung, dies liegt allerdings an einem Modellierungsfehler in den OSM-Daten,
sodass die Route in eine Sackgasse führt.
Bei der Suche nach der schnellsten oder kürzesten Route von Dresden nach München
schneidet die Heuristik a), abgesehen von der Aufteilung von zehn mal zehn Gitterzellen, immer schlechter ab als die anderen Heuristiken. Das liegt daran, dass sowohl die
kürzeste als auch die schnellste Route nicht entlang der Luftlinie führen kann, da diese
das Bundesgebiet zwischenzeitlich verlässt.
Die Tabelle 5.18 ist eine Erweiterung der Tabelle 5.11, in ihr ist zusätzlich die Abweichungen einer abhängigen Partitionierung dargestellt. Benutzt wurde also die Heuristik
c.b).
Bis auf eine Ausnahme sind mit einer abhängigen Partitionierung bessere kürzeste Routen als mit der unabhängigen errechnet worden. Durch die Heuristik c.b) werden die
Ergebnisse aber auch ungenauer mit größeren Partitionen, bis auf die Aufteilung vier
mal vier.
Aufteilung
10x10
8x8
6x6
4x4
abhängig
unabhängig
schnellste kürzeste schnellste kürzeste
40,39
2,85
51,09
20,28
43,45
8,94
18,56
9,20
48,47
12,27
k.E
4,15
19,00
1,12
15,50
1,19
Tabelle 5.18: Hamburg-Dresden, Vergleich Güte bzgl. der Partitionierung und den Metriken
Die Tabelle 5.19 zeigt im gleichen Format die Abweichungen der Strecke Dresden-München
mit der Heuristik c.b). Auf dieser Strecke wurde mit einer abhängigen Partitionierung
die schnellste Route gemessen an der Abweichung besser errechnet als die kürzeste. Das
hängt mit der Heuristik zusammen, die besser schnellere Routen bestimmen kann. Zudem
wird die Routensuche ohnehin schwieriger, da die sie nicht nicht nur der Luftlinie folgen
kann. Wurde die kürzeste Route bei der unabhängigen Partitionierung gefunden, wurde
KAPITEL 5. EXPERIMENTE
62
bis auf eine Ausnahme eine bessere kürzeste Route berechnet als mit der abhängigen
Partitionierung.
Die Tabelle zeigt aber vor allem eins, nämlich dass eine optimale Route in partitionierten
Straßendaten gefunden werden kann, allerdings wurden hierfür auch nur zwei Partitionen
untersucht.
Aufteilung
10x10
8x8
6x6
4x4
3x3
abhängig
unabhängig
schnellste kürzeste schnellste kürzeste
10,66
29,43
k.E.
k.E.
20,85
26,85
41,71
18,40
21,09
24,31
8,77
10,17
0,00
18,94
1,18
0,39
0,00
9,12
7,82
10,16
Tabelle 5.19: Dresden-München, Vergleich bzgl. der Partitionierung und den Metriken
Die Ergebnisse zeigen, dass eine abhängige Partitionierung in Verbindung mit der Heuristik a) meist bessere Ergebnisse liefert, als die abhängige Partitionierung. Liegt die
schnellste Route nicht auf der Luftlinie, tragen beide Partitionierungen selten zu einer
guten schnellsten Route bei.
5.8
Fazit
Die Ergebnisse der Experimente haben gezeigt, dass es schwierig ist die Partitionierung
und Heuristik im Vorfeld zu bestimmen, die zu einer optimalen kürzesten Route führt.
Siehe zum Beispiel das Beispiel der besseren Routenberechnung von Dijkstra, die nur
entsteht, da der Zielrand nicht erreicht werden kann. So kann ein Teil des optimalen
schnellsten Weges genutzt werden, der sonst nicht in Betracht gezogen wird, da er nicht
zum Zielrand führt.
Der A*-Algorithmus nützt die Zusatzinformation der geographischen Lage der Knoten
effizient aus, was sich in der Anzahl der untersuchten Knoten und damit in der Laufzeit
im Vergleich zu Dijkstra bemerkbar macht.
Eine Vergrößerung der Partitionen ist solange ein Vorteil, bis ein Zielrand nicht erreicht
wird. Dann muss die Partition gewechselt werden und Strecken der optimalen kürzesten
Route können nicht mehr erreicht werden.
Die Auswahl des Zielrandes über die kürzeste Entfernung zum Zielpunkt der Route ist für
die Findung der schnellsten Route nicht immer optimal, wenn die Partitionen nicht groß
genug gewählt wurde. Das man die optimale Route finden kann, zeigt die Tabelle 5.19.
KAPITEL 5. EXPERIMENTE
63
Die Heuristik a) liefert zum Teil auch bei kleinen Partitionen ein annehmbares Ergebnis,
solange der die kürzeste Route auch über die Luftlinie führt. Die Heuristik b) liefert in
der Regel die Ergebnisse mit der höchsten Abweichung. Das Problem des nicht erreichten
Zielknotens kann auch mit den Heuristiken b) und c.b) auftreten und macht dadurch
vor allem die Heuristik b) noch unattraktiver zur Bestimmung des kürzesten Weges.
Die abhängige Partitionierung kann die Ergebnisse der Routensuche verbessern, hat aber
die gleichen Probleme, wie die abhängige, wenn der Zielrand nicht erreicht wird oder die
gesuchte Route nicht mit der Luftlinie übereinstimmt.
Kapitel 6
Ausblick
Insgesamt hat sich gezeigt, dass es nicht zwangsläufig eine perfekte Partitionierung zur
optimalen Routenbestimmung gibt.
Es können noch alternative Routing-Möglichkeiten umgesetzt werden, zum Beispiel das
hierarchische Kürzeste-Wege-Suchen. Man könnte die Routensuche zusätzlich auch vom
Endpunkt zum Startpunkt durchführen und sie mit der normalen Route vergleichen.
Der A*-Algorithmus verletzt unter gewissen Umständen die Heuristik a), das könnte
vermieden werden, in dem alle markierten Randknoten gefunden werden und von diesen
den mit dem kürzesten Weg auszuwählen. Allerdings würde dann, falls ein einziger dieser
Randknoten nicht erreicht würde, wieder alle Kanten zu erreichbaren Knoten relaxiert
werden, was der A*-Algorithmus ja gerade vermeiden soll. Man könnte auch die Routensuche, die bisher nur von einem Start- zu einem Zielpunkt auf mehrere Start- und
Zielpunkte erweitern.
Die vorhandenen Daten aus OpenStreetMap könnten noch besser für das Routing genutzt werden. So existieren noch mehr Tags, die man beachten könnte, wie ein maxspeed -Tag oder ein Tag für Ampeln, den man benutzen könnte, um zum Beispiel eine
Wartezeit pro Ampel auf den schnellsten Weg addieren könnte.
Die Art der Partitionen könnten auf beliebige Polygone erweitert werden, wie Polygone,
die die Bundesländer beschreiben.
Falls man eine genauere Beschreibung der Route wünscht, müsste diese Beschreibung
ebenfalls bei der Komposition der Routen zusamengeführt werden, damit man nicht
durch die Partitionierung die Anweisungen “zehn Kilometer auf der A2” und “nach fünf
Kilometern rechts abbiegen” erhält, sondern “nach fünfzehn Kilometern auf der A2 rechts
abbiegen”.
64
Anhang A
Das simple
Osmosis-PostGIS-Schema
Osmosis ist ein Kommandozeilen-Werkzeug zur Verarbeitung von OSM-Daten. Um die
OSM-Daten, die zum Beispiel im .osm-Format vorliegen, in eine PostGIS-Datenbank
zu laden, muss diese Tabellen gemäß dem Osmosis PostGIS simple Schema vorliegen.
Im Osmosis-Software-Paket befindet sich ein Ordner script, in dem Erstellungsskripte
für dieses Schema vorliegen. Benötigt werden für unsere Anwendung die Skripte pgsimple schema 0.6.sql und pgsimple schema 0.6 linestring.sql, das in der WAYS-Tabelle eine
LineString-Geometrie hinzufügt. Insgesamt werden folgende Tabellen erstellt:
SCHEMA INFO
USERS
NODES
NODE TAGS
WAYS
WAY NODES
WAY TAGS
RELATIONS
RELATION MEMBERS
RELATION TAGS
(Version)
(Id, Name)
(Id, Version,User ID → USERS, Tstamp, Changeset Id, Geom)
(Node Id → NODES, K, V)
(Id, Version,User ID → USERS, Tstamp, Changeset Id, Linestring)
(Way Id → WAYS, Node Id → NODES, Sequence Id)
(Way Id → WAYS, K, VNULL )
(Id, Version,User ID → USERS, Tstamp, Changeset Id)
(Relation Id → RELATIONS, Member Id,
Member Type, Member Role, Sequence Id)
(Relation Id → RELATIONS, K, V)
Wenn nicht anders angegeben, wird NOT NULL für die Attribute angenommen. Zusätzlich werden räumliche Indexe für die Geometrien Geom und Linestring angelegt,
sowie Indexe für die Tag-Relationen für den jeweiligen Typen (Node Id, Relation Id und
Way Id ) und bei der WAY NODES-Relation für die Way Id.
65
Anhang B
PL/pgSQL-Funktionen
B.1
Funktionen zum Erstellen der Edges-Tabelle
Die folgende Funktion nimmt einen Linestring entgegen und gibt den Teil des Linestrings
zurück, der zwischen dem first-ten und last-en Knoten besteht.
CREATE OR REPLACE FUNCTION makeSubLineString(geometry, integer, integer)
RETURNS geometry AS
$BODY$
DECLARE
line ALIAS FOR $1;
first ALIAS FOR $2;
last ALIAS FOR $3;
result geometry;
BEGIN
--ST_PointN beginnt bei 1, die Sequence_id bei 0
result:=ST_MakeLine(ST_PointN(line,first+1),ST_PointN(line,first+2));
FOR i IN first+2..last LOOP
result:=ST_AddPoint(result, ST_PointN(line,i+1));
END LOOP;
return result;
END;
$BODY$
66
ANHANG B. PL/PGSQL-FUNKTIONEN
67
Die folgenden Sichten edge view und node view dienen nur zur Abkürzung der Abfragen
in der nächsten Funktion.
CREATE OR REPLACE VIEW edge_view AS
SELECT DISTINCT ways.id, ways.linestring, wt2.v AS highway,
CASE wt1.v
WHEN ’1’ THEN ’yes’
WHEN ’-1’ THEN ’vice_versa’
WHEN ’true’ THEN ’yes’
WHEN ’yes’ THEN ’yes’
ELSE ’no’
END AS oneway
FROM ways
LEFT JOIN way_tags wt1 ON ways.id = wt1.way_id AND wt1.k = ’oneway’
JOIN way_tags wt2 ON ways.id = wt2.way_id
WHERE wt2.k = ’highway’ AND (w2.v = ANY (ARRAY[’motorway’, ’motorway_link’,
’trunk’, ’trunk_link’, ’primary’, ’primary_link’,
’secondary’, ’tertiary’, ’residential’, ’living_street’,
’unclassified’, ’service’, ’track’]));
Die Highway-Tags sind in Abschnitt 3.3.2 beschrieben und die genaue Auflistung ist bei
der Node View weggelassen worden. Alternativ kann man auch zwei Edge Views joinen,
aber in denen wäre jeweils ein unnötiger Join für die LineString-Geometrie.
CREATE OR REPLACE VIEW node_view AS
SELECT DISTINCT wn1.way_id, wn1.node_id, nodes.geom, wn1.sequence_id
FROM way_nodes wn1
JOIN way_nodes wn2 ON wn1.node_id = wn2.node_id AND wn1.way_id <> wn2.way_id
JOIN nodes ON nodes.id = wn1.node_id
JOIN way_tags wt1 ON wn1.way_id = wt1.way_id
JOIN way_tags wt2 ON wn2.way_id = wt1.way_id
WHERE wt1.k = ’highway’ ... AND wt2.k = ’highway’ ... --siehe oben
ORDER BY wn1.way_id;
Die folgende Funktion legt die Tabelle EDGES neu an.
CREATE OR REPLACE FUNCTION buildEdgeTable()
RETURNS void AS
$BODY$
BEGIN
--Die Tabelle edges neu anlegen
DROP TABLE IF EXISTS edges;
ANHANG B. PL/PGSQL-FUNKTIONEN
68
CREATE TABLE edges (
edge_id text,
linestring geometry,
oneway text,
highway text,
length double precision,
node_id1 text,
node_id2 text,
);
CREATE INDEX index_linestring
ON edges
USING gist
(linestring);
END;
$BODY$
LANGUAGE plpgsql
Die Funktion buildEdges() erstellt die Kanten für den Graphen. Dabei werden für alle
Straßen, die jenigen OSM-Nodes identifiziert, die Kreuzungen auf dieser Straße darstellen
und an diesen wird der Linestring des OSM-Ways aufgespalten.
CREATE OR REPLACE FUNCTION buildedges()
RETURNS void AS
$BODY$
DECLARE
--Die richtigen Automobilstraßen herausfinden
way_cursor Cursor IS SELECT * FROM edge_view;
--Kreuzungen ermitteln
edge_cursor Cursor (id bigint) IS SELECT * FROM node_view WHERE way_id=id;
i integer;
edge_record Record;
old_seqence_id integer;
old_node_id bigint;
new_edge geometry;
line geometry;
BEGIN
SELECT buildEdgeTable();
--Jede Straße an den Kreuzungen in Kanten umwandeln
FOR way_record IN way_cursor
LOOP
i:=0;
Open edge_cursor(way_record.id);
FETCH edge_cursor INTO edge_record;
ANHANG B. PL/PGSQL-FUNKTIONEN
69
old_seqence_id:=edge_record.sequence_id;
old_node_id:=edge_record.node_id;
FETCH edge_cursor INTO edge_record;
--den LineString zwischen den sequence_id’s aufspalten
WHILE FOUND LOOP
line:=makeSubLineString(way_record.linestring, old_seqence_id,
edge_record.sequence_id);
INSERT INTO edges VALUES (way_record.id||’_’||i, line,
way_record.oneway,way_record.highway, ST_Length(line,true),
old_node_id,edge_record.node_id);
i:=i+1;
old_seqence_id:=edge_record.sequence_id;
old_node_id:=edge_record.node_id;
FETCH edge_cursor INTO edge_record;
END LOOP;
CLOSE edge_cursor;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql
B.2
Funktionen zum Finden von Start- und Zielknoten
Die Funktion liefert den Knoten, der innerhalb eines Radius von 500m, am nächsten
zum übergebenen Punkt liegt.
CREATE OR REPLACE FUNCTION getnodeidforPoint(IN geometry, OUT nodeid text,
OUT resultPoint geometry)
AS
$BODY$
DECLARE
point ALIAS FOR $1;
edge edges%ROWTYPE;
BEGIN
--Die Kante ermittelt die am nächsten zum übergebenen Punkt liegt
SELECT * FROM edges WHERE ST_DWithin(point,linestring,500,TRUE) ORDER BY
ST_Distance(linestring,point,TRUE) LIMIT 1 INTO edge;
IF ST_Distance(ST_Startpoint(edge.linestring),point)<=
ST_Distance(ST_Endpoint(edge.linestring),point) THEN
nodeId:=edge.node_id1;
ANHANG B. PL/PGSQL-FUNKTIONEN
70
resultPoint:=st_startpoint(edge.linestring);
ELSE
nodeId:=edge.node_id2;
resultPoint:=st_endpoint(edge.linestring);
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql
Die Funktion liefert ein Plygon zurück, in dem nach der Straße gesucht werden kann.
CREATE OR REPLACE FUNCTION getPostcodePolygon(IN postcode text,
IN street text, OUT postcodepolygon geometry)
RETURNS geometry AS
$BODY$
BEGIN
SELECT ST_Buildarea(ST_Collect(linestring)) INTO postcodepolygon
FROM relation_members r1 JOIN relation_tags r2 USING (relation_id) JOIN
ways ON r1.member_id=id WHERE (member_role=’outer’
OR member_role=’’) AND r2.k=’postal_code’ AND v=postcode;
--wenn kein Polygon gefunden wurde, dann selbst eines "basteln"...
IF postcodepolygon IS NULL THEN
SELECT ST_ENVELOPE(ST_COLLECT(ARRAY((
(SELECT geom FROM node_tags JOIN nodes n ON n.id=node_id
WHERE v=postcode AND k IN (’postal_code’,’addr:postcode’))
--...indem um alle Knoten und Kanten mit dieser Postleitzahl
--eine Boundingbox gelegt wird
UNION
(SELECT linestring FROM way_tags JOIN ways w ON w.id=way_id
WHERE v=postcode AND k IN (’postal_code’,’addr:postcode’))))))
INTO postcodepolygon;
END IF;
END;
$BODY$
LANGUAGE plpgsql
ANHANG B. PL/PGSQL-FUNKTIONEN
71
Die Funktion ermittelt einen Knoten, der zu einer Straße mit dem übergebenen Namen
und in dem Postleitzahlgebiet liegt.
CREATE OR REPLACE FUNCTION getnodeidforaddress(IN text, IN text,
OUT nodeid text, OUT point geometry)
AS
$BODY$
DECLARE
postcode ALIAS FOR $1;
street ALIAS FOR $2;
postcodepolygon geometry;
findEdge CURSOR (line geometry) IS SELECT node_id1,linestring,node_id2
FROM edges WHERE ST_Intersects(postcodepolygon,linestring)
ORDER BY ST_Distance(linestring,line) LIMIT 1;
findWay CURSOR (geom geometry) IS SELECT linestring FROM ways JOIN way_tags
ON id=way_id WHERE ST_Intersects(geom,linestring)
AND k=’name’ AND v=street LIMIT 1;
BEGIN
SELECT * FROM getPostCodePolygon(postcode,street) INTO postcodepolygon;
FOR way IN findWay(postcodepolygon) LOOP
FOR edge IN findEdge(way.linestring) LOOP
IF ST_Distance(ST_Startpoint(edge.linestring),way.linestring)<=
ST_Distance(ST_Endpoint(edge.linestring),way.linestring) THEN
nodeId:=edge.node_id1;
point:=ST_Startpoint(edge.linestring);
ELSE
nodeId:=edge.node_id2;
point:=ST_Endpoint(edge.linestring);
END IF;
END LOOP;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql
ANHANG B. PL/PGSQL-FUNKTIONEN
B.3
72
Funktionen zum Graphaufbau
Liefert alle Kanten, die nicht geclippt werden müssen.
CREATE OR REPLACE FUNCTION getEdges(polygon geometry) RETURNS SETOF edges AS
$BODY$
BEGIN
--einfach nur die Kanten zurückgeben, die im Polygon beginnen und auch enden
RETURN QUERY SELECT * FROM edges
WHERE ST_Contains(polygon,ST_Startpoint(linestring))
AND ST_Contains(polygon, ST_Endpoint(linestring));
END;
$BODY$
LANGUAGE plpgsql
Liefert alle Kanten, die in der gegebenen Partition beginnen und clippt sie vorher
CREATE OR REPLACE FUNCTION getBeginningBorderedges(polygon geometry)
RETURNS SETOF edges AS
$BODY$
DECLARE
count integer;
temp geometry;
result edges%rowtype;
--Die Kanten die in dieser Partition beginnen, können gleich mit "Intersection()"
--abgeschnitten werden, es wird ja nur dieser Teil des LineStrings benötigt
beginning CURSOR FOR SELECT edge_id,ST_Intersection(polygon,linestring) linestring,
oneway, highway,length,node_id1,node_id2 FROM edges WHERE
ST_Crosses(polygon, linestring) AND
ST_Contains(polygon, ST_Startpoint(linestring))
AND NOT ST_Contains(polygon, ST_Endpoint(linestring));
BEGIN
--Die abgeschnittenen Kanten können so zurückgegeben werden, es sei denn es
--gab mehrere Ergebnisse bei der Intersection(), dann wird nur die
--erste Geometrie übernommen
FOR edge IN beginning LOOP
result:=edge;
result.node_id2:=edge.edge_id||’_e’;
IF(ST_Geometrytype(edge.linestring)=’ST_MultiLineString’) THEN
result.linestring:=ST_Geometryn(edge.linestring,1);
END IF;
result.length:=ST_Length(result.linestring,TRUE);
ANHANG B. PL/PGSQL-FUNKTIONEN
73
RETURN NEXT result;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql
Liefert alle geclippten Kanten, die in der Partition enden.
CREATE OR REPLACE FUNCTION getEndingBorderEdges(polygon geometry)
RETURNS SETOF edges AS
$BODY$
DECLARE
count integer;
temp geometry;
result edges%rowtype;
--Für die Kanten, die in dieser Partition enden, muss noch geprüft werden,
--welche wie geclippt werden muss
ending CURSOR FOR SELECT * FROM edges WHERE ST_Crosses(polygon, linestring)
AND NOT ST_Contains(polygon, ST_Startpoint(linestring))
AND ST_Contains(polygon, ST_Endpoint(linestring));
BEGIN
FOR edge IN ending LOOP
result:=edge;
result.node_id1:=edge.edge_id||’_b’;
--Es wird der Schnittpunkt des Polygonrandes mit dem
--Kanten-LineString ermittelt
temp:=ST_Intersection(edge.linestring,ST_Boundary(polygon));
--wenn die Kante den Polygonrand nur einmal schneidet, ist alles
--in Ordnung, wenn nicht...
IF ST_Geometrytype(temp)=’ST_MultiPoint’ THEN
--muss hier geprüft werden, ob der erste oder letzte dieser Punkte näher
--zum Startpunkt liegt und dann dieser als Schnittpunkt festgelegt werden
IF ST_Distance(ST_Startpoint(edge.linestring),ST_Geometryn(temp,1)) <
ST_Distance(ST_Startpoint(edge.linestring),
ST_Geometryn(temp,ST_Numgeometries(temp))) THEN
temp:=ST_Geometryn(temp,1);
ELSE
temp:=ST_Geometryn(temp,ST_Numgeometries(temp));
END IF;
END IF;
--ST_Line_Substring(a,b) liefert den linestring von a nach b, aber es sind
--keine Koordinaten, sondern relative Werte z.B. 0.5, diesen
ANHANG B. PL/PGSQL-FUNKTIONEN
74
--berechnet ST_Locate_Point
result.linestring:=ST_Line_Substring(edge.linestring,
ST_Line_Locate_Point(edge.linestring,temp),1);
result.length:=ST_Length(result.linestring,TRUE);
RETURN NEXT result;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql
Gibt die Kanten zurück, die in der Partition enden oder beginnen.
CREATE OR REPLACE FUNCTION getSimpleBorderEdges(polygon geometry)
RETURNS SETOF edges AS
$BODY$
BEGIN
--einfach nur die Kanten dieser Funktionen zurückgeben
RETURN QUERY SELECT * FROM getBeginningBorderEdges(polygon)
UNION SELECT * FROM getEndingBorderEdges(polygon);
END;
$BODY$
LANGUAGE plpgsql
In den folgenden Funktion tauchen immer wieder die gleichen Parameter auf. Border1
bezeichnet die Geometrie, die entsteht, wenn man einen Rand, der den LineString der
Kante schneidet, border2 analog. Wenn firstBorder wahr ist, heißt das, dass border1 aus
der Sicht des LineStrings zuerst geschnitten wurde.
Prüft, ob der linestring das polygon an mindestens zwei Rändern schneidet und gibt
die Schnittmenge der Ränder mit dem Linienzug zurück, sowie welcher Rand zuerst
geschnitten wurde.
CREATE OR REPLACE FUNCTION realCrossing(IN polygon geometry,
IN linestring geometry, OUT firstborder boolean, OUT count integer,
OUT border1 geometry, OUT border2 geometry)
AS
$BODY$
DECLARE
temp geometry;
BEGIN
firstborder:=FALSE;
ANHANG B. PL/PGSQL-FUNKTIONEN
75
count:=0;
border1:=NULL; --die Geometrien, die entstehen, wenn
border2:=NULL; --man den Polygonrand mit dem LineString schneidet
--zählen, wie viele Polygonränder geschnitten wurden
FOR i IN 0..3 LOOP
temp:=makeSubLinestring(ST_Boundary(polygon),i,i+1);
IF ST_Intersects(temp,linestring) THEN
IF count=0 THEN
border1:=ST_Intersection(temp,linestring);
count:=count+1;
ELSE
border2:=ST_Intersection(temp,linestring);
--firstBorder zeigt an, dass der erste Rand der gefunden wurde näher
--am Startpunkt des Linestrings liegt
IF ST_Distance(border1,ST_Startpoint(linestring))<=
ST_Distance(border2,ST_Startpoint(linestring)) THEN
firstborder:=TRUE;
END IF;
count:=count+1;
END IF;
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql
Ermittelt, falls der linestring den Polygonrand mehrfach geschnitten hat, welcher der
Punkt zum clippen ist.
CREATE OR REPLACE FUNCTION getRightPoint(IN linestring geometry,
IN multipoint geometry, IN toEnd boolean, OUT point geometry)
AS
$BODY$
DECLARE
BEGIN
--toEnd bedeutet, dass der LineString "hinten" abgeschnitten werden soll
IF toEnd THEN
--prüfen, ob der erste oder letzte Punkt von
--multipoint näher zum Ende liegt
IF ST_Distance(ST_Endpoint(linestring),ST_Geometryn(multipoint,1)) <
ST_Distance(ST_Endpoint(linestring),
ST_Geometryn(multipoint,ST_Numgeometries(multipoint))) THEN
point:=ST_Geometryn(multipoint,1);
ANHANG B. PL/PGSQL-FUNKTIONEN
76
ELSE
point:=ST_Geometryn(multipoint,ST_Numgeometries(multipoint));
END IF;
ELSE
--analog, nur mit Startpunkt
IF ST_Distance(ST_Startpoint(linestring),ST_Geometryn(multipoint,1)) <
ST_Distance(ST_Startpoint(linestring),
ST_Geometryn(multipoint,ST_Numgeometries(multipoint))) THEN
point:=ST_Geometryn(multipoint,1);
ELSE
point:=ST_Geometryn(multipoint,ST_Numgeometries(multipoint));
END IF;
END IF;
END;
$BODY$
LANGUAGE plpgsql
Bastelt den LineString, der von beiden Seiten geclippt wurde
CREATE OR REPLACE FUNCTION buildSubLineString(IN linestring geometry,
IN border1 geometry, IN border2 geometry, OUT line geometry)
AS
$BODY$
DECLARE
BEGIN
line:=linestring;
--gegebenenfalls den richtigen Point aus den Multipoints auswählen
IF ST_Geometrytype(border1)=’ST_MultiPoint’ THEN
SELECT point INTO border1 FROM
getRightPoint(linestring, border1, FALSE);
END IF;
IF ST_Geometrytype(border2)=’ST_MultiPoint’ THEN
SELECT point INTO border2 FROM
getRightPoint(linestring, border1, TRUE);
END IF;
--nur einen neuen LineString erstellen, wenn der erste Randpunkt
--vor dem zweiten auf dem LineString liegt
IF ST_Line_Locate_Point(linestring,border2)>
ST_Line_Locate_Point(linestring,border1) THEN
line:=ST_Line_Substring(edge.linestring,
ST_Line_Locate_Point(linestring,border1),
ST_Line_Locate_Point(linestring,border2));
END IF;
ANHANG B. PL/PGSQL-FUNKTIONEN
77
END;
$BODY$
LANGUAGE plpgsql
Gibt die Kanten zurück, die von beiden Seiten geclippt wurden.
CREATE OR REPLACE FUNCTION getComplicatedBorderEdges(polygon geometry)
RETURNS SETOF edges AS
$BODY$
DECLARE
firstborder boolean;
count integer;
border1 geometry; --border1 und border2 sind Geometrien, die man erhält,
border2 geometry; --wenn man die Kante mit dem Polygonrand schneidet
result edges%rowtype;
middle CURSOR FOR SELECT * FROM edges WHERE ST_Crosses(polygon, linestring)
AND NOT ST_Contains(polygon, ST_Startpoint(linestring))
AND NOT ST_Contains(polygon, ST_Endpoint(linestring));
BEGIN
FOR edge IN middle LOOP
result:=edge;
SELECT * INTO firstborder,count,border1,border2
FROM realCrossing(polygon,result.linestring);
--wenn count>1, dann schneidet die Kante zwei Ränder dieses Polygons
IF count>1 THEN
result.node_id1:=edge.edge_id||’_b’;
result.node_id2:=edge.edge_id||’_e’;
--firstBorder zeigt an, an welchem Rand die Kante
--das Polygon zuerst schneidet
IF firstborder THEN
result.linestring:=buildSubLineString(
edge.linestring, border1, border2);
ELSE
result.linestring:=buildSubLineString(
edge.linestring, border2, border1);
END IF;
result.length:=st_length(result.linestring,TRUE);
RETURN NEXT result;
END IF;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql
ANHANG B. PL/PGSQL-FUNKTIONEN
Gibt alle Rankkanten zurück, die in dem übergebenen Polygon liegen.
CREATE OR REPLACE FUNCTION getBorderEdges(polygon geometry)
RETURNS SETOF edges AS
$BODY$
BEGIN
RETURN QUERY SELECT * FROM getSimpleBorderEdges(polygon)
UNION SELECT * FROM getComplicatedBorderEdges(polygon);
END;
$BODY$
LANGUAGE plpgsql
78
Abbildungsverzeichnis
2.1
EER-Diagramm zu Node, Way und deren Tags . . . . . . . . . . . . . . .
7
2.2
Ein gerichteter Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.3
Ein kürzester Weg zwischen zwei Knoten . . . . . . . . . . . . . . . . . .
11
3.1
Zwei Partitionierungsarten . . . . . . . . . . . . . . . . . . . . . . . . . .
20
3.2
Beispiel zur Erstellung einer abhängigen Partitionierung . . . . . . . . .
21
4.1
Die OpenJump-Oberfläche . . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.2
Das Einstellungsfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
4.3
Transformation des OSM-Graphen . . . . . . . . . . . . . . . . . . . . .
33
4.4
Clipping an der Partitionsgrenze . . . . . . . . . . . . . . . . . . . . . . .
34
4.5
Die Klassen des Graph-Packages . . . . . . . . . . . . . . . . . . . . . . .
36
4.6
Die Klassen des Partition-Packages . . . . . . . . . . . . . . . . . . . . .
38
4.7
Die Klassen des RoutingAlgorithm-Package . . . . . . . . . . . . . . . . .
39
4.8
Die Klassen des Controller- und View-Packages . . . . . . . . . . . . . .
40
4.9
Die Klassen des Connectors-Packages . . . . . . . . . . . . . . . . . . . .
42
4.10 Die Klassen des Model-Packages . . . . . . . . . . . . . . . . . . . . . . .
45
4.11 Der Ablauf des Routings als Sequenzdiagramm (vereinfacht) . . . . . . .
47
5.1
A* und Dijkstra, Vergleich untersuchter Knoten . . . . . . . . . . . . . .
51
5.2
Vorteil von Dijkstra gegenüber A* . . . . . . . . . . . . . . . . . . . . . .
53
5.3
Probleme der Heuristiken . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
5.4
Probleme der Heuristiken . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
5.5
Nachteil der unabhängige Partitionierung . . . . . . . . . . . . . . . . . .
60
79
Tabellenverzeichnis
3.1
Die Straßenarten und ihre Gewichtungen . . . . . . . . . . . . . . . . . .
25
5.1
Die optimalen kürzesten Routen . . . . . . . . . . . . . . . . . . . . . . .
49
5.2
Dresden-München, Vergleich Knotenanzahl Dijkstra und A* . . . . . . .
50
5.3
Dresden-München, Vergleich Routingdauer zwischen Dijkstra und A* . .
51
5.4
Hamburg-Dresden, Vergleich Routingdauer zwischen Dijkstra und A* . .
52
5.5
Hamburg-Dresden, Vergleich Güte zwischen Dijkstra und A* . . . . . . .
52
5.6
Hannover-München, Vergleich Partitionsgrößen . . . . . . . . . . . . . . .
54
5.7
Hamburg-Dresden, Vergleich Partitionsgrößen . . . . . . . . . . . . . . .
54
5.8
Dresden-München, Vergleich Partitionsgrößen . . . . . . . . . . . . . . .
54
5.9
Hannover-München, Vergleich Güte bezüglich der Metriken . . . . . . . .
55
5.10 Dresden-München, Vergleich Güte bezüglich der Metriken . . . . . . . . .
56
5.11 Hamburg-Dresden, Vergleich Güte bezüglich der Metriken
. . . . . . . .
56
5.12 Hamburg-Dresden, Güte der verschiedenen Heuristiken . . . . . . . . . .
56
5.13 Dresden-München, Güte der verschiedenen Heuristiken . . . . . . . . . .
57
5.14 Hannover-München, Güte der verschiedenen Heuristiken . . . . . . . . .
57
5.15 Hamburg-Dresden, Güte bzgl. der Partitionierung und Heuristiken . . . .
59
5.16 Hannover-München, Güte bzgl. der Partitionierung und Heuristiken . . .
60
5.17 Dresden-München, Güte bzgl. der Partitionierung und Heuristiken . . . .
61
5.18 Hamburg-Dresden, Vergleich Güte bzgl. der Partitionierung und den Metriken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.19 Dresden-München, Vergleich bzgl. der Partitionierung und den Metriken
62
80
Literaturverzeichnis
[Bri08]
T. Brinkhoff. Geodatenbanksysteme in Theorie und Praxis. Herbert Wichmann Verlag, 2. Aufl., 2008. ISBN 978-3-87907-472-3.
[CLRS10] T. H. Cormen, C. E. Leiserson, R. Rivest, C. Stein. Algorithmen - Eine
Einführung. Oldenbourg Verlag München, 3. Aufl., 2010. ISBN 978-3-48659002-9.
[HNR68] P. E. Hart, N. J. Nilsson, B. Raphael. A formal basis for the heuristic determination of minimum cost paths. IEEE Transactions on Systems, Science,
and Cybernetics, SSC-4(2), 1968, 100–107.
[Jun94]
D. Jungnickel. Graphen, Netzwerke und Algorithmen. BI-Wissenschaftsverlag,
3. Aufl., 1994. ISBN 3-411-14263-4.
[Lip12]
U. Lipeck. Vorlesung Datenstrukturen und Algorithmen, WiSe 2011/12. URL
http://www.dbs.uni-hannover.de/dsalg1112.html.
[Mee91]
J. H. Meeus. Astronomical Algorithms. Willmann-Bell, Incorporated, 1991.
ISBN 0943396352.
[Pap80]
U. Pape. Algorithm 562: Shortest Path Lengths [H]. ACM Trans. Math.
Softw., 6(3), 1980, 450–455. ISSN 0098-3500. URL http://doi.acm.org/
10.1145/355900.355919.
[RT09]
F. Ramm, J. Topf. OpenStreetMap - Die freie Weltkarte nutzen und mitgestalten. Lehmanns Media, Berlin, 2. Aufl., 2009. ISBN 978-3-86541-320-8.
[Sch00]
W. Schmid. Berechnung kürzester Wege in Straßennetzen mit Wegeverboten.
Dissertation, Universität Stuttgart, Holzgartenstr. 16, 70174 Stuttgart, 2000.
URL http://elib.uni-stuttgart.de/opus/volltexte/2002/1190.
[WL12]
H. Warneke, U. W. Lipeck. Ein Partitionierungsdienst für Geographische
Daten in Räumlichen Datenbanken. Proceedings of the 24th GI-Workshop
”Grundlagen von Datenbanken 2012”, 2012, 35–40. URL http://ceur-ws.
org/Vol-850/paper_warneke.pdf.
81
Erklärung
Hiermit versichere ich, dass ich die vorliegende Arbeit und die zugehörige Implementierung selbstständig verfasst und dabei nur die angegebenen Quellen und Hilfsmittel
verwendet habe.
Hannover, 15. Juli 2012
David Bormann
82
Herunterladen