Turbo-Overpass-Api: Ermittlung des Erstellungsdatums eines Elementes

Moin!

kann man zwischenzeitlich irgendwie auf das Datum der Ersterstellung eines Objektes zugreifen?

Gruß Jan

Das ist nun zwar schon ein paar Tage alt, aber ich habe einmal etwas Lustiges gebastelt, was auch viele weitere Nutzungsmöglichkeiten bei Overpass-Turbo eröffnet.

Wem jetzt zu viel Text folgt: Hier ist der Link zur Live-Demo, die abhängig vom Datum der Ersterstellung einer Node (Cafes in der Lübecker Innenstadt) diese rot (vor dem 01.01.2010 erstellt), gelb (zwischen 01.01.2010 und 31.12.2013 erstellt) oder grün (ab dem 01.01.2014 erstellt) einfärbt.

Im Vorfeld noch ein paar Infos bezüglich der History: Overpass-Turbo selbst bietet normalerweise keine automatisierte Zugriffsmöglichkeit auf die Zeitstempel. Man kann lediglich händisch auf der Karte auf ein Ergebnisobjekt klicken, im Popup auf die ID drücken und gelangt dann auf die OSM-Seite des jeweiligen Objektes, wo man über den bekannten Weg von “Chronik anzeigen” die History inklusive der Zeitstempel der verschiedenen Versionen sehen kann (wenn man mit der Maus über das Alter fährt). Auch die von Overpass-Turbo verwendete Overpass-API bietet nur unvollständigen und zudem äußerst umständlichen Zugriff auf vorherige Versionen eines Objekts (der älteste abrufbare Stand stammt vom ersten ODbL-kompatiblen Planetfile und ist der 12.09.2012 06:55:00 UTC - ältere Versionen eines Objektes als diejenige, die zu dem Zeitpunkt aktuell war, lassen sich dort nicht abrufen). Die einzig vollständige History kann man automatisiert nur über die OSM-Server selbst abrufen.

Die Idee ist nun, Overpass-Turbo live etwas zu modifizieren, um eigenen JavaScript-Code dort auf den Ergebnisobjekten auszuführen und so die zusätzlich gewünschten Informationen von den OSM-API-Servern abzurufen und verfügbar zu machen. Die Möglichkeit, über MapCSS eval() ausführen zu können, bietet einen passenden Ansatzpunkt, so dass man mit ein paar Tricks dort ein JavaScript-“Plugin” einfügen kann. Dazu braucht man einfach nur folgende Style-Definition bei Overpass-Turbo einfügen:

{{style:
  _jsPlugin {
    text: eval('if (!window._jsPlugin) eval(decodeURIComponent(atob(decodeURIComponent(/KGZ1bmN0aW9uJTIwKCklN0J3aW5kb3cuX2pzUGx1Z2luJTNEITAlMEF2YXIlMjBlJTNEJTIyZXZhbCgnaWYlMjAoIXdpbmRvdy5fanNQbHVnaW4pJTIwZXZhbChkZWNvZGVVUklDb21wb25lbnQoYXRvYihkZWNvZGVVUklDb21wb25lbnQoJTJGJTIyJTJDdCUzRG9zbXRvZ2VvanNvbi50b0dlb2pzb24lMEFvc210b2dlb2pzb24udG9HZW9qc29uJTNEZnVuY3Rpb24obyklN0J2YXIlMjBuJTNEdC5jYWxsKHRoaXMlMkNvKSUyQ3MlM0R0dXJiby5xdWVyeSgpJTBBcmV0dXJuJTIwcy5wYXJzZShpZGUuZ2V0UmF3UXVlcnkoKSUyQyU3QiU3RCUyQ2Z1bmN0aW9uKCklN0IlN0QpJTJDcy5oYXNTdGF0ZW1lbnQoJTIyc3R5bGUlMjIpJTI2JTI2LTEhJTNEJTNEcy5nZXRTdGF0ZW1lbnQoJTIyc3R5bGUlMjIpLmluZGV4T2YoZSklM0YlMjJGZWF0dXJlQ29sbGVjdGlvbiUyMiUzRCUzRCUzRG4udHlwZSUyNiUyNm4uZmVhdHVyZXMlMjYlMjZzLmhhc1N0YXRlbWVudCglMjJqYXZhc2NyaXB0JTIyKSUyNiUyNkZ1bmN0aW9uKCUyMmZlYXR1cmVzJTIyJTJDcy5nZXRTdGF0ZW1lbnQoJTIyamF2YXNjcmlwdCUyMikpKG4uZmVhdHVyZXMpJTNBKG9zbXRvZ2VvanNvbi50b0dlb2pzb24lM0R0JTJDZGVsZXRlJTIwd2luZG93Ll9qc1BsdWdpbiklMkNuJTdEJTdEKSgp/.toString().slice(1, -1)))))');
  }
}}

