Source Copenhagen transit data from Rejseplanen GTFS

- Replace OSM Overpass transit data with the Rejseplanen GTFS feed
  (routes keyed by (agency, short name); styles from modes.yaml)
- Draw one shape per (style, ref, direction), choosing the shape that
  serves the most in-area stops so lines pass the stops we show
- Collapse stops by names (verified unambiguous); prune stops whose
  serving refs have no drawn line within 300 m
- Patch Københavns Havn + Nordhavn into the area polygon so ferry
  routes and sub-harbour metro tunnels survive clipping; 100 m buffer
  closes relation boundary slivers
- Drop OSM download pipeline; keep tiled basemap
This commit is contained in:
marvin
2026-09-17 21:07:40 +02:00
parent 2dcaed7600
commit a6715eaff5
12 changed files with 522 additions and 751 deletions
+20 -1
View File
@@ -3,7 +3,9 @@
Fetches relation/{id}/full.json from the OSM API for each relation in
config/area.json, assembles member ways into rings (respecting outer/inner
roles), builds polygons, and dissolves the union into a single multipolygon.
roles), builds polygons, dissolves the union, and patches in any
config/area.json "waterways" polygons (the harbour is excluded from the
kommune boundaries but belongs to the City Pass zone in practice).
Outputs (in data/processed):
area.geojson (EPSG:4326, human-readable + portable)
@@ -130,6 +132,23 @@ def main():
if not dissolved.is_valid:
dissolved = dissolved.buffer(0)
# patch waterways into the zone: kommune boundaries exclude water, but
# the harbour is practically part of the City Pass area (metro tunnels
# beneath it, harbour ferries sail it). See area.json -> waterways.
for w in area_cfg.get("waterways", []):
wp = Polygon(w["ring"])
print(f"adding waterway: {w['name']}", flush=True)
dissolved = unary_union([dissolved, wp])
# Small outward buffer (100 m) to close boundary slivers: the three
# relation outlines don't abut perfectly, leaving metre-wide cracks that
# otherwise fragment lines clipped to the area (bridge nicks included).
dissolved = (
gpd.GeoSeries([dissolved], crs="EPSG:4326")
.to_crs("EPSG:25832").buffer(100)
.to_crs("EPSG:4326").iloc[0]
)
PROCESSED.mkdir(parents=True, exist_ok=True)
gdf = gpd.GeoDataFrame(
{"name": ["City Pass area"]}, geometry=[dissolved], crs="EPSG:4326"