Nach welchen Regeln führt die Overpass API eine Abfrage aus? Die Vorstellung der einzelnen Bausteine schafft das Verständnis, wie diese in Abfragen zusammenwirken.
Die meisten fortgeschrittenen Anwendungsfälle für Abfragen erfordern relative Auswahlen. Ein gutes Beispiel sind Supermärkte, die nahe an einem Bahnhof liegen. Die Supermärkte sind mit den Bahnhöfen nur dadurch verbunden, dass sie räumlich nahe beieinander sind.
Dem Satzbau zufolge suchen wir eigentlich erst Supermärkte, suchen dann an jedem Supermarkt nach Bahnhöfen in der Nähe und behalten nur Supermärkte in der Auswahl, bei denen wir einen Bahnhof gefunden haben. Diese Herangehensweise führt bei natürlicher Sprache schnell zu Relativsatzungetümen; auch in formaler Sprache wird das nicht besser.
Daher folgt die Abfragesprache der Ovepass API stattdessen einem Schritt-für-Schritt-Paradigma, der sogenannten imperativen Programmierung. Zu jedem Zeitpunkt wird nur eine überschaubare Aufgabe gelöst, und die komplexe Aufgabe durch Aneinanderreihung bewältigt. Das Herangehen ist dann wie folgt:
Das ergibt Zeile für Zeile folgende Abfrage. Sie können sie jetzt ausführen:
nwr[public_transport=station]({{bbox}}); nwr[shop=supermarket](around:100); out center;
Die Details der Syntax werden später erläutert.
Für einfachere Fälle mag man zwar eine noch einfachere Syntax wünschen, aber die entstehende Zwei-Zeilen-Lösung spiegelt die klare Aufgabenteilung wider:
nwr[shop=supermarket]({{bbox}}); out center;
Wir vergleichen die Abfrage nach einfach nur den Supermärkten im Sichtbarkeitsbereich
nwr[shop=supermarket]({{bbox}}); out center;
mit der obigen Abfrage
nwr[public_transport=station]({{bbox}}); nwr[shop=supermarket](around:100); out center;
um die einzelnen Komponenten zu identifizieren.
Das wichtigste Zeichen ist das Semikolon; es beendet jeweils ein Statement. Zeilenumbrüche, Leerzeichen (und Tabulatoren) sind dafür und auch für die Syntax insgesamt irrelevant. Diese Statements werden nacheinander in der Reihenfolge ausgeführt, in der sie aufgeschrieben sind. In beiden Abfragen gibt es also zusammen vier Statements:
nwr[shop=supermarket]({{bbox}});
nwr[public_transport=station]({{bbox}});
nwr[shop=supermarket](around:100);
out center;
Das Statement out center
ist ein Ausgabestatement ohne weitere Unterstrukturen.
Die Möglichkeiten, das Ausgabeformat zu steuern, werden im Abschnitt Datenformate thematisiert.
Die übrigen Statements sind alle query-Statements, d.h. sie dienen dazu Objekte anzuwählen.
Dies gilt für alle mit nwr
beginnenden Statements und weitere spezielle Schlüsselwörter:
die Schlüsselwörter node
, way
und relation
wählen gezielt die entsprechende Objektart,
und nwr
(kurz für nodes, ways, relations) lässt alle Objektarten im Ergebnis zu.
Die query-Statements haben hier mehrfach auftretende Unterstrukturen:
[shop=supermarket]
und [public_transport=station]
({{bbox}})
(around:100)
Alle Unterstrukturen eines query-Statements filtern die anzuwählenden Objekte und heißen daher Filter. Es ist möglich, beliebig viele Filter in einem Statement zu kombinieren; das query-Statement wählt genau solche Objekte an, die alle Filter erfüllen. Die Reihenfolge der Filter spielt keine Rolle, denn die Filter eines Statements werden gleichzeitig angewendet.
Während [shop=supermarket]
und [public_transport=station]
alle Objekte zulassen,
die ein spezifisches Tag besitzen (Supermärkte im einen Fall, Bahnhöfe im anderen),
dienen ({{bbox}})
und (around:100)
der räumlichen Filterung.
Der Filter ({{bbox}})
lässt genau solche Objekte zu,
die ganz oder teilweise in der übergebenen Bounding-Box liegen.
Etwas komplizierter arbeitet (around:100)
.
Es benötigt eine Vorgabe und lässt genau alle Objekte zu,
die zu irgendeinem der Vorgabe-Objekte einen Abstand von höchstens 100 Metern haben.
Hier greift die Schritt-für-Schritt-Ausführung:
Der Filter (around:100)
erhält hier als Eingabe exakt die in der vorhergehenden Zeile ausgewählten Bahnhöfe.
Wie kann man eine Oder-Verknpüfung erreichen? Auf diese Weise findet man alle Objekte, die ein Supermarkt oder ein Bahnhof sind:
( nwr[public_transport=station]({{bbox}}); nwr[shop=supermarket]({{bbox}}); ); out center;
Hier bilden die beiden query-Statements einen Block innerhalb einer größeren Struktur. Die durch die Klammern gekennzeichnete Struktur heißt daher Block-Statement.
Diese spezielle Block-Struktur heißt union, und sie dient dazu, mehrere Statements so zu verknüpfen, dass sie alle Objekte anwählt, die in irgendeinem der Statements im Block gefunden werden. Es muss mindestens eine und es können beliebig viele Statements im Block stehen.
Es gibt zahlreiche weitere Block-Statements:
Nicht geklärt ist damit, wie im Block-Statement if oder auch for die Bedingungen formuliert werden können.
Der dafür genutzte Mechanismus hilft aber auch für andere Aufgaben. Man kann damit z.B. eine Liste aller Straßennamen in einem Gebiet erstellen.
[out:csv(name)]; way[highway]({{bbox}}); for (t["name"]) { make Beispiel name=_.val; out; }
Die Zeilen 2 und 6 enthalten die einfachen Statements way[highway]({{bbox}})
bzw. out
.
Mit [out:csv(name)]
in Zeile 1 wird das Ausgabeformat gesteuert (siehe dort).
Die Zeilen 3, 4 und 7 bilden das Block-Statement for (t["name"])
;
dieses muss wissen, nach welchem Kriterium es gruppieren soll.
Dies wird durch den Evaluator t["name"]
beantwortet.
Ein Evaluator ist ein Ausdruck,
der im Rahmen der Ausführung eines Statements ausgewertet sind.
Hier handelt es sich um einen Ausdruck, der pro Element ausgewertet wird,
da for pro Element Informationen benötigt.
Der Ausdruck t["name"]
wertet zu einem Objekte den Wert von dessen Tag mit Schlüssel name aus.
Hat das Objekt kein Tag mit Schlüssel name,
so liefert der Ausdruck eine leere Zeichenkette als Wert.
Zeile 5 enthält mit _.val
ebenfalls einen Evaluator.
Hier geht es darum, den auszugebenden Wert zu erzeugen.
Das Statement make erzeugt stets nur ein Objekt aus potentiell vielen Objekten,
daher darf der Wert von _.val
nicht von einzelnen Objekten abhängen.
Der Evalutor _.val
liefert innerhalb einer Schleife den Wert des aktuellen Schleifenausdrucks,
hier also den Wert des Tags name aller hier einschlägigen Objekte.
Wenn ein unabhängiger Wert erwartet, aber ein objektabhängiger Wert angegeben wird, führt dies zu einer Fehlermeldung. Das passiert z.B., wenn wir uns die Längen der Straßen ausgeben lassen wollten: Probieren Sie es bitte aus:
[out:csv(length,name)]; way[highway]({{bbox}}); for (t["name"]) { make Beispiel name=_.val,length=length(); out; }
Die verschiedene Segmente einer Straße gleichen Namens können verschiedene Längen haben. Wir können dies beheben, indem wir vorgeben, auf welche Art die Objekte zusammengefasst werden sollen. Häufig möchte man eine Liste:
[out:csv(length,name)]; way[highway]({{bbox}}); for (t["name"]) { make Beispiel name=_.val,length=set(length()); out; }
In diesem speziellen Fall dürfte aber Summieren sinnvoller sein:
[out:csv(length,name)]; way[highway]({{bbox}}); for (t["name"]) { make Beispiel name=_.val,length=sum(length()); out; }
Das Statement make erzeugt immer genau ein neues Objekt, ein sogenanntes Derived (von englisch: abgeleitet). Warum überhaupt ein Objekt, warum nicht einfach ein OpenStreetMap-Objekt? Die Gründe dafür variieren von Anwendung zu Anwendung: hier brauchen wir etwas, das wir ausgeben können. In anderen Fällen möchte man Tags von OpenStreetMap-Objekten ändern und entfernen oder die Geometrie des OpenStreetMap-Objekts vereinfachen oder braucht einen Träger für spezielle Information. Scheinbare OpenStreetMap-Objekte müssen den Regeln für OpenStreetMap-Objekte folgen und lassen daher viele hilfreiche Freiheiten nicht zu. Vor allem aber könnten sie mit echten OpenStreetMap-Objekten verwechselt und irrtümlich hochgeladen werden.
Die erzeugten Objekte können Sie sehen, wenn Sie als Ausgabeformat es bei XML belassen:
way[highway]({{bbox}}); for (t["name"]) { make Beispiel name=_.val,length=sum(length()); out; }
In vielen Fällen kommt man aber mit einer einzigen Auswahl nicht aus. Daher können Auswahlen auch in benannten Variablen abgelegt und so mehrere Auswahl gleichzeitig behalten werden.
Wir wollen alle Objekte der einen Art finden, die nicht in der Nähe von Objekten der anderen Art sind. Praxisnähere Beispiel sind dabei häufig eher Suche nach Fehlern, z.B. Bahnsteige ohne Gleise oder Adressen ohne Straße. Wir werden uns aber jetzt nicht mit Feinheiten des Taggings auseinandersetzen.
Wir ermitteln daher alle Supermärkte, die nicht in der Nähe von Bahnhöfen sind:
nwr[public_transport=station]({{bbox}})->.all_stations; ( nwr[shop=supermarket]({{bbox}}); - nwr._(around.all_stations:300); ); out center;
In Zeile 3 wählt das Statement nwr[shop=supermarket]({{bbox}})
alle Supermärkte in der Bounding-Box aus.
Wir wollen davon eine Teilmenge abziehen und verwenden daher ein Block-Statement vom Typ difference;
dieses ist an den drei Komponenten (
in Zeile 2, -
in Zeile 4 und );
in Zeile 5 zu erkennen.
Wir müssen Supermärkte in der Nähe von Bahnhöfen auswählen.
Dazu müssen wir wie oben vorher die Bahnhöfe gewählt haben;
wir brauchen aber auch alle Supermärkte als Auswahl.
Daher leiten wir die Auswahl der Bahnhöfe durch die getrennte Set-Variable all_stations
.
Sie wird in Zeile 1 von einem gewöhnlichen Statement nwr[public_transport=station]({{bbox}})
mittels der Syntax ->.all_stations
in eben diese Variable geleitet.
Der Zusatz .all_stations
in (around.all_stations:300)
sorgt dann dafür,
dass diese Variable als Quelle anstelle der letzten Auswahl verwendet wird.
Damit wäre nwr[shop=supermarket]({{bbox}})(around.all_stations:300)
das richtige Statement,
um die genau zu entfernenen Supermärkte anzuwählen.
Zur Verkürzung der Laufzeit nutzen wir aber lieber die Auswahl des unmittelbar vorhergehenden Statements in Zeile 3 - dort stehen ja genau die Supermärkte in der Bounding-Box drin.
Dies passiert mittels des Filters ._
.
Es schränkt die Auswahl auf solche Ergebnisse ein,
die beim Start des Statements in der Eingabe stehen.
Da wir hier die Standardeingabe benutzt haben,
sprechen wir sie über ihren Namen _
(einfacher Unterstrich) an.
Der Ablauf mit Datenfluss nocheinmal im Detail:
->.all_stations
sind danach alle Bahnhöfe als all_stations
ausgewählt;
die Standardauswahl bleibt dagegen leer.nwr[shop=supermarket]({{bbox}})
ausgeführt.
Zeile 3 hat keine Umleitung,
so dass danach alle Supermärkte in der Standard-Auswahl ausgewählt sind.
Die Auswahl all_stations
wird nicht erwähnt und bleibt daher erhalten.._
als Einschränkung für sein Ergebnis,
und zusätzlich wird per (around.all_stations:300)
die Auswahl all_stations
als Quelle für die Umkreissuche around herangezogen.
Das Ergebnis ist die neue Standard-Auswahl und ersetzt daher die vorherige Standard-Auswahl.
Die Auswahl all_stations
bleibt unverändert.all_stations
bleibt nach wie vor unverändert.out
als Quelle die Standard-Auswahl.weiter: Grundsätze