Journey: add regional-only filter and service notices
- connection_type setting (all/regional/long_distance) restricts EFA trip requests via inclMOT_N params, so ICE/IC/EC can be filtered out for commutes that only care about regional/local transit. - Surface leg.infos[].infoLinks titles (real disruption/outage notices from EFA) on each journey row, marked with a red warning icon.
This commit is contained in:
1 parent
d5757a00df
commit
06b90ffe6c
3 files changed
+62
-6
No files matched your search
+49
-5
@@ -58,6 +58,23 @@ RED = (200, 0, 0)
|
||||
BLACK = (0, 0, 0)
|
||||
MUTED = (110, 110, 110)
|
||||
|
||||
# EFA's inclMOT_N flags select which transport modes a trip request is
|
||||
# allowed to use -- N is an index into EFA's internal mode table, not the
|
||||
# "class" value seen in responses. Long-distance rail (IC/EC/ICE) sits at
|
||||
# 14-16; everything else usable for a regional/local commute (regional
|
||||
# trains, S-Bahn, buses, trams, etc.) is the rest. Ported from db_info's
|
||||
# _build_mot_params()/_build_produktgattungen().
|
||||
_MOT_LONG_DISTANCE = {f"inclMOT_{i}": "true" for i in (14, 15, 16)}
|
||||
_MOT_REGIONAL = {f"inclMOT_{i}": "true" for i in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 17, 20)}
|
||||
|
||||
|
||||
def _mot_params(connection_type: str) -> dict:
|
||||
if connection_type == "regional":
|
||||
return {"includedMeans": "checkbox", **_MOT_REGIONAL}
|
||||
if connection_type == "long_distance":
|
||||
return {"includedMeans": "checkbox", **_MOT_LONG_DISTANCE}
|
||||
return {}
|
||||
|
||||
|
||||
def _in_bounds(lat, lon, bounds) -> bool:
|
||||
if bounds is None:
|
||||
@@ -85,6 +102,7 @@ class JourneyScreen(Screen):
|
||||
"destination_query": "", "destination_label": "", "destination_lat": None, "destination_lon": None,
|
||||
"results": 3,
|
||||
"update_minutes": 2,
|
||||
"connection_type": "all",
|
||||
}
|
||||
|
||||
def get_poll_interval(self, settings: dict) -> int:
|
||||
@@ -150,7 +168,8 @@ class JourneyScreen(Screen):
|
||||
except (TypeError, ValueError):
|
||||
results = 3
|
||||
|
||||
params = self._build_params(o_lat, o_lon, d_lat, d_lon, results)
|
||||
connection_type = settings.get("connection_type") or "all"
|
||||
params = self._build_params(o_lat, o_lon, d_lat, d_lon, results, connection_type)
|
||||
|
||||
for source in EFA_SOURCES:
|
||||
if not (_in_bounds(o_lat, o_lon, source["bounds"]) and _in_bounds(d_lat, d_lon, source["bounds"])):
|
||||
@@ -172,9 +191,9 @@ class JourneyScreen(Screen):
|
||||
|
||||
return None
|
||||
|
||||
def _build_params(self, o_lat, o_lon, d_lat, d_lon, results) -> dict:
|
||||
def _build_params(self, o_lat, o_lon, d_lat, d_lon, results, connection_type: str = "all") -> dict:
|
||||
now = datetime.now().astimezone()
|
||||
return {
|
||||
params = {
|
||||
"outputFormat": "rapidJSON",
|
||||
"language": "de",
|
||||
"type_origin": "coord",
|
||||
@@ -192,6 +211,8 @@ class JourneyScreen(Screen):
|
||||
"lineRestriction": "400",
|
||||
"serverInfo": "0",
|
||||
}
|
||||
params.update(_mot_params(connection_type))
|
||||
return params
|
||||
|
||||
def _parse_journeys(self, data: dict, results: int):
|
||||
journeys = []
|
||||
@@ -211,6 +232,7 @@ class JourneyScreen(Screen):
|
||||
"line": transportation.get("name") or "?",
|
||||
"origin": leg.get("origin") or {},
|
||||
"destination": leg.get("destination") or {},
|
||||
"infos": leg.get("infos") or [],
|
||||
})
|
||||
|
||||
if not legs:
|
||||
@@ -228,6 +250,18 @@ class JourneyScreen(Screen):
|
||||
delay_min = int((dep_estimated - dep_planned).total_seconds() // 60) if dep_estimated else 0
|
||||
arr_actual = arr_estimated or arr_planned
|
||||
|
||||
# Service notices (disruptions, elevator outages, etc.) come as
|
||||
# leg.infos[].infoLinks[].title -- collect titles across all legs
|
||||
# of this journey, deduped, since the same notice often repeats
|
||||
# on every leg it affects.
|
||||
notice_titles = []
|
||||
for leg in legs:
|
||||
for info in leg["infos"]:
|
||||
for link in info.get("infoLinks") or []:
|
||||
title = link.get("title") or link.get("subtitle")
|
||||
if title and title not in notice_titles:
|
||||
notice_titles.append(title)
|
||||
|
||||
# EFA returns UTC timestamps (Z suffix) -- astimezone() with no
|
||||
# args converts to the system's local timezone before display.
|
||||
# Without this, times showed 2h behind (CEST is UTC+2), making
|
||||
@@ -239,6 +273,7 @@ class JourneyScreen(Screen):
|
||||
"lines": [leg["line"] for leg in legs],
|
||||
"transfers": len(legs) - 1,
|
||||
"platform": (first["origin"].get("properties") or {}).get("platformName") or "",
|
||||
"notice": "; ".join(notice_titles),
|
||||
})
|
||||
|
||||
return journeys or None
|
||||
@@ -300,8 +335,17 @@ class JourneyScreen(Screen):
|
||||
detail_parts.append(f"{journey['transfers']}x umsteigen")
|
||||
if journey["platform"]:
|
||||
detail_parts.append(f"Gl. {journey['platform']}")
|
||||
detail_line = truncate_to_width(draw, " · ".join(detail_parts), detail_font, width - 2 * pad)
|
||||
draw.text((pad, top_line_y + time_font.size * 1.25), detail_line, font=detail_font, fill=MUTED)
|
||||
if journey["notice"]:
|
||||
detail_parts.append(journey["notice"])
|
||||
|
||||
detail_y = top_line_y + time_font.size * 1.25
|
||||
detail_x = pad
|
||||
if journey["notice"]:
|
||||
draw.text((detail_x, detail_y), "⚠", font=detail_font, fill=RED)
|
||||
detail_x += text_width(draw, "⚠", detail_font) + scale * 0.015
|
||||
|
||||
detail_line = truncate_to_width(draw, " · ".join(detail_parts), detail_font, width - pad - detail_x)
|
||||
draw.text((detail_x, detail_y), detail_line, font=detail_font, fill=MUTED)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
Reference in new issue
Block a user