Add timeline car import and CO2 trip stats
This commit is contained in:
parent
38dbb2b4e7
commit
3747e83ade
14 changed files with 1038 additions and 29 deletions
105
web_view.py
105
web_view.py
|
|
@ -3,6 +3,7 @@
|
|||
"""Web page to show upcoming events."""
|
||||
|
||||
import decimal
|
||||
import functools
|
||||
import hashlib
|
||||
import importlib
|
||||
import inspect
|
||||
|
|
@ -841,6 +842,16 @@ def sum_distances_by_transport_type(trips: list[Trip]) -> list[tuple[str, float]
|
|||
return list(distances_by_transport_type.items())
|
||||
|
||||
|
||||
def sum_co2_by_transport_type(trips: list[Trip]) -> list[tuple[str, float]]:
|
||||
"""Sum CO₂ by transport type."""
|
||||
co2_by_transport_type: defaultdict[str, float] = defaultdict(float)
|
||||
for trip in trips:
|
||||
for transport_type, co2_kg in trip.co2_by_transport_type():
|
||||
co2_by_transport_type[transport_type] += co2_kg
|
||||
|
||||
return list(co2_by_transport_type.items())
|
||||
|
||||
|
||||
def get_home_weather() -> list[StrDict]:
|
||||
"""Get Bristol home weather forecast, with date objects added for templates."""
|
||||
from datetime import date as date_type
|
||||
|
|
@ -911,6 +922,7 @@ def trip_past_list() -> str:
|
|||
total_distance=calc_total_distance(past),
|
||||
total_co2_kg=calc_total_co2_kg(past),
|
||||
distances_by_transport_type=sum_distances_by_transport_type(past),
|
||||
co2_by_transport_type=sum_co2_by_transport_type(past),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -973,6 +985,7 @@ def trip_future_list() -> str:
|
|||
total_distance=calc_total_distance(current + future),
|
||||
total_co2_kg=calc_total_co2_kg(current + future),
|
||||
distances_by_transport_type=sum_distances_by_transport_type(current + future),
|
||||
co2_by_transport_type=sum_co2_by_transport_type(current + future),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1084,6 +1097,7 @@ def _timezone_from_coordinates(latitude: float, longitude: float) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _get_timezone_finder() -> typing.Any:
|
||||
"""Get timezone finder instance if dependency is available."""
|
||||
try:
|
||||
|
|
@ -1098,6 +1112,94 @@ def _get_timezone_finder() -> typing.Any:
|
|||
return timezone_finder_cls()
|
||||
|
||||
|
||||
def _coordinates_from_location(location: typing.Any) -> tuple[float, float] | None:
|
||||
"""Extract latitude/longitude from a location mapping."""
|
||||
if not isinstance(location, dict):
|
||||
return None
|
||||
|
||||
latitude = location.get("latitude")
|
||||
longitude = location.get("longitude")
|
||||
if not isinstance(latitude, (int, float)) or not isinstance(
|
||||
longitude, (int, float)
|
||||
):
|
||||
return None
|
||||
|
||||
return (float(latitude), float(longitude))
|
||||
|
||||
|
||||
def _route_endpoints_for_car_item(
|
||||
item: StrDict,
|
||||
data_dir: str,
|
||||
route_cache: dict[str, tuple[tuple[float, float], tuple[float, float]] | None],
|
||||
) -> tuple[tuple[float, float], tuple[float, float]] | None:
|
||||
"""Return route endpoints for a car journey when available."""
|
||||
route_filename = item.get("geojson_filename")
|
||||
if not isinstance(route_filename, str):
|
||||
return None
|
||||
|
||||
if route_filename not in route_cache:
|
||||
geojson_text = agenda.trip.read_geojson(
|
||||
data_dir, os.path.join("car_routes", route_filename)
|
||||
)
|
||||
endpoints = agenda.trip.geojson_route_endpoints(json.loads(geojson_text))
|
||||
route_cache[route_filename] = endpoints
|
||||
|
||||
return route_cache[route_filename]
|
||||
|
||||
|
||||
def _timezone_name_for_car_item(
|
||||
item: StrDict,
|
||||
data_dir: str,
|
||||
route_cache: dict[str, tuple[tuple[float, float], tuple[float, float]] | None],
|
||||
timezone_cache: dict[tuple[float, float], str | None],
|
||||
) -> str | None:
|
||||
"""Resolve the local timezone for a car journey from endpoints."""
|
||||
candidate_coords: list[tuple[float, float]] = []
|
||||
|
||||
for field in ("from_location", "to_location"):
|
||||
coord = _coordinates_from_location(item.get(field))
|
||||
if coord is not None:
|
||||
candidate_coords.append(coord)
|
||||
|
||||
if not candidate_coords:
|
||||
endpoints = _route_endpoints_for_car_item(item, data_dir, route_cache)
|
||||
if endpoints is not None:
|
||||
candidate_coords.extend(endpoints)
|
||||
|
||||
for coord in candidate_coords:
|
||||
if coord not in timezone_cache:
|
||||
timezone_cache[coord] = _timezone_from_coordinates(coord[0], coord[1])
|
||||
timezone_name = timezone_cache[coord]
|
||||
if timezone_name:
|
||||
return timezone_name
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def localize_trip_car_journey_display_times(trip: Trip, data_dir: str) -> None:
|
||||
"""Add route-local display timestamps for car journeys on the trip page."""
|
||||
route_cache: dict[str, tuple[tuple[float, float], tuple[float, float]] | None] = {}
|
||||
timezone_cache: dict[tuple[float, float], str | None] = {}
|
||||
|
||||
for item in trip.travel:
|
||||
if item.get("type") != "car":
|
||||
continue
|
||||
|
||||
depart = item.get("depart")
|
||||
arrive = item.get("arrive")
|
||||
if not isinstance(depart, datetime) or not isinstance(arrive, datetime):
|
||||
continue
|
||||
|
||||
timezone_name = _timezone_name_for_car_item(
|
||||
item, data_dir, route_cache, timezone_cache
|
||||
)
|
||||
if not timezone_name:
|
||||
continue
|
||||
|
||||
item["display_depart"] = depart.astimezone(ZoneInfo(timezone_name))
|
||||
item["display_arrive"] = arrive.astimezone(ZoneInfo(timezone_name))
|
||||
|
||||
|
||||
def get_destination_timezones(trip: Trip) -> list[StrDict]:
|
||||
"""Build destination timezone metadata for the trip page."""
|
||||
per_location: dict[tuple[str, str], list[str]] = defaultdict(list)
|
||||
|
|
@ -1276,6 +1378,7 @@ def trip_page(start: str) -> str:
|
|||
trip,
|
||||
cache_only=True,
|
||||
)
|
||||
localize_trip_car_journey_display_times(trip, app.config["PERSONAL_DATA"])
|
||||
|
||||
return flask.render_template(
|
||||
"trip_page.html",
|
||||
|
|
@ -1424,7 +1527,9 @@ def trip_stats() -> str:
|
|||
"trip/stats.html",
|
||||
count=len(trip_list),
|
||||
total_distance=calc_total_distance(trip_list),
|
||||
total_co2_kg=calc_total_co2_kg(trip_list),
|
||||
distances_by_transport_type=sum_distances_by_transport_type(trip_list),
|
||||
co2_by_transport_type=sum_co2_by_transport_type(trip_list),
|
||||
yearly_stats=yearly_stats,
|
||||
overall_stats=overall_stats,
|
||||
conferences=conferences,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue