feat(extract): OpenStreetMap-Stand für Flamingo Park abrufen und ablegen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f32ea27e5f
commit
3d4d8380c8
3 files changed
+368
No files matched your search
@@ -0,0 +1,80 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user