Add orphan bus stops (platforms not in route relations)
Some bus stop platform nodes in OSM are not members of any route relation (e.g. Borgergade). These were missed by the route-membership download approach. Now download_osm.py: - tracks bus platform node ids captured by route queries - runs a supplementary query for all highway=bus_stop + platform nodes - appends orphans (with empty route_refs) to the bus stops layer render.py: orphan stops (empty route_refs) fall back to distance clustering instead of route-membership grouping. Results: 1164 route-member stops + 707 orphans = 1871 total -> 1370 after clip -> 1049 markers after grouping
This commit is contained in:
@@ -198,6 +198,40 @@ def parse_stations(data):
|
||||
return rows
|
||||
|
||||
|
||||
def orphan_bus_stops_query(bbox):
|
||||
s, n, w, e = bbox
|
||||
return f"""[out:json][timeout:180];
|
||||
(
|
||||
node["highway"="bus_stop"]["public_transport"="platform"]({s},{w},{n},{e});
|
||||
);
|
||||
out body;
|
||||
"""
|
||||
|
||||
|
||||
def parse_orphan_bus_stops(data, known_node_ids):
|
||||
"""Parse standalone bus platform nodes not already captured by routes."""
|
||||
stops = []
|
||||
for e in data["elements"]:
|
||||
if e["type"] != "node" or e["id"] in known_node_ids:
|
||||
continue
|
||||
tags = e.get("tags", {})
|
||||
if tags.get("public_transport") != "platform":
|
||||
continue
|
||||
name = tags.get("name")
|
||||
if not name:
|
||||
continue
|
||||
stops.append({
|
||||
"mode": "bus",
|
||||
"name": name,
|
||||
"ref": tags.get("ref"),
|
||||
"public_transport": tags.get("public_transport"),
|
||||
"railway": tags.get("railway"),
|
||||
"route_refs": "", # orphan — no route membership
|
||||
"geometry": Point(e["lon"], e["lat"]),
|
||||
})
|
||||
return stops
|
||||
|
||||
|
||||
def main():
|
||||
modes = yaml.safe_load((CONFIG / "modes.yaml").read_text())["modes"]
|
||||
area = gpd.read_file(PROCESSED / "area.gpkg")
|
||||
@@ -205,12 +239,18 @@ def main():
|
||||
bbox = (s, n, w, e)
|
||||
|
||||
OSM_RAW.mkdir(parents=True, exist_ok=True)
|
||||
bus_stop_node_ids = set()
|
||||
for mode, cfg in modes.items():
|
||||
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)
|
||||
lines, stops = parse(data, mode)
|
||||
# track bus stop node ids to avoid dupes with orphans
|
||||
if mode == "bus":
|
||||
for e in data["elements"]:
|
||||
if e["type"] == "node" and e.get("tags", {}).get("public_transport") == "platform":
|
||||
bus_stop_node_ids.add(e["id"])
|
||||
print(f" -> {len(lines)} lines, {len(stops)} stops", flush=True)
|
||||
if not lines:
|
||||
continue
|
||||
@@ -220,6 +260,30 @@ def main():
|
||||
sgdf = gpd.GeoDataFrame(stops, crs="EPSG:4326")
|
||||
sgdf.to_file(OSM_RAW / f"{mode}.gpkg", driver="GPKG", layer="stops")
|
||||
|
||||
# orphan bus stops (platform nodes not in any route relation)
|
||||
oql = orphan_bus_stops_query(bbox)
|
||||
(OSM_RAW / "orphan_bus_stops.overpassql").write_text(oql)
|
||||
print("[orphan_bus_stops] querying Overpass...", flush=True)
|
||||
odata = overpass(oql)
|
||||
orphans = parse_orphan_bus_stops(odata, bus_stop_node_ids)
|
||||
print(f" -> {len(orphans)} orphan bus stops (not in route relations)", flush=True)
|
||||
if orphans:
|
||||
# append to bus.gpkg stops layer
|
||||
existing = gpd.read_file(OSM_RAW / "bus.gpkg", layer="stops")
|
||||
combined = gpd.GeoDataFrame(
|
||||
__import__("pandas").concat([existing, gpd.GeoDataFrame(orphans, crs="EPSG:4326")],
|
||||
ignore_index=True),
|
||||
crs="EPSG:4326"
|
||||
)
|
||||
# rewrite stops layer
|
||||
import tempfile, shutil
|
||||
tmp = OSM_RAW / "bus_tmp.gpkg"
|
||||
lines_gdf = gpd.read_file(OSM_RAW / "bus.gpkg", layer="lines")
|
||||
lines_gdf.to_file(tmp, driver="GPKG", layer="lines")
|
||||
combined.to_file(tmp, driver="GPKG", layer="stops")
|
||||
shutil.move(str(tmp), str(OSM_RAW / "bus.gpkg"))
|
||||
print(f" -> bus stops layer now: {len(combined)} total", flush=True)
|
||||
|
||||
# stations (named station nodes/areas) for labelling
|
||||
sql = stations_query(bbox)
|
||||
(OSM_RAW / "stations.overpassql").write_text(sql)
|
||||
|
||||
Reference in New Issue
Block a user