diff --git a/gtfs_realtime_translators/bindings/intersection.proto b/gtfs_realtime_translators/bindings/intersection.proto
index 1ea4165..df617a3 100644
--- a/gtfs_realtime_translators/bindings/intersection.proto
+++ b/gtfs_realtime_translators/bindings/intersection.proto
@@ -10,6 +10,7 @@ message IntersectionTripUpdate {
optional string route_text_color = 5;
optional string block_id = 6;
optional string agency_timezone = 7;
+ optional string custom_status = 8;
}
message IntersectionStopTimeUpdate {
diff --git a/gtfs_realtime_translators/bindings/intersection_pb2.py b/gtfs_realtime_translators/bindings/intersection_pb2.py
index 7b4e3a2..db859bf 100644
--- a/gtfs_realtime_translators/bindings/intersection_pb2.py
+++ b/gtfs_realtime_translators/bindings/intersection_pb2.py
@@ -20,7 +20,7 @@
package='',
syntax='proto2',
serialized_options=None,
- serialized_pb=_b('\n\x12intersection.proto\x1a\x13gtfs-realtime.proto\"\xb7\x01\n\x16IntersectionTripUpdate\x12\x10\n\x08headsign\x18\x01 \x01(\t\x12\x18\n\x10route_short_name\x18\x02 \x01(\t\x12\x17\n\x0froute_long_name\x18\x03 \x01(\t\x12\x13\n\x0broute_color\x18\x04 \x01(\t\x12\x18\n\x10route_text_color\x18\x05 \x01(\t\x12\x10\n\x08\x62lock_id\x18\x06 \x01(\t\x12\x17\n\x0f\x61gency_timezone\x18\x07 \x01(\t\"\xce\x01\n\x1aIntersectionStopTimeUpdate\x12\r\n\x05track\x18\x01 \x01(\t\x12\x45\n\x11scheduled_arrival\x18\x02 \x01(\x0b\x32*.transit_realtime.TripUpdate.StopTimeEvent\x12G\n\x13scheduled_departure\x18\x03 \x01(\x0b\x32*.transit_realtime.TripUpdate.StopTimeEvent\x12\x11\n\tstop_name\x18\x04 \x01(\t:X\n\x18intersection_trip_update\x12\x1c.transit_realtime.TripUpdate\x18\xc3\x0f \x01(\x0b\x32\x17.IntersectionTripUpdate:p\n\x1dintersection_stop_time_update\x12+.transit_realtime.TripUpdate.StopTimeUpdate\x18\xc3\x0f \x01(\x0b\x32\x1b.IntersectionStopTimeUpdate')
+ serialized_pb=_b('\n\x12intersection.proto\x1a\x13gtfs-realtime.proto\"\xce\x01\n\x16IntersectionTripUpdate\x12\x10\n\x08headsign\x18\x01 \x01(\t\x12\x18\n\x10route_short_name\x18\x02 \x01(\t\x12\x17\n\x0froute_long_name\x18\x03 \x01(\t\x12\x13\n\x0broute_color\x18\x04 \x01(\t\x12\x18\n\x10route_text_color\x18\x05 \x01(\t\x12\x10\n\x08\x62lock_id\x18\x06 \x01(\t\x12\x17\n\x0f\x61gency_timezone\x18\x07 \x01(\t\x12\x15\n\rcustom_status\x18\x08 \x01(\t\"\xce\x01\n\x1aIntersectionStopTimeUpdate\x12\r\n\x05track\x18\x01 \x01(\t\x12\x45\n\x11scheduled_arrival\x18\x02 \x01(\x0b\x32*.transit_realtime.TripUpdate.StopTimeEvent\x12G\n\x13scheduled_departure\x18\x03 \x01(\x0b\x32*.transit_realtime.TripUpdate.StopTimeEvent\x12\x11\n\tstop_name\x18\x04 \x01(\t:X\n\x18intersection_trip_update\x12\x1c.transit_realtime.TripUpdate\x18\xc3\x0f \x01(\x0b\x32\x17.IntersectionTripUpdate:p\n\x1dintersection_stop_time_update\x12+.transit_realtime.TripUpdate.StopTimeUpdate\x18\xc3\x0f \x01(\x0b\x32\x1b.IntersectionStopTimeUpdate')
,
dependencies=[gtfs__realtime__pb2.DESCRIPTOR,])
@@ -99,6 +99,13 @@
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='custom_status', full_name='IntersectionTripUpdate.custom_status', index=7,
+ number=8, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@@ -112,7 +119,7 @@
oneofs=[
],
serialized_start=44,
- serialized_end=227,
+ serialized_end=250,
)
@@ -163,8 +170,8 @@
extension_ranges=[],
oneofs=[
],
- serialized_start=230,
- serialized_end=436,
+ serialized_start=253,
+ serialized_end=459,
)
_INTERSECTIONSTOPTIMEUPDATE.fields_by_name['scheduled_arrival'].message_type = gtfs__realtime__pb2._TRIPUPDATE_STOPTIMEEVENT
diff --git a/gtfs_realtime_translators/factories/factories.py b/gtfs_realtime_translators/factories/factories.py
index 0afd196..f978ec4 100644
--- a/gtfs_realtime_translators/factories/factories.py
+++ b/gtfs_realtime_translators/factories/factories.py
@@ -42,6 +42,7 @@ def create(*args, **kwargs):
route_text_color = kwargs.get('route_text_color', None)
block_id = kwargs.get('block_id', None)
agency_timezone = kwargs.get('agency_timezone', None)
+ custom_status = kwargs.get('custom_status', None)
trip_descriptor = gtfs_realtime.TripDescriptor(trip_id=trip_id,
route_id=route_id)
@@ -76,6 +77,8 @@ def create(*args, **kwargs):
trip_update.Extensions[intersection_gtfs_realtime.intersection_trip_update].block_id = block_id
if agency_timezone:
trip_update.Extensions[intersection_gtfs_realtime.intersection_trip_update].agency_timezone = agency_timezone
+ if custom_status:
+ trip_update.Extensions[intersection_gtfs_realtime.intersection_trip_update].custom_status = custom_status
return Entity.create(entity_id,
trip_update=trip_update)
diff --git a/gtfs_realtime_translators/translators/njt_rail.py b/gtfs_realtime_translators/translators/njt_rail.py
index 304a2d9..63094a3 100644
--- a/gtfs_realtime_translators/translators/njt_rail.py
+++ b/gtfs_realtime_translators/translators/njt_rail.py
@@ -18,6 +18,9 @@ class NjtRailGtfsRealtimeTranslator:
https://usermanual.wiki/Document/NJTRANSIT20REAL20Time20Data20Interface20Instructions2020Ver2025.785373145.pdf
"""
+
+ TIMEZONE = 'America/New_York'
+
def __call__(self, data):
station_data = xmltodict.parse(data)
entities = self.__make_trip_updates(station_data)
@@ -25,7 +28,7 @@ def __call__(self, data):
@classmethod
def __to_unix_time(cls, time):
- datetime = pendulum.from_format(time, 'DD-MMM-YYYY HH:mm:ss A', tz='America/New_York').in_tz('UTC')
+ datetime = pendulum.from_format(time, 'DD-MMM-YYYY HH:mm:ss A', tz=cls.TIMEZONE).in_tz('UTC')
return datetime
@classmethod
@@ -35,11 +38,10 @@ def __make_trip_updates(cls, data):
station_data_item = data['STATION']['ITEMS'].values()
for value in station_data_item:
for idx, item_entry in enumerate(value):
- route_id = None
# Intersection Extensions
headsign = item_entry['DESTINATION']
- route_short_name = item_entry['LINEABBREVIATION']
+ route_short_name = cls.__get_route_short_name(item_entry)
route_long_name = cls.__get_route_long_name(item_entry)
route_color = item_entry['BACKCOLOR']
route_text_color = item_entry['FORECOLOR']
@@ -50,14 +52,13 @@ def __make_trip_updates(cls, data):
scheduled_datetime = cls.__to_unix_time(item_entry['SCHED_DEP_DATE'])
departure_time = int(scheduled_datetime.add(seconds=int(item_entry['SEC_LATE'])).timestamp())
scheduled_departure_time = int(scheduled_datetime.timestamp())
+ custom_status = item_entry['STATUS']
+ origin_and_destination = None
for stop in item_entry['STOPS'].values():
origin_and_destination = [stop[i] for i in (0, -1)]
- route_id = cls.__get_route_id(line=item_entry['LINE'],
- line_abbreviation=item_entry['LINEABBREVIATION'],
- origin=origin_and_destination[0],
- destination=origin_and_destination[1])
+ route_id = cls.__get_route_id(item_entry, origin_and_destination)
trip_update = TripUpdate.create(entity_id=str(idx + 1),
departure_time=departure_time,
@@ -74,65 +75,94 @@ def __make_trip_updates(cls, data):
headsign=headsign,
track=track,
block_id=block_id,
- agency_timezone='America/New_York')
+ agency_timezone=cls.TIMEZONE,
+ custom_status=custom_status)
trip_updates.append(trip_update)
return trip_updates
@classmethod
- def __get_route_id(cls, **data):
+ def __get_route_id(cls, data, origin_and_destination):
"""
- This function resolves route_ids for NJT. The logic of determining a route_id based
- on origin or destination is necessary to discern multiple routes that are mapped to the same line.
+ This function resolves route_ids for NJT.
- For instance, the North Jersey Coastline line operates two different routes. All trains with an
- origin or destination of New York Penn Station should resolve to route_id 10 and the others route_id 11
+ The algorithm is as follows:
+ 1) Try to get the route_id based on the line name (or line abbreviation for Amtrak), otherwise...
+ 2) Try to get the route_id based on the origin and destination, otherwise return None
+
+ For #2, this logic is necessary to discern multiple routes that are mapped to the same line. For instance, the
+ North Jersey Coast Line operates two different routes. All trains with an origin or destination
+ of New York Penn Station should resolve to route_id 10 and the others route_id 11
:param data: keyword args containing data needed to perform the route logic
+ :param origin_and_destination: an array containing the origin at index 0 and destination at index 1
:return: route_id
"""
+
+ route_id = cls.__get_route_id_by_line_data(data)
+ if route_id:
+ return route_id
+ if origin_and_destination:
+ return cls.__get_route_id_by_origin_or_destination(data, origin_and_destination)
+ return None
+
+ @classmethod
+ def __get_route_id_by_origin_or_destination(cls, data, origin_and_destination):
+ origin = origin_and_destination[0]
+ destination = origin_and_destination[1]
+ origin_name = origin['NAME'].replace(' ', '_').lower()
+ destination_name = destination['NAME'].replace(' ', '_').lower()
+
+ key = data['LINE'].replace(' ', '_').lower()
+ if key == 'montclair-boonton_line':
+ hoboken = 'hoboken'
+ origins_and_destinations = {'denville', 'dover', 'mount_olive', 'lake_hopatcong', 'hackettstown'}
+ if origin_name == hoboken and destination_name in origins_and_destinations:
+ return '2'
+ if origin_name in origins_and_destinations and destination_name == hoboken:
+ return '2'
+ return '3'
+
+ if key == 'north_jersey_coast_line':
+ origins_and_destinations = {'new_york_penn_station'}
+ if origin_name in origins_and_destinations or destination_name in origins_and_destinations:
+ return '10'
+ return '11'
+ return None
+
+ @classmethod
+ def __get_route_id_by_line_data(cls, data):
route_id_lookup = {
'atlantic_city_line': '1',
- 'montclair-boonton_line': None,
'main_line': '5',
'bergen_county_line': '6',
'morristown_line': '7',
'gladstone_branch': '8',
'northeast_corridor_line': '9',
- 'north_jersey_coast_line': None,
+ 'pascack_valley_line': '13',
+ 'princeton_shuttle': '14',
+ 'raritan_valley_line': '15',
+ 'meadowlands_rail_line': '17',
}
- key = 'amtrak' if data['line_abbreviation'] == 'AMTK' else data['line'].replace(' ', '_').lower()
- route_id = route_id_lookup.get(key, None)
- if route_id is not None:
- return route_id
+ amtrak_route_id = 'AMTK'
+ if data['LINEABBREVIATION'] == amtrak_route_id:
+ return amtrak_route_id
- def get_route_id_by_origin_or_destination(line_key, line_name, origin, destination):
- origin_name = origin['NAME'].replace(' ', '_').lower()
- destination_name = destination['NAME'].replace(' ', '_').lower()
-
- if line_key == 'montclair-boonton_line':
- hoboken = 'hoboken'
- origins_and_destinations = {'denville', 'dover', 'mount_olive', 'lake_hopatcong', 'hackettstown'}
- if origin_name == hoboken and destination_name in origins_and_destinations:
- return '2'
- if origin_name in origins_and_destinations and destination_name == hoboken:
- return '2'
- return '3'
-
- if line_key == 'north_jersey_coast_line':
- origins_and_destinations = {'new_york_penn_station'}
- if origin_name in origins_and_destinations or destination_name in origins_and_destinations:
- return '10'
- return '11'
- if line_key == 'amtrak':
- return line_name
- return None
-
- return get_route_id_by_origin_or_destination(key, data['line'], data['origin'], data['destination'])
+ key = data['LINE'].replace(' ', '_').lower()
+ route_id = route_id_lookup.get(key, None)
+ return route_id if route_id else None
@classmethod
def __get_route_long_name(cls, data):
- if data['LINEABBREVIATION'] == 'AMTK':
- return f"Amtrak {data['LINE']}".title()
+ amtrak_prefix = 'AMTRAK'
+ abbreviation = data['LINEABBREVIATION']
+ if abbreviation == 'AMTK':
+ return amtrak_prefix.title() if data['LINE'] == amtrak_prefix else f"Amtrak {data['LINE']}".title()
return data['LINE']
+
+ @classmethod
+ def __get_route_short_name(cls, data):
+ if data['LINEABBREVIATION'] == 'AMTK':
+ return data['LINE']
+ return data['LINEABBREVIATION']
diff --git a/test/fixtures/njt_rail.xml b/test/fixtures/njt_rail.xml
index 5865831..6b7f27f 100644
--- a/test/fixtures/njt_rail.xml
+++ b/test/fixtures/njt_rail.xml
@@ -9,7 +9,7 @@
02-Oct-2019 03:02:00 PM
Boston
- REGIONAL
+ AMTRAK
A176
@@ -659,7 +659,7 @@
3154
- CANCELLED
+
0
02-Oct-2019 02:51:30 PM
black
diff --git a/test/test_njt_rail.py b/test/test_njt_rail.py
index bc42447..72e84df 100644
--- a/test/test_njt_rail.py
+++ b/test/test_njt_rail.py
@@ -38,6 +38,7 @@ def test_njt_data(njt_rail):
assert intersection_trip_update.route_text_color == 'white'
assert intersection_trip_update.block_id == '3154'
assert intersection_trip_update.agency_timezone == 'America/New_York'
+ assert intersection_trip_update.custom_status == ''
intersection_stop_time_update = stop_time_update.Extensions[intersection_gtfs_realtime.intersection_stop_time_update]
assert intersection_stop_time_update.track == '1'
@@ -58,7 +59,7 @@ def test_njt_data_amtrak(njt_rail):
assert entity.id == '1'
assert trip_update.trip.trip_id == ''
- assert trip_update.trip.route_id == 'REGIONAL'
+ assert trip_update.trip.route_id == 'AMTK'
assert stop_time_update.stop_id == 'NP'
assert stop_time_update.departure.time == 1570044525
@@ -66,14 +67,44 @@ def test_njt_data_amtrak(njt_rail):
intersection_trip_update = trip_update.Extensions[intersection_gtfs_realtime.intersection_trip_update]
assert intersection_trip_update.headsign == 'Boston'
- assert intersection_trip_update.route_short_name == 'AMTK'
- assert intersection_trip_update.route_long_name == 'Amtrak Regional'
+ assert intersection_trip_update.route_short_name == 'AMTRAK'
+ assert intersection_trip_update.route_long_name == 'Amtrak'
assert intersection_trip_update.route_color == 'yellow'
assert intersection_trip_update.route_text_color == 'black'
assert intersection_trip_update.block_id == 'A176'
assert intersection_trip_update.agency_timezone == 'America/New_York'
+ assert intersection_trip_update.custom_status == 'All Aboard'
intersection_stop_time_update = stop_time_update.Extensions[intersection_gtfs_realtime.intersection_stop_time_update]
assert intersection_stop_time_update.track == '2'
assert intersection_stop_time_update.scheduled_arrival.time == 1570042920
assert intersection_stop_time_update.scheduled_departure.time == 1570042920
+
+ entity = message.entity[15]
+ trip_update = entity.trip_update
+ stop_time_update = trip_update.stop_time_update[0]
+
+ assert message.header.gtfs_realtime_version == FeedMessage.VERSION
+ assert entity.id == '16'
+
+ assert trip_update.trip.trip_id == ''
+ assert trip_update.trip.route_id == 'AMTK'
+
+ assert stop_time_update.stop_id == 'NP'
+ assert stop_time_update.departure.time == 1570047420
+ assert stop_time_update.arrival.time == 1570047420
+
+ intersection_trip_update = trip_update.Extensions[intersection_gtfs_realtime.intersection_trip_update]
+ assert intersection_trip_update.headsign == 'Washington'
+ assert intersection_trip_update.route_short_name == 'ACELA EXPRESS'
+ assert intersection_trip_update.route_long_name == 'Amtrak Acela Express'
+ assert intersection_trip_update.route_color == 'yellow'
+ assert intersection_trip_update.route_text_color == 'black'
+ assert intersection_trip_update.block_id == 'A2165'
+ assert intersection_trip_update.agency_timezone == 'America/New_York'
+ assert intersection_trip_update.custom_status == ''
+
+ intersection_stop_time_update = stop_time_update.Extensions[intersection_gtfs_realtime.intersection_stop_time_update]
+ assert intersection_stop_time_update.track == '3'
+ assert intersection_stop_time_update.scheduled_arrival.time == 1570047420
+ assert intersection_stop_time_update.scheduled_departure.time == 1570047420