From 9e078efad3c7d91a59d9802b012d002d2e65706f Mon Sep 17 00:00:00 2001 From: marvin Date: Sun, 13 Sep 2026 23:26:19 +0200 Subject: [PATCH] Merge bus stops into rail stations when names match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bus stops sharing a name with a rail station (metro/s_tog/regional) are now skipped from bus stop markers — the rail station marker represents that stop. 32 bus stops across 9 names (Fasanvej, Frederiksberg Allé, Jyllingevej, Mozarts Plads, Peter Bangs Vej, Ryparken, Rådhuspladsen, Sluseholmen, Vigerslev Allé) are absorbed. --- copenhagen/scripts/render.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/copenhagen/scripts/render.py b/copenhagen/scripts/render.py index b48e202..ed3794a 100644 --- a/copenhagen/scripts/render.py +++ b/copenhagen/scripts/render.py @@ -234,8 +234,25 @@ def plot_stations(ax, stations, styling, active_styles): linewidths=lw, zorder=top_z, alpha=1.0) -def plot_bus_stops(ax, stops, styling, active_styles): - """Draw small markers for bus stops from the stops layer.""" +def rail_station_names(stations): + """Return the set of names for rail stations (metro/s_tog/regional).""" + if stations is None or stations.empty: + return set() + names = set() + for _, r in stations.iterrows(): + if classify_station(r) is not None: + name = r.get("name") + if isinstance(name, str) and name.strip(): + names.add(name) + return names + + +def plot_bus_stops(ax, stops, styling, active_styles, rail_names=None): + """Draw small markers for bus stops from the stops layer. + + Bus stops whose name matches a rail station are skipped — the rail + station marker represents that stop. + """ if stops is None or stops.empty or "bus" not in active_styles: return cfg = styling.get("stations", {}) @@ -245,6 +262,9 @@ def plot_bus_stops(ax, stops, styling, active_styles): if bus.empty: return + if rail_names is None: + rail_names = set() + cluster_cfg = cfg.get("stop_cluster_m", {}) cluster_m = cluster_cfg.get("bus", 50) if isinstance(cluster_cfg, dict) else cluster_cfg mk = cfg.get("marker", {}) @@ -258,8 +278,13 @@ def plot_bus_stops(ax, stops, styling, active_styles): top_z = max(styling["zorder"].values()) + 1 # cluster same-name bus stops within threshold (opposite sides of road) + # skip names that match a rail station (merged into station marker) centroids = [] + skipped = 0 for name, grp in bus.groupby("name"): + if name and name in rail_names: + skipped += len(grp) + continue coords = [(r.geometry.x, r.geometry.y) for _, r in grp.iterrows()] if name and len(coords) > 1: centroids.extend(cluster_stops(coords, cluster_m)) @@ -372,7 +397,8 @@ def main(): # station markers (symbols, no text) if not args.no_stops: plot_stations(ax, stations, styling, active_styles) - plot_bus_stops(ax, stops, styling, active_styles) + rail_names = rail_station_names(stations) + plot_bus_stops(ax, stops, styling, active_styles, rail_names=rail_names) # station labels (opt-in via --labels) if args.labels: