Also ich hab’s dann mal auf meinem Bastelserver umgesetzt und funktioniert wie man hier sehen kann:
https://umap.openstreetmap.de/de/map/unbenannte-karte_14376#12/53.8902/10.6725
Da auf Bastelserver kein Zertifkat für die verwendete Domain existiert, zuvor einmal irgendeine Abfrage wie diese öffnen und dann bestätigen, dass man eine Ausnahme hinzufügt. Danach sollte es auch in uMap dann von dort ohne Meckermeldung laden können.
Wie man dann in uMap sieht, wird statt von Overpass 4x je BBox-Bewegung die Daten abzufragen, nun von https://a404.de/dnd/osm/multioverpass/index.php 4x Daten abgefragt. Hierbei enthält allerdings nicht wie zuvor jede Abfrage-URL auch nur eine Overpass-Abfrage, sondern sie enthält gleich alle Overpass-Abfragen, welche in dem Bereich passieren sollen.
Somit ist es möglich, dass der erste Aufruf vom “multioverpass”-Script halt gebündelt einmalig bei Overpass alles für die gegebene BBox abfragt und dann die Antwort zwischenspeichert. Alle drei weiteren Aufrufe von “multioverpass” (ich nenn das jetzt MOP) seitens uMap, werden dann aus dem Zwischenspeicher beantwortet.
Die Anfrage an MOP besteht aus mehreren Parametern:
-
target - gibt an, welche Antworten aus der Overpass-Anfrage man jetzt spezifisch haben möchte. Man hat ja in dem Falle 4 gleichzeitig gestellt und hiermit wählt man aus, welche man nun als Daten zurückgeliefert bekommt haben möchte. Dies ist der einzige Wert, welcher sich dann jeweils in den URL-Angaben in uMap ändert.
-
timeout - gibt das Timeout für die Overpass-Anfrage an (also für alle kombinierten)
-
cache - gibt an in Sekunden, ab wann eine Cache-Datei als veraltet gilt und neu abgefragt wird
-
bbox - gibt die BBox an
-
alle weiteren Parameter werden dann als Overpass-Anfragen verarbeitet. D.h. ein weiterer Parameter, dessen Name kann in “target” angegeben werden und der Wert wird an Overpass weitergeleitet
Beispiel:
In uMap kann man nun eintragen:
https://a404.de/dnd/osm/multioverpass/?target=signal_box
&timeout=60
&cache=30
&bbox={south},{west},{north},{east}
&disused_signal_box=nw["disused:railway"="signal_box"]({{bbox}});out center;
&signal_box=nw["railway"="signal_box"]({{bbox}});out center;
&water_tower=(nw["railway"="water_tower"]({{bbox}});nw["man_made"="water_tower"]({{bbox}});nw["disused:railway"="water_tower"]({{bbox}}););out center;
&abandoned_signal_box=nw["abandoned:railway"="signal_box"]({{bbox}});out center;
Wie man sieht, sind hier 4 weitere Parameter/Overpass-Anfragen enthalten (disused_signal_box, signal_box, water_tower und abandoned_signal_box) und in target wird angegeben, dass man allerdings nur die Werte für signal_box haben möchte. An Overpass wird aber alles 4 zusammen als eine Anfrage gestellt und die eine Rückantwort mit Ergebnissen für alle 4 Unterabfragen im Cache gespeichert.
Die nächste uMap-Ebene hat dann
https://a404.de/dnd/osm/multioverpass/?target=water_tower&timeout=60&cache=30&...
und so weiter.
BBox:
Ein weitere Vorteil beim Entwickeln ist nun:
Overpass-Turbo und uMap haben beide eine eingebaute Möglichkeit, die bbox zu setzen. In Overpass-Turbo via {{bbox}} und in uMap wie ich feststellen konnte auch via {bbox}. Allerdings unterscheidet es sich einmal daran, dass uMap nur einmal geschweifte Klammern verwendet und in der Reihenfolge der Werte (uMap = west,south,east,north vs. Overpass = south,west,north,east).
Ich händel das ganze jetzt ebenfalls via Script und man kann weiterhin in den Abfragen welche in uMap in der URL stehen {{bbox}} verwenden, obwohl dies dann eigentlich zerschossen wird zu “{10.123,53.123,11.123,53.001}” womit Overpass nichts anfangen kann, da es “53.123,10.123,53.001,11.123” erwarten würde. Aber mein Script friemelt da wieder gerade, weshalb es (u.a.) den Parameter bbox gibt. Man kann also eine Unterabfrage aus uMap 1:1 in Overpass-Turbo kopieren und schauen/bearbeiten.
multioverpass-Abschnitt in JSON-Antwort:
Achja und in der JSON-Antwort gibt es jetzt angereichert noch einen Abschnitt “multioverpass”, der wie folgt aussieht:
"multioverpass": {
"request_time": "2021-05-31T05:42:51+02:00",
"overpassURI": "https://overpass-api.de/api/interpreter?data=[out:json][timeout:60];make seperator \"name\" = \"signal_box\";out;nw[\"railway\"=\"signal_box\"](53.7892944367402,10.531425476074219,53.97931054041794,10.75492858886719);out center;",
"source": "overpass"
}
Hier kann man einerseits dann die URI sehen, welche letztlich an Overpass geschickt wurde. Zudem den Zeitpunkt von wann die Daten stammen. In “source” steht entweder “overpass” (dann wurde in diesem MOP-Request eben auch der Overpass-Request abgesendet) oder halt “cache”, dann kommen die Daten aus der Cache-Datei.
Das sieht man dann auch in der oberigen uMap mit den 4 Ebenen. 1x hat man dann im source “overpass” zu stehen und 3x halt “cache”.
ToDo:
-
Fehlerbehandlungen (Overpass antwortet nicht, etc.)
-
bbox - Verarbeitung ordentlich machen
-
direktes Löschen zu alter Caches
-
Typofehler in der URL “mulitoverpass” den ich gerade sehe -.- fixed
-
Tests
-
ggf. komplettes Redesign und saubereres aufsetzen. Aktuell hatte ich den Fokus auf Funktionsdemonstration und “alles in eine Script-Datei”.
Könnte mir vorstellen, dass es durchaus allgemein recht sinnvoll sein könnte, denn es entlastet die Overpass-API-Server und hatte ich bislang auch selbst vermisst.
Code:
Hier nun der komplette Code. Hab ein paar Kommentare zum Verständnis eingefügt, aber bei Fragen gern fragen.
<?
define(KEY_PARAMS, array('bbox', 'timeout', 'target', 'cache'));
$cacheDir = 'cache/';
if(!is_dir($cacheDir)) mkdir($cacheDir);
$filename = $cacheDir . md5($_REQUEST['bbox']) . ".json";
$f = fopen($filename, 'x');
if($f !== FALSE) { // Cachefile existiert nicht, API muss abgefragt werden
flock($f, LOCK_EX); // Lock auf leeres Cachefile setzen
// Overpass-Abfrage-URI zusammenbauen:
$timeout = (empty($_REQUEST['timeout'])) ? '60' : $_REQUEST['timeout'];
$fullRequest = '[out:json][timeout:'.$timeout.'];';
foreach($_REQUEST as $key => $value) {
if(!in_array($key, KEY_PARAMS)) {
$fullRequest .= 'make seperator "name" = "'.$key.'";out;';
$fullRequest .= $value;
}
}
$fullRequest = str_replace(' ', '%20', $fullRequest);
$fullRequest = solveStupidWrongUmapBBox($fullRequest, $_REQUEST['bbox']);
$overpassURI = 'https://overpass-api.de/api/interpreter?data='.$fullRequest;
// Overpass einmalig abfragen:
$overpassAnswer = file_get_contents($overpassURI);
if($overpassAnswer === FALSE) {
// Overpass-Anfrage fehlerhaft:
header('Content-Type: application/json');
$answer = array();
$answer['multioverpass'] = array();
$answer['error'] = "overpass-request failed, request URI: ".$overpassURI;
echo json_encode($answer);
flock($f, LOCK_UN);
fclose($f);
unlink($filename);
} else {
// Anreichern der Antwort mit Abschnitt multioverpass/request_time:
$overpassAnswerJSON = json_decode($overpassAnswer, true);
$overpassAnswerJSON['multioverpass'] = array();
$overpassAnswerJSON['multioverpass']['request_time'] = date('c');
$overpassAnswerJSON['multioverpass']['overpassURI'] = $overpassURI;
// Overpass-Antwort in Cache schreiben:
ftruncate($f, 0);
fwrite($f, json_encode($overpassAnswerJSON));
fflush($f);
flock($f, LOCK_UN); // Lock entfernen
fclose($f);
// spezifische Antwort extrahieren:
handleAnswer($overpassAnswerJSON, $_REQUEST['target']);
}
} else { // Cache vorhanden, Daten aus Cache auslesen:
$f = fopen($filename, 'r');
if(flock($f, LOCK_SH)){ // Warten auf Ende vom Schreiblock
// Auslesen gecachte Antwort:
$cachedAnswer = fread($f, filesize($filename));
fclose($f);
$cacheTimeout = (empty($_REQUEST['cache'])) ? '300' : $_REQUEST['cache'];
// spezifische Antwort extrahieren:
handleAnswer(json_decode($cachedAnswer, true), $_REQUEST['target'], $cacheTimeout);
}
fclose($f);
}
function handleAnswer($answerJSON, $target, $cacheTimeout = null) {
global $filename;
header('Access-Control-Allow-Origin: *');
header('Access-Control-Max-Age: 600');
header('Content-Type: application/json');
// spezifische Antwort in $jsonResult:
$jsonResult = array();
// alles schonmal außer 'elements' übertragen:
foreach($answerJSON as $key => $value) {
if($key != 'elements') {
$jsonResult[$key] = $value;
}
}
if($cacheTimeout !== null) { // Overpass-Antwort stammt aus Cache:
// prüfen ob Cache zu alt ist:
$request_time = strtotime($answerJSON['multioverpass']['request_time']);
if(time() - $request_time > $cacheTimeout) { // Cache zu alt, Cache-Datei löschen:
if(unlink($filename) !== FALSE) {
// Sender auffordern, erneut Anfrage auszulösen:
header('Location: '.$_SERVER['REQUEST_URI'], true, 303);
die();
} else {
$jsonResult['multioverpass']['warning'] = 'could not delete cache file, see request_time';
}
}
$jsonResult['multioverpass']['source'] = 'cache';
} else { // Overpass-Antwort stammt nicht aus Cache:
$jsonResult['multioverpass']['source'] = 'overpass';
}
// spezifisch angefragte Elemente aus 'elements' extrahieren:
$targetElements = array();
$foundTarget = false;
for($elementsPos = 0; $elementsPos < count($answerJSON['elements']); $elementsPos++) {
$element = $answerJSON['elements'][$elementsPos];
if($element['type'] == 'seperator') { // Seperator-Element gefunden:
if($foundTarget) { // Wenn foundTarget schon true ist, ist dies der 2. Seperator, somit Ende der angeforderten Elemente:
break; // Abbruch des Durchsuchens
}
if($element['tags']['name'] == $target) { // Seperator hat Name des gesuchten Abschnitts:
$foundTarget = true;
}
} else {
if($foundTarget) { // gesuchte Elemente übertragen
$targetElements[] = $element;
}
}
}
$jsonResult['elements'] = $targetElements;
echo json_encode($jsonResult);
}
// BBox-Werte sind unterschiedlich, daher... hmpf...
// {bbox} in uMap = west,south,east,north
// {{bbox}} in Overpass = south,west,north,east
function solveStupidWrongUmapBBox($uri, $bbox) {
$bboxParts = explode(',', $bbox);
$uMapBox = '{'.$bboxParts[1].','.$bboxParts[0].','.$bboxParts[3].','.$bboxParts[2].'}';
return str_replace($uMapBox, $bbox, $uri);
}
Gruß,
asca
PS: Gern kann für Eigenbedarf gern mein aktuell gehostetes MOP genutzt werden. Ich garantiere aber natürlich für nichts, auch wenn ich eigentlich es nicht löschen sollte (“dnd” steht in der URL für “do not delete” als Hinweis für mich selbst), aber es tut mir sicherlich nicht weh, wenn das jetzt jemand für seine private uMap-Karte mit zig Overpass-Abfrage-URLs benutzt.