Die sieht sehr komisch aus, ich erkläre ganz unten die technischen Details, die man nicht unbedingt wissen muss. Das sorgt dafür, dass dann neben {{style: xxx}} auch ein Abschnitt der Form

{{javascript: 
  Hier Code einfügen.
}}

interpretiert wird. Der dortige JavaScript-Code wird immer aufgerufen, nachdem das gesamte Ergebnis der Abfrage von Overpass-Turbo empfangen, geparst und in GeoJSON umgewandelt wurde - aber bevor Styles darauf angewendet werden und es dargestellt wird. Einzige Bedingung ist, dass im JavaScript-Code selbst keine zwei schließenden geschweiften Klammern unmittelbar hintereinander vorkommen (in dem Fall Leerzeichen einfügen oder Strings in zwei Teile trennen), weil das das Ende des JavaScript-Blocks darstellt. Dem Code wird die Variable features zur Verfügung gestellt, welche ein Array mit den GeoJSON-Ergebnisobjekten darstellt.

In unserem Fall bauen wir dort für jedes Ergebnisobjekt eine Abfrage beim OSM-API-Server nach der ersten Version des Objektes ein, parsen den entsprechenden Zeitstempel und fügen ihn unter dem Key _creationDate im lesefreundlichen Format und unter dem Key _creationTS als Zahlenwert in Form von Millisekunden seit dem 01.01.1970 00:00 UTC dem Objekt hinzu. Den Key _creationTS nutzen wir dann, um mittels MapCSS die Objekte unterschiedlich einzufärben. Bei den Vergleichsoperationen ist lediglich zu beachten, dass “>=” und “<=” in der MapCSS-Implementierung von Overpass Turbo nicht funktionieren.

So kann man also Overpass Turbo funktionell erheblich erweitern, indem man ihm JavaScript-Code mitgibt.

Ab hier ist es nur noch für Leute interessant, die an den technischen Details Interesse haben:

Die durch Overpass Turbo in MapCSS implementierte eval()-Funktion hat ein paar Haken: Außer in der äußersten Begrenzung des Strings dürfen im Ausdruck keine Anführungszeichen und Hochkomma vorkommen, es darf kein Semikolon vorkommen, es dürfen keine schließenden geschweiften Klammern vorkommen und Zeilenumbrüche werden vorher entfernt. Das sind alles denkbar schlechte Voraussetzungen dafür, dort mit JavaScript etwas Passendes zu programmieren. Aber auf trickreiche Weise geht es eben doch. Dazu wird der Code in einen regulären Ausdruck gepackt, denn dort braucht man für den Code-“String” keine Anführungszeichen, kann ihn aber mit toString() in einen normalen String überführen und muss nur noch die beiden Slashes vorn und hinten wegschneiden. Weil Programmcode aber meist kein gültiger regulärer Ausdruck ist, wird er vorher Base64 kodiert. Da die Base64-Kodierung aber Probleme mit Unicode-Zeichen machen kann, wird vorher encodeURIComponent() auf den Code angewendet, so dass sämtliche Sonderzeichen mit %-Kodierung ersetzt werden. Nach der Base64-Kodierung können aber wiederum Schrägstriche im Ausdruck auftreten, was sich wiederum mit den Begrenzungszeichen des regulären Ausdrucks nicht verträgt, also nochmals encodeURIComponent() darauf anwenden. Zum Ausführen wird das dann alles also in umgekehrter Reihenfolge “entpackt”:

decodeURIComponent(atob(decodeURIComponent(/RegExp/.toString().slice(1, -1))))

Die eigentliche Funktion, die dort dahintersteckt, sieht wie folgt aus:

(function() {
  window._jsPlugin = true;
  var signature = "eval('if (!window._jsPlugin) eval(decodeURIComponent(atob(decodeURIComponent(/";
  var origToGeojson = osmtogeojson.toGeojson;
  osmtogeojson.toGeojson = function(a_data) {
    var result = origToGeojson.call(this, a_data);
    var queryParser = turbo.query();
    queryParser.parse(ide.getRawQuery(), {}, function(){});
    if (queryParser.hasStatement('style') && (queryParser.getStatement('style').indexOf(signature) !== -1)) {
      if ((result.type === 'FeatureCollection') && result.features && queryParser.hasStatement('javascript')) {
        (new Function("features", queryParser.getStatement('javascript')))(result.features);
      }
    }
    else {
      osmtogeojson.toGeojson = origToGeojson;
      delete window._jsPlugin;
    }
    return result;
  }
})();

Dieser Code wurde lediglich noch mit UglifyJS komprimiert, damit das Ergebnis nach dem Kodieren nicht ganz so lang wird. Da die MapCSS-eval()-Funktionen bereits beim Parsen der Abfrage zum ersten Mal testweise ausgeführt werden, nutzen wir diesen Moment, um uns an den GeoJSON-Parser zu hooken. Damit das nicht mehrfach geschieht und um zu vermeiden, dass das Dekodieren des Codes und der innere Eval-Ausdruck mehrfach zeitaufwendig ausgeführt werden, wird die globale Variable window._jsPlugin gesetzt. Umgekehrt muss aber auch darauf geachtet werden, dass wenn der Nutzer Änderungen an der Abfrage vornimmt und den Code für das “Plugin” entfernt, der Ursprungszustand wieder hergestellt werden muss (es wird gesucht, ob die eigene Signatur in der aktuellen Abfrage noch innerhalb des Style-Bereichs vorkommt).

Da wir diesen Code in den MapCSS-Style-Bereich mit dem eigenen Selektor _jsPlugin einbringen, der auf kein gültiges Objekt zutreffen kann, kommt es auch nicht zu Konflikten mit anderen Style-Definitionen.

Moin!

sieht schon einmal klasse aus. Leider konnte ich das Ergebnis für wenig Daten auf dem Smartphone nicht testen.
Kein Ergebnis .

Wäre schön, wenn das als Funktion zur Verfügung stehen könnte - dann wäre das vielleicht Performance.

Gruss Jan

Das hat nichts mit der Performance des Codes zu tun, sondern der standardmäßig eingestellte Overpass-API-Server (overpass-api.de) funktioniert im Moment schlicht und einfach nicht. Wenn man in Overpass-Turbo unter den Einstellungen einen der anderen Server wählt und “Speichern” klickt, sollte es funktionieren.

