Anleitung

Änderungen in OpenStreetMap zwischen zwei Daten

Welche Restaurants in einem Teil einer Stadt zwischen zwei Daten eröffnet, geschlossen oder geändert wurden, aus dem Verlauf von OpenStreetMap, mit einer Overpass-Abfrage und einem kurzen Python-Skript.

Führen Sie die Abfrage mit [adiff:"START","ENDE"] aus. Jede <action> in der Antwort ist eine Änderung: create für ein neues Element, delete mit visible="false" für ein gelöschtes, delete mit visible="true" für eines, das noch auf der Karte ist, aber nicht mehr passt, und modify für eine Bearbeitung. Eine Version, die nicht zur Abfrage passt, kommt ohne Tags. Fragen Sie die Elemente der dritten Art am Enddatum nach ihrer ID ab, um zu sehen, was daraus wurde.

Das Skript

Die Restaurants im Niederdorf, der Zürcher Altstadt östlich der Limmat, vom 1. Januar 2023 bis zum 1. Januar 2025. Es braucht Python 3 und sonst nichts; der Schlüssel kommt aus der Umgebungsvariablen OVERPASS_CH_KEY.

import json
import os
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET

URL = "https://api.overpass.ch/v1/" + os.environ["OVERPASS_CH_KEY"] + "/interpreter"
BBOX = "47.369,8.541,47.376,8.547"  # the Niederdorf in Zurich
START, END = "2023-01-01T00:00:00Z", "2025-01-01T00:00:00Z"


def overpass(query):
    data = urllib.parse.urlencode({"data": query}).encode()
    return urllib.request.urlopen(URL, data=data, timeout=90).read()


def name(el):
    t = el.find("tag[@k='name']")
    return t.get("v") if t is not None else f"{el.tag} {el.get('id')}"


# 1. every change to the restaurants of the area between the two dates
root = ET.fromstring(overpass(f"""[out:xml][timeout:60][adiff:"{START}","{END}"];
nw({BBOX})(if:t["amenity"]=="restaurant");
out meta center;"""))

gone = {}  # still on the map, but no longer a restaurant
edited = 0
for action in root.findall("action"):
    kind = action.get("type")
    old, new = action.find("old/*"), action.find("new/*")
    # a version that does not match the query comes without tags
    if kind == "create" or (kind == "modify" and not old.findall("tag")):
        el = new if new is not None else action[0]
        print(f"opened   {name(el)}  ({el.get('timestamp')[:10]}, {el.get('user')})")
    elif kind == "delete" and new.get("visible") == "false":
        print(f"deleted  {name(old)}  ({new.get('timestamp')[:10]}, {new.get('user')})")
    elif kind == "delete":
        gone[(old.tag, int(old.get("id")))] = name(old)
    else:
        edited += 1

# 2. what the others are at the end date
if gone:
    ids = "".join(f"{t}({i});" for t, i in gone)
    for e in json.loads(overpass(f'[out:json][date:"{END}"];({ids});out tags;'))["elements"]:
        tags = e.get("tags", {})
        now = next((f"{k}={v}" for k, v in tags.items() if k in ("amenity", "shop") or k.startswith("disused:")), "no tags")
        print(f"changed  {gone[(e['type'], e['id'])]}: now {now}")
print(f"{edited} other restaurants were edited (opening hours, website, ...)")

Das Ergebnis

opened   Da Raffaele  (2024-09-27, 1ucian0)
opened   Rank  (2023-05-27, Fabilando)
opened   Rank  (2024-10-26, paulbrunner)
opened   Wien Zeile  (2024-07-23, 1ucian0)
deleted  Kormasutra  (2023-04-24, paulbrunner)
opened   Rising Sun  (2023-04-24, paulbrunner)
deleted  Bauernschänke  (2023-04-22, paulbrunner)
deleted  Long Wai  (2023-04-17, d_berger)
deleted  node 9270127717  (2023-04-17, d_berger)
deleted  node 9870177417  (2023-11-02, baprischka)
opened   Hirschli  (2023-09-02, 1ucian0)
opened   roots  (2023-09-02, 1ucian0)
opened   Restaurant zum weissen Kreuz  (2024-04-01, d_berger)
changed  Enzian vegan food: now amenity=fast_food
changed  Isebähnli: now amenity=bar
changed  The Butcher: now disused:amenity=restaurant
changed  Öpfelchammer: now disused:amenity=restaurant
changed  barfüsser: now no tags
61 other restaurants were edited (opening hours, website, ...)

Beide Anfragen zusammen dauerten weniger als 3 Sekunden. Eine Abfrage desselben Gebiets mit [out:json] an jedem der beiden Daten ergibt dieselben Summen: 8 Restaurants mehr, 10 weniger, 61 bearbeitet.

So lesen Sie es

  • Eine Eröffnung ist ein Element, das am Enddatum zur Abfrage passt und am Anfang nicht: ein neuer Punkt, oder ein bestehender, der zum Restaurant wurde. In der Antwort ist die zweite Art ein modify, dessen alte Version keine Tags hat.
  • Eine Löschung ist nicht immer eine Schliessung. Bauernschänke war ein zweiter Punkt für das Restaurant Bauern Schänke; wer am 22. April 2023 den ersten Punkt bearbeitete, löschte am selben Tag den zweiten. Suchen Sie in der Nähe nach demselben Namen, bevor Sie eine Schliessung zählen.
  • Ein Element, das nicht mehr passt, ist noch auf der Karte. In diesem Gebiet ist Enzian vegan food heute ein Burger King, Isebähnli eine Bar, und The Butcher und Öpfelchammer tragen disused:amenity=restaurant, den Tag für einen Ort, der geschlossen hat.
  • out meta center fügt Zeit und Benutzer jeder Version hinzu und einen Mittelpunkt für Gebäude, damit Sie die Änderungen auf eine Karte setzen können.

Andere Orte und andere Tags

  • Ein anderes Gebiet: Ändern Sie BBOX (Süd, West, Nord, Ost), oder verwenden Sie area["name"="…"]->.a; nw(area.a). Flächenfilter verwenden die Grenze von heute, auch an einem früheren Datum.
  • Läden statt Restaurants: (if:t["shop"]!=""). Filter mit if: sind an einem früheren Datum viel schneller als Filter in eckigen Klammern.
  • Die Änderungen der letzten Woche, jede Woche: Nehmen Sie dafür eine gespeicherte Abfrage auf den aktuellen Daten. Gespeicherte Abfragen laufen nach Zeitplan und melden, was seit dem letzten Lauf hinzukam, wegfiel oder sich änderte.

Bei overpass.ch

Verlaufsabfragen gehören zu den Tarifen Medium und Large; andere Tarife erhalten HTTP 403 history_not_in_plan. Der Verlauf beginnt am 12. September 2012. Overpass-Verlaufsabfragen erklärt [date:], [diff:] und [adiff:], und das Beispiel mit denselben Restaurants zeigt sie auf einer Karte.