"""Holt die OpenStreetMap-Daten für Flamingo Park einmal und legt sie ins Repo. cd tools/extract && PYTHONUTF8=1 uv run python osm.py Spec: docs/superpowers/specs/2026-09-15-flamingo-park-design.md run.py liest nur die geschriebene Datei und braucht kein Netz; ein neuer Abruf ist eine bewusste Handlung mit eigenem Commit. """ import json import urllib.parse import urllib.request from pathlib import Path URL = "https://overpass-api.de/api/interpreter" UA = {"User-Agent": "leonida-extract/0.1 (local hobby project)"} BBOX = (25.7812, -80.1392, 25.7872, -80.1358) # Süd, West, Nord, Ost: Flamingo Park mit Rand OUT = Path(__file__).resolve().parent / "data" / "osm" / "flamingo-park.json" PATH_HIGHWAYS = ("footway", "path", "pedestrian", "steps", "cycleway", "service") LEISURE = ("pitch", "playground", "dog_park", "sports_centre", "swimming_pool") def overpass_query(bbox=BBOX) -> str: box = "({},{},{},{})".format(*bbox) return (f"[out:json][timeout:60];(" f"way{box}[highway];way{box}[leisure];way{box}[amenity=parking];way{box}[building];" f");out geom;") def keep(element: dict) -> bool: """Nur was der Park braucht: Wege, Sportflächen, Pool, Parkplatz, Gebäude.""" if element.get("type") != "way" or not element.get("geometry"): return False tags = element.get("tags", {}) if "building" in tags: return True highway = tags.get("highway") if highway: if highway not in PATH_HIGHWAYS: return False # Gehwege an Straßen gehören zu Teil 2; ein Stück des 13th-St-Wegs ist trotzdem als sidewalk getaggt. return tags.get("footway") != "sidewalk" or tags.get("name", "").startswith("Flamingo Park Path") return tags.get("leisure") in LEISURE or tags.get("amenity") == "parking" def snapshot(raw: dict) -> dict: """Gefilterter Stand mit Herkunft, Lizenz und Abrufzeit; Geometrie als [Breite, Länge].""" elements = [{"id": e["id"], "tags": e.get("tags", {}), "geometry": [[g["lat"], g["lon"]] for g in e["geometry"]]} for e in raw.get("elements", []) if keep(e)] elements.sort(key=lambda e: e["id"]) return { "source": "https://www.openstreetmap.org", "license": "ODbL 1.0, © OpenStreetMap-Mitwirkende", "fetched": raw.get("osm3s", {}).get("timestamp_osm_base", ""), "elements": elements, } def dumps(snap: dict) -> str: """Eine Zeile je Element, damit ein neuer Abruf als lesbarer Diff erscheint.""" head = [f' "{key}": {json.dumps(value, ensure_ascii=False)}' for key, value in snap.items() if key != "elements"] rows = ",\n".join(" " + json.dumps(e, ensure_ascii=False, separators=(",", ":")) for e in snap["elements"]) return "{\n" + ",\n".join(head) + ',\n "elements": [\n' + rows + "\n ]\n}\n" def fetch(query: str) -> dict: request = urllib.request.Request(URL, data=urllib.parse.urlencode({"data": query}).encode(), headers=UA) with urllib.request.urlopen(request, timeout=90) as response: return json.loads(response.read().decode("utf-8")) def main() -> None: snap = snapshot(fetch(overpass_query())) OUT.parent.mkdir(parents=True, exist_ok=True) OUT.write_text(dumps(snap), encoding="utf-8") print(f"{len(snap['elements'])} Elemente, Stand {snap['fetched']} -> {OUT}") if __name__ == "__main__": main()