4 kurze Anmerkungen dazu:

  1. SSL-Zertifikat auf overpass-api.de ist gestern Abend abgelaufen, Roland ist informiert. → erledigt
  2. Beliebigen Javascript-Code in turbo auszuführen ist m.E. keine gute Idee, habe Martin Raifer dazu kontaktiert.
  3. OSM API ist eigentlich nur zum Editieren gedacht, nicht jedoch für solche Auswertungen
  4. Für Overpass API gibt es das folgende Ticket zu dem Thema: https://github.com/drolbr/Overpass-API/issues/282
  • zu 2.: Ich fände ein solches Feature dagegen ungeheuer praktisch und wäre sogar dafür, {{javascript:}} regulär einzuführen. Damit kann man auch problemlos solche Dinge ohne Veränderung der OSM-Daten lösen, indem man einfach die passende Geometrie für die von Overpass zurückgelieferten Objekte setzt. Es ergeben sich daraus extrem viele neue oder erweiterte Nutzungsmöglichkeiten.

    Natürlich kann man damit auch Quatsch machen (beispielsweise Scare-Meldungen anzeigen). Aber da Overpass-Turbo ohnehin komplett auf dem Client läuft, wird dadurch nicht die Sicherheit eines Servers gefährdet. Lediglich der Nutzer selbst könnte hier falsche Erwartungen haben, bezüglich dem, was ihn erwartet. Aber letztlich ist das auch nicht anders als das, was ihm bei jedem anderen Klick auf einen Link auch passieren kann.

  • zu 3.: Das ist mir bewusst, aber es fehlen die Alternativen (im Gegensatz zu einer ganzen Menge anderer Dinge, für die das OSM-API derzeit sonst noch so verwendet wird). Denn eine vollständige History hält niemand sonst vor. Overpass selbst hat nur eine rudimentäre History, durch die man sich umständlich mit passend modifizierten Date-Zeitstempeln durchhangeln müsste. Selbst wenn man da mehrere Objekte auf einmal abfragt, um so mit Glück nicht nur ein Objekt in der passenden Version zu erhalten, sind das immer noch eine ganze Menge Anfragen. Zudem hat laut Wiki nur overpass-api.de überhaupt einen Attic und dieser Server ist ziemlich überlastet, so dass die Antwortzeiten relativ lang sind und er häufig auch mal eine 429 wirft, selbst bei rein sequentiellen Anfragen. Und witzigerweise fragst du im verlinkten Issue selbst, warum jemand für solche Dinge Overpass nehmen will, wenn API 0.6 das doch macht. :slight_smile:

Das ist korrekt und mittlerweile auch nicht mehr möglich, durch die längst überfällige Implementierung eines wirklichen MapCSS-eval Parsers (anstatt dem “temporären” Javascript-eval()-basierten Hacks).

[…] dafür, {{javascript:}}regulär einzuführen.

So ein Feature hatte ich tatsächlich anfangs für Overpass Turbo angedacht (bin dann aber nie dazu gekommen es zu implementieren), würde heute allerdings eher zu einem Plugin-System tendieren, mit dem (ähnlich wie in QGIS) spezifische Zusatzfunktionen dynamisch hinzugeladen werden können.

Ich würde dich dazu einladen, mal ein Ticket dazu auf Github aufzumachen.

Finde ich von der Idee her nicht so prickelnd. Overpass-Turbo ist in meinen Augen etwas, um schnell irgendeine individualisierte Auswertung vornehmen zu können - meist sogar nur ein Einzelfall. Das bedeutet aber, dass das Anforderungsspektrum extrem breit und wenig repetitiv ist. Man brauch also wenig spezifische Funktionen, sondern eine allgemeine Bastelkiste/Frickelplattform. Vorgegebene Plugins würden nur relativ viel Arbeit für relativ seltenen Einsatz machen und umgekehrt würde dann doch wieder genau die Funktion fehlen, die in irgendeinem Fall praktisch gewesen wäre.

Du hast im Prinzip schon recht, was die geringere Flexibilität einer solchen Lösung angeht. Andererseits kann sowieso nur ein Pluginsystem (mit dokumentierten Schnittstellen) sicherstellen, dass eine einmal entwickelte Lösung überhaupt dauerhaft funktioniert, auch wenn z.B. im Hintergrund Dinge umprogrammiert werden: Ein Plugin könnte man auf die jeweils aktuelle Version der Basis-Software updaten, (ein in die Query integriertes Javascript hingegen nicht).

Was würde denn dagegen sprechen, flexibel einsetzbare Plugins zu entwickeln? Z.B. wie hier: Nicht ein Plugin, das nur das Erstellungsdatum eines Elements holt, sondern ein Plugin, das beliebigen Content von einer externen URL zu jedem Objekt nachläd? (Einzubinden etwa über {{plugin:load-external-data-per-object,url=https://www.openstreetmap.org/api/0.6///1,format=xml,fields=meta:timestamp}}).

Das ist jetzt der Versuch den Einzelfall zu verallgemeinern. Vermutlich könnte man da auch ein schönes Plugin bauen. Aber morgen kommt jemand und sagt, er braucht das nicht auf allen, sondern nur auf bestimmten Objekten des Ergebnisses, die in irgendeiner Weise zu selektieren sind. Oder aber es ist eine mehrstufige Anfrage in Abhängigkeit des Resultates der ersten Anfrage (beispielsweise wenn man zur Schonung der OSM-API auf Overpass geht und sich dort durch die Versionszeitstempel durchfragt - hatte ich tatsächlich zuerst so gemacht, aber der Overpass-API-Server arbeitet da einfach zu unzuverlässig und zu langsam).

Und dann kommt jemand auf ganz verrückte Ideen, z. B., dass er die Geometrie eines Objektes in der Darstellung geändert haben will (war schon verlinkt) oder dass man Weglängen oder Gebietsgrößen braucht, weil man die kürzeste und längste Buslinie einer Stadt bestimmen will oder den größten Park sucht. Lauter Dinge eben, die vorher nie jemand gebraucht hat, vielleicht auch nie wieder jemand braucht, aber die man halt einfach mal in diesem Moment wissen will.

Da wäre man mit einem Plugin-System immer zu schwerfällig und hinten dran. Und dann macht man sich umgekehrt viel Arbeit mit der Pflege, aber eigentlich braucht es nur sehr selten jemand. Natürlich kann eine einmal entwickelte Lösung durch Änderungen plötzlich nicht mehr funktionieren (beispielsweise meine hier :wink: ). Aber das kann auch sein, weil ein bestimmter Server nicht mehr verfügbar ist oder weil sich eine API oder ein Datenformat geändert hat. Das Risiko hat man also immer. Wenn es dann wirklich noch einmal jemand braucht (die Halbwertszeit der meisten über Overpass-Turbo abgefragten Informationen ist ja eher gering), muss man es halt anpassen oder sich etwas Neues einfallen lassen.

Ich mag auch für den generischen Ansatz von Kontinentalverschieber: diese {{javascript: …}}-Implementierung tut niemandem weh, wenns kaputtgeht ists eigenes Pech.

Den “Plugins für OT vereinfachen”-Ansatz mag ich allerdings auch.

Nunja, sinnbefreite Popups wären noch das kleinste Problem. Man kann sich auch mit wenig Fantasie beliebige andere Szenarien überlegen: sämtliche Einstellungen in turbo manipulieren und permanent speichern, alle gespeicherte Queries löschen oder mit Unsinn-Queries vollmülllen oder an irgendwelche anderen Server übertragen, irgendwelchen Schadcode nachladen, DoS gegen andere Server fahren, ohne dass der Aufrufer der Query das großartig merken würde, …

Und da die meisten Leute wohl turbo z.B. in NoScript gewhitelisted haben, reicht ein einfacher Post eines Shortlink irgendwo (pseudo)anonym - der Code liegt dann bei Martin auf dem Server und wartet nur auf den ersten Deppen der draufklickt. Zusammen mit Autorun lassen sich herrliche Sachen anstellen.

Daher: Javascript in overpass turbo? Muss nicht unbedingt sein.

Ihr könnt gerne lokal sowas bauen, aber auf den Plattformen die hier so in allgemein genutzt werden, wird das Einstreuen von Javascript praktisch überall recht restriktiv gehandhabt (osm.org, Wiki, …).