Copenhagen Phase 2: prepare.py merges + clips + colours -> master.gpkg

- prepare.py: merge per-mode OSM lines, resolve colours
  (GTFS route_color -> OSM colour tag -> keyed palette, robust to pd.NA),
  categorise buses (A/C/S/regular via regex), clip lines+stops to area,
  write master.gpkg layers (lines/stops/stations)
- download_osm.py: add stations query (public_transport=station /
  railway=station nodes+areas, out center) -> stations.gpkg with proper
  human-readable names + subway/light_rail mode tags for labelling
- verified: 135 lines (8 metro/16 s-tog/28 regional/83 bus), 90 named
  stations, zero NaN colours
This commit is contained in:
2026-09-13 01:19:34 +02:00
parent 43c04a94f1
commit 8e32ca2b1c
2 changed files with 268 additions and 1 deletions
+63 -1
View File
@@ -126,14 +126,64 @@ def parse(data, mode):
return lines, stops
def stations_query(bbox):
s, n, w, e = bbox
return f"""[out:json][timeout:180];
(
node["railway"="station"]({s},{w},{n},{e});
node["public_transport"="station"]({s},{w},{n},{e});
way["railway"="station"]({s},{w},{n},{e});
way["public_transport"="station"]({s},{w},{n},{e});
);
out center;
"""
def parse_stations(data):
"""Stations (nodes or area centroids) with proper human-readable names."""
rows = []
seen = set()
for e in data["elements"]:
if e["type"] not in ("node", "way"):
continue
tags = e.get("tags", {})
name = tags.get("name")
if not name:
continue
if e["type"] == "node":
geom = Point(e["lon"], e["lat"])
else:
c = e.get("center")
if not c:
continue
geom = Point(c["lon"], c["lat"])
key = (name, round(geom.x, 5), round(geom.y, 5))
if key in seen:
continue
seen.add(key)
rows.append({
"name": name,
"railway": tags.get("railway"),
"public_transport": tags.get("public_transport"),
"station": tags.get("station"),
"subway": tags.get("subway"),
"light_rail": tags.get("light_rail"),
"train": tags.get("train"),
"uic_ref": tags.get("uic_ref"),
"geometry": geom,
})
return rows
def main():
modes = yaml.safe_load((CONFIG / "modes.yaml").read_text())["modes"]
area = gpd.read_file(PROCESSED / "area.gpkg")
w, s, e, n = area.total_bounds # (minx, miny, maxx, maxy) = (west, south, east, north)
bbox = (s, n, w, e)
OSM_RAW.mkdir(parents=True, exist_ok=True)
for mode, cfg in modes.items():
ql = build_query(cfg, (s, n, w, e))
ql = build_query(cfg, bbox)
(OSM_RAW / f"{mode}.overpassql").write_text(ql)
print(f"[{mode}] querying Overpass bbox=({s:.4f},{w:.4f},{n:.4f},{e:.4f})...", flush=True)
data = overpass(ql)
@@ -146,6 +196,18 @@ def main():
if stops:
sgdf = gpd.GeoDataFrame(stops, crs="EPSG:4326")
sgdf.to_file(OSM_RAW / f"{mode}.gpkg", driver="GPKG", layer="stops")
# stations (named station nodes/areas) for labelling
sql = stations_query(bbox)
(OSM_RAW / "stations.overpassql").write_text(sql)
print("[stations] querying Overpass...", flush=True)
sdata = overpass(sql)
st = parse_stations(sdata)
print(f" -> {len(st)} named stations", flush=True)
if st:
gpd.GeoDataFrame(st, crs="EPSG:4326").to_file(
OSM_RAW / "stations.gpkg", driver="GPKG", layer="stations"
)
print("done.")