Use platform nodes + route membership for bus stop clustering
Replaces distance-based clustering with route-membership grouping: - download_osm.py: collect route_refs per stop node; use platform roles for bus stops, stop roles for rail stops - prepare.py: carry route_refs through to master.gpkg - render.py: group_by_route_membership() uses union-find to cluster same-name stops sharing at least one route into one marker; stops on disjoint routes are kept separate. No distance threshold needed. Results: 786 bus platforms -> 465 markers (was 1610 -> 781). Veksøvej: 2 platforms sharing routes 2A+5C -> 1 marker. Peter Bangs Vej: 7 platforms on routes 10/21/22/4A/7A -> 2 markers.
This commit is contained in:
@@ -8,7 +8,8 @@ fallback + exponential backoff) and writes:
|
||||
|
||||
Lines: one MultiLineString per route relation (concatenation of member way
|
||||
geometries), carrying ref/name/network/colour/operator/route/mode tags.
|
||||
Stops: one Point per stop/platform node member, carrying ref/name/mode.
|
||||
Stops: one Point per platform node member, carrying ref/name/mode/route_refs
|
||||
(route refs of routes using this stop, for disambiguation).
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
@@ -28,7 +29,11 @@ MIRRORS = [
|
||||
]
|
||||
|
||||
PLATFORM_ROLES = {"platform", "platform_entry", "platform_exit"}
|
||||
STOP_ROLES = {"stop", "stop_entry", "stop_exit", "platform", "platform_entry", "platform_exit"}
|
||||
STOP_ROLES = {"stop", "stop_entry", "stop_exit", "stop_entry_only", "stop_exit_only"}
|
||||
# bus stops: use platform nodes (where passengers wait)
|
||||
# rail stops: use stop nodes (platforms are often areas, stop nodes are reliable points)
|
||||
BUS_STOP_ROLES = PLATFORM_ROLES
|
||||
RAIL_STOP_ROLES = STOP_ROLES | PLATFORM_ROLES
|
||||
|
||||
|
||||
def build_query(mode_cfg, bbox):
|
||||
@@ -72,19 +77,25 @@ def overpass(ql):
|
||||
|
||||
|
||||
def parse(data, mode):
|
||||
is_bus = mode == "bus"
|
||||
stop_roles = BUS_STOP_ROLES if is_bus else RAIL_STOP_ROLES
|
||||
|
||||
nodes = {}
|
||||
for e in data["elements"]:
|
||||
if e["type"] == "node":
|
||||
nodes[e["id"]] = e
|
||||
|
||||
lines, stops = [], []
|
||||
stop_seen = set()
|
||||
lines = []
|
||||
# collect route_refs per stop node: {node_id: set(route_refs)}
|
||||
node_routes = {}
|
||||
for e in data["elements"]:
|
||||
if e["type"] != "relation" or "tags" not in e:
|
||||
continue
|
||||
tags = e["tags"]
|
||||
if tags.get("route") is None:
|
||||
continue
|
||||
route_ref = tags.get("ref")
|
||||
# collect line geometries
|
||||
segs = []
|
||||
for m in e["members"]:
|
||||
if m["type"] == "way" and "geometry" in m and m.get("role") not in PLATFORM_ROLES:
|
||||
@@ -96,7 +107,7 @@ def parse(data, mode):
|
||||
continue
|
||||
lines.append({
|
||||
"mode": mode,
|
||||
"ref": tags.get("ref"),
|
||||
"ref": route_ref,
|
||||
"name": tags.get("name"),
|
||||
"network": tags.get("network"),
|
||||
"colour": tags.get("colour"),
|
||||
@@ -104,25 +115,37 @@ def parse(data, mode):
|
||||
"route": tags.get("route"),
|
||||
"geometry": geom,
|
||||
})
|
||||
# record route_ref on each stop member node
|
||||
for m in e["members"]:
|
||||
if m["type"] != "node" or m.get("role") not in STOP_ROLES:
|
||||
if m["type"] != "node" or m.get("role") not in stop_roles:
|
||||
continue
|
||||
nid = m["ref"]
|
||||
if nid not in nodes or nid in stop_seen:
|
||||
if nid not in nodes:
|
||||
continue
|
||||
nd = nodes[nid]
|
||||
if "lat" not in nd or "lon" not in nd:
|
||||
continue
|
||||
stop_seen.add(nid)
|
||||
ntags = nd.get("tags", {})
|
||||
stops.append({
|
||||
"mode": mode,
|
||||
"name": ntags.get("name") or ntags.get("public_transport") or "stop",
|
||||
"ref": ntags.get("ref"),
|
||||
"public_transport": ntags.get("public_transport"),
|
||||
"railway": ntags.get("railway"),
|
||||
"geometry": Point(nd["lon"], nd["lat"]),
|
||||
})
|
||||
node_routes.setdefault(nid, set())
|
||||
if route_ref:
|
||||
node_routes[nid].add(route_ref)
|
||||
|
||||
# build stops from collected nodes
|
||||
stops = []
|
||||
stop_seen = set()
|
||||
for nid, routes in node_routes.items():
|
||||
if nid in stop_seen:
|
||||
continue
|
||||
nd = nodes[nid]
|
||||
if "lat" not in nd or "lon" not in nd:
|
||||
continue
|
||||
stop_seen.add(nid)
|
||||
ntags = nd.get("tags", {})
|
||||
stops.append({
|
||||
"mode": mode,
|
||||
"name": ntags.get("name") or ntags.get("public_transport") or "stop",
|
||||
"ref": ntags.get("ref"),
|
||||
"public_transport": ntags.get("public_transport"),
|
||||
"railway": ntags.get("railway"),
|
||||
"route_refs": ";".join(sorted(routes)),
|
||||
"geometry": Point(nd["lon"], nd["lat"]),
|
||||
})
|
||||
return lines, stops
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user