python-openpublictransport 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openpublictransport/__init__.py +5 -0
- openpublictransport/const.py +75 -0
- openpublictransport/models.py +95 -0
- openpublictransport/parsers.py +122 -0
- openpublictransport/providers/__init__.py +127 -0
- openpublictransport/providers/avv.py +44 -0
- openpublictransport/providers/base.py +77 -0
- openpublictransport/providers/beg.py +46 -0
- openpublictransport/providers/bsvg.py +44 -0
- openpublictransport/providers/bvg.py +32 -0
- openpublictransport/providers/db.py +39 -0
- openpublictransport/providers/ding.py +44 -0
- openpublictransport/providers/efa_base.py +209 -0
- openpublictransport/providers/fptf_base.py +169 -0
- openpublictransport/providers/gtfsde.py +21 -0
- openpublictransport/providers/hvv.py +40 -0
- openpublictransport/providers/kvv.py +38 -0
- openpublictransport/providers/mvv.py +48 -0
- openpublictransport/providers/nta.py +288 -0
- openpublictransport/providers/nvbw.py +44 -0
- openpublictransport/providers/nwl.py +46 -0
- openpublictransport/providers/oebb.py +182 -0
- openpublictransport/providers/otp.py +378 -0
- openpublictransport/providers/otp_base.py +268 -0
- openpublictransport/providers/otp_custom.py +22 -0
- openpublictransport/providers/rmv.py +272 -0
- openpublictransport/providers/rvv.py +43 -0
- openpublictransport/providers/sbb.py +174 -0
- openpublictransport/providers/trafiklab.py +280 -0
- openpublictransport/providers/transitous.py +198 -0
- openpublictransport/providers/trias_base.py +323 -0
- openpublictransport/providers/vagfr.py +44 -0
- openpublictransport/providers/vbn.py +73 -0
- openpublictransport/providers/vgn.py +46 -0
- openpublictransport/providers/vrn.py +51 -0
- openpublictransport/providers/vrr.py +51 -0
- openpublictransport/providers/vvo.py +45 -0
- openpublictransport/providers/vvs.py +48 -0
- python_openpublictransport-0.1.0.dist-info/METADATA +14 -0
- python_openpublictransport-0.1.0.dist-info/RECORD +42 -0
- python_openpublictransport-0.1.0.dist-info/WHEEL +5 -0
- python_openpublictransport-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Base provider for TRIAS (VDV 431-2) protocol APIs."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from zoneinfo import ZoneInfo
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
|
|
11
|
+
from ..models import UnifiedDeparture
|
|
12
|
+
from .base import BaseProvider
|
|
13
|
+
|
|
14
|
+
_LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
NS = {
|
|
17
|
+
"trias": "http://www.vdv.de/trias",
|
|
18
|
+
"siri": "http://www.siri.org.uk/siri",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_dt(s: str) -> Optional[datetime]:
|
|
23
|
+
"""Parse an ISO datetime string, returning None on failure."""
|
|
24
|
+
try:
|
|
25
|
+
return datetime.fromisoformat(s)
|
|
26
|
+
except (ValueError, TypeError):
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _find(element: Optional[ET.Element], path: str) -> Optional[ET.Element]:
|
|
31
|
+
if element is None:
|
|
32
|
+
return None
|
|
33
|
+
return element.find(path, NS)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _findall(element: Optional[ET.Element], path: str) -> List[ET.Element]:
|
|
37
|
+
if element is None:
|
|
38
|
+
return []
|
|
39
|
+
return element.findall(path, NS)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _text(element: Optional[ET.Element], path: str, default: str = "") -> str:
|
|
43
|
+
if element is None:
|
|
44
|
+
return default
|
|
45
|
+
child = element.find(path, NS)
|
|
46
|
+
return child.text if child is not None and child.text else default
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
DEFAULT_MODE_MAPPING = {
|
|
50
|
+
"rail": "train",
|
|
51
|
+
"urbanRail": "train",
|
|
52
|
+
"metro": "subway",
|
|
53
|
+
"underground": "subway",
|
|
54
|
+
"tram": "tram",
|
|
55
|
+
"bus": "bus",
|
|
56
|
+
"coach": "bus",
|
|
57
|
+
"water": "ferry",
|
|
58
|
+
"telecabin": "tram",
|
|
59
|
+
"funicular": "train",
|
|
60
|
+
"taxi": "taxi",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TRIASBaseProvider(BaseProvider):
|
|
65
|
+
"""Base class for TRIAS-based providers."""
|
|
66
|
+
|
|
67
|
+
trias_base_url: str = ""
|
|
68
|
+
requestor_ref: str = "openpublictransport"
|
|
69
|
+
trias_version: str = "1.1"
|
|
70
|
+
|
|
71
|
+
def get_timezone(self) -> str:
|
|
72
|
+
return "Europe/Berlin"
|
|
73
|
+
|
|
74
|
+
def get_mode_mapping(self) -> Dict[str, str]:
|
|
75
|
+
return DEFAULT_MODE_MAPPING
|
|
76
|
+
|
|
77
|
+
def _build_stop_event_request(self, stop_id: str, limit: int) -> str:
|
|
78
|
+
now = datetime.now(ZoneInfo(self.get_timezone())).isoformat()
|
|
79
|
+
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
80
|
+
<Trias version="{self.trias_version}" xmlns="http://www.vdv.de/trias"
|
|
81
|
+
xmlns:siri="http://www.siri.org.uk/siri">
|
|
82
|
+
<ServiceRequest>
|
|
83
|
+
<siri:RequestTimestamp>{now}</siri:RequestTimestamp>
|
|
84
|
+
<siri:RequestorRef>{self.requestor_ref}</siri:RequestorRef>
|
|
85
|
+
<RequestPayload>
|
|
86
|
+
<StopEventRequest>
|
|
87
|
+
<Location>
|
|
88
|
+
<LocationRef>
|
|
89
|
+
<StopPointRef>{stop_id}</StopPointRef>
|
|
90
|
+
</LocationRef>
|
|
91
|
+
<DepArrTime>{now}</DepArrTime>
|
|
92
|
+
</Location>
|
|
93
|
+
<Params>
|
|
94
|
+
<NumberOfResults>{limit}</NumberOfResults>
|
|
95
|
+
<StopEventType>departure</StopEventType>
|
|
96
|
+
<IncludePreviousCalls>false</IncludePreviousCalls>
|
|
97
|
+
<IncludeOnwardCalls>false</IncludeOnwardCalls>
|
|
98
|
+
<IncludeRealtimeData>true</IncludeRealtimeData>
|
|
99
|
+
</Params>
|
|
100
|
+
</StopEventRequest>
|
|
101
|
+
</RequestPayload>
|
|
102
|
+
</ServiceRequest>
|
|
103
|
+
</Trias>"""
|
|
104
|
+
|
|
105
|
+
def _build_location_request(self, search_term: str) -> str:
|
|
106
|
+
now = datetime.now(ZoneInfo(self.get_timezone())).isoformat()
|
|
107
|
+
return f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
108
|
+
<Trias version="{self.trias_version}" xmlns="http://www.vdv.de/trias"
|
|
109
|
+
xmlns:siri="http://www.siri.org.uk/siri">
|
|
110
|
+
<ServiceRequest>
|
|
111
|
+
<siri:RequestTimestamp>{now}</siri:RequestTimestamp>
|
|
112
|
+
<siri:RequestorRef>{self.requestor_ref}</siri:RequestorRef>
|
|
113
|
+
<RequestPayload>
|
|
114
|
+
<LocationInformationRequest>
|
|
115
|
+
<InitialInput>
|
|
116
|
+
<LocationName>{search_term}</LocationName>
|
|
117
|
+
</InitialInput>
|
|
118
|
+
<Restrictions>
|
|
119
|
+
<Type>stop</Type>
|
|
120
|
+
<NumberOfResults>15</NumberOfResults>
|
|
121
|
+
</Restrictions>
|
|
122
|
+
</LocationInformationRequest>
|
|
123
|
+
</RequestPayload>
|
|
124
|
+
</ServiceRequest>
|
|
125
|
+
</Trias>"""
|
|
126
|
+
|
|
127
|
+
def _extra_headers(self) -> Dict[str, str]:
|
|
128
|
+
return {}
|
|
129
|
+
|
|
130
|
+
async def _post_trias(self, xml_body: str) -> Optional[ET.Element]:
|
|
131
|
+
headers = {"Content-Type": "text/xml; charset=utf-8", **self._extra_headers()}
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
async with self.session.post(
|
|
135
|
+
self.trias_base_url,
|
|
136
|
+
data=xml_body.encode("utf-8"),
|
|
137
|
+
headers=headers,
|
|
138
|
+
timeout=aiohttp.ClientTimeout(total=15),
|
|
139
|
+
) as response:
|
|
140
|
+
if response.status == 200:
|
|
141
|
+
text = await response.text()
|
|
142
|
+
return ET.fromstring(text)
|
|
143
|
+
else:
|
|
144
|
+
_LOGGER.warning("%s TRIAS API returned status %s", self.provider_name, response.status)
|
|
145
|
+
except aiohttp.ClientError as e:
|
|
146
|
+
_LOGGER.warning("%s TRIAS API request failed: %s", self.provider_name, e)
|
|
147
|
+
except ET.ParseError as e:
|
|
148
|
+
_LOGGER.warning("%s TRIAS XML parse error: %s", self.provider_name, e)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
_LOGGER.warning("%s TRIAS error: %s", self.provider_name, e)
|
|
151
|
+
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
async def fetch_departures(
|
|
155
|
+
self,
|
|
156
|
+
station_id: Optional[str],
|
|
157
|
+
place_dm: str,
|
|
158
|
+
name_dm: str,
|
|
159
|
+
departures_limit: int,
|
|
160
|
+
) -> Optional[Dict[str, Any]]:
|
|
161
|
+
if not station_id:
|
|
162
|
+
_LOGGER.warning("%s provider requires a station_id", self.provider_name)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
xml_body = self._build_stop_event_request(station_id, departures_limit)
|
|
166
|
+
root = await self._post_trias(xml_body)
|
|
167
|
+
if root is None:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
results = root.findall(".//trias:StopEventResult", NS)
|
|
171
|
+
|
|
172
|
+
if not results:
|
|
173
|
+
results = root.findall(
|
|
174
|
+
".//trias:ServiceDelivery/trias:DeliveryPayload/trias:StopEventResponse/trias:StopEventResult",
|
|
175
|
+
NS,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if not results:
|
|
179
|
+
_LOGGER.debug("%s: No StopEventResult elements found", self.provider_name)
|
|
180
|
+
return {"stopEvents": []}
|
|
181
|
+
|
|
182
|
+
stop_events = []
|
|
183
|
+
for result in results:
|
|
184
|
+
stop_event = _find(result, "trias:StopEvent")
|
|
185
|
+
if stop_event is not None:
|
|
186
|
+
stop_events.append(self._stop_event_to_dict(stop_event))
|
|
187
|
+
|
|
188
|
+
return {"stopEvents": stop_events}
|
|
189
|
+
|
|
190
|
+
def _stop_event_to_dict(self, stop_event: ET.Element) -> Dict[str, Any]:
|
|
191
|
+
call = _find(stop_event, "trias:ThisCall/trias:CallAtStop")
|
|
192
|
+
service = _find(stop_event, "trias:Service")
|
|
193
|
+
|
|
194
|
+
timetabled = _text(call, "trias:ServiceDeparture/trias:TimetabledTime")
|
|
195
|
+
estimated = _text(call, "trias:ServiceDeparture/trias:EstimatedTime")
|
|
196
|
+
|
|
197
|
+
platform_text = _text(call, "trias:PlannedBay/trias:Text")
|
|
198
|
+
estimated_platform = _text(call, "trias:EstimatedBay/trias:Text")
|
|
199
|
+
|
|
200
|
+
line_name = _text(service, "trias:PublishedLineName/trias:Text")
|
|
201
|
+
mode = _text(service, "trias:Mode/trias:PtMode")
|
|
202
|
+
submode = (
|
|
203
|
+
_text(service, "trias:Mode/trias:RailSubmode")
|
|
204
|
+
or _text(service, "trias:Mode/trias:BusSubmode")
|
|
205
|
+
or _text(service, "trias:Mode/trias:TramSubmode")
|
|
206
|
+
or _text(service, "trias:Mode/trias:MetroSubmode")
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
destination = _text(service, "trias:DestinationText/trias:Text")
|
|
210
|
+
if not destination:
|
|
211
|
+
dest_stop = _find(service, "trias:DestinationStopPointRef")
|
|
212
|
+
destination = _text(dest_stop, "trias:StopPointName/trias:Text")
|
|
213
|
+
|
|
214
|
+
operator = _text(service, "trias:OperatorRef")
|
|
215
|
+
is_realtime = bool(estimated)
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
"timetabledTime": timetabled,
|
|
219
|
+
"estimatedTime": estimated,
|
|
220
|
+
"platform": estimated_platform or platform_text,
|
|
221
|
+
"plannedPlatform": platform_text,
|
|
222
|
+
"lineName": line_name,
|
|
223
|
+
"mode": mode,
|
|
224
|
+
"submode": submode,
|
|
225
|
+
"destination": destination,
|
|
226
|
+
"operator": operator,
|
|
227
|
+
"isRealtime": is_realtime,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
def parse_departure(
|
|
231
|
+
self, stop: Dict[str, Any], tz: Union[ZoneInfo, Any], now: datetime
|
|
232
|
+
) -> Optional[UnifiedDeparture]:
|
|
233
|
+
try:
|
|
234
|
+
timetabled_str = stop.get("timetabledTime", "")
|
|
235
|
+
estimated_str = stop.get("estimatedTime", "")
|
|
236
|
+
|
|
237
|
+
if not timetabled_str:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
planned = _parse_dt(timetabled_str)
|
|
241
|
+
if not planned:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
planned_local = planned.astimezone(tz)
|
|
245
|
+
|
|
246
|
+
if estimated_str:
|
|
247
|
+
when = _parse_dt(estimated_str)
|
|
248
|
+
when_local = when.astimezone(tz) if when else planned_local
|
|
249
|
+
else:
|
|
250
|
+
when_local = planned_local
|
|
251
|
+
|
|
252
|
+
delay_seconds = (when_local - planned_local).total_seconds()
|
|
253
|
+
delay_minutes = max(0, int(delay_seconds / 60))
|
|
254
|
+
|
|
255
|
+
mode = stop.get("mode", "")
|
|
256
|
+
mode_mapping = self.get_mode_mapping()
|
|
257
|
+
transport_type = mode_mapping.get(mode, "unknown")
|
|
258
|
+
|
|
259
|
+
platform = stop.get("platform", "")
|
|
260
|
+
planned_platform = stop.get("plannedPlatform", "")
|
|
261
|
+
platform_changed = bool(platform and planned_platform and platform != planned_platform)
|
|
262
|
+
|
|
263
|
+
time_diff = when_local - now
|
|
264
|
+
minutes_until = max(0, int(time_diff.total_seconds() / 60))
|
|
265
|
+
|
|
266
|
+
return UnifiedDeparture(
|
|
267
|
+
line=stop.get("lineName", ""),
|
|
268
|
+
destination=stop.get("destination", "Unknown"),
|
|
269
|
+
departure_time=when_local.strftime("%H:%M"),
|
|
270
|
+
planned_time=planned_local.strftime("%H:%M"),
|
|
271
|
+
delay=delay_minutes,
|
|
272
|
+
platform=platform,
|
|
273
|
+
transportation_type=transport_type,
|
|
274
|
+
is_realtime=stop.get("isRealtime", False),
|
|
275
|
+
minutes_until_departure=minutes_until,
|
|
276
|
+
departure_time_obj=when_local,
|
|
277
|
+
description=None,
|
|
278
|
+
agency=stop.get("operator"),
|
|
279
|
+
notices=None,
|
|
280
|
+
planned_platform=planned_platform if platform_changed else None,
|
|
281
|
+
platform_changed=platform_changed,
|
|
282
|
+
)
|
|
283
|
+
except Exception as e:
|
|
284
|
+
_LOGGER.debug("Error parsing %s TRIAS departure: %s", self.provider_name, e)
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
async def search_stops(self, search_term: str) -> List[Dict[str, Any]]:
|
|
288
|
+
xml_body = self._build_location_request(search_term)
|
|
289
|
+
root = await self._post_trias(xml_body)
|
|
290
|
+
if root is None:
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
results = root.findall(".//trias:LocationResult", NS)
|
|
294
|
+
if not results:
|
|
295
|
+
results = root.findall(".//trias:LocationInformationResponse/trias:LocationResult", NS)
|
|
296
|
+
|
|
297
|
+
stops = []
|
|
298
|
+
for result in results:
|
|
299
|
+
location = _find(result, "trias:Location")
|
|
300
|
+
if location is None:
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
stop_point = _find(location, "trias:StopPoint")
|
|
304
|
+
if stop_point is None:
|
|
305
|
+
continue
|
|
306
|
+
|
|
307
|
+
stop_id = _text(stop_point, "trias:StopPointRef")
|
|
308
|
+
name = _text(stop_point, "trias:StopPointName/trias:Text")
|
|
309
|
+
place = _text(location, "trias:LocationName/trias:Text")
|
|
310
|
+
|
|
311
|
+
if not stop_id or not name:
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
stops.append(
|
|
315
|
+
{
|
|
316
|
+
"id": stop_id,
|
|
317
|
+
"name": name,
|
|
318
|
+
"place": place,
|
|
319
|
+
"area_type": "stop",
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return stops
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""VAG Freiburg provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VAGFR
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VAGFRProvider(EFABaseProvider):
|
|
10
|
+
"""VAG (Freiburg) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VAGFR
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VAG (Freiburg)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://efa.vagfr.de/vagfr3/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://efa.vagfr.de/vagfr3/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # Fernverkehr
|
|
34
|
+
1: "train", # S-Bahn
|
|
35
|
+
4: "tram", # Straßenbahn
|
|
36
|
+
5: "bus", # Stadtbus
|
|
37
|
+
6: "bus", # Regionalbus
|
|
38
|
+
7: "bus", # Schnellbus
|
|
39
|
+
8: "bus", # Nachtbus
|
|
40
|
+
13: "train", # Regionalzug (RE/RB)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
44
|
+
return lambda s, est, plan: est != plan if est and plan else False
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""VBN (Verkehrsverbund Bremen/Niedersachsen) providers."""
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from ..const import PROVIDER_VBN_OTP, PROVIDER_VBN_TRIAS
|
|
7
|
+
from .otp_base import OTPBaseProvider
|
|
8
|
+
from .trias_base import TRIASBaseProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VBNOTPProvider(OTPBaseProvider):
|
|
12
|
+
"""VBN via OpenTripPlanner REST API (http://gtfsr.vbn.de/api/)."""
|
|
13
|
+
|
|
14
|
+
otp_base_url = "http://gtfsr.vbn.de/api/routers/default"
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
session: aiohttp.ClientSession,
|
|
19
|
+
api_key: Optional[str] = None,
|
|
20
|
+
api_key_secondary: Optional[str] = None,
|
|
21
|
+
custom_url: Optional[str] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(session, api_key, api_key_secondary)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def provider_id(self) -> str:
|
|
27
|
+
return PROVIDER_VBN_OTP
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def provider_name(self) -> str:
|
|
31
|
+
return "VBN OTP (Bremen/Niedersachsen)"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def requires_api_key(self) -> bool:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
def _auth_headers(self) -> Dict[str, str]:
|
|
38
|
+
h = super()._auth_headers()
|
|
39
|
+
if self.api_key:
|
|
40
|
+
h["Authorization"] = self.api_key
|
|
41
|
+
return h
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VBNTriasProvider(TRIASBaseProvider):
|
|
45
|
+
"""VBN via TRIAS XML API (https://fahrplaner.vbn.de/triasproxy/)."""
|
|
46
|
+
|
|
47
|
+
trias_base_url = "https://fahrplaner.vbn.de/triasproxy/"
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
session: aiohttp.ClientSession,
|
|
52
|
+
api_key: Optional[str] = None,
|
|
53
|
+
api_key_secondary: Optional[str] = None,
|
|
54
|
+
custom_url: Optional[str] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
super().__init__(session, api_key, api_key_secondary)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def provider_id(self) -> str:
|
|
60
|
+
return PROVIDER_VBN_TRIAS
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def provider_name(self) -> str:
|
|
64
|
+
return "VBN TRIAS (Bremen/Niedersachsen)"
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def requires_api_key(self) -> bool:
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def _extra_headers(self) -> Dict[str, str]:
|
|
71
|
+
if self.api_key:
|
|
72
|
+
return {"Authorization": self.api_key}
|
|
73
|
+
return {}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""VGN (Verkehrsverbund Großraum Nürnberg) provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VGN
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VGNProvider(EFABaseProvider):
|
|
10
|
+
"""VGN (Nürnberg) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VGN
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VGN (Nürnberg)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://efa.vgn.de/vgnExt_oeffi/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://efa.vgn.de/vgnExt_oeffi/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # Fernverkehr
|
|
34
|
+
1: "train", # S-Bahn
|
|
35
|
+
2: "subway", # U-Bahn
|
|
36
|
+
3: "subway", # U-Bahn variant
|
|
37
|
+
4: "tram", # Tram
|
|
38
|
+
5: "bus", # Stadtbus
|
|
39
|
+
6: "bus", # Regionalbus
|
|
40
|
+
7: "bus", # Schnellbus
|
|
41
|
+
8: "bus", # Nachtbus
|
|
42
|
+
13: "train", # Regionalzug (RE/RB)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
46
|
+
return lambda s, est, plan: est != plan if est and plan else False
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""VRN (Verkehrsverbund Rhein-Neckar) provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VRN
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VRNProvider(EFABaseProvider):
|
|
10
|
+
"""VRN (Rhein-Neckar) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VRN
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VRN (Rhein-Neckar)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://www.vrn.de/mngvrn/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://www.vrn.de/mngvrn/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # High-speed trains (ICE, IC, EC)
|
|
34
|
+
1: "train", # Regional trains (RE, RB)
|
|
35
|
+
2: "subway", # U-Bahn
|
|
36
|
+
3: "subway", # U-Bahn variant
|
|
37
|
+
4: "tram", # Tram/Streetcar
|
|
38
|
+
5: "bus", # City bus
|
|
39
|
+
6: "bus", # Regional bus
|
|
40
|
+
7: "bus", # Express bus
|
|
41
|
+
8: "bus", # Night bus
|
|
42
|
+
9: "ferry", # Ferry/Ship
|
|
43
|
+
10: "taxi", # Taxi
|
|
44
|
+
11: "bus", # Other/Special transport
|
|
45
|
+
13: "train", # Regionalzug (RE)
|
|
46
|
+
15: "train", # InterCity (IC)
|
|
47
|
+
16: "train", # InterCityExpress (ICE)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
51
|
+
return lambda s, est, plan: est != plan if est and plan else False
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""VRR (Verkehrsverbund Rhein-Ruhr) provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VRR
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VRRProvider(EFABaseProvider):
|
|
10
|
+
"""VRR (Verkehrsverbund Rhein-Ruhr) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VRR
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VRR (NRW)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://openservice-test.vrr.de/static03/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://openservice-test.vrr.de/static03/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # High-speed trains (ICE, IC, EC)
|
|
34
|
+
1: "train", # Regional trains (RE, RB)
|
|
35
|
+
2: "subway", # U-Bahn
|
|
36
|
+
3: "subway", # U-Bahn variant
|
|
37
|
+
4: "tram", # Tram/Streetcar
|
|
38
|
+
5: "bus", # City bus
|
|
39
|
+
6: "bus", # Regional bus
|
|
40
|
+
7: "bus", # Express bus
|
|
41
|
+
8: "bus", # Night bus
|
|
42
|
+
9: "ferry", # Ferry/Ship
|
|
43
|
+
10: "taxi", # Taxi
|
|
44
|
+
11: "bus", # Other/Special transport
|
|
45
|
+
13: "train", # Regionalzug (RE)
|
|
46
|
+
15: "train", # InterCity (IC)
|
|
47
|
+
16: "train", # InterCityExpress (ICE)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
51
|
+
return lambda s, est, plan: "MONITORED" in s.get("realtimeStatus", [])
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""VVO (Verkehrsverbund Oberelbe) provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VVO
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VVOProvider(EFABaseProvider):
|
|
10
|
+
"""VVO (Dresden) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VVO
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VVO (Dresden)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://efa.vvo-online.de/VMSSL3/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://efa.vvo-online.de/VMSSL3/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # High-speed trains (ICE, IC, EC)
|
|
34
|
+
1: "train", # Regional trains (RE, RB, S-Bahn)
|
|
35
|
+
4: "tram", # Tram/Streetcar
|
|
36
|
+
5: "bus", # City bus
|
|
37
|
+
6: "bus", # Regional bus
|
|
38
|
+
7: "bus", # Express bus
|
|
39
|
+
8: "bus", # Night bus
|
|
40
|
+
9: "ferry", # Ferry/Ship
|
|
41
|
+
13: "train", # Regionalzug (RE)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
45
|
+
return lambda s, est, plan: est != plan if est and plan else False
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""VVS (Verkehrs- und Tarifverbund Stuttgart) provider implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..const import PROVIDER_VVS
|
|
6
|
+
from .efa_base import EFABaseProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VVSProvider(EFABaseProvider):
|
|
10
|
+
"""VVS (Stuttgart) provider."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def provider_id(self) -> str:
|
|
14
|
+
return PROVIDER_VVS
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def provider_name(self) -> str:
|
|
18
|
+
return "VVS (Stuttgart)"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def dm_base_url(self) -> str:
|
|
22
|
+
return "https://www3.vvs.de/mngvvs/XML_DM_REQUEST"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def sf_base_url(self) -> str:
|
|
26
|
+
return "https://www3.vvs.de/mngvvs/XML_STOPFINDER_REQUEST"
|
|
27
|
+
|
|
28
|
+
def get_timezone(self) -> str:
|
|
29
|
+
return "Europe/Berlin"
|
|
30
|
+
|
|
31
|
+
def get_transport_type_mapping(self) -> Dict[Any, str]:
|
|
32
|
+
return {
|
|
33
|
+
0: "train", # Fernverkehr (ICE, IC, EC)
|
|
34
|
+
1: "train", # S-Bahn
|
|
35
|
+
2: "subway", # Stadtbahn (SSB)
|
|
36
|
+
3: "subway", # Stadtbahn variant
|
|
37
|
+
4: "tram", # Tram
|
|
38
|
+
5: "bus", # Stadtbus
|
|
39
|
+
6: "bus", # Regionalbus
|
|
40
|
+
7: "bus", # Schnellbus
|
|
41
|
+
8: "bus", # Nachtbus
|
|
42
|
+
9: "ferry", # Fähre
|
|
43
|
+
10: "taxi", # Rufbus
|
|
44
|
+
13: "train", # Regionalzug (RE/RB)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def get_realtime_fn(self) -> Callable[[Dict[str, Any], Optional[str], Optional[str]], bool]:
|
|
48
|
+
return lambda s, est, plan: est != plan if est and plan else False
|