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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyTransportNSWv2
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Get detailed per-trip transport information from TransportNSW
5
5
  Home-page: https://github.com/andystewart999/TransportNSW
6
6
  Author: andystewart999
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyTransportNSWv2
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Get detailed per-trip transport information from TransportNSW
5
5
  Home-page: https://github.com/andystewart999/TransportNSW
6
6
  Author: andystewart999
@@ -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
- ATTR_OCCUPANCY = 'occupancy'
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
- ATTR_OCCUPANCY : 'n/a',
67
- ATTR_REAL_TIME_TRIP_ID : 'n/a',
68
- ATTR_LATITUDE : 'n/a',
69
- ATTR_LONGITUDE : 'n/a',
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, home_assistant = False):
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, transport_type = 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, forced_gtfs_uri = [],
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
- alert_type = alert_type.lower()
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
- #return None
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
- journey, next_journey_index = self._find_next_journey(result['journeys'], current_journey_index, transport_type, strict_transport_type, route_filter)
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 ((journey is None) or (journey['legs']) is None):
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
- legs = journey['legs']
269
- first_leg = self._find_first_leg(legs, transport_type, strict_transport_type, route_filter)
270
-
271
- #Executive decision - don't be strict on the last leg, there's often some walking (transport type 100) involved.
272
- last_leg = self._find_last_leg(legs, transport_type, False)
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 = origin['id']
282
- origin_name = origin['name']
283
- origin_departure_time = origin['departureTimeEstimated']
284
- origin_departure_time_planned = origin['departureTimePlanned']
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 = destination['id']
295
- destination_name = destination['name']
296
- destination_arrival_time = destination['arrivalTimeEstimated']
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(transportation['product']['class'])
300
- origin_mode_name = transportation['product']['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
- realtimetripid = 'n/a'
304
- if 'properties' in transportation and 'RealtimeTripId' in transportation['properties']:
305
- realtimetripid = transportation['properties']['RealtimeTripId']
326
+ origin_realtimetripid = 'n/a'
327
+ origin_agencyid = ''
328
+ destination_realtimetripid = 'n/a'
329
+ destination_agencyid = ''
306
330
 
307
- # We're also going to need the agency_id if it's a bus journey
308
- agencyid = transportation['operator']['id']
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
- # AVMSTripID is for Home Assistant, if needed
311
- avmstripid = 'n/a'
312
- if 'properties' in transportation and 'AVMSTripID' in transportation['properties']:
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 = "unknown"
317
- if 'disassembledName' in transportation:
318
- origin_line_name_short = transportation['disassembledName']
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
- origin_line_name = "unknown"
321
- if 'number' in transportation:
322
- origin_line_name = transportation['number']
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
- occupancy = 'unknown'
326
- if 'properties' in first_stop and 'occupancy' in first_stop['properties']:
327
- occupancy = first_stop['properties']['occupancy']
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
- latitude = 'n/a'
336
- longitude = 'n/a'
337
- mode_url = 'n/a'
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 realtimetripid != 'n/a':
340
- # See if we can get the latitute and longitude via the Realtime Vehicle Positions API
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
- bFoundTripID = False
346
- url_base_path = self._get_base_url(origin_mode)
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
- ATTR_OCCUPANCY : occupancy,
408
- ATTR_REAL_TIME_TRIP_ID : realtimetripid,
409
- ATTR_GTFS_URI: mode_url,
410
- ATTR_LATITUDE : latitude,
411
- ATTR_LONGITUDE : longitude,
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, journeytype, strict, route_filter):
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
- leg = self._find_first_leg(journeys[journey_index]['legs'], journeytype, strict, route_filter)
450
- if leg is not None:
451
- return journeys[journey_index], journey_index + 1
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, legtype, strict, route_filter):
457
+ def _find_first_leg(self, legs, transport_type, strict, route_filter):
460
458
  # Find the first leg of the requested type
461
- leg_count = len(legs)
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 legs[leg_index] and 'disassembledName' in legs[leg_index]['transportation']:
468
- origin_line_name_short = legs[leg_index]['transportation']['disassembledName'].lower()
469
- origin_line_name = legs[leg_index]['transportation']['number'].lower()
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
- leg_class = legs[leg_index]['transportation']['product']['class']
473
- # We've got a filter, and the leg type matches it, so return that leg
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
- if legtype == 0 and leg_class < 99:
479
- return legs[leg_index]
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 legtype != 0 and strict == True:
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
- leg_count = len(legs)
492
- for leg_index in range (leg_count - 1, -1, -1):
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
- if legtype != 0 and leg_class == legtype:
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
- if legtype == 0 and leg_class < 99:
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 first leg
504
- if legtype != 0 and strict == True:
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, legtype):
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
- leg_count = len(legs)
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
- for leg_index in range (0, leg_count, 1):
517
- leg_class = legs[leg_index]['transportation']['product']['class']
518
- if leg_class == legtype or legtype == 0:
519
- changes = changes + 1
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
- return changes - 1
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 leg_index in range (0, leg_count, 1):
532
- current_leg = legs[leg_index]
533
- if 'infos' in current_leg:
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 (alert_type == 'all') or (alert['type'].lower() in alert_list):
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 _find_hints(self, legs, legtype, priority):
544
- # Return an array of all the hints on this trip that meet the priority type
545
- leg_count = len(legs)
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
- for leg_index in range (0, leg_count, 1):
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