diff --git a/application/src/main/java/org/opentripplanner/model/Timetable.java b/application/src/main/java/org/opentripplanner/model/Timetable.java index c437a60b0d5..edba1ff4b69 100644 --- a/application/src/main/java/org/opentripplanner/model/Timetable.java +++ b/application/src/main/java/org/opentripplanner/model/Timetable.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; import org.opentripplanner.transit.model.framework.DataValidationException; @@ -483,4 +484,16 @@ private static TripTimes getRepresentativeTripTimes( return null; } } + + /** + * Get a copy of the scheduled timetable valid for the specified service date only + */ + public Timetable copyForServiceDate(LocalDate date) { + if (serviceDate != null) { + throw new RuntimeException( + "Can only copy scheduled timetable for a specific date if a date hasn't been specified yet." + ); + } + return copyOf().withServiceDate(date).build(); + } } diff --git a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 44ce1e5cb18..11969cf250b 100644 --- a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -14,10 +14,12 @@ import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Predicate; @@ -100,7 +102,7 @@ public class TimetableSnapshot { * include ones from the scheduled GTFS, as well as ones added by realtime messages and * tracked by the TripPatternCache.
* Note that the keys do not include all scheduled TripPatterns, only those for which we have at - * least one update.
+ * least one update, and those for which we had updates before but just recently cleared.
* The members of the SortedSet (the Timetable for a particular day) are treated as copy-on-write
* when we're updating them. If an update will modify the timetable for a particular day, that
* timetable is replicated before any modifications are applied to avoid affecting any previous
@@ -111,6 +113,7 @@ public class TimetableSnapshot {
* TripPattern and date.
*/
private final Map
+ * Refer to bug #6197 for details.
+ */
+ @Test
+ void testTransitLayerUpdateAfterClear() {
+ var resolver = new TimetableSnapshot();
+
+ // make an updated trip
+ var pattern = patternIndex.get(new FeedScopedId(feedId, "1.1"));
+ var trip = pattern.scheduledTripsAsStream().findFirst().orElseThrow();
+ var scheduledTimetable = pattern.getScheduledTimetable();
+ var updatedTripTimes = Objects
+ .requireNonNull(scheduledTimetable.getTripTimes(trip))
+ .copyScheduledTimes();
+ for (var i = 0; i < updatedTripTimes.getNumStops(); ++i) {
+ updatedTripTimes.updateArrivalDelay(i, 30);
+ updatedTripTimes.updateDepartureDelay(i, 30);
+ }
+ updatedTripTimes.setRealTimeState(RealTimeState.UPDATED);
+ var realTimeTripUpdate = new RealTimeTripUpdate(
+ pattern,
+ updatedTripTimes,
+ SERVICE_DATE,
+ null,
+ false,
+ false
+ );
+
+ var addedDepartureStopTime = new StopTime();
+ var addedArrivalStopTime = new StopTime();
+ addedDepartureStopTime.setDepartureTime(0);
+ addedDepartureStopTime.setArrivalTime(0);
+ addedDepartureStopTime.setStop(RegularStop.of(new FeedScopedId(feedId, "XX"), () -> 0).build());
+ addedArrivalStopTime.setDepartureTime(300);
+ addedArrivalStopTime.setArrivalTime(300);
+ addedArrivalStopTime.setStop(RegularStop.of(new FeedScopedId(feedId, "YY"), () -> 1).build());
+ var addedStopTimes = List.of(addedDepartureStopTime, addedArrivalStopTime);
+ var addedStopPattern = new StopPattern(addedStopTimes);
+ var route = patternIndex.values().stream().findFirst().orElseThrow().getRoute();
+ var addedTripPattern = TripPattern
+ .of(new FeedScopedId(feedId, "1.1"))
+ .withRoute(route)
+ .withStopPattern(addedStopPattern)
+ .withCreatedByRealtimeUpdater(true)
+ .build();
+ var addedTripTimes = TripTimesFactory.tripTimes(
+ Trip.of(new FeedScopedId(feedId, "addedTrip")).withRoute(route).build(),
+ addedStopTimes,
+ new Deduplicator()
+ );
+ var addedTripUpdate = new RealTimeTripUpdate(
+ addedTripPattern,
+ addedTripTimes,
+ SERVICE_DATE,
+ null,
+ true,
+ false
+ );
+
+ var transitLayerUpdater = new TransitLayerUpdater(null) {
+ int count = 0;
+
+ /**
+ * Test that the TransitLayerUpdater receives correct updated timetables upon commit
+ *
+ * This method is called 3 times.
+ * When count = 0, the buffer contains one added and one updated trip, and the timetables
+ * should reflect this fact.
+ * When count = 1, the buffer is empty, however, this method should still receive the
+ * timetables of the previous added and updated patterns in order to restore them to the
+ * initial scheduled timetable.
+ * When count = 2, the buffer is still empty, and no changes should be made.
+ */
+ @Override
+ public void update(
+ Collection