Skip to content

Commit 5aacb43

Browse files
Extract framework code
1 parent c0f6ee3 commit 5aacb43

File tree

5 files changed

+103
-87
lines changed

5 files changed

+103
-87
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.opentripplanner.gtfs.mapping;
2+
3+
import java.util.Map;
4+
import java.util.Optional;
5+
import java.util.OptionalDouble;
6+
import java.util.OptionalInt;
7+
import javax.annotation.Nullable;
8+
import org.opentripplanner.utils.lang.StringUtils;
9+
10+
public class GtfsRow {
11+
12+
protected final Map<String, String> fields;
13+
14+
public GtfsRow(Map<String, String> fields) {
15+
this.fields = Map.copyOf(fields);
16+
}
17+
18+
public String string(String field) {
19+
return optionalString(field).orElseThrow(() -> iae(field));
20+
}
21+
22+
@Nullable
23+
public String nullableString(String field) {
24+
return fields.get(field);
25+
}
26+
27+
public Optional<String> optionalString(String field) {
28+
return Optional.ofNullable(fields.get(field));
29+
}
30+
31+
public int integer(String field) {
32+
return optionalInteger(field).orElseThrow(() -> iae(field));
33+
}
34+
public OptionalInt optionalInteger(String field) {
35+
return Optional.ofNullable(fields.get(field)).stream().mapToInt(Integer::parseInt).findFirst();
36+
}
37+
38+
/**
39+
* I know that 'double' is not spelled like this, but it's a reserved word.
40+
*/
41+
public double doubble(String field) {
42+
return optionalDouble(field).orElseThrow(() -> iae(field));
43+
}
44+
45+
public OptionalDouble optionalDouble(String field) {
46+
return optionalString(field).stream().mapToDouble(Double::parseDouble).findFirst();
47+
}
48+
49+
private IllegalArgumentException iae(String field) {
50+
return new IllegalArgumentException(
51+
"Field '%s' is required but not present in CSV row %s".formatted(field, fields)
52+
);
53+
}
54+
}

application/src/main/java/org/opentripplanner/gtfs/mapping/ShapePointMapper.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.opentripplanner.gtfs.mapping;
22

