Ä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.
[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 centerfü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 Siearea["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 mitif: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.