Skip to content

Commit 4dc8e10

Browse files
Extract framework code
1 parent 6290675 commit 4dc8e10

File tree

5 files changed

+109
-93
lines changed

5 files changed

+109
-93
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
35+
public OptionalInt optionalInteger(String field) {
36+
return optionalString(field).stream().mapToInt(Integer::parseInt).findFirst();
37+
}
38+
39+
/**
40+
* I know that 'double' is not spelled like this, but it's a reserved word.
41+
*/
42+
public double doubble(String field) {
43+
return optionalDouble(field).orElseThrow(() -> iae(field));
44+
}
45+
46+
public OptionalDouble optionalDouble(String field) {
47+
return optionalString(field).stream().mapToDouble(Double::parseDouble).findFirst();
48+
}
49+
50+
private IllegalArgumentException iae(String field) {
51+
return new IllegalArgumentException(
52+
"Field '%s' is required but not present in CSV row %s".formatted(field, fields)
53+
);
54+
}
55+
}

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;
@@ -15,11 +12,13 @@
1512
class ShapePointMapper {
1613

1714
private static final String FILE = "shapes.txt";
15+
1816
private static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled";
1917
private static final String SHAPE_ID = "shape_id";
2018
private static final String SHAPE_PT_SEQUENCE = "shape_pt_sequence";
2119
private static final String SHAPE_PT_LAT = "shape_pt_lat";
2220
private static final String SHAPE_PT_LON = "shape_pt_lon";
21+
2322
private final IdFactory idFactory;
2423

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

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

4743
shapeBuilder.addPoint(point);
4844
ret.put(shapeId, shapeBuilder);
Lines changed: 38 additions & 59 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,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
}

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: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,12 @@ public void put(Trip key, Collection<StopTime> list) {
5555
}
5656

5757
public void removeIf(Predicate<Trip> test) {
58-
List<Trip> removeKeys = map.keySet().stream().filter(test).collect(Collectors.toList());
58+
List<Trip> removeKeys = map.keySet().stream().filter(test).toList();
5959
for (Trip removeKey : removeKeys) {
6060
map.remove(removeKey);
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)