209 lines
6.1 KiB
Python
209 lines
6.1 KiB
Python
import argparse
|
|
import datetime as dt
|
|
import json
|
|
import uuid
|
|
from urllib import parse, request
|
|
|
|
from db import get_conn, init_db
|
|
|
|
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
|
DEFAULT_BBOX = (51.16, 6.79, 51.38, 7.15) # Kreis Mettmann approx: s,w,n,e
|
|
|
|
|
|
def now_iso() -> str:
|
|
return dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
|
|
|
|
def build_query(south: float, west: float, north: float, east: float) -> str:
|
|
bbox = f"({south},{west},{north},{east})"
|
|
return f"""
|
|
[out:json][timeout:120];
|
|
(
|
|
node[amenity=police]{bbox};
|
|
way[amenity=police]{bbox};
|
|
relation[amenity=police]{bbox};
|
|
|
|
node[amenity=fire_station]{bbox};
|
|
way[amenity=fire_station]{bbox};
|
|
relation[amenity=fire_station]{bbox};
|
|
|
|
node[amenity=hospital]{bbox};
|
|
way[amenity=hospital]{bbox};
|
|
relation[amenity=hospital]{bbox};
|
|
|
|
way[landuse=residential]{bbox};
|
|
relation[landuse=residential]{bbox};
|
|
|
|
way[landuse=industrial]{bbox};
|
|
relation[landuse=industrial]{bbox};
|
|
|
|
way[landuse=commercial]{bbox};
|
|
relation[landuse=commercial]{bbox};
|
|
|
|
node[amenity=parking]{bbox};
|
|
way[amenity=parking]{bbox};
|
|
relation[amenity=parking]{bbox};
|
|
|
|
way[natural=wood]{bbox};
|
|
relation[natural=wood]{bbox};
|
|
way[leisure=nature_reserve]{bbox};
|
|
relation[leisure=nature_reserve]{bbox};
|
|
|
|
way[highway=primary]{bbox};
|
|
way[highway=secondary]{bbox};
|
|
way[highway=residential]{bbox};
|
|
way[highway=service]{bbox};
|
|
);
|
|
out center;
|
|
"""
|
|
|
|
|
|
def element_coords(element: dict) -> tuple[float, float] | None:
|
|
if "lat" in element and "lon" in element:
|
|
return float(element["lat"]), float(element["lon"])
|
|
center = element.get("center")
|
|
if center and "lat" in center and "lon" in center:
|
|
return float(center["lat"]), float(center["lon"])
|
|
return None
|
|
|
|
|
|
def map_poi(tags: dict) -> str | None:
|
|
amenity = tags.get("amenity")
|
|
if amenity == "police":
|
|
return "police"
|
|
if amenity == "fire_station":
|
|
return "fire"
|
|
if amenity == "hospital":
|
|
return "hospital"
|
|
return None
|
|
|
|
|
|
def map_zone(tags: dict) -> str | None:
|
|
landuse = tags.get("landuse")
|
|
amenity = tags.get("amenity")
|
|
natural = tags.get("natural")
|
|
leisure = tags.get("leisure")
|
|
|
|
if landuse == "residential":
|
|
return "residential"
|
|
if landuse == "industrial":
|
|
return "industrial"
|
|
if landuse == "commercial":
|
|
return "commercial"
|
|
if amenity == "parking":
|
|
return "parking"
|
|
if natural in {"wood", "scrub", "heath"} or leisure == "nature_reserve":
|
|
return "nature"
|
|
return None
|
|
|
|
|
|
def map_road(tags: dict) -> str | None:
|
|
highway = tags.get("highway")
|
|
if highway in {"primary", "secondary", "residential", "service"}:
|
|
return highway
|
|
return None
|
|
|
|
|
|
def fetch_overpass(query: str) -> dict:
|
|
payload = parse.urlencode({"data": query}).encode("utf-8")
|
|
req = request.Request(OVERPASS_URL, data=payload, method="POST")
|
|
with request.urlopen(req, timeout=180) as resp:
|
|
raw = resp.read()
|
|
return json.loads(raw.decode("utf-8"))
|
|
|
|
|
|
def import_osm(south: float, west: float, north: float, east: float) -> dict:
|
|
init_db()
|
|
data = fetch_overpass(build_query(south, west, north, east))
|
|
elements = data.get("elements", [])
|
|
|
|
imported_at = now_iso()
|
|
source = "osm_overpass"
|
|
|
|
poi_rows = []
|
|
zone_rows = []
|
|
road_rows = []
|
|
|
|
for element in elements:
|
|
tags = element.get("tags", {})
|
|
coords = element_coords(element)
|
|
if not coords:
|
|
continue
|
|
|
|
lat, lon = coords
|
|
base_id = f"{element.get('type', 'x')}:{element.get('id', uuid.uuid4())}"
|
|
|
|
poi_type = map_poi(tags)
|
|
if poi_type:
|
|
poi_rows.append((f"poi:{base_id}", poi_type, lat, lon, source, imported_at))
|
|
|
|
zone_type = map_zone(tags)
|
|
if zone_type:
|
|
zone_rows.append((f"zone:{base_id}", zone_type, lat, lon, source, imported_at))
|
|
|
|
road_type = map_road(tags)
|
|
if road_type:
|
|
road_rows.append((f"road:{base_id}", road_type, lat, lon, source, imported_at))
|
|
|
|
with get_conn() as conn:
|
|
conn.execute("DELETE FROM osm_poi WHERE source = ?", (source,))
|
|
conn.execute("DELETE FROM osm_zone WHERE source = ?", (source,))
|
|
conn.execute("DELETE FROM osm_road WHERE source = ?", (source,))
|
|
|
|
conn.executemany(
|
|
"""
|
|
INSERT INTO osm_poi (id, poi_type, lat, lon, source, imported_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""",
|
|
poi_rows,
|
|
)
|
|
conn.executemany(
|
|
"""
|
|
INSERT INTO osm_zone (id, zone_type, lat, lon, source, imported_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""",
|
|
zone_rows,
|
|
)
|
|
conn.executemany(
|
|
"""
|
|
INSERT INTO osm_road (id, road_type, lat, lon, source, imported_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""",
|
|
road_rows,
|
|
)
|
|
|
|
total = len(poi_rows) + len(zone_rows) + len(road_rows)
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO data_source_state (source_name, imported_at, record_count, notes)
|
|
VALUES (?, ?, ?, ?)
|
|
ON CONFLICT(source_name) DO UPDATE SET
|
|
imported_at = excluded.imported_at,
|
|
record_count = excluded.record_count,
|
|
notes = excluded.notes
|
|
""",
|
|
("osm_overpass", imported_at, total, f"bbox={south},{west},{north},{east}"),
|
|
)
|
|
|
|
return {
|
|
"elements": len(elements),
|
|
"pois": len(poi_rows),
|
|
"zones": len(zone_rows),
|
|
"roads": len(road_rows),
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Import OSM data for StaySense from Overpass")
|
|
parser.add_argument("--south", type=float, default=DEFAULT_BBOX[0])
|
|
parser.add_argument("--west", type=float, default=DEFAULT_BBOX[1])
|
|
parser.add_argument("--north", type=float, default=DEFAULT_BBOX[2])
|
|
parser.add_argument("--east", type=float, default=DEFAULT_BBOX[3])
|
|
args = parser.parse_args()
|
|
|
|
result = import_osm(args.south, args.west, args.north, args.east)
|
|
print(json.dumps(result, ensure_ascii=True))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|