diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 56e3c380b67..961370dea79 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -771,6 +771,7 @@ private Result addTripToGraphAndBuffer( // Create StopTimes final List stopTimes = new ArrayList<>(stopTimeUpdates.size()); + List skippedStopIndices = new ArrayList<>(); for (int index = 0; index < stopTimeUpdates.size(); ++index) { final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(index); final var stop = stops.get(index); @@ -781,7 +782,11 @@ private Result addTripToGraphAndBuffer( stopTime.setStop(stop); // Set arrival time if (stopTimeUpdate.hasArrival() && stopTimeUpdate.getArrival().hasTime()) { - final long arrivalTime = stopTimeUpdate.getArrival().getTime() - midnightSecondsSinceEpoch; + final int delay = stopTimeUpdate.getArrival().hasDelay() + ? stopTimeUpdate.getArrival().getDelay() + : 0; + final long arrivalTime = + stopTimeUpdate.getArrival().getTime() - midnightSecondsSinceEpoch - delay; if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -794,8 +799,11 @@ private Result addTripToGraphAndBuffer( } // Set departure time if (stopTimeUpdate.hasDeparture() && stopTimeUpdate.getDeparture().hasTime()) { + final int delay = stopTimeUpdate.getDeparture().hasDelay() + ? stopTimeUpdate.getDeparture().getDelay() + : 0; final long departureTime = - stopTimeUpdate.getDeparture().getTime() - midnightSecondsSinceEpoch; + stopTimeUpdate.getDeparture().getTime() - midnightSecondsSinceEpoch - delay; if (departureTime < 0 || departureTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -815,14 +823,23 @@ private Result addTripToGraphAndBuffer( stopTime.setDropOffType(added.dropOff()); // Add stop time to list stopTimes.add(stopTime); + + // Add skipped stop to list + if ( + stopTimeUpdate.hasScheduleRelationship() && + stopTimeUpdate.getScheduleRelationship() == StopTimeUpdate.ScheduleRelationship.SKIPPED + ) { + skippedStopIndices.add(index); + } } // TODO: filter/interpolate stop times like in PatternHopFactory? - // Create StopPattern - final StopPattern stopPattern = new StopPattern(stopTimes); - final TripPattern originalTripPattern = transitEditorService.getPatternForTrip(trip); + // Create StopPattern + final StopPattern stopPattern = originalTripPattern.copyPlannedStopPattern() + .cancelStops(skippedStopIndices) + .build(); // Get cached trip pattern or create one if it doesn't exist yet final TripPattern pattern = tripPatternCache.getOrCreateTripPattern( stopPattern, @@ -838,12 +855,30 @@ private Result addTripToGraphAndBuffer( ); // Update all times to mark trip times as realtime - // TODO: should we incorporate the delay field if present? + // TODO: This is based on the proposal at https://github.com/google/transit/issues/490 for (int stopIndex = 0; stopIndex < newTripTimes.getNumStops(); stopIndex++) { - newTripTimes.updateArrivalTime(stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex)); + final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(stopIndex); + + if ( + stopTimeUpdate.hasScheduleRelationship() && + stopTimeUpdate.getScheduleRelationship() == StopTimeUpdate.ScheduleRelationship.SKIPPED + ) { + newTripTimes.setCancelled(stopIndex); + } + + final int arrivalDelay = stopTimeUpdate.hasArrival() + ? stopTimeUpdate.getArrival().hasDelay() ? stopTimeUpdate.getArrival().getDelay() : 0 + : 0; + final int departureDelay = stopTimeUpdate.hasDeparture() + ? stopTimeUpdate.getDeparture().hasDelay() ? stopTimeUpdate.getDeparture().getDelay() : 0 + : 0; + newTripTimes.updateArrivalTime( + stopIndex, + newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay + ); newTripTimes.updateDepartureTime( stopIndex, - newTripTimes.getScheduledDepartureTime(stopIndex) + newTripTimes.getScheduledDepartureTime(stopIndex) + departureDelay ); } diff --git a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index 2960d92a9cd..eeebad4ff62 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -49,6 +49,26 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes) { ); } + public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + delay, + delay, + DEFAULT_SCHEDULE_RELATIONSHIP, + null + ); + } + + public TripUpdateBuilder addStopTime( + String stopId, + int minutes, + StopTimeUpdate.ScheduleRelationship scheduleRelationship + ) { + return addStopTime(stopId, minutes, NO_VALUE, NO_DELAY, NO_DELAY, scheduleRelationship, null); + } + public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupType pickDrop) { return addStopTime( stopId, diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 8371c5dda3a..5f8b36e8bbc 100644 --- a/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -1,12 +1,15 @@ package org.opentripplanner.updater.trip.moduletests.addition; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.ADDED; +import static com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; +import static org.opentripplanner.updater.trip.BackwardsDelayPropagationType.REQUIRED_NO_DATA; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_A1_ID; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_B1_ID; @@ -16,9 +19,12 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.model.PickDrop; +import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.spi.UpdateSuccess; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; import org.opentripplanner.updater.trip.TripUpdateBuilder; @@ -120,6 +126,54 @@ void repeatedlyAddedTripWithNewRoute() { assertNotNull(env.getTransitService().getRouteForId(firstRoute.getId())); } + @Test + public void addedTripWithSkippedStop() { + var env = RealtimeTestEnvironment.gtfs(); + var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); + builder.addStopTime("A", 30).addStopTime("C", 40, SKIPPED).addStopTime("E", 55); + var tripUpdate = builder.build(); + + env.applyTripUpdate(tripUpdate); + + // THEN + final TripPattern tripPattern = assertAddedTrip(ADDED_TRIP_ID, env); + assertEquals(PickDrop.SCHEDULED, tripPattern.getBoardType(0)); + assertEquals(PickDrop.CANCELLED, tripPattern.getAlightType(1)); + assertEquals(PickDrop.CANCELLED, tripPattern.getBoardType(1)); + assertEquals(PickDrop.SCHEDULED, tripPattern.getAlightType(2)); + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + final Timetable forToday = snapshot.resolve(tripPattern, SERVICE_DATE); + final int forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); + final TripTimes tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); + assertFalse(tripTimes.isCancelledStop(0)); + assertTrue(tripTimes.isCancelledStop(1)); + } + + @Test + public void addedTripWithDelay() { + var env = RealtimeTestEnvironment.gtfs(); + var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); + + // A: scheduled 08:30:00 + // C: scheduled 08:40:00, delay 300 seconds (actual 08:45:00) + // E: scheduled 08:55:00 + builder.addStopTime("A", 30).addStopTime("C", 45, 300).addStopTime("E", 55); + + var tripUpdate = builder.build(); + env.applyTripUpdate(tripUpdate); + + // THEN + final TripPattern tripPattern = assertAddedTrip(ADDED_TRIP_ID, env); + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + final Timetable forToday = snapshot.resolve(tripPattern, SERVICE_DATE); + final int forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); + final TripTimes tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); + assertEquals(0, tripTimes.getDepartureDelay(0)); + assertEquals(30600, tripTimes.getDepartureTime(0)); // 08:30:00 + assertEquals(300, tripTimes.getArrivalDelay(1)); + assertEquals(31500, tripTimes.getArrivalTime(1)); // 08:45:00 + } + private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { var snapshot = env.getTimetableSnapshot(); var stopA = env.transitModel.getStopModel().getRegularStop(env.stopA1.getId());