From b1ac6032d158787cbbb689ab4d13e197a2f37126 Mon Sep 17 00:00:00 2001 From: Marco Schmid Date: Tue, 21 Apr 2026 19:02:58 +0200 Subject: [PATCH] Task 4: logging, new modul queries --- main.py | 144 ++++++++++++++++++++-------------------------------- overpass.py | 97 +++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 90 deletions(-) create mode 100644 overpass.py diff --git a/main.py b/main.py index 8dafa70..aab2504 100644 --- a/main.py +++ b/main.py @@ -1,108 +1,72 @@ +from main_start import QUERY +from overpass import fetch_overpass import requests -from pprint import pprint -OVERPASS_URL = "https://overpass-api.de/api/interpreter" +# --------------------------------------------------------------------------- +# Konfiguration +# --------------------------------------------------------------------------- -BERGBAHN_QUERY = """ -[out:json][timeout:3][maxsize:500000]; -( - node["aerialway"="station"]({bbox}); - way["aerialway"="station"]({bbox}); - node["railway"="funicular"]({bbox}); - way["railway"="funicular"]({bbox}); - node["railway"="station"]["funicular"="yes"]({bbox}); -); -out center body; -""" +BBOXEN = { + "davos": (46.72, 9.70, 46.92, 10.00), + "schweiz": (45.8, 5.9, 47.8, 10.5), +} - -def fetch_bergbahnen(bbox: tuple) -> dict: +QUERY = """ + [out:json][timeout:2][maxsize:500000]; + ( + node["aerialway"="station"]({bbox}); + way["aerialway"="station"]({bbox}); + node["railway"="funicular"]({bbox}); + way["railway"="funicular"]({bbox}); + node["railway"="station"]["funicular"="yes"]({bbox}); + ); + out center body; """ - Fragt die Overpass API nach Bergbahnen in der angegebenen Bounding Box ab. - Sendet einen HTTP-POST-Request an die Overpass API und gibt die geparste - JSON-Antwort zurück. Die Funktion prüft den HTTP-Status-Code, bevor sie - versucht die Antwort als JSON zu parsen. - Args: - bbox (tuple): Bounding Box als 4-Tuple in Dezimalgrad: - (south, west, north, east) - Beispiel Davos: (46.72, 9.70, 46.92, 10.00) - Beispiel Schweiz: (45.8, 5.9, 47.8, 10.5) +# --------------------------------------------------------------------------- +# Hauptlogik +# --------------------------------------------------------------------------- +def main() -> None: - Returns: - dict: Geparste JSON-Antwort der Overpass API. Die Antwort enthält - unter dem Schlüssel "elements" eine Liste von OSM-Objekten - (nodes und ways) mit ihren Tags und Koordinaten. - Beispiel: - { - "elements": [ - { - "type": "node", - "id": 123456, - "lat": 46.8, "lon": 9.8, - "tags": {"aerialway": "station", "name": "Jakobshorn"} - }, - ... - ] - } - - Raises: - RuntimeError: Wenn die API einen HTTP-Fehlercode zurückgibt - (z.B. 429 Too Many Requests, 504 Gateway Timeout). - RuntimeError: Wenn der Response-Body kein gültiges JSON enthält - (z.B. bei leerem Body nach einem Server-Timeout). - requests.Timeout: Wenn die API nicht innerhalb einer bestimmten Zeit - (timeout) antwortet. - """ - bbox_str = ",".join(map(str, bbox)) - query = BERGBAHN_QUERY.format(bbox=bbox_str) - - resp = requests.post( - OVERPASS_URL, - data={"data": query}, - timeout=10, # clientseitiges timeout ist nicht serverseitiges timeout (das ist im query selbst auf z.B. 3 Sekunden gestellt) - headers={"User-Agent": "GeoService/1.0 (poi-generator)"}, - ) - - if resp.status_code != 200: - raise RuntimeError( - f"Overpass API Fehler: {resp.status_code}\n{resp.text}" - ) - try: - data = resp.json() - except requests.exceptions.JSONDecodeError: - raise RuntimeError( - f"Antwort ist kein gültiges JSON:\n{resp.text[:200]}" - ) - - # HTTP 200 bedeutet nicht immer Erfolg — Overpass meldet Fehler im Body unter 'remark'! - if "remark" in data: - raise RuntimeError( - f"Overpass Query-Fehler: {data['remark']}" - ) - - return data + for name, bbox in BBOXEN.items(): + try: + result = fetch_overpass(overpass_query=QUERY, bbox=bbox) + except RuntimeError as e: + print(f"API-Fehler bei '{name}': {e}") + continue + except requests.Timeout: + print(f"Timeout bei '{name}' — bbox zu gross oder Server überlastet") + continue + elements = result.get("elements", []) + print(elements) if __name__ == "__main__": + main() - bbox = (46.72, 9.70, 46.92, 10.00) - # bbox = (45.8, 5.9, 47.8, 10.5) - result = fetch_bergbahnen(bbox) - pprint(result) # Was ist passiert? - # Wir haben verschiedene Fehler (verschiedene Ebenen) abgefangen: - # Ebene 1 — Netzwerk: requests.Timeout → Client wartet vergebens - # Ebene 2 — HTTP: status_code != 200 → Server meldet Fehler - # Ebene 3 — Fachlich: "remark" im JSON-Body → Query lief, aber mit Fehler + # * Wir haben die Logik in ein eigenes Modul 'overpass' verfrachtet + # * Wir haben die Funktion von einer reinen Bergbahn-Logik befreit und für alle möglichen Overpass-Queries bereit + # gemacht (generalisiert) # TASK: - # * verlagert als nächstes die Logik von 'fetch_bergbahnen' in ein eigenes Modul 'overpass.py' aus. - # * nennt die Funktion allgemeiner 'fetch_overpass' (anstelle fetch_bergbahnen) -> somit würde es Sinn - # machen, wenn wir den Query der Funktion als Argument mitgeben könnten (ist genereller). - # * Erstellt in diesem main.py eine eigene 'main-Funktion', welche nur die Hauptlogik beinhalten und somit 'fetch_overpass' - # importiert und aufruft. \ No newline at end of file + + # * Jetzt wäre doch ein guter Zeitpunkt um anstelle der print-Statements das Logging einzubauen -> verwendet das + # Logging-Modul in Python und ersetzt die print-statements! + # * Hier haben wir einen weiteren Query für restaurants -> bildet ein neues Modul 'queries' und baut dort sowohl den + # Bergbahn- als auch den Restaurant-Query ein: + # + # RESTAURANT_QUERY = """ + # [out:json][timeout:15][maxsize:500000]; + # ( + # node["amenity"="restaurant"]({bbox}); + # way["amenity"="restaurant"]({bbox}); + # node["amenity"="cafe"]({bbox}); + # way["amenity"="cafe"]({bbox}); + # ); + # out center body; + # """ \ No newline at end of file diff --git a/overpass.py b/overpass.py new file mode 100644 index 0000000..028c27c --- /dev/null +++ b/overpass.py @@ -0,0 +1,97 @@ +import requests +from pprint import pprint + +OVERPASS_URL = "https://overpass-api.de/api/interpreter" + +def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: + """ Fragt die Overpass API mit einem beliebigen Query nach POIs in der angegebenen Bounding Box ab. + + Sendet einen HTTP-POST-Request an die Overpass API und gibt die geparste + JSON-Antwort zurück. Die Funktion prüft den HTTP-Status-Code, einen + allfälligen Fehler im Response-Body sowie die JSON-Struktur der Antwort. + + Args: + overpass_query (str): Overpass-QL-Query mit dem Platzhalter {bbox}. + Beispiel: + '[out:json][timeout:5]; + (node["aerialway"="station"]({bbox});); + out center body;' + bbox (tuple): Bounding Box als 4-Tuple in Dezimalgrad: + (south, west, north, east) + Beispiel Davos: (46.72, 9.70, 46.92, 10.00) + Beispiel Schweiz: (45.8, 5.9, 47.8, 10.5) + + Returns: + dict: Geparste JSON-Antwort der Overpass API. Die Antwort enthält + unter dem Schlüssel "elements" eine Liste von OSM-Objekten + (nodes und ways) mit ihren Tags und Koordinaten. + Beispiel: + { + "elements": [ + { + "type": "node", + "id": 123456, + "lat": 46.8, "lon": 9.8, + "tags": {"aerialway": "station", "name": "Jakobshorn"} + }, + ... + ] + } + + Raises: + RuntimeError: Wenn die API einen HTTP-Fehlercode zurückgibt + (z.B. 429 Too Many Requests, 504 Gateway Timeout). + RuntimeError: Wenn der Response-Body kein gültiges JSON enthält + (z.B. bei leerem Body nach einem Server-Timeout). + RuntimeError: Wenn Overpass einen fachlichen Fehler meldet + (HTTP 200, aber "remark" im Body, z.B. bei + Speicher- oder Timeout-Überschreitung). + requests.Timeout: Wenn die API nicht innerhalb des Client-Timeouts + antwortet. + """ + bbox_str = ",".join(map(str, bbox)) + query = overpass_query.format(bbox=bbox_str) + resp = requests.post( + OVERPASS_URL, + data={"data": query}, + timeout=40, # clientseitiges timeout ist nicht serverseitiges timeout (das ist im query selbst auf z.B. 3 Sekunden gestellt) + headers={"User-Agent": "GeoService/1.0 (poi-generator)"}, + ) + if resp.status_code != 200: + raise RuntimeError( + f"Overpass API Fehler: {resp.status_code}\n{resp.text}" + ) + try: + data = resp.json() + except requests.exceptions.JSONDecodeError: + raise RuntimeError( + f"Antwort ist kein gültiges JSON:\n{resp.text[:200]}" + ) + + # HTTP 200 bedeutet nicht immer Erfolg — Overpass meldet Fehler im Body! + if "remark" in data: + raise RuntimeError( + f"Overpass Query-Fehler: {data['remark']}" + ) + + return data + + +if __name__ == "__main__": + + BERGBAHN_QUERY = """ + [out:json][timeout:2][maxsize:500000]; + ( + node["aerialway"="station"]({bbox}); + way["aerialway"="station"]({bbox}); + node["railway"="funicular"]({bbox}); + way["railway"="funicular"]({bbox}); + node["railway"="station"]["funicular"="yes"]({bbox}); + ); + out center body; + """ + + bbox = (46.72, 9.70, 46.92, 10.00) + # bbox = (45.8, 5.9, 47.8, 10.5) + result = fetch_overpass(overpass_query=BERGBAHN_QUERY, bbox=bbox) + pprint(result) \ No newline at end of file