66import java .util .Map ;
77import java .util .Objects ;
88import java .util .Optional ;
9- import java .util .stream .Collectors ;
109import java .util .stream .Stream ;
1110import javax .annotation .Nullable ;
1211import org .onebusaway .csv_entities .CsvInputSource ;
13- import org .onebusaway .csv_entities .schema .DefaultEntitySchemaFactory ;
14- import org .onebusaway .csv_entities .schema .EntitySchema ;
12+ import org .opentripplanner .framework .i18n .I18NString ;
1513import org .opentripplanner .model .StopTime ;
16- import org .opentripplanner .transit .model .framework .AbstractTransitEntity ;
14+ import org .opentripplanner .model .impl .OtpTransitServiceBuilder ;
15+ import org .opentripplanner .transit .model .framework .EntityById ;
1716import org .opentripplanner .transit .model .framework .FeedScopedId ;
1817import org .opentripplanner .transit .model .timetable .Trip ;
19- import org .opentripplanner .transit .service .SiteRepositoryBuilder ;
2018import org .opentripplanner .utils .lang .StringUtils ;
2119
2220/**
2321 * Responsible for mapping GTFS StopTime into the OTP Transit model.
2422 */
2523class StopTimeMapper {
2624
27- public static final EntitySchema SCHEMA = new DefaultEntitySchemaFactory ()
28- .getSchema (org .onebusaway .gtfs .model .StopTime .class );
29- public static final String TIMEPOINT = "timepoint" ;
30- public static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled" ;
31- public static final String STOP_SEQUENCE = "stop_sequence" ;
32- public static final String DEPARTURE_TIME = "departure_time" ;
33- public static final String ARRIVAL_TIME = "arrival_time" ;
34- public static final String PICKUP_TYPE = "pickup_type" ;
35- public static final String DROP_OFF_TYPE = "drop_off_type" ;
36- public static final String PICKUP_BOOKING_RULE_ID = "pickup_booking_rule_id" ;
37- public static final String DROP_OFF_BOOKING_RULE_ID = "drop_off_booking_rule_id" ;
38- public static final String START_PICKUP_DROP_OFF_WINDOW = "start_pickup_drop_off_window" ;
39- public static final String END_PICKUP_DROP_OFF_WINDOW = "end_pickup_drop_off_window" ;
40- public static final String CONTINUOUS_PICKUP = "continuous_pickup" ;
41- public static final String CONTINUOUS_DROP_OFF = "continuous_drop_off" ;
25+ private static final String TIMEPOINT = "timepoint" ;
26+ private static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled" ;
27+ private static final String STOP_SEQUENCE = "stop_sequence" ;
28+ private static final String DEPARTURE_TIME = "departure_time" ;
29+ private static final String ARRIVAL_TIME = "arrival_time" ;
30+ private static final String PICKUP_TYPE = "pickup_type" ;
31+ private static final String DROP_OFF_TYPE = "drop_off_type" ;
32+ private static final String PICKUP_BOOKING_RULE_ID = "pickup_booking_rule_id" ;
33+ private static final String DROP_OFF_BOOKING_RULE_ID = "drop_off_booking_rule_id" ;
34+ private static final String START_PICKUP_DROP_OFF_WINDOW = "start_pickup_drop_off_window" ;
35+ private static final String END_PICKUP_DROP_OFF_WINDOW = "end_pickup_drop_off_window" ;
36+ private static final String CONTINUOUS_PICKUP = "continuous_pickup" ;
37+ private static final String CONTINUOUS_DROP_OFF = "continuous_drop_off" ;
38+ private static final String STOP_ID = "stop_id" ;
39+ private static final String LOCATION_ID = "location_id" ;
40+ private static final String STOP_GROUP_ID = "stop_group_id" ;
41+ private static final String STOP_HEADSIGN = "stop_headsign" ;
42+ private static final String FILE = "stops_times.txt" ;
4243 private final IdFactory idFactory ;
4344
44- private final TripMapper tripMapper ;
45-
4645 private final BookingRuleMapper bookingRuleMapper ;
4746 private final TranslationHelper translationHelper ;
48- private final SiteRepositoryBuilder siteRepositoryBuilder ;
47+ private final OtpTransitServiceBuilder builder ;
4948
5049 StopTimeMapper (
5150 IdFactory idFactory ,
52- StopMapper stopMapper ,
53- TripMapper tripMapper ,
51+ OtpTransitServiceBuilder builder ,
5452 BookingRuleMapper bookingRuleMapper ,
5553 TranslationHelper translationHelper
5654 ) {
5755 this .idFactory = idFactory ;
58- this .siteRepositoryBuilder = stopMapper .siteRepositoryBuilder ();
59- this .tripMapper = tripMapper ;
56+ this .builder = builder ;
6057 this .bookingRuleMapper = bookingRuleMapper ;
6158 this .translationHelper = translationHelper ;
6259 }
6360
6461 Stream <StopTime > map (CsvInputSource inputSource ) throws IOException {
65- var trips = tripMapper
66- .getMappedTrips ()
67- .stream ()
68- .collect (Collectors .toMap (AbstractTransitEntity ::getId , t -> t ));
6962 return new StreamingCsvReader (inputSource )
70- .rows (SCHEMA . getFilename () )
71- .map (st -> this .doMap (new StopTimeRow (st , idFactory ), trips ));
63+ .rows (FILE )
64+ .map (st -> this .doMap (new StopTimeRow (st , idFactory ), builder . getTripsById () ));
7265 }
7366
74- private StopTime doMap (StopTimeRow row , Map < FeedScopedId , Trip > trips ) {
67+ private StopTime doMap (StopTimeRow row , EntityById < Trip > trips ) {
7568 StopTime lhs = new StopTime ();
7669
7770 var tripId = idFactory .createId (row .requiredString ("trip_id" ), "stop time's trip" );
@@ -81,23 +74,36 @@ private StopTime doMap(StopTimeRow row, Map<FeedScopedId, Trip> trips) {
8174 );
8275 lhs .setTrip (trip );
8376
84- var stopId = row .string ("stop_id" );
85- var stopLocationId = row .string ("stop_location_id" );
86- var locationGroupId = row .string ("stop_group_id" );
77+ var stopId = row .string (STOP_ID );
78+ var stopLocationId = row .string (LOCATION_ID );
79+ var locationGroupId = row .string (STOP_GROUP_ID );
8780
81+ var siteRepositoryBuilder = builder .siteRepository ();
8882 if (stopId != null ) {
8983 var id = idFactory .createId (stopId , "stop_time's stop" );
90- lhs .setStop (Objects .requireNonNull (siteRepositoryBuilder .regularStopsById ().get (id )));
84+ var stop = Objects .requireNonNull (
85+ siteRepositoryBuilder .regularStopsById ().get (id ),
86+ "Stop '%s' not found" .formatted (stopId )
87+ );
88+ lhs .setStop (stop );
9189 } else if (stopLocationId != null ) {
92- var id = idFactory .createId (stopLocationId , "stop time's stop location" );
93- lhs .setStop (Objects .requireNonNull (siteRepositoryBuilder .areaStopById ().get (id )));
90+ var id = idFactory .createId (stopLocationId , "stop time's location" );
91+ var stop = Objects .requireNonNull (
92+ siteRepositoryBuilder .areaStopById ().get (id ),
93+ "Stop location '%s' not found" .formatted (id )
94+ );
95+ lhs .setStop (stop );
9496 } else if (locationGroupId != null ) {
9597 var id = idFactory .createId (locationGroupId , "stop time's location group" );
9698 lhs .setStop (Objects .requireNonNull (siteRepositoryBuilder .groupStopById ().get (id )));
99+ } else {
100+ throw new IllegalArgumentException (
101+ "Stop time must have either a %s, %s, or %s" .formatted (STOP_ID , LOCATION_ID , STOP_GROUP_ID )
102+ );
97103 }
98104
99- lhs .setArrivalTime (row .requiredTime (ARRIVAL_TIME ));
100- lhs .setDepartureTime (row .requiredTime (DEPARTURE_TIME ));
105+ lhs .setArrivalTime (row .time (ARRIVAL_TIME ));
106+ lhs .setDepartureTime (row .time (DEPARTURE_TIME ));
101107 lhs .setStopSequence (row .integer (STOP_SEQUENCE ));
102108
103109 lhs .setTimepoint (row .integer (TIMEPOINT ));
@@ -115,15 +121,26 @@ private StopTime doMap(StopTimeRow row, Map<FeedScopedId, Trip> trips) {
115121 PickDropMapper .mapFlexContinuousPickDrop (row .integer (CONTINUOUS_DROP_OFF ))
116122 );
117123
124+ row .optionalString (STOP_HEADSIGN ).ifPresent (hs -> lhs .setStopHeadsign (I18NString .of (hs )));
118125 row
119126 .id (PICKUP_BOOKING_RULE_ID )
120127 .ifPresent (id ->
121- lhs .setPickupBookingInfo (Objects .requireNonNull (bookingRuleMapper .findBookingRule (id )))
128+ lhs .setPickupBookingInfo (
129+ Objects .requireNonNull (
130+ bookingRuleMapper .findBookingRule (id ),
131+ "Pickup booking rule '%s' not found" .formatted (id )
132+ )
133+ )
122134 );
123135 row
124136 .id (DROP_OFF_BOOKING_RULE_ID )
125137 .ifPresent (id ->
126- lhs .setPickupBookingInfo (Objects .requireNonNull (bookingRuleMapper .findBookingRule (id )))
138+ lhs .setPickupBookingInfo (
139+ Objects .requireNonNull (
140+ bookingRuleMapper .findBookingRule (id ),
141+ "Drop off booking rule '%s' not found" .formatted (id )
142+ )
143+ )
127144 );
128145
129146 return lhs ;
@@ -135,7 +152,7 @@ public String requiredString(String field) {
135152 return row .get (field );
136153 } else {
137154 throw new IllegalArgumentException (
138- "Missing required field '%s' in stop time CSV row" .formatted (field )
155+ "Missing required field '%s' in stop time CSV row: %s " .formatted (field , row )
139156 );
140157 }
141158 }
@@ -159,16 +176,6 @@ public double getDouble(String field) {
159176 }
160177 }
161178
162- public int requiredTime (String field ) {
163- var value = row .get (field );
164- if (value != null ) {
165- return getStringAsSeconds (value );
166- } else {
167- throw new IllegalArgumentException (
168- "Missing required field '%s' in stop_times.txt" .formatted (field )
169- );
170- }
171- }
172179 public int time (String field ) {
173180 var value = row .get (field );
174181 if (value != null ) {
@@ -181,5 +188,9 @@ public int time(String field) {
181188 public Optional <FeedScopedId > id (String field ) {
182189 return Optional .ofNullable (row .get (field )).map (s -> idFactory .createId (s , field ));
183190 }
191+
192+ public Optional <String > optionalString (String stopHeadsign ) {
193+ return Optional .ofNullable (row .get (stopHeadsign )).filter (StringUtils ::hasValue );
194+ }
184195 }
185196}
0 commit comments