11package org .opentripplanner .gtfs .mapping ;
22
33import static org .onebusaway .gtfs .serialization .mappings .StopTimeFieldMappingFactory .getStringAsSeconds ;
4+ import static org .opentripplanner .model .StopTime .MISSING_VALUE ;
45
56import java .io .IOException ;
6- import java .util .Map ;
77import java .util .Objects ;
88import java .util .Optional ;
99import java .util .stream .Stream ;
10- import javax .annotation .Nullable ;
1110import org .onebusaway .csv_entities .CsvInputSource ;
1211import org .opentripplanner .framework .i18n .I18NString ;
1312import org .opentripplanner .model .StopTime ;
1413import org .opentripplanner .model .impl .OtpTransitServiceBuilder ;
1514import org .opentripplanner .transit .model .framework .EntityById ;
1615import org .opentripplanner .transit .model .framework .FeedScopedId ;
1716import org .opentripplanner .transit .model .timetable .Trip ;
18- import org .opentripplanner .utils .lang .StringUtils ;
1917
2018/**
2119 * Responsible for mapping GTFS StopTime into the OTP Transit model.
2220 */
2321class StopTimeMapper {
2422
23+ private static final String FILE = "stop_times.txt" ;
24+
2525 private static final String TIMEPOINT = "timepoint" ;
2626 private static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled" ;
2727 private static final String STOP_SEQUENCE = "stop_sequence" ;
@@ -39,9 +39,9 @@ class StopTimeMapper {
3939 private static final String LOCATION_ID = "location_id" ;
4040 private static final String STOP_GROUP_ID = "stop_group_id" ;
4141 private static final String STOP_HEADSIGN = "stop_headsign" ;
42- private static final String FILE = "stops_times.txt" ;
43- private final IdFactory idFactory ;
42+ private static final String TRIP_ID = "trip_id" ;
4443
44+ private final IdFactory idFactory ;
4545 private final BookingRuleMapper bookingRuleMapper ;
4646 private final TranslationHelper translationHelper ;
4747 private final OtpTransitServiceBuilder builder ;
@@ -61,22 +61,23 @@ class StopTimeMapper {
6161 Stream <StopTime > map (CsvInputSource inputSource ) throws IOException {
6262 return new StreamingCsvReader (inputSource )
6363 .rows (FILE )
64+ .map (r -> new StopTimeRow (r , idFactory ))
6465 .map (st -> this .doMap (new StopTimeRow (st , idFactory ), builder .getTripsById ()));
6566 }
6667
6768 private StopTime doMap (StopTimeRow row , EntityById <Trip > trips ) {
6869 StopTime lhs = new StopTime ();
6970
70- var tripId = idFactory .createId (row .requiredString ( "trip_id" ), "stop time's trip" );
71+ var tripId = idFactory .createId (row .string ( TRIP_ID ), "stop time's trip" );
7172 var trip = Objects .requireNonNull (
7273 trips .get (tripId ),
7374 "Stop time refers to non-existent trip with id %s" .formatted (tripId )
7475 );
7576 lhs .setTrip (trip );
7677
77- var stopId = row .string (STOP_ID );
78- var stopLocationId = row .string (LOCATION_ID );
79- var locationGroupId = row .string (STOP_GROUP_ID );
78+ var stopId = row .nullableString (STOP_ID );
79+ var stopLocationId = row .nullableString (LOCATION_ID );
80+ var locationGroupId = row .nullableString (STOP_GROUP_ID );
8081
8182 var siteRepositoryBuilder = builder .siteRepository ();
8283 if (stopId != null ) {
@@ -98,32 +99,35 @@ private StopTime doMap(StopTimeRow row, EntityById<Trip> trips) {
9899 lhs .setStop (Objects .requireNonNull (siteRepositoryBuilder .groupStopById ().get (id )));
99100 } else {
100101 throw new IllegalArgumentException (
101- "Stop time must have either a %s, %s, or %s" .formatted (STOP_ID , LOCATION_ID , STOP_GROUP_ID )
102+ "Stop time entry must have either a %s, %s, or %s" .formatted (
103+ STOP_ID ,
104+ LOCATION_ID ,
105+ STOP_GROUP_ID
106+ )
102107 );
103108 }
104109
105110 lhs .setArrivalTime (row .time (ARRIVAL_TIME ));
106111 lhs .setDepartureTime (row .time (DEPARTURE_TIME ));
107112 lhs .setStopSequence (row .integer (STOP_SEQUENCE ));
108113
109- lhs .setTimepoint (row .integer (TIMEPOINT ));
110- lhs .setShapeDistTraveled (row .getDouble (SHAPE_DIST_TRAVELED ));
111- lhs .setPickupType (PickDropMapper .map (row .string (PICKUP_TYPE )));
112- lhs .setDropOffType (PickDropMapper .map (row .string (DROP_OFF_TYPE )));
114+ lhs .setTimepoint (row .optionalInteger (TIMEPOINT ). orElse ( MISSING_VALUE ));
115+ lhs .setShapeDistTraveled (row .optionalDouble (SHAPE_DIST_TRAVELED ). orElse ( MISSING_VALUE ));
116+ lhs .setPickupType (PickDropMapper .map (row .nullableString (PICKUP_TYPE )));
117+ lhs .setDropOffType (PickDropMapper .map (row .nullableString (DROP_OFF_TYPE )));
113118
114119 lhs .setFlexWindowStart (row .time (START_PICKUP_DROP_OFF_WINDOW ));
115120 lhs .setFlexWindowEnd (row .time (END_PICKUP_DROP_OFF_WINDOW ));
116-
117- lhs .setFlexContinuousPickup (
118- PickDropMapper .mapFlexContinuousPickDrop (row .integer (CONTINUOUS_PICKUP ))
119- );
120- lhs .setFlexContinuousDropOff (
121- PickDropMapper .mapFlexContinuousPickDrop (row .integer (CONTINUOUS_DROP_OFF ))
122- );
121+ row
122+ .optionalInteger (CONTINUOUS_PICKUP )
123+ .ifPresent (i -> lhs .setFlexContinuousPickup (PickDropMapper .mapFlexContinuousPickDrop (i )));
124+ row
125+ .optionalInteger (CONTINUOUS_DROP_OFF )
126+ .ifPresent (i -> lhs .setFlexContinuousDropOff (PickDropMapper .mapFlexContinuousPickDrop (i )));
123127
124128 row .optionalString (STOP_HEADSIGN ).ifPresent (hs -> lhs .setStopHeadsign (I18NString .of (hs )));
125129 row
126- .id (PICKUP_BOOKING_RULE_ID )
130+ .optionalId (PICKUP_BOOKING_RULE_ID )
127131 .ifPresent (id ->
128132 lhs .setPickupBookingInfo (
129133 Objects .requireNonNull (
@@ -133,7 +137,7 @@ private StopTime doMap(StopTimeRow row, EntityById<Trip> trips) {
133137 )
134138 );
135139 row
136- .id (DROP_OFF_BOOKING_RULE_ID )
140+ .optionalId (DROP_OFF_BOOKING_RULE_ID )
137141 .ifPresent (id ->
138142 lhs .setPickupBookingInfo (
139143 Objects .requireNonNull (
@@ -146,51 +150,26 @@ private StopTime doMap(StopTimeRow row, EntityById<Trip> trips) {
146150 return lhs ;
147151 }
148152
149- record StopTimeRow (Map <String , String > row , IdFactory idFactory ) {
150- public String requiredString (String field ) {
151- if (row .containsKey (field )) {
152- return row .get (field );
153- } else {
154- throw new IllegalArgumentException (
155- "Missing required field '%s' in stop time CSV row: %s" .formatted (field , row )
156- );
157- }
158- }
159- @ Nullable
160- public String string (String field ) {
161- return row .get (field );
162- }
163- public int integer (String field ) {
164- var value = row .get (field );
165- if (StringUtils .hasValue (value )) {
166- return Integer .parseInt (value );
167- } else {
168- return StopTime .MISSING_VALUE ;
169- }
170- }
171- public double getDouble (String field ) {
172- if (row .containsKey (field )) {
173- return Double .parseDouble (row .get (field ));
174- } else {
175- return StopTime .MISSING_VALUE ;
176- }
153+ static final class StopTimeRow extends GtfsRow {
154+
155+ private final IdFactory idFactory ;
156+
157+ StopTimeRow (GtfsRow row , IdFactory idFactory ) {
158+ super (row .fields );
159+ this .idFactory = idFactory ;
177160 }
178161
179162 public int time (String field ) {
180- var value = row .get (field );
163+ var value = fields .get (field );
181164 if (value != null ) {
182165 return getStringAsSeconds (value );
183166 } else {
184- return StopTime . MISSING_VALUE ;
167+ return MISSING_VALUE ;
185168 }
186169 }
187170
188- public Optional <FeedScopedId > id (String field ) {
189- return Optional .ofNullable (row .get (field )).map (s -> idFactory .createId (s , field ));
190- }
191-
192- public Optional <String > optionalString (String stopHeadsign ) {
193- return Optional .ofNullable (row .get (stopHeadsign )).filter (StringUtils ::hasValue );
171+ public Optional <FeedScopedId > optionalId (String field ) {
172+ return optionalString (field ).map (String ::intern ).map (s -> idFactory .createId (s , field ));
194173 }
195174 }
196175}
0 commit comments