diff --git a/agenda/trip.py b/agenda/trip.py index cb96981..9502103 100644 --- a/agenda/trip.py +++ b/agenda/trip.py @@ -124,6 +124,38 @@ def load_coaches( return coaches +def load_buses( + data_dir: str, route_distances: travel.RouteDistances | None = None +) -> list[StrDict]: + """Load buses.""" + items = load_travel("bus", "buses", data_dir) + stops = travel.parse_yaml("bus_stops", data_dir) + by_name = {stop["name"]: stop for stop in stops} + + for item in items: + add_station_objects(item, by_name) + # Add route distance and CO2 calculation if available + if route_distances: + travel.add_leg_route_distance(item, route_distances) + if "distance" in item: + # Calculate CO2 emissions for bus (0.027 kg CO2e per passenger per km) + item["co2_kg"] = item["distance"] * 0.1 + # Include GeoJSON route if defined in stations data + from_station = item.get("from_stop") + to_station = item.get("to_stop") + if from_station and to_station: + # Support scalar or mapping routes: string or dict of station name -> geojson base name + routes_val = from_station.get("routes", {}) + if isinstance(routes_val, str): + geo = routes_val + else: + geo = routes_val.get(to_station.get("name")) + if geo: + item["geojson_filename"] = geo + + return items + + def process_flight( flight: StrDict, by_iata: dict[str, Airline], airports: list[StrDict] ) -> None: @@ -179,7 +211,8 @@ def collect_travel_items( load_flights(load_flight_bookings(data_dir)) + load_trains(data_dir, route_distances=route_distances) + load_ferries(data_dir, route_distances=route_distances) - + load_coaches(data_dir, route_distances=route_distances), + + load_coaches(data_dir, route_distances=route_distances) + + load_buses(data_dir, route_distances=route_distances), key=depart_datetime, ) diff --git a/agenda/types.py b/agenda/types.py index ac4fcbc..81813a5 100644 --- a/agenda/types.py +++ b/agenda/types.py @@ -16,6 +16,7 @@ from agenda import format_list_with_ampersand from . import utils + StrDict = dict[str, typing.Any] DateOrDateTime = datetime.datetime | datetime.date @@ -44,6 +45,7 @@ class TripElement: "flight": ":airplane:", "ferry": ":ferry:", "coach": ":bus:", + "bus": ":bus:", } alias = emoji_map.get(self.element_type) @@ -414,6 +416,25 @@ class Trip: ) ) + if item["type"] == "bus": + # Include bus journeys in trip elements + from_country = agenda.get_country(item["from_stop"]["country"]) + to_country = agenda.get_country(item["to_stop"]["country"]) + name = f"{item['from']} → {item['to']}" + elements.append( + TripElement( + start_time=item["depart"], + end_time=item.get("arrive"), + title=name, + detail=item, + element_type="bus", + start_loc=item["from"], + end_loc=item["to"], + start_country=from_country, + end_country=to_country, + ) + ) + return sorted(elements, key=lambda e: utils.as_datetime(e.start_time)) def elements_grouped_by_day(self) -> list[tuple[datetime.date, list[TripElement]]]: diff --git a/static/js/map.js b/static/js/map.js index 91b39da..a150008 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -186,7 +186,7 @@ function build_map(map_id, coordinates, routes) { // Draw routes routes.forEach(function(route) { - var color = {"train": "blue", "flight": "red", "unbooked_flight": "orange", "coach": "green"}[route.type]; + var color = {"train": "blue", "flight": "red", "unbooked_flight": "orange", "coach": "green", "bus": "purple"}[route.type]; var style = { weight: 3, opacity: 0.5, color: color }; if (route.geojson) { L.geoJSON(JSON.parse(route.geojson), {