PyTransportNSWv2 2.0.0__tar.gz → 2.0.1__tar.gz
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.
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PKG-INFO +1 -1
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PyTransportNSWv2.egg-info/PKG-INFO +1 -1
- {PyTransportNSWv2-2.0.0/TransportNSWv2 → PyTransportNSWv2-2.0.1/PyTransportNSWv2.egg-info}/TransportNSWv2.py +229 -182
- PyTransportNSWv2-2.0.1/TransportNSWv2/TransportNSWv2.py +725 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/setup.py +1 -1
- PyTransportNSWv2-2.0.0/PyTransportNSWv2.egg-info/TransportNSWv2.py +0 -592
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/LICENSE +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PyTransportNSWv2.egg-info/SOURCES.txt +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PyTransportNSWv2.egg-info/dependency_links.txt +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PyTransportNSWv2.egg-info/requires.txt +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/PyTransportNSWv2.egg-info/top_level.txt +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/README.md +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/TransportNSWv2/__init__.py +0 -0
- {PyTransportNSWv2-2.0.0 → PyTransportNSWv2-2.0.1}/setup.cfg +0 -0
@@ -27,15 +27,23 @@ ATTR_ORIGIN_TRANSPORT_TYPE = 'origin_transport_type'
|
|
27
27
|
ATTR_ORIGIN_TRANSPORT_NAME = 'origin_transport_name'
|
28
28
|
ATTR_ORIGIN_LINE_NAME = 'origin_line_name'
|
29
29
|
ATTR_ORIGIN_LINE_NAME_SHORT = 'origin_line_name_short'
|
30
|
+
ATTR_DESTINATION_TRANSPORT_TYPE = 'destination_transport_type'
|
31
|
+
ATTR_DESTINATION_TRANSPORT_NAME = 'destination_transport_name'
|
32
|
+
ATTR_DESTINATION_LINE_NAME = 'destination_line_name'
|
33
|
+
ATTR_DESTINATION_LINE_NAME_SHORT = 'destination_line_name_short'
|
34
|
+
|
30
35
|
ATTR_CHANGES = 'changes'
|
31
36
|
|
32
|
-
|
37
|
+
ATTR_ORIGIN_OCCUPANCY = 'origin_occupancy'
|
38
|
+
ATTR_DESTINATION_OCCUPANCY = 'destination_occupancy'
|
39
|
+
|
40
|
+
ATTR_ORIGIN_REAL_TIME_TRIP_ID = 'origin_real_time_trip_id'
|
41
|
+
ATTR_DESTINATION_REAL_TIME_TRIP_ID = 'destination_real_time_trip_id'
|
42
|
+
ATTR_ORIGIN_LATITUDE = 'origin_latitude'
|
43
|
+
ATTR_ORIGIN_LONGITUDE = 'origin_longitude'
|
44
|
+
ATTR_DESTINATION_LATITUDE = 'destination_latitude'
|
45
|
+
ATTR_DESTINATION_LONGITUDE = 'destination_longitude'
|
33
46
|
|
34
|
-
ATTR_AVMS_TRIP_ID = 'avms_trip_id'
|
35
|
-
ATTR_REAL_TIME_TRIP_ID = 'real_time_trip_id'
|
36
|
-
ATTR_LATITUDE = 'latitude'
|
37
|
-
ATTR_LONGITUDE = 'longitude'
|
38
|
-
ATTR_GTFS_URI = 'gtfs_uri'
|
39
47
|
ATTR_ALERTS = 'alerts'
|
40
48
|
|
41
49
|
logger = logging.getLogger(__name__)
|
@@ -62,16 +70,24 @@ class TransportNSWv2(object):
|
|
62
70
|
ATTR_ORIGIN_TRANSPORT_NAME : 'n/a',
|
63
71
|
ATTR_ORIGIN_LINE_NAME : 'n/a',
|
64
72
|
ATTR_ORIGIN_LINE_NAME_SHORT : 'n/a',
|
73
|
+
ATTR_DESTINATION_TRANSPORT_TYPE : 'n/a',
|
74
|
+
ATTR_DESTINATION_TRANSPORT_NAME : 'n/a',
|
75
|
+
ATTR_DESTINATION_LINE_NAME : 'n/a',
|
76
|
+
ATTR_DESTINATION_LINE_NAME_SHORT : 'n/a',
|
65
77
|
ATTR_CHANGES : 'n/a',
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
78
|
+
ATTR_ORIGIN_OCCUPANCY: 'n/a',
|
79
|
+
ATTR_DESTINATION_OCCUPANCY: 'n/a',
|
80
|
+
ATTR_ORIGIN_REAL_TIME_TRIP_ID : 'n/a',
|
81
|
+
ATTR_ORIGIN_LATITUDE : 'n/a',
|
82
|
+
ATTR_ORIGIN_LONGITUDE : 'n/a',
|
83
|
+
ATTR_DESTINATION_REAL_TIME_TRIP_ID : 'n/a',
|
84
|
+
ATTR_DESTINATION_LATITUDE : 'n/a',
|
85
|
+
ATTR_DESTINATION_LONGITUDE : 'n/a',
|
70
86
|
ATTR_ALERTS: '[]'
|
71
87
|
}
|
72
88
|
|
73
89
|
|
74
|
-
def check_stops(self, api_key, stops
|
90
|
+
def check_stops(self, api_key, stops):
|
75
91
|
# Check the list of stops and return a JSON array of the stop details, plus if all the checked stops existed
|
76
92
|
# Return a JSON array of the results
|
77
93
|
|
@@ -164,17 +180,27 @@ class TransportNSWv2(object):
|
|
164
180
|
raise StopError(f"Error '{ex}' calling stop finder API for stop ID {stop}", stop)
|
165
181
|
|
166
182
|
|
167
|
-
def get_trip(self, name_origin, name_destination , api_key, journey_wait_time = 0,
|
183
|
+
def get_trip(self, name_origin, name_destination , api_key, journey_wait_time = 0, origin_transport_type = [0], destination_transport_type = [0], \
|
168
184
|
strict_transport_type = False, raw_output = False, journeys_to_return = 1, route_filter = '', \
|
169
|
-
include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True
|
170
|
-
home_assistant = False):
|
185
|
+
include_realtime_location = True, include_alerts = 'none', alert_type = ['all'], check_stop_ids = True):
|
171
186
|
|
172
187
|
"""Get the latest data from Transport NSW."""
|
173
188
|
fmt = '%Y-%m-%dT%H:%M:%SZ'
|
174
189
|
|
175
190
|
route_filter = route_filter.lower()
|
176
191
|
include_alerts = include_alerts.lower()
|
177
|
-
|
192
|
+
|
193
|
+
# Sanity checking - convert any single-instance variables to lists
|
194
|
+
if isinstance(origin_transport_type, int):
|
195
|
+
origin_transport_type = [origin_transport_type]
|
196
|
+
|
197
|
+
if isinstance(destination_transport_type, int):
|
198
|
+
destiation_transport_type = [destination_transport_type]
|
199
|
+
|
200
|
+
if isinstance(alert_type, str):
|
201
|
+
alert_type = alert_type.split('|')
|
202
|
+
|
203
|
+
alert_type = [alert.lower() for alert in alert_type]
|
178
204
|
|
179
205
|
# This query always uses the current date and time - but add in any 'journey_wait_time' minutes
|
180
206
|
now_plus_wait = datetime.now() + timedelta(minutes = journey_wait_time)
|
@@ -192,13 +218,13 @@ class TransportNSWv2(object):
|
|
192
218
|
if not data['all_stops_valid']:
|
193
219
|
# One or both of those stops was invalid - log an error and exit
|
194
220
|
stop_error = ""
|
221
|
+
|
195
222
|
for stop in data['stop_list']:
|
196
223
|
if not stop['valid']:
|
197
224
|
stop_error += stop['stop_id']+ ", "
|
198
225
|
|
199
|
-
#logger.error(f"Stop ID(s) {stop_error[:-2]} do not exist - exiting")
|
200
226
|
raise StopError (f"Stop ID(s) {stop_error[:-2]} do not exist", stop_error)
|
201
|
-
|
227
|
+
|
202
228
|
|
203
229
|
# We don't control how many journeys are returned any more, so need to be careful of running out of valid journeys if there is a filter in place, particularly a strict filter
|
204
230
|
# It would be more efficient to return one journey, check if the filter is met and then retrieve the next one via a new query if not, but for now we'll only be making use of the journeys we've been given
|
@@ -232,8 +258,6 @@ class TransportNSWv2(object):
|
|
232
258
|
else:
|
233
259
|
raise TripError(f"Error '{str(response.status_cude)}' calling trip API for journey {name_origin} to {name_destination}")
|
234
260
|
|
235
|
-
return None
|
236
|
-
|
237
261
|
result = response.json()
|
238
262
|
|
239
263
|
# The API will always return a valid trip, so it's just a case of grabbing the metadata that we need...
|
@@ -260,28 +284,23 @@ class TransportNSWv2(object):
|
|
260
284
|
|
261
285
|
for current_journey_index in range (0, retrieved_journeys, 1):
|
262
286
|
# Look for a trip with a matching transport type filter in at least one of its legs. Either ANY, or the first leg, depending on how strict we're being
|
263
|
-
|
287
|
+
legs, next_journey_index, first_leg, last_leg, changes = self._find_next_journey(result['journeys'], current_journey_index, origin_transport_type, destination_transport_type, strict_transport_type, route_filter)
|
264
288
|
|
265
|
-
if
|
289
|
+
if legs is None:
|
290
|
+
# An empty journey that didn't meet the criteria - which means all the valid journeys have been found already
|
266
291
|
pass
|
267
292
|
else:
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
changes = self._find_changes(legs, transport_type)
|
274
|
-
|
275
|
-
origin = first_leg['origin']
|
276
|
-
first_stop = first_leg['destination']
|
277
|
-
destination = last_leg['destination']
|
278
|
-
transportation = first_leg['transportation']
|
293
|
+
origin_leg = first_leg['origin']
|
294
|
+
origin_stop = first_leg['destination']
|
295
|
+
destination_stop = last_leg['destination']
|
296
|
+
origin_transportation = first_leg['transportation']
|
297
|
+
destination_transportation = last_leg['transportation']
|
279
298
|
|
280
299
|
# Origin info
|
281
|
-
origin_stop_id =
|
282
|
-
origin_name =
|
283
|
-
origin_departure_time =
|
284
|
-
origin_departure_time_planned =
|
300
|
+
origin_stop_id = origin_leg['id']
|
301
|
+
origin_name = origin_leg['name']
|
302
|
+
origin_departure_time = origin_leg['departureTimeEstimated']
|
303
|
+
origin_departure_time_planned = origin_leg['departureTimePlanned']
|
285
304
|
|
286
305
|
t1 = datetime.strptime(origin_departure_time, fmt).timestamp()
|
287
306
|
t2 = datetime.strptime(origin_departure_time_planned, fmt).timestamp()
|
@@ -291,40 +310,58 @@ class TransportNSWv2(object):
|
|
291
310
|
due = self._get_due(datetime.strptime(origin_departure_time, fmt))
|
292
311
|
|
293
312
|
# Destination info
|
294
|
-
destination_stop_id =
|
295
|
-
destination_name =
|
296
|
-
destination_arrival_time =
|
313
|
+
destination_stop_id = destination_stop['id']
|
314
|
+
destination_name = destination_stop['name']
|
315
|
+
destination_arrival_time = destination_stop['arrivalTimeEstimated']
|
297
316
|
|
298
317
|
# Origin type info - train, bus, etc
|
299
|
-
origin_mode = self._get_mode(
|
300
|
-
origin_mode_name =
|
318
|
+
origin_mode = self._get_mode(origin_transportation['product']['class'])
|
319
|
+
origin_mode_name = origin_transportation['product']['name']
|
320
|
+
|
321
|
+
# Destination type info - train, bus, etc
|
322
|
+
destination_mode = self._get_mode(destination_transportation['product']['class'])
|
323
|
+
destination_mode_name = destination_transportation['product']['name']
|
301
324
|
|
302
325
|
# RealTimeTripID info so we can try and get the current location later
|
303
|
-
|
304
|
-
|
305
|
-
|
326
|
+
origin_realtimetripid = 'n/a'
|
327
|
+
origin_agencyid = ''
|
328
|
+
destination_realtimetripid = 'n/a'
|
329
|
+
destination_agencyid = ''
|
306
330
|
|
307
|
-
|
308
|
-
|
331
|
+
if 'properties' in origin_transportation and 'RealtimeTripId' in origin_transportation['properties']:
|
332
|
+
origin_realtimetripid = origin_transportation['properties']['RealtimeTripId']
|
333
|
+
origin_agencyid = origin_transportation['operator']['id']
|
309
334
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
avmstripid = transportation['properties']['AVMSTripID']
|
335
|
+
if 'properties' in destination_transportation and 'RealtimeTripId' in destination_transportation['properties']:
|
336
|
+
destination_realtimetripid = destination_transportation['properties']['RealtimeTripId']
|
337
|
+
destination_agencyid = destination_transportation['operator']['id']
|
314
338
|
|
315
339
|
# Line info
|
316
|
-
origin_line_name_short =
|
317
|
-
if 'disassembledName' in
|
318
|
-
origin_line_name_short =
|
340
|
+
origin_line_name_short = 'n/a'
|
341
|
+
if 'disassembledName' in origin_transportation:
|
342
|
+
origin_line_name_short = origin_transportation['disassembledName']
|
343
|
+
|
344
|
+
origin_line_name = 'n/a'
|
345
|
+
if 'number' in origin_transportation:
|
346
|
+
origin_line_name = origin_transportation['number']
|
319
347
|
|
320
|
-
|
321
|
-
if '
|
322
|
-
|
348
|
+
destination_line_name_short = 'n/a'
|
349
|
+
if 'disassembledName' in destination_transportation:
|
350
|
+
destination_line_name_short = destination_transportation['disassembledName']
|
351
|
+
|
352
|
+
destination_line_name = 'n/a'
|
353
|
+
if 'number' in destination_transportation:
|
354
|
+
destination_line_name = destination_transportation['number']
|
323
355
|
|
324
356
|
# Occupancy info, if it's there
|
325
|
-
|
326
|
-
|
327
|
-
|
357
|
+
origin_occupancy = 'n/a'
|
358
|
+
destination_occupancy = 'n/a'
|
359
|
+
|
360
|
+
if 'properties' in origin_stop and 'occupancy' in origin_stop['properties']:
|
361
|
+
origin_occupancy = origin_stop['properties']['occupancy']
|
362
|
+
|
363
|
+
if 'properties' in destination_stop and 'occupancy' in destination_stop['properties']:
|
364
|
+
destination_occupancy = destination_stop['properties']['occupancy']
|
328
365
|
|
329
366
|
alerts = "[]"
|
330
367
|
if include_alerts != 'none':
|
@@ -332,63 +369,17 @@ class TransportNSWv2(object):
|
|
332
369
|
# Only include alerts of the specified priority or greater, and of the specified type
|
333
370
|
alerts = self._find_alerts(legs, include_alerts, alert_type)
|
334
371
|
|
335
|
-
|
336
|
-
|
337
|
-
|
372
|
+
origin_latitude = 'n/a'
|
373
|
+
origin_longitude = 'n/a'
|
374
|
+
destination_latitude = 'n/a'
|
375
|
+
destination_longitude = 'n/a'
|
338
376
|
|
339
|
-
if include_realtime_location and
|
340
|
-
|
341
|
-
# Build the URL(s) - some modes have multiple GTFS sources, unforunately
|
342
|
-
# Some travel modes require brute-forcing the API call a few times, so if we're sure of the URI,
|
343
|
-
# ie it's been determined elsewhere then it can be forced
|
377
|
+
if include_realtime_location and origin_realtimetripid != 'n/a':
|
378
|
+
origin_latitude, origin_longitude = self._find_location(api_key, origin_mode, origin_realtimetripid, origin_agencyid)
|
344
379
|
|
345
|
-
|
346
|
-
|
380
|
+
if include_realtime_location and destination_realtimetripid != 'n/a':
|
381
|
+
destination_latitude, destination_longitude = self._find_location(api_key, destination_mode, destination_realtimetripid, destination_agencyid)
|
347
382
|
|
348
|
-
# Check for a forced URI
|
349
|
-
if not forced_gtfs_uri:
|
350
|
-
url_mode_list = self._get_mode_list(origin_mode, agencyid)
|
351
|
-
else:
|
352
|
-
# We've been forced to use a specific URI!
|
353
|
-
url_mode_list = forced_gtfs_uri
|
354
|
-
|
355
|
-
if not url_mode_list is None:
|
356
|
-
for mode_url in url_mode_list:
|
357
|
-
url = url_base_path + mode_url
|
358
|
-
response = requests.get(url, headers=header, timeout=10)
|
359
|
-
|
360
|
-
# Only try and process the results if we got a good return code
|
361
|
-
if response.status_code == 200:
|
362
|
-
# Search the feed and see if we can match realtimetripid to trip_id
|
363
|
-
# If we do, capture the latitude and longitude
|
364
|
-
feed = gtfs_realtime_pb2.FeedMessage()
|
365
|
-
feed.ParseFromString(response.content)
|
366
|
-
reg = re.compile(realtimetripid)
|
367
|
-
|
368
|
-
for entity in feed.entity:
|
369
|
-
if bool(re.match(reg, entity.vehicle.trip.trip_id)):
|
370
|
-
latitude = entity.vehicle.position.latitude
|
371
|
-
longitude = entity.vehicle.position.longitude
|
372
|
-
|
373
|
-
# We found it, so flag it and break out
|
374
|
-
bFoundTripID = True
|
375
|
-
break
|
376
|
-
else:
|
377
|
-
# Warn that we didn't get a good return
|
378
|
-
if response.status_code == 401:
|
379
|
-
logger.error(f"Error 'Invalid API key' calling {url} API")
|
380
|
-
elif response.status_code == 403 or response.status_code == 429:
|
381
|
-
logger.error(f"Error 'API rate limit exceded' calling {url} API")
|
382
|
-
else:
|
383
|
-
logger.error(f"Error '{str(response.status_code)}' calling {url} API")
|
384
|
-
|
385
|
-
if bFoundTripID == True:
|
386
|
-
# No need to look any further
|
387
|
-
break
|
388
|
-
|
389
|
-
# Put in a quick pause here to try and make sure we stay under the 5 API calls/second limit
|
390
|
-
# Not usually an issue but if multiple processes are running multiple calls we might hit it
|
391
|
-
time.sleep(0.75)
|
392
383
|
|
393
384
|
self.info = {
|
394
385
|
ATTR_DUE_IN: due,
|
@@ -403,20 +394,22 @@ class TransportNSWv2(object):
|
|
403
394
|
ATTR_ORIGIN_TRANSPORT_NAME: origin_mode_name,
|
404
395
|
ATTR_ORIGIN_LINE_NAME : origin_line_name,
|
405
396
|
ATTR_ORIGIN_LINE_NAME_SHORT : origin_line_name_short,
|
397
|
+
ATTR_DESTINATION_TRANSPORT_TYPE : destination_mode,
|
398
|
+
ATTR_DESTINATION_TRANSPORT_NAME: destination_mode_name,
|
399
|
+
ATTR_DESTINATION_LINE_NAME : destination_line_name,
|
400
|
+
ATTR_DESTINATION_LINE_NAME_SHORT : destination_line_name_short,
|
406
401
|
ATTR_CHANGES: changes,
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
402
|
+
ATTR_ORIGIN_OCCUPANCY: origin_occupancy,
|
403
|
+
ATTR_DESTINATION_OCCUPANCY: destination_occupancy,
|
404
|
+
ATTR_ORIGIN_REAL_TIME_TRIP_ID: origin_realtimetripid,
|
405
|
+
ATTR_DESTINATION_REAL_TIME_TRIP_ID: destination_realtimetripid,
|
406
|
+
ATTR_ORIGIN_LATITUDE: origin_latitude,
|
407
|
+
ATTR_ORIGIN_LONGITUDE: origin_longitude,
|
408
|
+
ATTR_DESTINATION_LATITUDE: destination_latitude,
|
409
|
+
ATTR_DESTINATION_LONGITUDE: destination_longitude,
|
412
410
|
ATTR_ALERTS: json.loads(alerts)
|
413
411
|
}
|
414
412
|
|
415
|
-
if home_assistant:
|
416
|
-
self.info.update (
|
417
|
-
{ATTR_AVMS_TRIP_ID: avmstripid}
|
418
|
-
)
|
419
|
-
|
420
413
|
found_journeys = found_journeys + 1
|
421
414
|
|
422
415
|
# Add to the return array
|
@@ -437,118 +430,175 @@ class TransportNSWv2(object):
|
|
437
430
|
return json_output
|
438
431
|
|
439
432
|
|
440
|
-
def _find_next_journey(self, journeys, start_journey_index,
|
433
|
+
def _find_next_journey(self, journeys, start_journey_index, origin_transport_type, destination_transport_type, strict, route_filter):
|
441
434
|
# Find the next journey that has a leg of the requested type, and/or that satisfies the route filter
|
442
435
|
journey_count = len(journeys)
|
443
436
|
|
444
437
|
# Some basic error checking
|
445
438
|
if start_journey_index > journey_count:
|
446
|
-
return None, None
|
439
|
+
return None, None, None, None, None
|
447
440
|
|
448
441
|
for journey_index in range (start_journey_index, journey_count, 1):
|
449
|
-
|
450
|
-
|
451
|
-
|
442
|
+
origin_leg = self._find_first_leg(journeys[journey_index]['legs'], origin_transport_type, strict, route_filter)
|
443
|
+
|
444
|
+
if origin_leg is not None:
|
445
|
+
destination_leg = self._find_last_leg(journeys[journey_index]['legs'], destination_transport_type, strict)
|
446
|
+
|
447
|
+
if origin_leg is not None and destination_leg is not None:
|
448
|
+
changes = self._find_changes(journeys[journey_index]['legs'], origin_leg, destination_leg)
|
449
|
+
return journeys[journey_index]['legs'], journey_index + 1, origin_leg, destination_leg, changes
|
452
450
|
else:
|
453
|
-
return None, None
|
451
|
+
return None, None, None, None, None
|
454
452
|
|
455
453
|
# Hmm, we didn't find one
|
456
|
-
return None, None
|
454
|
+
return None, None, None, None, None
|
457
455
|
|
458
456
|
|
459
|
-
def _find_first_leg(self, legs,
|
457
|
+
def _find_first_leg(self, legs, transport_type, strict, route_filter):
|
460
458
|
# Find the first leg of the requested type
|
461
|
-
|
462
|
-
for leg_index in range (0, leg_count, 1):
|
459
|
+
for leg in legs:
|
463
460
|
#First, check against the route filter
|
464
461
|
origin_line_name_short = 'n/a'
|
465
462
|
origin_line_name = 'n/a'
|
466
463
|
|
467
|
-
if 'transportation' in
|
468
|
-
origin_line_name_short =
|
469
|
-
origin_line_name =
|
464
|
+
if 'transportation' in leg and 'disassembledName' in leg['transportation']:
|
465
|
+
origin_line_name_short = leg['transportation']['disassembledName'].lower()
|
466
|
+
origin_line_name = leg['transportation']['number'].lower()
|
470
467
|
|
471
468
|
if (route_filter in origin_line_name_short or route_filter in origin_line_name):
|
472
|
-
|
473
|
-
|
474
|
-
if legtype != 0 and leg_class == legtype:
|
475
|
-
return legs[leg_index]
|
469
|
+
# This leg passes the route filter, check it passes any transport type filter as well
|
470
|
+
leg_class = leg['transportation']['product']['class']
|
476
471
|
|
472
|
+
if leg_class in transport_type:
|
473
|
+
# This leg meets the transport type criteria
|
474
|
+
return leg
|
475
|
+
|
476
|
+
if 0 in transport_type and leg_class < 99:
|
477
477
|
# We don't have a filter, and this is the first non-walk/cycle leg so return that leg
|
478
|
-
|
479
|
-
|
478
|
+
return leg
|
479
|
+
|
480
|
+
if leg_class not in transport_type and leg_class < 99:
|
481
|
+
# The transport type is something other than has been requested, but it isn't walking, so this entire journey is no good
|
482
|
+
return None
|
480
483
|
|
481
|
-
# Exit if we're doing strict filtering and we haven't found that type in the first leg
|
482
|
-
if
|
484
|
+
# Exit if we're doing strict filtering and we haven't found that type in the first leg, which we haven't if we've got this far
|
485
|
+
if strict == True:
|
483
486
|
return None
|
484
487
|
|
485
488
|
# Hmm, we didn't find one
|
486
489
|
return None
|
487
490
|
|
488
|
-
|
489
|
-
def _find_last_leg(self, legs, legtype, strict):
|
491
|
+
def _find_last_leg(self, legs, transport_type, strict):
|
490
492
|
# Find the last leg of the requested type
|
491
|
-
|
492
|
-
|
493
|
-
leg_class = legs[leg_index]['transportation']['product']['class']
|
493
|
+
for leg in reversed(legs):
|
494
|
+
leg_class = leg['transportation']['product']['class']
|
494
495
|
|
496
|
+
if leg_class in transport_type:
|
495
497
|
# We've got a filter, and the leg type matches it, so return that leg
|
496
|
-
|
497
|
-
return legs[leg_index]
|
498
|
+
return leg
|
498
499
|
|
500
|
+
if 0 in transport_type and leg_class < 99:
|
499
501
|
# We don't have a filter, and this is the first non-walk/cycle leg so return that leg
|
500
|
-
|
501
|
-
return legs[leg_index]
|
502
|
+
return leg
|
502
503
|
|
503
|
-
# Exit if we're doing strict filtering and we haven't found that type in the
|
504
|
-
if
|
504
|
+
# Exit if we're doing strict filtering and we haven't found that type in the last leg
|
505
|
+
if strict == True:
|
505
506
|
return None
|
506
507
|
|
507
508
|
# Hmm, we didn't find one
|
508
509
|
return None
|
509
510
|
|
510
511
|
|
511
|
-
def _find_changes(self, legs,
|
512
|
+
def _find_changes(self, legs, origin_leg, destination_leg):
|
512
513
|
# Find out how often we have to change
|
513
514
|
changes = 0
|
514
|
-
|
515
|
+
bInJourney = False
|
516
|
+
|
517
|
+
for leg in legs:
|
518
|
+
if leg == destination_leg:
|
519
|
+
# We've found the last leg (which oould be the same as the first leg), stop counting
|
520
|
+
return changes
|
515
521
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
522
|
+
if leg == origin_leg:
|
523
|
+
# We've found the first leg, start counting
|
524
|
+
bInJourney = True
|
525
|
+
else:
|
526
|
+
if bInJourney:
|
527
|
+
leg_class = leg['transportation']['product']['class']
|
528
|
+
if leg_class < 99:
|
529
|
+
changes += 1
|
520
530
|
|
521
|
-
|
531
|
+
# We should never get here!
|
532
|
+
return 999
|
522
533
|
|
523
534
|
|
524
535
|
def _find_alerts(self, legs, priority_filter, alert_type):
|
525
536
|
# Return an array of all the alerts on this trip that meet the priority level and alert type
|
526
|
-
leg_count = len(legs)
|
527
537
|
found_alerts = []
|
528
538
|
priority_minimum = self._get_alert_priority(priority_filter)
|
529
|
-
alert_list = alert_type.split("|")
|
530
539
|
|
531
|
-
for
|
532
|
-
|
533
|
-
|
534
|
-
alerts = current_leg['infos']
|
535
|
-
for alert in alerts:
|
540
|
+
for leg in legs:
|
541
|
+
if 'infos' in leg:
|
542
|
+
for alert in leg['infos']:
|
536
543
|
if (self._get_alert_priority(alert['priority'])) >= priority_minimum:
|
537
|
-
if (
|
544
|
+
if ('all' in alert_type) or (alert['type'].lower() in alert_type):
|
538
545
|
found_alerts.append (alert)
|
539
546
|
|
540
547
|
return json.dumps(found_alerts)
|
541
548
|
|
542
549
|
|
543
|
-
def
|
544
|
-
#
|
545
|
-
|
550
|
+
def _find_location(self, api_key, mode, realtimetripid, agencyid):
|
551
|
+
# See if we can get the latitude and longitude via the Realtime Vehicle Positions API
|
552
|
+
# Build the URL(s) - some modes have multiple GTFS sources, unforunately
|
553
|
+
|
554
|
+
bFoundTripID = False
|
555
|
+
latitude = 'n/a'
|
556
|
+
longitude = 'n/a'
|
557
|
+
|
558
|
+
auth = 'apikey ' + api_key
|
559
|
+
header = {'Accept': 'application/json', 'Authorization': auth}
|
560
|
+
|
561
|
+
url_base_path = self._get_base_url(mode)
|
562
|
+
|
563
|
+
url_mode_list = self._get_mode_list(mode, agencyid)
|
564
|
+
if not url_mode_list is None:
|
565
|
+
for mode_url in url_mode_list:
|
566
|
+
url = url_base_path + mode_url
|
567
|
+
response = requests.get(url, headers=header, timeout=10)
|
568
|
+
# Only try and process the results if we got a good return code
|
569
|
+
if response.status_code == 200:
|
570
|
+
# Search the feed and see if we can match realtimetripid to trip_id
|
571
|
+
# If we do, capture the latitude and longitude
|
572
|
+
feed = gtfs_realtime_pb2.FeedMessage()
|
573
|
+
feed.ParseFromString(response.content)
|
574
|
+
reg = re.compile(realtimetripid)
|
575
|
+
|
576
|
+
for entity in feed.entity:
|
577
|
+
if bool(re.match(reg, entity.vehicle.trip.trip_id)):
|
578
|
+
latitude = entity.vehicle.position.latitude
|
579
|
+
longitude = entity.vehicle.position.longitude
|
580
|
+
|
581
|
+
# We found it, so flag it and break out
|
582
|
+
bFoundTripID = True
|
583
|
+
break
|
584
|
+
else:
|
585
|
+
# Warn that we didn't get a good return
|
586
|
+
if response.status_code == 401:
|
587
|
+
logger.error(f"Error 'Invalid API key' calling {url} API")
|
588
|
+
elif response.status_code == 403 or response.status_code == 429:
|
589
|
+
logger.error(f"Error 'API rate limit exceded' calling {url} API")
|
590
|
+
else:
|
591
|
+
logger.error(f"Error '{str(response.status_code)}' calling {url} API")
|
592
|
+
|
593
|
+
if bFoundTripID == True:
|
594
|
+
# No need to look any further
|
595
|
+
break
|
596
|
+
|
597
|
+
# Put in a quick pause here to try and make sure we stay under the 5 API calls/second limit
|
598
|
+
# Not usually an issue but if multiple processes are running multiple calls we might hit it
|
599
|
+
time.sleep(0.75)
|
546
600
|
|
547
|
-
|
548
|
-
current_leg = legs[leg_index]
|
549
|
-
leg_class = current_leg['transportation']['product']['class']
|
550
|
-
if 'hints' in current_leg:
|
551
|
-
hints = current_leg['hints']
|
601
|
+
return latitude, longitude
|
552
602
|
|
553
603
|
|
554
604
|
def _get_mode(self, iconId):
|
@@ -563,7 +613,7 @@ class TransportNSWv2(object):
|
|
563
613
|
11 : "School bus",
|
564
614
|
99 : "Walk",
|
565
615
|
100 : "Walk",
|
566
|
-
107 : "Cycle"
|
616
|
+
107 : "Cycle",
|
567
617
|
}
|
568
618
|
|
569
619
|
return modes.get(iconId, None)
|
@@ -670,9 +720,6 @@ class StopError(Exception):
|
|
670
720
|
super().__init__(message)
|
671
721
|
self.stop_detail = stop_detail
|
672
722
|
|
673
|
-
# def __str__(self):
|
674
|
-
# return f"{self.message}"
|
675
|
-
|
676
723
|
class TripError(Exception):
|
677
724
|
"""" Trip-finder related error """
|
678
725
|
|