diff --git a/TASK.md b/TASK.md index 6ac122f..27b6588 100644 --- a/TASK.md +++ b/TASK.md @@ -1,8 +1,20 @@ -# TASK 4: +# TASK 5: -Wir arbeiten nun intern direkt mit den Daten von Overpass. Macht das Sinn? Warum vielleicht nicht? +* 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: -* Versucht einen Adapter zu bauen, wir wollen intern mit einer eigenen Dataclass `POI` arbeiten. Wir bauen also dazu eine - Funktion `load_pois`, welche einerseits die Daten fetched und andererseits auch parsed. Den Fetching-Teil haben wir - bereits (`fetch_overpass`), den Pasing-Teil haben wir noch nicht. - Schreibt bitte eine eigene Dataclass`Poi` in welche die gefetchten Daten 'abgefüllt' werden können. \ No newline at end of file +``` + RESTAURANT_QUERY = """ + [out:json][timeout:5][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/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..1001271 Binary files /dev/null and b/__pycache__/models.cpython-313.pyc differ diff --git a/__pycache__/overpass.cpython-313.pyc b/__pycache__/overpass.cpython-313.pyc index 069e044..54e9105 100644 Binary files a/__pycache__/overpass.cpython-313.pyc and b/__pycache__/overpass.cpython-313.pyc differ diff --git a/main.py b/main.py index 916c22b..720a30e 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ -from overpass import fetch_overpass, OverpassApiError +from overpass import load_pois, OverpassApiError +from models import POI # --------------------------------------------------------------------------- # Konfiguration @@ -28,13 +29,15 @@ QUERY = """ def main() -> None: for name, bbox in BBOXEN.items(): try: - result = fetch_overpass(overpass_query=QUERY, bbox=bbox) + pois: list[POI] = load_pois(overpass_query=QUERY, bbox=bbox) except OverpassApiError as exc: - print(f" Fehler : {exc}") + print(f"Fehler bei '{name}': {exc}") continue - elements = result.get("elements", []) - print(elements) + print(f"\n{name}: {len(pois)} POIs gefunden") + for poi in pois: + print(f" {poi.id}: ({poi.lat}, {poi.lon})") + if __name__ == "__main__": main() \ No newline at end of file diff --git a/models.py b/models.py new file mode 100644 index 0000000..1435a60 --- /dev/null +++ b/models.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass, field + +@dataclass +class POI: + id: str + type: str + lat: float + lon: float + tags: dict = field(default_factory=dict) # weil mutable defaults in Dataclasses eine bekannte Python-Falle sind + # (alle Instanzen würden dasselbe Dict teilen...) + + +# REMARK: +# Wann eine eigene Dataclass für tags? +# Nur wenn die tags strukturiert und vorhersehbar sind, was bei OSM-Daten nicht der Fall ist... \ No newline at end of file diff --git a/overpass.py b/overpass.py index 803d0e2..e6892c1 100644 --- a/overpass.py +++ b/overpass.py @@ -1,15 +1,27 @@ import requests from pprint import pprint - +from models import POI OVERPASS_URL = "https://overpass-api.de/api/interpreter" +# REMARK: +# zwei Strategien: +# Fail-fast: Ein Fehler bricht alles ab → sinnvoll, wenn jedes Element kritisch ist +# Best-effort: Fehlerhafte Elemente überspringen, Rest verarbeiten → sinnvoll bei OSM-Daten, wo einzelne Einträge unvollständig sein können + + class OverpassApiError(Exception): pass -def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: +def load_pois(overpass_query: str, bbox: tuple) -> list[POI]: + """Führt Fetch und Parse zusammen aus.""" + raw = _fetch_overpass(overpass_query=overpass_query, bbox=bbox) + return _parse_pois(raw) + + +def _fetch_overpass(overpass_query: str, bbox: tuple) -> dict: """ 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 @@ -67,7 +79,49 @@ def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: raise OverpassApiError("Overpass-API Timeout") from exc except requests.RequestException as exc: raise OverpassApiError("Overpass-API Request fehlgeschlagen") from exc - return response.json() + + data = response.json() + + # zusätzliche Fehlermöglichkeit -> Status ist zwar 200, aber Liste mit Ergebnissen ist leer... + if "remark" in data: + raise OverpassApiError(f"Overpass Query-Fehler: {data['remark']}") + + return data + + +def _parse_poi(data: dict) -> POI: + """ Wandelt ein einzelnes Overpass-Element in ein POI-Objekt um. + + :param data: dictionary mit Daten für ein POI-Objekt + :return: POI-Objekt + """ + try: + return POI( + id=data['id'], + type=data.get('type', ''), + lat=float(data.get("lat") or data["center"]["lat"]), + lon=float(data.get("lon") or data["center"]["lon"]), + tags=data.get('tags', {}), + ) + except KeyError as exc: + raise OverpassApiError("Feld in API - Antwort fehlt") from exc + except (TypeError, ValueError) as exc: + raise OverpassApiError("API - Antwort hat falsches Format ") from exc + + + +def _parse_pois(raw: dict) -> list[POI]: + """Extrahiert alle Elemente aus der API-Antwort und parst sie. + Fehlerhafte Elemente werden übersprungen und geloggt. + """ + pois = [] + for element in raw.get("elements", []): + try: + pois.append(_parse_poi(element)) + except OverpassApiError as exc: + print(f"POI übersprungen (id={element.get('id', '?')}): {exc}") + return pois + if __name__ == "__main__": @@ -86,5 +140,5 @@ if __name__ == "__main__": 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) + result = load_pois(overpass_query=BERGBAHN_QUERY, bbox=bbox) pprint(result) \ No newline at end of file