3-
import static java.lang.Double.parseDouble;
4-
import static java.lang.Integer.parseInt;
5-
63
import java.io.IOException;
74
import java.util.HashMap;
85
import java.util.Map;
@@ -14,11 +11,13 @@
1411
class ShapePointMapper {
1512

1613
private static final String FILE = "shapes.txt";
14+
1715
private static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled";
1816
private static final String SHAPE_ID = "shape_id";
1917
private static final String SHAPE_PT_SEQUENCE = "shape_pt_sequence";
2018
private static final String SHAPE_PT_LAT = "shape_pt_lat";
2119
private static final String SHAPE_PT_LON = "shape_pt_lon";
20+
2221
private final IdFactory idFactory;
2322

2423
ShapePointMapper(IdFactory idFactory) {
@@ -29,19 +28,16 @@ Map<FeedScopedId, CompactShape> map(CsvInputSource inputSource) throws IOExcepti
2928
var ret = new HashMap<FeedScopedId, CompactShape>();
3029
new StreamingCsvReader(inputSource)
3130
.rows(FILE)
32-
.forEach(sp -> {
33-
var shapeId = idFactory.createId(sp.get(SHAPE_ID), "shape point");
31+
.forEach(row -> {
32+
var shapeId = idFactory.createId(row.string(SHAPE_ID), "shape point");
3433
var shapeBuilder = ret.getOrDefault(shapeId, new CompactShape());
3534

3635
var point = new ShapePoint();
37-
point.setSequence(parseInt(sp.get(SHAPE_PT_SEQUENCE)));
38-
point.setLat(parseDouble(sp.get(SHAPE_PT_LAT)));
39-
point.setLon(parseDouble(sp.get(SHAPE_PT_LON)));
40-
41-
var distTraveled = sp.get(SHAPE_DIST_TRAVELED);
42-
if (distTraveled != null) {
43-
point.setDistTraveled(parseDouble(distTraveled));
44-
}
36+
point.setSequence(row.integer(SHAPE_PT_SEQUENCE));
37+
point.setLat(row.doubble(SHAPE_PT_LAT));
38+
point.setLon(row.doubble(SHAPE_PT_LON));
39+
40+
row.optionalDouble(SHAPE_DIST_TRAVELED).ifPresent(point::setDistTraveled);
4541

4642
shapeBuilder.addPoint(point);
4743
ret.put(shapeId, shapeBuilder);
Lines changed: 34 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
package org.opentripplanner.gtfs.mapping;
22

33
import static org.onebusaway.gtfs.serialization.mappings.StopTimeFieldMappingFactory.getStringAsSeconds;
4+
import static org.opentripplanner.model.StopTime.MISSING_VALUE;
45

56
import java.io.IOException;
6-
import java.util.Map;
77
import java.util.Objects;
88
import java.util.Optional;
99
import java.util.stream.Stream;
10-
import javax.annotation.Nullable;
1110
import org.onebusaway.csv_entities.CsvInputSource;
1211
import org.opentripplanner.framework.i18n.I18NString;
1312
import org.opentripplanner.model.StopTime;
1413
import org.opentripplanner.model.impl.OtpTransitServiceBuilder;
1514
import org.opentripplanner.transit.model.framework.EntityById;
1615
import org.opentripplanner.transit.model.framework.FeedScopedId;
1716
import 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
*/
2321
class 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,36 @@ 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));
116121

117122
lhs.setFlexContinuousPickup(
118-
PickDropMapper.mapFlexContinuousPickDrop(row.integer(CONTINUOUS_PICKUP))
123+
PickDropMapper.mapFlexContinuousPickDrop(row.optionalInteger(CONTINUOUS_PICKUP).orElse(MISSING_VALUE))
119124
);
120125
lhs.setFlexContinuousDropOff(
121-
PickDropMapper.mapFlexContinuousPickDrop(row.integer(CONTINUOUS_DROP_OFF))
126+
PickDropMapper.mapFlexContinuousPickDrop(row.optionalInteger(CONTINUOUS_DROP_OFF).orElse(MISSING_VALUE))
122127
);
123128

124129
row.optionalString(STOP_HEADSIGN).ifPresent(hs -> lhs.setStopHeadsign(I18NString.of(hs)));
125130
row
126-
.id(PICKUP_BOOKING_RULE_ID)
131+
.optionalId(PICKUP_BOOKING_RULE_ID)
127132
.ifPresent(id ->
128133
lhs.setPickupBookingInfo(
129134
Objects.requireNonNull(
@@ -133,7 +138,7 @@ private StopTime doMap(StopTimeRow row, EntityById<Trip> trips) {
133138
)
134139
);
135140
row
136-
.id(DROP_OFF_BOOKING_RULE_ID)
141+
.optionalId(DROP_OFF_BOOKING_RULE_ID)
137142
.ifPresent(id ->
138143
lhs.setPickupBookingInfo(
139144
Objects.requireNonNull(
@@ -146,51 +151,26 @@ private StopTime doMap(StopTimeRow row, EntityById<Trip> trips) {
146151
return lhs;
147152
}
148153

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-
}
154+
static final class StopTimeRow extends GtfsRow {
155+
156+
private final IdFactory idFactory;
157+
158+
StopTimeRow(GtfsRow row, IdFactory idFactory) {
159+
super(row.fields);
160+
this.idFactory = idFactory;
177161
}
178162

179163
public int time(String field) {
180-
var value = row.get(field);
164+
var value = fields.get(field);
181165
if (value != null) {
182166
return getStringAsSeconds(value);
183167
} else {
184-
return StopTime.MISSING_VALUE;
168+
return MISSING_VALUE;
185169
}
186170
}
187171

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);
172+
public Optional<FeedScopedId> optionalId(String field) {
173+
return optionalString(field).map(String::intern).map(s -> idFactory.createId(s, field));
194174
}
195175
}
196176
}

application/src/main/java/org/opentripplanner/gtfs/mapping/StreamingCsvReader.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public StreamingCsvReader(CsvInputSource inputSource) {
1515
this.inputSource = Objects.requireNonNull(inputSource);
1616
}
1717

18-
public Stream<Map<String, String>> rows(String fileName) throws IOException {
18+
public Stream<GtfsRow> rows(String fileName) throws IOException {
1919
if (inputSource.hasResource(fileName)) {
2020
return stream(fileName);
2121
} else {
2222
return Stream.empty();
2323
}
2424
}
2525

26-
private Stream<Map<String, String>> stream(String fileName) throws IOException {
26+
private Stream<GtfsRow> stream(String fileName) throws IOException {
2727
var source = inputSource.getResource(fileName);
2828
var streamReader = new InputStreamReader(source);
2929
BufferedReader lineReader = new BufferedReader(streamReader);
@@ -46,17 +46,13 @@ private Stream<Map<String, String>> stream(String fileName) throws IOException {
4646

4747
for (int i = 0; i < fields.size() && i < elements.size(); i++) {
4848
var fieldName = fields.get(i);
49-
try {
50-
var value = elements.get(i);
51-
if (StringUtils.hasValue(value)) {
52-
values.put(fieldName, value);
53-
}
54-
} catch (Exception e) {
55-
System.out.println(elements);
49+
var value = elements.get(i);
50+
if (StringUtils.hasValue(value)) {
51+
values.put(fieldName, value);
5652
}
5753
}
5854

59-
return values;
55+
return new GtfsRow(values);
6056
});
6157
}
6258
}

application/src/main/java/org/opentripplanner/model/TripStopTimes.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,6 @@ public void removeIf(Predicate<Trip> test) {
6161
}
6262
}
6363

64-
/**
65-
* Return a copy of the internal map. Changes in the source are not reflected in the destination
66-
* (returned Map), and visa versa.
67-
* <p>
68-
* The returned map is immutable.
69-
*/
70-
public Map<Trip, List<StopTime>> asImmutableMap() {
71-
return Map.copyOf(map);
72-
}
73-
7464
public int size() {
7565
return map.size();
7666
}

0 commit comments

Comments
 (0)