Add bus whitelist, route exclusions, crow-fly shape filter, and KML export #2
@@ -234,8 +234,25 @@ def plot_stations(ax, stations, styling, active_styles):
|
|||||||
linewidths=lw, zorder=top_z, alpha=1.0)
|
linewidths=lw, zorder=top_z, alpha=1.0)
|
||||||
|
|
||||||
|
|
||||||
def plot_bus_stops(ax, stops, styling, active_styles):
|
def rail_station_names(stations):
|
||||||
"""Draw small markers for bus stops from the stops layer."""
|
"""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:
|
if stops is None or stops.empty or "bus" not in active_styles:
|
||||||
return
|
return
|
||||||
cfg = styling.get("stations", {})
|
cfg = styling.get("stations", {})
|
||||||
@@ -245,6 +262,9 @@ def plot_bus_stops(ax, stops, styling, active_styles):
|
|||||||
if bus.empty:
|
if bus.empty:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if rail_names is None:
|
||||||
|
rail_names = set()
|
||||||
|
|
||||||
cluster_cfg = cfg.get("stop_cluster_m", {})
|
cluster_cfg = cfg.get("stop_cluster_m", {})
|
||||||
cluster_m = cluster_cfg.get("bus", 50) if isinstance(cluster_cfg, dict) else cluster_cfg
|
cluster_m = cluster_cfg.get("bus", 50) if isinstance(cluster_cfg, dict) else cluster_cfg
|
||||||
mk = cfg.get("marker", {})
|
mk = cfg.get("marker", {})
|
||||||
@@ -258,8 +278,13 @@ def plot_bus_stops(ax, stops, styling, active_styles):
|
|||||||
top_z = max(styling["zorder"].values()) + 1
|
top_z = max(styling["zorder"].values()) + 1
|
||||||
|
|
||||||
# cluster same-name bus stops within threshold (opposite sides of road)
|
# cluster same-name bus stops within threshold (opposite sides of road)
|
||||||
|
# skip names that match a rail station (merged into station marker)
|
||||||
centroids = []
|
centroids = []
|
||||||
|
skipped = 0
|
||||||
for name, grp in bus.groupby("name"):
|
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()]
|
coords = [(r.geometry.x, r.geometry.y) for _, r in grp.iterrows()]
|
||||||
if name and len(coords) > 1:
|
if name and len(coords) > 1:
|
||||||
centroids.extend(cluster_stops(coords, cluster_m))
|
centroids.extend(cluster_stops(coords, cluster_m))
|
||||||
@@ -372,7 +397,8 @@ def main():
|
|||||||
# station markers (symbols, no text)
|
# station markers (symbols, no text)
|
||||||
if not args.no_stops:
|
if not args.no_stops:
|
||||||
plot_stations(ax, stations, styling, active_styles)
|
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)
|
# station labels (opt-in via --labels)
|
||||||
if args.labels:
|
if args.labels:
|
||||||
|
|||||||
Reference in New Issue
Block a user