From fbcbe5d414595ce33e2e07e1923cdafa81c4330d Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 14 Aug 2024 14:55:31 +0100 Subject: [PATCH 01/23] implement delay and skipped stops on added / replacement trips --- .../updater/trip/TimetableSnapshotSource.java | 35 +++++++++-- .../updater/trip/TripUpdateBuilder.java | 20 +++++++ .../trip/moduletests/addition/AddedTest.java | 60 +++++++++++++++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 56e3c380b67..9069cb0879c 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -781,7 +781,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 +798,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(), @@ -838,12 +845,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..61546ddd663 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,60 @@ 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(STOP_A1_ID, 30) + .addStopTime(STOP_B1_ID, 40, SKIPPED) + .addStopTime(STOP_C1_ID, 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); + + // A1: scheduled 08:30:00 + // B1: scheduled 08:40:00, delay 300 seconds (actual 08:45:00) + // C1: scheduled 08:55:00 + builder + .addStopTime(STOP_A1_ID, 30) + .addStopTime(STOP_B1_ID, 45, 300) + .addStopTime(STOP_C1_ID, 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()); From d7f506ff06e1c46b61bc8c382f02b34e9ccc2d6d Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Sat, 31 Aug 2024 12:03:45 +0100 Subject: [PATCH 02/23] strengthen test case --- .../updater/trip/RealtimeTestEnvironment.java | 3 ++- .../updater/trip/TripUpdateBuilder.java | 17 +++++++++++++++++ .../trip/moduletests/addition/AddedTest.java | 16 +++++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java index bf6f743eac7..1fbc93e69c4 100644 --- a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java +++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java @@ -59,6 +59,7 @@ public final class RealtimeTestEnvironment { public static final String STOP_A1_ID = "A1"; public static final String STOP_B1_ID = "B1"; public static final String STOP_C1_ID = "C1"; + public static final String STOP_D1_ID = "D1"; private final TransitModelForTest testModel = TransitModelForTest.of(); public final ZoneId timeZone = ZoneId.of(TransitModelForTest.TIME_ZONE_ID); public final Station stationA = testModel.station("A").build(); @@ -69,7 +70,7 @@ public final class RealtimeTestEnvironment { public final RegularStop stopB1 = testModel.stop(STOP_B1_ID).withParentStation(stationB).build(); public final RegularStop stopB2 = testModel.stop("B2").withParentStation(stationB).build(); public final RegularStop stopC1 = testModel.stop(STOP_C1_ID).withParentStation(stationC).build(); - public final RegularStop stopD1 = testModel.stop("D1").withParentStation(stationD).build(); + public final RegularStop stopD1 = testModel.stop(STOP_D1_ID).withParentStation(stationD).build(); public final StopModel stopModel = testModel .stopModelBuilder() .withRegularStop(stopA1) diff --git a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index eeebad4ff62..67d770096d3 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -81,6 +81,23 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy ); } + public TripUpdateBuilder addStopTime( + String stopId, + int minutes, + DropOffPickupType pickDrop, + StopTimeUpdate.ScheduleRelationship scheduleRelationship + ) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + scheduleRelationship, + pickDrop + ); + } + public TripUpdateBuilder addDelayedStopTime(int stopSequence, int delay) { return addStopTime(null, -1, stopSequence, delay, delay, DEFAULT_SCHEDULE_RELATIONSHIP, null); } 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 61546ddd663..78c91bb4aaf 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 @@ -14,6 +14,7 @@ import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_A1_ID; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_B1_ID; import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_C1_ID; +import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.STOP_D1_ID; import de.mfdz.MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType; import java.util.List; @@ -131,25 +132,30 @@ public void addedTripWithSkippedStop() { var env = RealtimeTestEnvironment.gtfs(); var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); builder - .addStopTime(STOP_A1_ID, 30) - .addStopTime(STOP_B1_ID, 40, SKIPPED) - .addStopTime(STOP_C1_ID, 55); + .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) + .addStopTime(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER, SKIPPED) + .addStopTime(STOP_C1_ID, 48, SKIPPED) + .addStopTime(STOP_D1_ID, 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.CALL_AGENCY, tripPattern.getBoardType(0)); assertEquals(PickDrop.CANCELLED, tripPattern.getAlightType(1)); assertEquals(PickDrop.CANCELLED, tripPattern.getBoardType(1)); - assertEquals(PickDrop.SCHEDULED, tripPattern.getAlightType(2)); + assertEquals(PickDrop.CANCELLED, tripPattern.getAlightType(2)); + assertEquals(PickDrop.CANCELLED, tripPattern.getBoardType(2)); + assertEquals(PickDrop.SCHEDULED, tripPattern.getAlightType(3)); 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)); + assertTrue(tripTimes.isCancelledStop(2)); + assertFalse(tripTimes.isCancelledStop(3)); } @Test From 7affd6cfab963473238f19ce923776430ad11758 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Sat, 31 Aug 2024 12:31:34 +0100 Subject: [PATCH 03/23] Skipped stops should always have PickDrop = CANCELLED regardless of the original PickDrop specified in the MFDZ extension --- .../updater/trip/AddedStopTime.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index 654c420da62..73b47e93af5 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -35,19 +35,22 @@ PickDrop dropOff() { } static AddedStopTime ofStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate props) { - if (props.getStopTimeProperties().hasExtension(MfdzRealtimeExtensions.stopTimeProperties)) { + final var scheduleRelationship = props.getScheduleRelationship(); + var pickupType = toPickDrop(scheduleRelationship); + var dropOffType = pickupType; + if ( + scheduleRelationship != GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED && + props.getStopTimeProperties().hasExtension(MfdzRealtimeExtensions.stopTimeProperties) + ) { var ext = props .getStopTimeProperties() .getExtension(MfdzRealtimeExtensions.stopTimeProperties); var pickup = ext.getPickupType(); var dropOff = ext.getDropoffType(); - var dropOffType = PickDropMapper.map(dropOff.getNumber()); - var pickupType = PickDropMapper.map(pickup.getNumber()); - return new AddedStopTime(pickupType, dropOffType); - } else { - var pickDrop = toPickDrop(props.getScheduleRelationship()); - return new AddedStopTime(pickDrop, pickDrop); + dropOffType = PickDropMapper.map(dropOff.getNumber()); + pickupType = PickDropMapper.map(pickup.getNumber()); } + return new AddedStopTime(pickupType, dropOffType); } private static PickDrop toPickDrop( From 4240da2dee7e9a5be305687f12bbfeace1a96ab6 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 3 Sep 2024 13:31:42 +0100 Subject: [PATCH 04/23] use "var" instead of specifying types --- .../trip/moduletests/addition/AddedTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 78c91bb4aaf..d7c417ac140 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 @@ -148,10 +148,10 @@ public void addedTripWithSkippedStop() { assertEquals(PickDrop.CANCELLED, tripPattern.getAlightType(2)); assertEquals(PickDrop.CANCELLED, tripPattern.getBoardType(2)); assertEquals(PickDrop.SCHEDULED, tripPattern.getAlightType(3)); - 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); + var snapshot = env.getTimetableSnapshot(); + var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); + var forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); + var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); assertFalse(tripTimes.isCancelledStop(0)); assertTrue(tripTimes.isCancelledStop(1)); assertTrue(tripTimes.isCancelledStop(2)); @@ -175,11 +175,11 @@ public void addedTripWithDelay() { 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); + var tripPattern = assertAddedTrip(ADDED_TRIP_ID, env); + var snapshot = env.getTimetableSnapshot(); + var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); + var forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); + var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); assertEquals(0, tripTimes.getDepartureDelay(0)); assertEquals(30600, tripTimes.getDepartureTime(0)); // 08:30:00 assertEquals(300, tripTimes.getArrivalDelay(1)); From 30e70fb9c535f030433d1b374366e1a3633a3e51 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 3 Sep 2024 13:46:54 +0100 Subject: [PATCH 05/23] add overloads of TripUpdateBuilder.addSkippedStop to specify skipped stops in added trips --- .../updater/trip/TripUpdateBuilder.java | 49 +++++++++---------- .../trip/moduletests/addition/AddedTest.java | 4 +- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index 67d770096d3..3a4c9315258 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -61,14 +61,6 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { ); } - 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, @@ -81,23 +73,6 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy ); } - public TripUpdateBuilder addStopTime( - String stopId, - int minutes, - DropOffPickupType pickDrop, - StopTimeUpdate.ScheduleRelationship scheduleRelationship - ) { - return addStopTime( - stopId, - minutes, - NO_VALUE, - NO_DELAY, - NO_DELAY, - scheduleRelationship, - pickDrop - ); - } - public TripUpdateBuilder addDelayedStopTime(int stopSequence, int delay) { return addStopTime(null, -1, stopSequence, delay, delay, DEFAULT_SCHEDULE_RELATIONSHIP, null); } @@ -148,6 +123,30 @@ public TripUpdateBuilder addSkippedStop(int stopSequence) { ); } + public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + StopTimeUpdate.ScheduleRelationship.SKIPPED, + null + ); + } + + public TripUpdateBuilder addSkippedStop(String stopId, int minutes, DropOffPickupType pickDrop) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + StopTimeUpdate.ScheduleRelationship.SKIPPED, + pickDrop + ); + } + /** * As opposed to the other convenience methods, this one takes a raw {@link StopTimeUpdate} and * adds it to the trip. This is useful if you want to test invalid ones. 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 d7c417ac140..4e817409d3f 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 @@ -133,8 +133,8 @@ public void addedTripWithSkippedStop() { var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); builder .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) - .addStopTime(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER, SKIPPED) - .addStopTime(STOP_C1_ID, 48, SKIPPED) + .addSkippedStop(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER) + .addSkippedStop(STOP_C1_ID, 48) .addStopTime(STOP_D1_ID, 55); var tripUpdate = builder.build(); From 979a6566142e22447f9c64d19f6f9e5f5be5bcf6 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 3 Sep 2024 14:27:56 +0100 Subject: [PATCH 06/23] move checks against StopTimeUpdate to AddedStopTime --- .../updater/trip/AddedStopTime.java | 77 ++++++++++++++++++- .../updater/trip/TimetableSnapshotSource.java | 76 ++++++++---------- 2 files changed, 105 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index 73b47e93af5..093c8fbdf54 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -1,8 +1,14 @@ package org.opentripplanner.updater.trip; +import com.esotericsoftware.kryo.util.Null; import com.google.transit.realtime.GtfsRealtime; import de.mfdz.MfdzRealtimeExtensions; +import gnu.trove.set.hash.TIntHashSet; +import io.grpc.netty.shaded.io.netty.util.concurrent.ProgressivePromise; +import java.sql.Time; import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Future; import javax.annotation.Nullable; import org.opentripplanner.gtfs.mapping.PickDropMapper; import org.opentripplanner.model.PickDrop; @@ -19,11 +25,38 @@ final class AddedStopTime { @Nullable private final PickDrop dropOff; + @Nullable + public final String stopId; + + @Nullable + private final GtfsRealtime.TripUpdate.StopTimeEvent arrival; + + @Nullable + private final GtfsRealtime.TripUpdate.StopTimeEvent departure; + + @Nullable + public final Integer stopSequence; + + public final GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship; + public static final PickDrop DEFAULT_PICK_DROP = PickDrop.SCHEDULED; - AddedStopTime(@Nullable PickDrop pickup, @Nullable PickDrop dropOff) { + AddedStopTime( + @Nullable PickDrop pickup, + @Nullable PickDrop dropOff, + @Nullable String stopId, + @Nullable GtfsRealtime.TripUpdate.StopTimeEvent arrival, + @Nullable GtfsRealtime.TripUpdate.StopTimeEvent departure, + @Nullable Integer stopSequence, + @Nullable GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship + ) { this.pickup = pickup; this.dropOff = dropOff; + this.stopId = stopId; + this.arrival = arrival; + this.departure = departure; + this.stopSequence = stopSequence; + this.scheduleRelationship = scheduleRelationship; } PickDrop pickup() { @@ -34,6 +67,38 @@ PickDrop dropOff() { return Objects.requireNonNullElse(dropOff, DEFAULT_PICK_DROP); } + @Nullable + Long getArrivalTime() { + if (arrival == null) { + return null; + } + return arrival.hasTime() ? arrival.getTime() : null; + } + + @Nullable + Long getDepartureTime() { + if (departure == null) { + return null; + } + return departure.hasTime() ? departure.getTime() : null; + } + + @Nullable + Integer getArrivalDelay() { + if (arrival == null) { + return null; + } + return arrival.hasDelay() ? arrival.getDelay() : null; + } + + @Nullable + Integer getDepartureDelay() { + if (departure == null) { + return null; + } + return departure.hasDelay() ? departure.getDelay() : null; + } + static AddedStopTime ofStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate props) { final var scheduleRelationship = props.getScheduleRelationship(); var pickupType = toPickDrop(scheduleRelationship); @@ -50,7 +115,15 @@ static AddedStopTime ofStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate props) { dropOffType = PickDropMapper.map(dropOff.getNumber()); pickupType = PickDropMapper.map(pickup.getNumber()); } - return new AddedStopTime(pickupType, dropOffType); + return new AddedStopTime( + pickupType, + dropOffType, + props.hasStopId() ? props.getStopId() : null, + props.hasArrival() ? props.getArrival() : null, + props.hasDeparture() ? props.getDeparture() : null, + props.hasStopSequence() ? props.getStopSequence() : null, + scheduleRelationship + ); } private static PickDrop toPickDrop( diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 9069cb0879c..711aa10e783 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -531,12 +531,11 @@ private List checkNewStopTimeUpdatesAndFindStops( final List stops = new ArrayList<>(stopTimeUpdates.size()); for (int index = 0; index < stopTimeUpdates.size(); ++index) { - final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(index); + final var addedStopTime = AddedStopTime.ofStopTime(stopTimeUpdates.get(index)); // Check stop sequence - if (stopTimeUpdate.hasStopSequence()) { - final Integer stopSequence = stopTimeUpdate.getStopSequence(); - + final var stopSequence = addedStopTime.stopSequence; + if (stopSequence != null) { // Check non-negative if (stopSequence < 0) { debug(tripId, "Trip update contains negative stop sequence, skipping."); @@ -554,20 +553,17 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Find stops - if (stopTimeUpdate.hasStopId()) { + final var stopId = addedStopTime.stopId; + if (stopId != null) { // Find stop final var stop = transitEditorService.getRegularStop( - new FeedScopedId(tripId.getFeedId(), stopTimeUpdate.getStopId()) + new FeedScopedId(tripId.getFeedId(), stopId) ); if (stop != null) { // Remember stop stops.add(stop); } else { - debug( - tripId, - "Graph doesn't contain stop id '{}' of trip update, skipping.", - stopTimeUpdate.getStopId() - ); + debug(tripId, "Graph doesn't contain stop id '{}' of trip update, skipping.", stopId); return null; } } else { @@ -576,28 +572,28 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Check arrival time - if (stopTimeUpdate.hasArrival() && stopTimeUpdate.getArrival().hasTime()) { + final var arrival = addedStopTime.getArrivalTime(); + if (arrival != null) { // Check for increasing time - final Long time = stopTimeUpdate.getArrival().getTime(); - if (previousTime != null && previousTime > time) { + if (previousTime != null && previousTime > arrival) { debug(tripId, "Trip update contains decreasing times, skipping."); return null; } - previousTime = time; + previousTime = arrival; } else { debug(tripId, "Trip update misses arrival time, skipping."); return null; } // Check departure time - if (stopTimeUpdate.hasDeparture() && stopTimeUpdate.getDeparture().hasTime()) { + final var departure = addedStopTime.getDepartureTime(); + if (departure != null) { // Check for increasing time - final Long time = stopTimeUpdate.getDeparture().getTime(); - if (previousTime != null && previousTime > time) { + if (previousTime != null && previousTime > departure) { debug(tripId, "Trip update contains decreasing times, skipping."); return null; } - previousTime = time; + previousTime = departure; } else { debug(tripId, "Trip update misses departure time, skipping."); return null; @@ -772,7 +768,7 @@ private Result addTripToGraphAndBuffer( // Create StopTimes final List stopTimes = new ArrayList<>(stopTimeUpdates.size()); for (int index = 0; index < stopTimeUpdates.size(); ++index) { - final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(index); + final var added = AddedStopTime.ofStopTime(stopTimeUpdates.get(index)); final var stop = stops.get(index); // Create stop time @@ -780,12 +776,10 @@ private Result addTripToGraphAndBuffer( stopTime.setTrip(trip); stopTime.setStop(stop); // Set arrival time - if (stopTimeUpdate.hasArrival() && stopTimeUpdate.getArrival().hasTime()) { - final int delay = stopTimeUpdate.getArrival().hasDelay() - ? stopTimeUpdate.getArrival().getDelay() - : 0; - final long arrivalTime = - stopTimeUpdate.getArrival().getTime() - midnightSecondsSinceEpoch - delay; + final var arrivalSecondsSinceEpoch = added.getArrivalTime(); + if (arrivalSecondsSinceEpoch != null) { + final int delay = Objects.requireNonNullElse(added.getArrivalDelay(), 0); + final long arrivalTime = arrivalSecondsSinceEpoch - midnightSecondsSinceEpoch - delay; if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -797,12 +791,10 @@ private Result addTripToGraphAndBuffer( stopTime.setArrivalTime((int) arrivalTime); } // 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 - delay; + final var departureSecondsSinceEpoch = added.getDepartureTime(); + if (departureSecondsSinceEpoch != null) { + final int delay = Objects.requireNonNullElse(added.getDepartureDelay(), 0); + final long departureTime = departureSecondsSinceEpoch - midnightSecondsSinceEpoch - delay; if (departureTime < 0 || departureTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -814,10 +806,9 @@ private Result addTripToGraphAndBuffer( stopTime.setDepartureTime((int) departureTime); } stopTime.setTimepoint(1); // Exact time - if (stopTimeUpdate.hasStopSequence()) { - stopTime.setStopSequence(stopTimeUpdate.getStopSequence()); + if (added.stopSequence != null) { + stopTime.setStopSequence(added.stopSequence); } - var added = AddedStopTime.ofStopTime(stopTimeUpdate); stopTime.setPickupType(added.pickup()); stopTime.setDropOffType(added.dropOff()); // Add stop time to list @@ -847,21 +838,14 @@ private Result addTripToGraphAndBuffer( // Update all times to mark trip times as realtime // TODO: This is based on the proposal at https://github.com/google/transit/issues/490 for (int stopIndex = 0; stopIndex < newTripTimes.getNumStops(); stopIndex++) { - final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(stopIndex); + final var addedStopTime = AddedStopTime.ofStopTime(stopTimeUpdates.get(stopIndex)); - if ( - stopTimeUpdate.hasScheduleRelationship() && - stopTimeUpdate.getScheduleRelationship() == StopTimeUpdate.ScheduleRelationship.SKIPPED - ) { + if (addedStopTime.scheduleRelationship == 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; + final int arrivalDelay = Objects.requireNonNullElse(addedStopTime.getArrivalDelay(), 0); + final int departureDelay = Objects.requireNonNullElse(addedStopTime.getDepartureDelay(), 0); newTripTimes.updateArrivalTime( stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay From dddd6febd982b162082c8329233c0f13b042342f Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Sep 2024 11:46:45 +0100 Subject: [PATCH 07/23] store the original StopTimeUpdate in AddedStopTime --- .../updater/trip/AddedStopTime.java | 168 ++++++++---------- .../updater/trip/TimetableSnapshotSource.java | 57 +++--- 2 files changed, 109 insertions(+), 116 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index 093c8fbdf54..c1000b9c2ae 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -1,14 +1,10 @@ package org.opentripplanner.updater.trip; -import com.esotericsoftware.kryo.util.Null; import com.google.transit.realtime.GtfsRealtime; import de.mfdz.MfdzRealtimeExtensions; -import gnu.trove.set.hash.TIntHashSet; -import io.grpc.netty.shaded.io.netty.util.concurrent.ProgressivePromise; -import java.sql.Time; -import java.util.Objects; import java.util.Optional; -import java.util.concurrent.Future; +import java.util.OptionalInt; +import java.util.OptionalLong; import javax.annotation.Nullable; import org.opentripplanner.gtfs.mapping.PickDropMapper; import org.opentripplanner.model.PickDrop; @@ -19,113 +15,107 @@ */ final class AddedStopTime { - @Nullable - private final PickDrop pickup; + private final GtfsRealtime.TripUpdate.StopTimeUpdate stopTimeUpdate; - @Nullable - private final PickDrop dropOff; - - @Nullable - public final String stopId; + AddedStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate stopTimeUpdate) { + this.stopTimeUpdate = stopTimeUpdate; + } - @Nullable - private final GtfsRealtime.TripUpdate.StopTimeEvent arrival; + PickDrop pickup() { + return getPickDrop( + getStopTimePropertiesExtension() + .map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getPickupType) + .orElse(null) + ); + } - @Nullable - private final GtfsRealtime.TripUpdate.StopTimeEvent departure; + PickDrop dropOff() { + return getPickDrop( + getStopTimePropertiesExtension() + .map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getDropoffType) + .orElse(null) + ); + } - @Nullable - public final Integer stopSequence; + private PickDrop getPickDrop( + @Nullable MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType extensionDropOffPickup + ) { + if (isSkipped()) { + return PickDrop.CANCELLED; + } - public final GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship; + if (extensionDropOffPickup == null) { + return toPickDrop(stopTimeUpdate.getScheduleRelationship()); + } - public static final PickDrop DEFAULT_PICK_DROP = PickDrop.SCHEDULED; + return PickDropMapper.map(extensionDropOffPickup.getNumber()); + } - AddedStopTime( - @Nullable PickDrop pickup, - @Nullable PickDrop dropOff, - @Nullable String stopId, - @Nullable GtfsRealtime.TripUpdate.StopTimeEvent arrival, - @Nullable GtfsRealtime.TripUpdate.StopTimeEvent departure, - @Nullable Integer stopSequence, - @Nullable GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship - ) { - this.pickup = pickup; - this.dropOff = dropOff; - this.stopId = stopId; - this.arrival = arrival; - this.departure = departure; - this.stopSequence = stopSequence; - this.scheduleRelationship = scheduleRelationship; + private Optional getStopTimePropertiesExtension() { + return stopTimeUpdate + .getStopTimeProperties() + .hasExtension(MfdzRealtimeExtensions.stopTimeProperties) + ? Optional.of( + stopTimeUpdate + .getStopTimeProperties() + .getExtension(MfdzRealtimeExtensions.stopTimeProperties) + ) + : Optional.empty(); } - PickDrop pickup() { - return Objects.requireNonNullElse(pickup, DEFAULT_PICK_DROP); + OptionalLong getArrivalTime() { + return stopTimeUpdate.hasArrival() + ? getTime(stopTimeUpdate.getArrival()) + : OptionalLong.empty(); } - PickDrop dropOff() { - return Objects.requireNonNullElse(dropOff, DEFAULT_PICK_DROP); + OptionalLong getDepartureTime() { + return stopTimeUpdate.hasDeparture() + ? getTime(stopTimeUpdate.getDeparture()) + : OptionalLong.empty(); } - @Nullable - Long getArrivalTime() { - if (arrival == null) { - return null; - } - return arrival.hasTime() ? arrival.getTime() : null; + private OptionalLong getTime(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { + return stopTimeEvent.hasTime() + ? OptionalLong.of(stopTimeEvent.getTime()) + : OptionalLong.empty(); } - @Nullable - Long getDepartureTime() { - if (departure == null) { - return null; - } - return departure.hasTime() ? departure.getTime() : null; + OptionalInt getArrivalDelay() { + return stopTimeUpdate.hasArrival() + ? getDelay(stopTimeUpdate.getArrival()) + : OptionalInt.empty(); } - @Nullable - Integer getArrivalDelay() { - if (arrival == null) { - return null; - } - return arrival.hasDelay() ? arrival.getDelay() : null; + OptionalInt getDepartureDelay() { + return stopTimeUpdate.hasDeparture() + ? getDelay(stopTimeUpdate.getDeparture()) + : OptionalInt.empty(); } - @Nullable - Integer getDepartureDelay() { - if (departure == null) { - return null; - } - return departure.hasDelay() ? departure.getDelay() : null; + private OptionalInt getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { + return stopTimeEvent.hasDelay() + ? OptionalInt.of(stopTimeEvent.getDelay()) + : OptionalInt.empty(); } - static AddedStopTime ofStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate props) { - final var scheduleRelationship = props.getScheduleRelationship(); - var pickupType = toPickDrop(scheduleRelationship); - var dropOffType = pickupType; - if ( - scheduleRelationship != GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED && - props.getStopTimeProperties().hasExtension(MfdzRealtimeExtensions.stopTimeProperties) - ) { - var ext = props - .getStopTimeProperties() - .getExtension(MfdzRealtimeExtensions.stopTimeProperties); - var pickup = ext.getPickupType(); - var dropOff = ext.getDropoffType(); - dropOffType = PickDropMapper.map(dropOff.getNumber()); - pickupType = PickDropMapper.map(pickup.getNumber()); - } - return new AddedStopTime( - pickupType, - dropOffType, - props.hasStopId() ? props.getStopId() : null, - props.hasArrival() ? props.getArrival() : null, - props.hasDeparture() ? props.getDeparture() : null, - props.hasStopSequence() ? props.getStopSequence() : null, - scheduleRelationship + boolean isSkipped() { + return ( + stopTimeUpdate.getScheduleRelationship() == + GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED ); } + OptionalInt getStopSequence() { + return stopTimeUpdate.hasStopSequence() + ? OptionalInt.of(stopTimeUpdate.getStopSequence()) + : OptionalInt.empty(); + } + + Optional getStopId() { + return stopTimeUpdate.hasStopId() ? Optional.of(stopTimeUpdate.getStopId()) : Optional.empty(); + } + private static PickDrop toPickDrop( GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship ) { diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 711aa10e783..f34c504efe8 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -531,11 +531,13 @@ private List checkNewStopTimeUpdatesAndFindStops( final List stops = new ArrayList<>(stopTimeUpdates.size()); for (int index = 0; index < stopTimeUpdates.size(); ++index) { - final var addedStopTime = AddedStopTime.ofStopTime(stopTimeUpdates.get(index)); + final var addedStopTime = new AddedStopTime(stopTimeUpdates.get(index)); // Check stop sequence - final var stopSequence = addedStopTime.stopSequence; - if (stopSequence != null) { + final var optionalStopSequence = addedStopTime.getStopSequence(); + if (optionalStopSequence.isPresent()) { + final var stopSequence = optionalStopSequence.getAsInt(); + // Check non-negative if (stopSequence < 0) { debug(tripId, "Trip update contains negative stop sequence, skipping."); @@ -553,8 +555,9 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Find stops - final var stopId = addedStopTime.stopId; - if (stopId != null) { + final var optionalStopId = addedStopTime.getStopId(); + if (optionalStopId.isPresent()) { + final var stopId = optionalStopId.get(); // Find stop final var stop = transitEditorService.getRegularStop( new FeedScopedId(tripId.getFeedId(), stopId) @@ -573,13 +576,14 @@ private List checkNewStopTimeUpdatesAndFindStops( // Check arrival time final var arrival = addedStopTime.getArrivalTime(); - if (arrival != null) { + if (arrival.isPresent()) { + final var time = arrival.getAsLong(); // Check for increasing time - if (previousTime != null && previousTime > arrival) { + if (previousTime != null && previousTime > time) { debug(tripId, "Trip update contains decreasing times, skipping."); return null; } - previousTime = arrival; + previousTime = time; } else { debug(tripId, "Trip update misses arrival time, skipping."); return null; @@ -587,13 +591,14 @@ private List checkNewStopTimeUpdatesAndFindStops( // Check departure time final var departure = addedStopTime.getDepartureTime(); - if (departure != null) { + if (departure.isPresent()) { + final var time = departure.getAsLong(); // Check for increasing time - if (previousTime != null && previousTime > departure) { + if (previousTime != null && previousTime > time) { debug(tripId, "Trip update contains decreasing times, skipping."); return null; } - previousTime = departure; + previousTime = time; } else { debug(tripId, "Trip update misses departure time, skipping."); return null; @@ -768,7 +773,7 @@ private Result addTripToGraphAndBuffer( // Create StopTimes final List stopTimes = new ArrayList<>(stopTimeUpdates.size()); for (int index = 0; index < stopTimeUpdates.size(); ++index) { - final var added = AddedStopTime.ofStopTime(stopTimeUpdates.get(index)); + final var added = new AddedStopTime(stopTimeUpdates.get(index)); final var stop = stops.get(index); // Create stop time @@ -776,10 +781,10 @@ private Result addTripToGraphAndBuffer( stopTime.setTrip(trip); stopTime.setStop(stop); // Set arrival time - final var arrivalSecondsSinceEpoch = added.getArrivalTime(); - if (arrivalSecondsSinceEpoch != null) { - final int delay = Objects.requireNonNullElse(added.getArrivalDelay(), 0); - final long arrivalTime = arrivalSecondsSinceEpoch - midnightSecondsSinceEpoch - delay; + final var arrival = added.getArrivalTime(); + if (arrival.isPresent()) { + final var delay = added.getArrivalDelay().orElse(0); + final var arrivalTime = arrival.getAsLong() - midnightSecondsSinceEpoch - delay; if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -791,10 +796,10 @@ private Result addTripToGraphAndBuffer( stopTime.setArrivalTime((int) arrivalTime); } // Set departure time - final var departureSecondsSinceEpoch = added.getDepartureTime(); - if (departureSecondsSinceEpoch != null) { - final int delay = Objects.requireNonNullElse(added.getDepartureDelay(), 0); - final long departureTime = departureSecondsSinceEpoch - midnightSecondsSinceEpoch - delay; + final var departure = added.getDepartureTime(); + if (departure.isPresent()) { + final var delay = added.getDepartureDelay().orElse(0); + final long departureTime = departure.getAsLong() - midnightSecondsSinceEpoch - delay; if (departureTime < 0 || departureTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( trip.getId(), @@ -806,9 +811,7 @@ private Result addTripToGraphAndBuffer( stopTime.setDepartureTime((int) departureTime); } stopTime.setTimepoint(1); // Exact time - if (added.stopSequence != null) { - stopTime.setStopSequence(added.stopSequence); - } + added.getStopSequence().ifPresent(stopTime::setStopSequence); stopTime.setPickupType(added.pickup()); stopTime.setDropOffType(added.dropOff()); // Add stop time to list @@ -838,14 +841,14 @@ private Result addTripToGraphAndBuffer( // Update all times to mark trip times as realtime // TODO: This is based on the proposal at https://github.com/google/transit/issues/490 for (int stopIndex = 0; stopIndex < newTripTimes.getNumStops(); stopIndex++) { - final var addedStopTime = AddedStopTime.ofStopTime(stopTimeUpdates.get(stopIndex)); + final var addedStopTime = new AddedStopTime(stopTimeUpdates.get(stopIndex)); - if (addedStopTime.scheduleRelationship == StopTimeUpdate.ScheduleRelationship.SKIPPED) { + if (addedStopTime.isSkipped()) { newTripTimes.setCancelled(stopIndex); } - final int arrivalDelay = Objects.requireNonNullElse(addedStopTime.getArrivalDelay(), 0); - final int departureDelay = Objects.requireNonNullElse(addedStopTime.getDepartureDelay(), 0); + final int arrivalDelay = addedStopTime.getArrivalDelay().orElse(0); + final int departureDelay = addedStopTime.getDepartureDelay().orElse(0); newTripTimes.updateArrivalTime( stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay From f097243701671600c3205480a9e6e643dcae8beb Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 30 Sep 2024 11:22:13 +0100 Subject: [PATCH 08/23] fix test cases --- .../updater/trip/moduletests/addition/AddedTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 b752b055100..56028f8b0af 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 @@ -128,8 +128,8 @@ void repeatedlyAddedTripWithNewRoute() { @Test public void addedTripWithSkippedStop() { - var env = RealtimeTestEnvironment.gtfs(); - var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); + var env = RealtimeTestEnvironment.gtfs().build(); + var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE); builder .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) .addSkippedStop(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER) @@ -159,8 +159,8 @@ public void addedTripWithSkippedStop() { @Test public void addedTripWithDelay() { - var env = RealtimeTestEnvironment.gtfs(); - var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, env.timeZone); + var env = RealtimeTestEnvironment.gtfs().build(); + var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE); // A1: scheduled 08:30:00 // B1: scheduled 08:40:00, delay 300 seconds (actual 08:45:00) From 21688586551fe5a13e3dfd794e13361066c6cece Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 30 Sep 2024 14:21:46 +0100 Subject: [PATCH 09/23] consistency in getter naming --- .../updater/trip/AddedStopTime.java | 12 +++++----- .../updater/trip/TimetableSnapshotSource.java | 22 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index c1000b9c2ae..ed33c4df134 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -63,13 +63,13 @@ private Optional getStopTime : Optional.empty(); } - OptionalLong getArrivalTime() { + OptionalLong arrivalTime() { return stopTimeUpdate.hasArrival() ? getTime(stopTimeUpdate.getArrival()) : OptionalLong.empty(); } - OptionalLong getDepartureTime() { + OptionalLong departureTime() { return stopTimeUpdate.hasDeparture() ? getTime(stopTimeUpdate.getDeparture()) : OptionalLong.empty(); @@ -81,13 +81,13 @@ private OptionalLong getTime(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent : OptionalLong.empty(); } - OptionalInt getArrivalDelay() { + OptionalInt arrivalDelay() { return stopTimeUpdate.hasArrival() ? getDelay(stopTimeUpdate.getArrival()) : OptionalInt.empty(); } - OptionalInt getDepartureDelay() { + OptionalInt departureDelay() { return stopTimeUpdate.hasDeparture() ? getDelay(stopTimeUpdate.getDeparture()) : OptionalInt.empty(); @@ -106,13 +106,13 @@ boolean isSkipped() { ); } - OptionalInt getStopSequence() { + OptionalInt stopSequence() { return stopTimeUpdate.hasStopSequence() ? OptionalInt.of(stopTimeUpdate.getStopSequence()) : OptionalInt.empty(); } - Optional getStopId() { + Optional stopId() { return stopTimeUpdate.hasStopId() ? Optional.of(stopTimeUpdate.getStopId()) : Optional.empty(); } diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index ff67ba1973d..f25e62ad7b2 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -548,7 +548,7 @@ private List checkNewStopTimeUpdatesAndFindStops( final var addedStopTime = new AddedStopTime(stopTimeUpdates.get(index)); // Check stop sequence - final var optionalStopSequence = addedStopTime.getStopSequence(); + final var optionalStopSequence = addedStopTime.stopSequence(); if (optionalStopSequence.isPresent()) { final var stopSequence = optionalStopSequence.getAsInt(); @@ -569,7 +569,7 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Find stops - final var optionalStopId = addedStopTime.getStopId(); + final var optionalStopId = addedStopTime.stopId(); if (optionalStopId.isPresent()) { final var stopId = optionalStopId.get(); // Find stop @@ -589,7 +589,7 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Check arrival time - final var arrival = addedStopTime.getArrivalTime(); + final var arrival = addedStopTime.arrivalTime(); if (arrival.isPresent()) { final var time = arrival.getAsLong(); // Check for increasing time @@ -604,7 +604,7 @@ private List checkNewStopTimeUpdatesAndFindStops( } // Check departure time - final var departure = addedStopTime.getDepartureTime(); + final var departure = addedStopTime.departureTime(); if (departure.isPresent()) { final var time = departure.getAsLong(); // Check for increasing time @@ -795,9 +795,9 @@ private Result addTripToGraphAndBuffer( stopTime.setTrip(trip); stopTime.setStop(stop); // Set arrival time - final var arrival = added.getArrivalTime(); + final var arrival = added.arrivalTime(); if (arrival.isPresent()) { - final var delay = added.getArrivalDelay().orElse(0); + final var delay = added.arrivalDelay().orElse(0); final var arrivalTime = arrival.getAsLong() - midnightSecondsSinceEpoch - delay; if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( @@ -810,9 +810,9 @@ private Result addTripToGraphAndBuffer( stopTime.setArrivalTime((int) arrivalTime); } // Set departure time - final var departure = added.getDepartureTime(); + final var departure = added.departureTime(); if (departure.isPresent()) { - final var delay = added.getDepartureDelay().orElse(0); + final var delay = added.departureDelay().orElse(0); final long departureTime = departure.getAsLong() - midnightSecondsSinceEpoch - delay; if (departureTime < 0 || departureTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( @@ -825,7 +825,7 @@ private Result addTripToGraphAndBuffer( stopTime.setDepartureTime((int) departureTime); } stopTime.setTimepoint(1); // Exact time - added.getStopSequence().ifPresent(stopTime::setStopSequence); + added.stopSequence().ifPresent(stopTime::setStopSequence); stopTime.setPickupType(added.pickup()); stopTime.setDropOffType(added.dropOff()); // Add stop time to list @@ -861,8 +861,8 @@ private Result addTripToGraphAndBuffer( newTripTimes.setCancelled(stopIndex); } - final int arrivalDelay = addedStopTime.getArrivalDelay().orElse(0); - final int departureDelay = addedStopTime.getDepartureDelay().orElse(0); + final int arrivalDelay = addedStopTime.arrivalDelay().orElse(0); + final int departureDelay = addedStopTime.departureDelay().orElse(0); newTripTimes.updateArrivalTime( stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay From 86bc0e51de45c926ec65b8c18b92082779f71cf6 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 30 Sep 2024 14:25:25 +0100 Subject: [PATCH 10/23] assume that the delay is 0 when it is missing --- .../updater/trip/AddedStopTime.java | 14 +++++++------- .../updater/trip/TimetableSnapshotSource.java | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index ed33c4df134..cb190a6bb12 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -81,22 +81,22 @@ private OptionalLong getTime(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent : OptionalLong.empty(); } - OptionalInt arrivalDelay() { + int arrivalDelay() { return stopTimeUpdate.hasArrival() ? getDelay(stopTimeUpdate.getArrival()) - : OptionalInt.empty(); + : 0; } - OptionalInt departureDelay() { + int departureDelay() { return stopTimeUpdate.hasDeparture() ? getDelay(stopTimeUpdate.getDeparture()) - : OptionalInt.empty(); + : 0; } - private OptionalInt getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { + private int getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { return stopTimeEvent.hasDelay() - ? OptionalInt.of(stopTimeEvent.getDelay()) - : OptionalInt.empty(); + ? stopTimeEvent.getDelay() + : 0; } boolean isSkipped() { diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index f25e62ad7b2..12c20bf5015 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -797,7 +797,7 @@ private Result addTripToGraphAndBuffer( // Set arrival time final var arrival = added.arrivalTime(); if (arrival.isPresent()) { - final var delay = added.arrivalDelay().orElse(0); + final var delay = added.arrivalDelay(); final var arrivalTime = arrival.getAsLong() - midnightSecondsSinceEpoch - delay; if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( @@ -812,7 +812,7 @@ private Result addTripToGraphAndBuffer( // Set departure time final var departure = added.departureTime(); if (departure.isPresent()) { - final var delay = added.departureDelay().orElse(0); + final var delay = added.departureDelay(); final long departureTime = departure.getAsLong() - midnightSecondsSinceEpoch - delay; if (departureTime < 0 || departureTime > MAX_ARRIVAL_DEPARTURE_TIME) { debug( @@ -861,8 +861,8 @@ private Result addTripToGraphAndBuffer( newTripTimes.setCancelled(stopIndex); } - final int arrivalDelay = addedStopTime.arrivalDelay().orElse(0); - final int departureDelay = addedStopTime.departureDelay().orElse(0); + final int arrivalDelay = addedStopTime.arrivalDelay(); + final int departureDelay = addedStopTime.departureDelay(); newTripTimes.updateArrivalTime( stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay From 8c72fba30a0f90b6e9ef75e933a667946676cb50 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 1 Oct 2024 19:55:23 +0100 Subject: [PATCH 11/23] formatting --- .../opentripplanner/updater/trip/AddedStopTime.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index cb190a6bb12..f02956c7779 100644 --- a/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -82,21 +82,15 @@ private OptionalLong getTime(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent } int arrivalDelay() { - return stopTimeUpdate.hasArrival() - ? getDelay(stopTimeUpdate.getArrival()) - : 0; + return stopTimeUpdate.hasArrival() ? getDelay(stopTimeUpdate.getArrival()) : 0; } int departureDelay() { - return stopTimeUpdate.hasDeparture() - ? getDelay(stopTimeUpdate.getDeparture()) - : 0; + return stopTimeUpdate.hasDeparture() ? getDelay(stopTimeUpdate.getDeparture()) : 0; } private int getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { - return stopTimeEvent.hasDelay() - ? stopTimeEvent.getDelay() - : 0; + return stopTimeEvent.hasDelay() ? stopTimeEvent.getDelay() : 0; } boolean isSkipped() { From b5c16aae2e9bfda445eb7d786394876f6853eeaf Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 15:31:00 +0000 Subject: [PATCH 12/23] update proto file --- .../src/main/proto/gtfs-realtime.proto | 256 ++++++++++++++++-- 1 file changed, 230 insertions(+), 26 deletions(-) diff --git a/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto b/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto index 6c3e11ee53f..5d2adaa767d 100644 --- a/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto +++ b/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto @@ -106,6 +106,8 @@ message FeedEntity { // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. optional Shape shape = 6; + optional Stop stop = 7; + optional TripModifications trip_modifications = 8; // The extensions namespace allows 3rd-party developers to extend the // GTFS Realtime Specification in order to add and evaluate new features and @@ -188,6 +190,11 @@ message TripUpdate { // To specify a completely certain prediction, set its uncertainty to 0. optional int32 uncertainty = 3; + // Scheduled time for an added or replacement trip. + // In Unix time (i.e., number of seconds since January 1st 1970 00:00:00 + // UTC). + optional int64 scheduled_time = 4; + // The extensions namespace allows 3rd-party developers to extend the // GTFS Realtime Specification in order to add and evaluate new features // and modifications to the spec. @@ -216,7 +223,7 @@ message TripUpdate { // Expected occupancy after departure from the given stop. // Should be provided only for future stops. // In order to provide departure_occupancy_status without either arrival or - // departure StopTimeEvents, ScheduleRelationship should be set to NO_DATA. + // departure StopTimeEvents, ScheduleRelationship should be set to NO_DATA. optional VehiclePosition.OccupancyStatus departure_occupancy_status = 7; // The relation between the StopTimeEvents and the static schedule. @@ -270,6 +277,15 @@ message TripUpdate { // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. optional string assigned_stop_id = 1; + // The updated headsign of the vehicle at the stop. + optional string stop_headsign = 2; + + // The updated pickup of the vehicle at the stop. + optional DropOffPickupType pickup_type = 3; + + // The updated drop off of the vehicle at the stop. + optional DropOffPickupType drop_off_type = 4; + // The extensions namespace allows 3rd-party developers to extend the // GTFS Realtime Specification in order to add and evaluate new features // and modifications to the spec. @@ -277,6 +293,20 @@ message TripUpdate { // The following extension IDs are reserved for private use by any organization. extensions 9000 to 9999; + + enum DropOffPickupType { + // Regularly scheduled pickup/dropoff. + REGULAR = 0; + + // No pickup/dropoff available + NONE = 1; + + // Must phone agency to arrange pickup/dropoff. + PHONE_AGENCY = 2; + + // Must coordinate with driver to arrange pickup/dropoff. + COORDINATE_WITH_DRIVER = 3; + } } // Realtime updates for certain properties defined within GTFS stop_times.txt @@ -340,7 +370,7 @@ message TripUpdate { optional int32 delay = 5; // Defines updated properties of the trip, such as a new shape_id when there is a detour. Or defines the - // trip_id, start_date, and start_time of a DUPLICATED trip. + // trip_id, start_date, and start_time of a DUPLICATED trip. // NOTE: This message is still experimental, and subject to change. It may be formally adopted in the future. message TripProperties { // Defines the identifier of a new trip that is a duplicate of an existing trip defined in (CSV) GTFS trips.txt @@ -374,9 +404,17 @@ message TripUpdate { // or a Shape in the (protobuf) real-time feed. The order of stops (stop sequences) for this trip must remain the same as // (CSV) GTFS. Stops that are a part of the original trip but will no longer be made, such as when a detour occurs, should // be marked as schedule_relationship=SKIPPED. - // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. optional string shape_id = 4; + // Specifies the headsign for this trip when it differs from the original. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + optional string trip_headsign = 5; + + // Specifies the name for this trip when it differs from the original. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + optional string trip_short_name = 6; + // The extensions namespace allows 3rd-party developers to extend the // GTFS Realtime Specification in order to add and evaluate new features // and modifications to the spec. @@ -450,7 +488,7 @@ message VehiclePosition { // The state of passenger occupancy for the vehicle or carriage. // Individual producers may not publish all OccupancyStatus values. Therefore, consumers // must not assume that the OccupancyStatus values follow a linear scale. - // Consumers should represent OccupancyStatus values as the state indicated + // Consumers should represent OccupancyStatus values as the state indicated // and intended by the producer. Likewise, producers must use OccupancyStatus values that // correspond to actual vehicle occupancy states. // For describing passenger occupancy levels on a linear scale, see `occupancy_percentage`. @@ -504,7 +542,7 @@ message VehiclePosition { // including both seated and standing capacity, and current operating regulations allow. // The value may exceed 100 if there are more passengers than the maximum designed capacity. // The precision of occupancy_percentage should be low enough that individual passengers cannot be tracked boarding or alighting the vehicle. - // If multi_carriage_status is populated with per-carriage occupancy_percentage, + // If multi_carriage_status is populated with per-carriage occupancy_percentage, // then this field should describe the entire vehicle with all carriages accepting passengers considered. // This field is still experimental, and subject to change. It may be formally adopted in the future. optional uint32 occupancy_percentage = 10; @@ -539,7 +577,7 @@ message VehiclePosition { // For example, the first carriage in the direction of travel has a value of 1. // If the second carriage in the direction of travel has a value of 3, // consumers will discard data for all carriages (i.e., the multi_carriage_details field). - // Carriages without data must be represented with a valid carriage_sequence number and the fields + // Carriages without data must be represented with a valid carriage_sequence number and the fields // without data should be omitted (alternately, those fields could also be included and set to the "no data" values). // This message/field is still experimental, and subject to change. It may be formally adopted in the future. optional uint32 carriage_sequence = 5; @@ -554,12 +592,12 @@ message VehiclePosition { } // Details of the multiple carriages of this given vehicle. - // The first occurrence represents the first carriage of the vehicle, - // given the current direction of travel. - // The number of occurrences of the multi_carriage_details + // The first occurrence represents the first carriage of the vehicle, + // given the current direction of travel. + // The number of occurrences of the multi_carriage_details // field represents the number of carriages of the vehicle. - // It also includes non boardable carriages, - // like engines, maintenance carriages, etc… as they provide valuable + // It also includes non boardable carriages, + // like engines, maintenance carriages, etc… as they provide valuable // information to passengers about where to stand on a platform. // This message/field is still experimental, and subject to change. It may be formally adopted in the future. repeated CarriageDetails multi_carriage_details = 11; @@ -648,7 +686,7 @@ message Alert { optional SeverityLevel severity_level = 14 [default = UNKNOWN_SEVERITY]; // TranslatedImage to be displayed along the alert text. Used to explain visually the alert effect of a detour, station closure, etc. The image must enhance the understanding of the alert. Any essential information communicated within the image must also be contained in the alert text. - // The following types of images are discouraged : image containing mainly text, marketing or branded images that add no additional information. + // The following types of images are discouraged : image containing mainly text, marketing or branded images that add no additional information. // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. optional TranslatedImage image = 15; @@ -791,13 +829,7 @@ message TripDescriptor { // enough to the scheduled trip to be associated with it. SCHEDULED = 0; - // An extra trip that was added in addition to a running schedule, for - // example, to replace a broken vehicle or to respond to sudden passenger - // load. - // NOTE: Currently, behavior is unspecified for feeds that use this mode. There are discussions on the GTFS GitHub - // [(1)](https://github.com/google/transit/issues/106) [(2)](https://github.com/google/transit/pull/221) - // [(3)](https://github.com/google/transit/pull/219) around fully specifying or deprecating ADDED trips and the - // documentation will be updated when those discussions are finalized. + // An extra trip unrelated to any existing trips, for example, to respond to sudden passenger load. ADDED = 1; // A trip that is running with no schedule associated to it (GTFS frequencies.txt exact_times=0). @@ -807,8 +839,8 @@ message TripDescriptor { // A trip that existed in the schedule but was removed. CANCELED = 3; - // Should not be used - for backwards-compatibility only. - REPLACEMENT = 5 [deprecated = true]; + // A trip that replaces an existing trip in the schedule. + REPLACEMENT = 5; // An extra trip that was added in addition to a running schedule, for example, to replace a broken vehicle or to // respond to sudden passenger load. Used with TripUpdate.TripProperties.trip_id, TripUpdate.TripProperties.start_date, @@ -816,8 +848,9 @@ message TripDescriptor { // date and/or time. Duplicating a trip is allowed if the service related to the original trip in (CSV) GTFS // (in calendar.txt or calendar_dates.txt) is operating within the next 30 days. The trip to be duplicated is // identified via TripUpdate.TripDescriptor.trip_id. This enumeration does not modify the existing trip referenced by - // TripUpdate.TripDescriptor.trip_id - if a producer wants to cancel the original trip, it must publish a separate - // TripUpdate with the value of CANCELED or DELETED. Trips defined in GTFS frequencies.txt with exact_times that is + // TripUpdate.TripDescriptor.trip_id - if a producer wants to replace the original trip, a value of `REPLACEMENT` should be used instead. + // + // Trips defined in GTFS frequencies.txt with exact_times that is // empty or equal to 0 cannot be duplicated. The VehiclePosition.TripDescriptor.trip_id for the new trip must contain // the matching value from TripUpdate.TripProperties.trip_id and VehiclePosition.TripDescriptor.ScheduleRelationship // must also be set to DUPLICATED. @@ -840,6 +873,32 @@ message TripDescriptor { } optional ScheduleRelationship schedule_relationship = 4; + message ModifiedTripSelector { + // The 'id' from the FeedEntity in which the contained TripModifications object affects this trip. + optional string modifications_id = 1; + + // The trip_id from the GTFS feed that is modified by the modifications_id + optional string affected_trip_id = 2; + + // The initially scheduled start time of this trip instance, applied to the frequency based modified trip. Same definition as start_time in TripDescriptor. + optional string start_time = 3; + + // The start date of this trip instance in YYYYMMDD format, applied to the modified trip. Same definition as start_date in TripDescriptor. + optional string start_date = 4; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; + } + + // Linkage to any modifications done to this trip (shape changes, removal or addition of stops). + // If this field is provided, the `trip_id`, `route_id`, `direction_id`, `start_time`, `start_date` fields of the `TripDescriptor` MUST be left empty, to avoid confusion by consumers that aren't looking for the `ModifiedTripSelector` value. + optional ModifiedTripSelector modified_trip = 7; + // The extensions namespace allows 3rd-party developers to extend the // GTFS Realtime Specification in order to add and evaluate new features and // modifications to the spec. @@ -970,12 +1029,12 @@ message TranslatedString { message TranslatedImage { message LocalizedImage { // String containing an URL linking to an image - // The image linked must be less than 2MB. + // The image linked must be less than 2MB. // If an image changes in a significant enough way that an update is required on the consumer side, the producer must update the URL to a new one. - // The URL should be a fully qualified URL that includes http:// or https://, and any special characters in the URL must be correctly escaped. See the following http://www.w3.org/Addressing/URL/4_URI_Recommentations.html for a description of how to create fully qualified URL values. + // The URL should be a fully qualified URL that includes http:// or https://, and any special characters in the URL must be correctly escaped. See the following http://www.w3.org/Addressing/URL/4_URI_Recommentations.html for a description of how to create fully qualified URL values. required string url = 1; - // IANA media type as to specify the type of image to be displayed. + // IANA media type as to specify the type of image to be displayed. // The type must start with "image/" required string media_type = 2; @@ -1033,3 +1092,148 @@ message Shape { // The following extension IDs are reserved for private use by any organization. extensions 9000 to 9999; } + +// Describes a stop which is served by trips. All fields are as described in the GTFS-Static specification. +// NOTE: This message is still experimental, and subject to change. It may be formally adopted in the future. +message Stop { + enum WheelchairBoarding { + UNKNOWN = 0; + AVAILABLE = 1; + NOT_AVAILABLE = 2; + } + + optional string stop_id = 1; + optional TranslatedString stop_code = 2; + optional TranslatedString stop_name = 3; + optional TranslatedString tts_stop_name = 4; + optional TranslatedString stop_desc = 5; + optional float stop_lat = 6; + optional float stop_lon = 7; + optional string zone_id = 8; + optional TranslatedString stop_url = 9; + optional string parent_station = 11; + optional string stop_timezone = 12; + optional WheelchairBoarding wheelchair_boarding = 13 [default = UNKNOWN]; + optional string level_id = 14; + optional TranslatedString platform_code = 15; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; +} + +// NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. +message TripModifications { + // A `Modification` message replaces a span of n stop times from each affected trip starting at `start_stop_selector`. + message Modification { + // The stop selector of the first stop_time of the original trip that is to be affected by this modification. + // Used in conjuction with `end_stop_selector`. + // `start_stop_selector` is required and is used to define the reference stop used with `travel_time_to_stop`. + optional StopSelector start_stop_selector = 1; + + // The stop selector of the last stop of the original trip that is to be affected by this modification. + // The selection is inclusive, so if only one stop_time is replaced by that modification, `start_stop_selector` and `end_stop_selector` must be equivalent. + // If no stop_time is replaced, `end_stop_selector` must not be provided. It's otherwise required. + optional StopSelector end_stop_selector = 2; + + // The number of seconds of delay to add to all departure and arrival times following the end of this modification. + // If multiple modifications apply to the same trip, the delays accumulate as the trip advances. + optional int32 propagated_modification_delay = 3 [default = 0]; + + // A list of replacement stops, replacing those of the original trip. + // The length of the new stop times may be less, the same, or greater than the number of replaced stop times. + repeated ReplacementStop replacement_stops = 4; + + // An `id` value from the `FeedEntity` message that contains the `Alert` describing this Modification for user-facing communication. + optional string service_alert_id = 5; + + // This timestamp identifies the moment when the modification has last been changed. + // In POSIX time (i.e., number of seconds since January 1st 1970 00:00:00 UTC). + optional uint64 last_modified_time = 6; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; + } + + message SelectedTrips { + // A list of trips affected with this replacement that all have the same new `shape_id`. A `TripUpdate` with `schedule_relationship=REPLACEMENT` must not already exist for the trip. + repeated string trip_ids = 1; + // The ID of the new shape for the modified trips in this SelectedTrips. + // May refer to a new shape added using a GTFS-RT Shape message, or to an existing shape defined in the GTFS-Static feed’s shapes.txt. + optional string shape_id = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; + } + + // A list of selected trips affected by this TripModifications. + repeated SelectedTrips selected_trips = 1; + + // A list of start times in the real-time trip descriptor for the trip_id defined in trip_ids. + // Useful to target multiple departures of a trip_id in a frequency-based trip. + repeated string start_times = 2; + + // Dates on which the modifications occurs, in the YYYYMMDD format. Producers SHOULD only transmit detours occurring within the next week. + // The dates provided should not be used as user-facing information, if a user-facing start and end date needs to be provided, they can be provided in the linked service alert with `service_alert_id` + repeated string service_dates = 3; + + // A list of modifications to apply to the affected trips. + repeated Modification modifications = 4; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; +} + +// NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. +// Select a stop by stop sequence or by stop_id. At least one of the two values must be provided. +message StopSelector { + // Must be the same as in stop_times.txt in the corresponding GTFS feed. + optional uint32 stop_sequence = 1; + // Must be the same as in stops.txt in the corresponding GTFS feed. + optional string stop_id = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; +} + +// NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. +message ReplacementStop { + // The difference in seconds between the arrival time at this stop and the arrival time at the reference stop. The reference stop is the stop prior to start_stop_selector. If the modification begins at the first stop of the trip, then the first stop of the trip is the reference stop. + // This value MUST be monotonically increasing and may only be a negative number if the first stop of the original trip is the reference stop. + optional int32 travel_time_to_stop = 1; + + // The replacement stop ID which will now be visited by the trip. May refer to a new stop added using a GTFS-RT Stop message, or to an existing stop defined in the GTFS-Static feed’s stops.txt. The stop MUST have location_type=0 (routable stops). + optional string stop_id = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; +} \ No newline at end of file From fe83e7d819416ddd3d520162283677d32325b6b5 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 16:01:38 +0000 Subject: [PATCH 13/23] process pickup / drop off in StopTimeProperties --- .../updater/trip/AddedStopTime.java | 51 ++++++++------- .../updater/trip/TripUpdateBuilder.java | 62 ++++++++++++++++--- .../trip/moduletests/addition/AddedTest.java | 8 ++- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index f02956c7779..a69f7b22476 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -23,46 +23,60 @@ final class AddedStopTime { PickDrop pickup() { return getPickDrop( + getStopTimeProperties() + .map(properties -> properties.hasPickupType() ? properties.getPickupType() : null) + .orElse(null), getStopTimePropertiesExtension() - .map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getPickupType) + .map(properties -> properties.hasPickupType() ? properties.getPickupType() : null) .orElse(null) ); } PickDrop dropOff() { return getPickDrop( + getStopTimeProperties() + .map(properties -> properties.hasDropOffType() ? properties.getDropOffType() : null) + .orElse(null), getStopTimePropertiesExtension() - .map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getDropoffType) + .map(properties -> properties.hasDropoffType() ? properties.getDropoffType() : null) .orElse(null) ); } private PickDrop getPickDrop( + @Nullable GtfsRealtime.TripUpdate.StopTimeUpdate.StopTimeProperties.DropOffPickupType dropOffPickupType, @Nullable MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType extensionDropOffPickup ) { if (isSkipped()) { return PickDrop.CANCELLED; } - if (extensionDropOffPickup == null) { - return toPickDrop(stopTimeUpdate.getScheduleRelationship()); + if (dropOffPickupType != null) { + return PickDropMapper.map(dropOffPickupType.getNumber()); } - return PickDropMapper.map(extensionDropOffPickup.getNumber()); + if (extensionDropOffPickup != null) { + return PickDropMapper.map(extensionDropOffPickup.getNumber()); + } + + return PickDrop.SCHEDULED; } - private Optional getStopTimePropertiesExtension() { - return stopTimeUpdate - .getStopTimeProperties() - .hasExtension(MfdzRealtimeExtensions.stopTimeProperties) - ? Optional.of( - stopTimeUpdate - .getStopTimeProperties() - .getExtension(MfdzRealtimeExtensions.stopTimeProperties) - ) + private Optional getStopTimeProperties() { + return stopTimeUpdate.hasStopTimeProperties() + ? Optional.of(stopTimeUpdate.getStopTimeProperties()) : Optional.empty(); } + private Optional getStopTimePropertiesExtension() { + return getStopTimeProperties() + .map(stopTimeProperties -> + stopTimeProperties.hasExtension(MfdzRealtimeExtensions.stopTimeProperties) + ? stopTimeProperties.getExtension(MfdzRealtimeExtensions.stopTimeProperties) + : null + ); + } + OptionalLong arrivalTime() { return stopTimeUpdate.hasArrival() ? getTime(stopTimeUpdate.getArrival()) @@ -109,13 +123,4 @@ OptionalInt stopSequence() { Optional stopId() { return stopTimeUpdate.hasStopId() ? Optional.of(stopTimeUpdate.getStopId()) : Optional.empty(); } - - private static PickDrop toPickDrop( - GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship scheduleRelationship - ) { - return switch (scheduleRelationship) { - case SCHEDULED, NO_DATA, UNSCHEDULED -> PickDrop.SCHEDULED; - case SKIPPED -> PickDrop.CANCELLED; - }; - } } diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index da879319f82..49ff51f9aae 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; +import javax.annotation.Nullable; import org.opentripplanner.utils.time.ServiceDateUtils; public class TripUpdateBuilder { @@ -45,6 +46,7 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes) { NO_DELAY, NO_DELAY, DEFAULT_SCHEDULE_RELATIONSHIP, + null, null ); } @@ -57,6 +59,7 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { delay, delay, DEFAULT_SCHEDULE_RELATIONSHIP, + null, null ); } @@ -69,12 +72,39 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy NO_DELAY, NO_DELAY, DEFAULT_SCHEDULE_RELATIONSHIP, + pickDrop, + null + ); + } + + public TripUpdateBuilder addStopTime( + String stopId, + int minutes, + StopTimeUpdate.StopTimeProperties.DropOffPickupType pickDrop + ) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + DEFAULT_SCHEDULE_RELATIONSHIP, + null, pickDrop ); } public TripUpdateBuilder addDelayedStopTime(int stopSequence, int delay) { - return addStopTime(null, -1, stopSequence, delay, delay, DEFAULT_SCHEDULE_RELATIONSHIP, null); + return addStopTime( + null, + -1, + stopSequence, + delay, + delay, + DEFAULT_SCHEDULE_RELATIONSHIP, + null, + null + ); } public TripUpdateBuilder addDelayedStopTime( @@ -89,6 +119,7 @@ public TripUpdateBuilder addDelayedStopTime( arrivalDelay, departureDelay, DEFAULT_SCHEDULE_RELATIONSHIP, + null, null ); } @@ -104,6 +135,7 @@ public TripUpdateBuilder addNoDataStop(int stopSequence) { NO_DELAY, NO_DELAY, StopTimeUpdate.ScheduleRelationship.NO_DATA, + null, null ); } @@ -119,6 +151,7 @@ public TripUpdateBuilder addSkippedStop(int stopSequence) { NO_DELAY, NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, + null, null ); } @@ -131,6 +164,7 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { NO_DELAY, NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, + null, null ); } @@ -143,7 +177,8 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes, DropOffPicku NO_DELAY, NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, - pickDrop + pickDrop, + null ); } @@ -157,13 +192,14 @@ public TripUpdateBuilder addRawStopTime(StopTimeUpdate stopTime) { } private TripUpdateBuilder addStopTime( - String stopId, + @Nullable String stopId, int minutes, int stopSequence, int arrivalDelay, int departureDelay, StopTimeUpdate.ScheduleRelationship scheduleRelationShip, - DropOffPickupType pickDrop + @Nullable DropOffPickupType pickDrop, + @Nullable StopTimeUpdate.StopTimeProperties.DropOffPickupType gtfsPickDrop ) { final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); stopTimeUpdateBuilder.setScheduleRelationship(scheduleRelationShip); @@ -176,14 +212,20 @@ private TripUpdateBuilder addStopTime( stopTimeUpdateBuilder.setStopSequence(stopSequence); } - if (pickDrop != null) { + if (pickDrop != null || gtfsPickDrop != null) { var stopTimePropsBuilder = stopTimeUpdateBuilder.getStopTimePropertiesBuilder(); - var b = MfdzRealtimeExtensions.StopTimePropertiesExtension.newBuilder(); - b.setDropoffType(pickDrop); - b.setPickupType(pickDrop); - var ext = b.build(); - stopTimePropsBuilder.setExtension(MfdzRealtimeExtensions.stopTimeProperties, ext); + if (gtfsPickDrop != null) { + stopTimePropsBuilder.setDropOffType(gtfsPickDrop); + stopTimePropsBuilder.setPickupType(gtfsPickDrop); + } + if (pickDrop != null) { + var b = MfdzRealtimeExtensions.StopTimePropertiesExtension.newBuilder(); + b.setDropoffType(pickDrop); + b.setPickupType(pickDrop); + var ext = b.build(); + stopTimePropsBuilder.setExtension(MfdzRealtimeExtensions.stopTimeProperties, ext); + } } final GtfsRealtime.TripUpdate.StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 77ee4acc630..02cd0d61734 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -1,7 +1,6 @@ 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; @@ -10,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; +import com.google.transit.realtime.GtfsRealtime; import de.mfdz.MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType; import java.util.List; import org.junit.jupiter.api.Test; @@ -49,7 +49,11 @@ void addedTripWithNewRoute() { var tripUpdate = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE) .addTripExtension() .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) - .addStopTime(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER) + .addStopTime( + STOP_B1_ID, + 40, + GtfsRealtime.TripUpdate.StopTimeUpdate.StopTimeProperties.DropOffPickupType.COORDINATE_WITH_DRIVER + ) .addStopTime(STOP_B1_ID, 55, DropOffPickupType.NONE) .build(); From cafa318d91f60b9a28067975bcbd4ca4da671c06 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 16:14:52 +0000 Subject: [PATCH 14/23] process stop headsign --- .../updater/trip/AddedStopTime.java | 9 +++++ .../updater/trip/TimetableSnapshotSource.java | 3 ++ .../updater/trip/TripUpdateBuilder.java | 35 +++++++++++++++++-- .../trip/moduletests/addition/AddedTest.java | 5 ++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index a69f7b22476..77710a3ab8d 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -123,4 +123,13 @@ OptionalInt stopSequence() { Optional stopId() { return stopTimeUpdate.hasStopId() ? Optional.of(stopTimeUpdate.getStopId()) : Optional.empty(); } + + Optional stopHeadsign() { + return ( + stopTimeUpdate.hasStopTimeProperties() && + stopTimeUpdate.getStopTimeProperties().hasStopHeadsign() + ) + ? Optional.of(stopTimeUpdate.getStopTimeProperties().getStopHeadsign()) + : Optional.empty(); + } } diff --git a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index b35ccebccea..d8c547bb4c8 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -878,6 +878,9 @@ private Result addTripToGraphAndBuffer( added.stopSequence().ifPresent(stopTime::setStopSequence); stopTime.setPickupType(added.pickup()); stopTime.setDropOffType(added.dropOff()); + added + .stopHeadsign() + .ifPresent(headsign -> stopTime.setStopHeadsign(new NonLocalizedString(headsign))); // Add stop time to list stopTimes.add(stopTime); } diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index 49ff51f9aae..5f44cfa0f87 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -47,10 +47,25 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes) { NO_DELAY, DEFAULT_SCHEDULE_RELATIONSHIP, null, + null, null ); } + public TripUpdateBuilder addStopTime(String stopId, int minutes, String headsign) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + DEFAULT_SCHEDULE_RELATIONSHIP, + null, + null, + headsign + ); + } + public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { return addStopTime( stopId, @@ -60,6 +75,7 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { delay, DEFAULT_SCHEDULE_RELATIONSHIP, null, + null, null ); } @@ -73,6 +89,7 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy NO_DELAY, DEFAULT_SCHEDULE_RELATIONSHIP, pickDrop, + null, null ); } @@ -90,7 +107,8 @@ public TripUpdateBuilder addStopTime( NO_DELAY, DEFAULT_SCHEDULE_RELATIONSHIP, null, - pickDrop + pickDrop, + null ); } @@ -103,6 +121,7 @@ public TripUpdateBuilder addDelayedStopTime(int stopSequence, int delay) { delay, DEFAULT_SCHEDULE_RELATIONSHIP, null, + null, null ); } @@ -120,6 +139,7 @@ public TripUpdateBuilder addDelayedStopTime( departureDelay, DEFAULT_SCHEDULE_RELATIONSHIP, null, + null, null ); } @@ -136,6 +156,7 @@ public TripUpdateBuilder addNoDataStop(int stopSequence) { NO_DELAY, StopTimeUpdate.ScheduleRelationship.NO_DATA, null, + null, null ); } @@ -152,6 +173,7 @@ public TripUpdateBuilder addSkippedStop(int stopSequence) { NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, null, + null, null ); } @@ -165,6 +187,7 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, null, + null, null ); } @@ -178,6 +201,7 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes, DropOffPicku NO_DELAY, StopTimeUpdate.ScheduleRelationship.SKIPPED, pickDrop, + null, null ); } @@ -199,7 +223,8 @@ private TripUpdateBuilder addStopTime( int departureDelay, StopTimeUpdate.ScheduleRelationship scheduleRelationShip, @Nullable DropOffPickupType pickDrop, - @Nullable StopTimeUpdate.StopTimeProperties.DropOffPickupType gtfsPickDrop + @Nullable StopTimeUpdate.StopTimeProperties.DropOffPickupType gtfsPickDrop, + @Nullable String headsign ) { final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); stopTimeUpdateBuilder.setScheduleRelationship(scheduleRelationShip); @@ -212,9 +237,13 @@ private TripUpdateBuilder addStopTime( stopTimeUpdateBuilder.setStopSequence(stopSequence); } - if (pickDrop != null || gtfsPickDrop != null) { + if (pickDrop != null || gtfsPickDrop != null || headsign != null) { var stopTimePropsBuilder = stopTimeUpdateBuilder.getStopTimePropertiesBuilder(); + if (headsign != null) { + stopTimePropsBuilder.setStopHeadsign(headsign); + } + if (gtfsPickDrop != null) { stopTimePropsBuilder.setDropOffType(gtfsPickDrop); stopTimePropsBuilder.setPickupType(gtfsPickDrop); diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 02cd0d61734..50aff774169 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -13,6 +13,7 @@ import de.mfdz.MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.model.PickDrop; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; @@ -138,7 +139,8 @@ public void addedTripWithSkippedStop() { .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) .addSkippedStop(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER) .addSkippedStop(STOP_C1_ID, 48) - .addStopTime(STOP_D1_ID, 55); + .addStopTime(STOP_D1_ID, 55, "A (non-stop)") + .addStopTime(STOP_A1_ID, 60); var tripUpdate = builder.build(); env.applyTripUpdate(tripUpdate); @@ -159,6 +161,7 @@ public void addedTripWithSkippedStop() { assertTrue(tripTimes.isCancelledStop(1)); assertTrue(tripTimes.isCancelledStop(2)); assertFalse(tripTimes.isCancelledStop(3)); + assertEquals(new NonLocalizedString("A (non-stop)"), tripTimes.getHeadsign(3)); } @Test From 5236c7eaf54a83e5482cd9c1c7eda658d31d8ae6 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 16:26:50 +0000 Subject: [PATCH 15/23] process trip headsign for added trips --- .../updater/trip/TimetableSnapshotSource.java | 8 ++++++++ .../updater/trip/TripUpdateBuilder.java | 13 +++++++++++++ .../trip/moduletests/addition/AddedTest.java | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index d8c547bb4c8..f36dea53ea4 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -726,6 +726,14 @@ private Result handleAddedTrip( // Just use first service id of set tripBuilder.withServiceId(serviceIds.iterator().next()); } + + if (tripUpdate.hasTripProperties()) { + var tripProperties = tripUpdate.getTripProperties(); + if (tripProperties.hasTripHeadsign()) { + tripBuilder.withHeadsign(new NonLocalizedString(tripProperties.getTripHeadsign())); + } + } + return addTripToGraphAndBuffer( tripBuilder.build(), tripUpdate.getVehicle(), diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index 5f44cfa0f87..06fca701119 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -38,6 +38,19 @@ public TripUpdateBuilder( this.midnight = ServiceDateUtils.asStartOfService(serviceDate, zoneId); } + public TripUpdateBuilder( + String tripId, + LocalDate serviceDate, + GtfsRealtime.TripDescriptor.ScheduleRelationship scheduleRelationship, + ZoneId zoneId, + String tripHeadsign + ) { + this(tripId, serviceDate, scheduleRelationship, zoneId); + tripUpdateBuilder.setTripProperties( + GtfsRealtime.TripUpdate.TripProperties.newBuilder().setTripHeadsign(tripHeadsign).build() + ); + } + public TripUpdateBuilder addStopTime(String stopId, int minutes) { return addStopTime( stopId, diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 50aff774169..8685a571bb0 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -134,7 +134,7 @@ void repeatedlyAddedTripWithNewRoute() { @Test public void addedTripWithSkippedStop() { var env = RealtimeTestEnvironment.gtfs().build(); - var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE); + var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE, "A loop"); builder .addStopTime(STOP_A1_ID, 30, DropOffPickupType.PHONE_AGENCY) .addSkippedStop(STOP_B1_ID, 40, DropOffPickupType.COORDINATE_WITH_DRIVER) @@ -157,6 +157,7 @@ public void addedTripWithSkippedStop() { var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); var forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); + assertEquals(new NonLocalizedString("A loop"), tripTimes.getHeadsign(0)); assertFalse(tripTimes.isCancelledStop(0)); assertTrue(tripTimes.isCancelledStop(1)); assertTrue(tripTimes.isCancelledStop(2)); From 18adc11f3730ddb89f8a04337a19850b332166af Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 16:42:32 +0000 Subject: [PATCH 16/23] process scheduled time in added trips --- .../updater/trip/AddedStopTime.java | 6 +- .../updater/trip/TripUpdateBuilder.java | 63 +++++++++++++++---- .../trip/moduletests/addition/AddedTest.java | 6 +- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java index 77710a3ab8d..58a87c434e2 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java @@ -104,7 +104,11 @@ int departureDelay() { } private int getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) { - return stopTimeEvent.hasDelay() ? stopTimeEvent.getDelay() : 0; + return stopTimeEvent.hasDelay() + ? stopTimeEvent.getDelay() + : stopTimeEvent.hasScheduledTime() + ? (int) (stopTimeEvent.getTime() - stopTimeEvent.getScheduledTime()) + : 0; } boolean isSkipped() { diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index 06fca701119..add0fedf91b 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -61,7 +61,8 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes) { DEFAULT_SCHEDULE_RELATIONSHIP, null, null, - null + null, + NO_VALUE ); } @@ -75,11 +76,12 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, String headsign DEFAULT_SCHEDULE_RELATIONSHIP, null, null, - headsign + headsign, + NO_VALUE ); } - public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { + public TripUpdateBuilder addStopTimeWithDelay(String stopId, int minutes, int delay) { return addStopTime( stopId, minutes, @@ -89,7 +91,27 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, int delay) { DEFAULT_SCHEDULE_RELATIONSHIP, null, null, - null + null, + NO_VALUE + ); + } + + public TripUpdateBuilder addStopTimeWithScheduled( + String stopId, + int minutes, + int scheduledMinutes + ) { + return addStopTime( + stopId, + minutes, + NO_VALUE, + NO_DELAY, + NO_DELAY, + DEFAULT_SCHEDULE_RELATIONSHIP, + null, + null, + null, + scheduledMinutes ); } @@ -103,7 +125,8 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy DEFAULT_SCHEDULE_RELATIONSHIP, pickDrop, null, - null + null, + NO_VALUE ); } @@ -121,7 +144,8 @@ public TripUpdateBuilder addStopTime( DEFAULT_SCHEDULE_RELATIONSHIP, null, pickDrop, - null + null, + NO_VALUE ); } @@ -135,7 +159,8 @@ public TripUpdateBuilder addDelayedStopTime(int stopSequence, int delay) { DEFAULT_SCHEDULE_RELATIONSHIP, null, null, - null + null, + NO_VALUE ); } @@ -153,7 +178,8 @@ public TripUpdateBuilder addDelayedStopTime( DEFAULT_SCHEDULE_RELATIONSHIP, null, null, - null + null, + NO_VALUE ); } @@ -170,7 +196,8 @@ public TripUpdateBuilder addNoDataStop(int stopSequence) { StopTimeUpdate.ScheduleRelationship.NO_DATA, null, null, - null + null, + NO_VALUE ); } @@ -187,7 +214,8 @@ public TripUpdateBuilder addSkippedStop(int stopSequence) { StopTimeUpdate.ScheduleRelationship.SKIPPED, null, null, - null + null, + NO_VALUE ); } @@ -201,7 +229,8 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { StopTimeUpdate.ScheduleRelationship.SKIPPED, null, null, - null + null, + NO_VALUE ); } @@ -215,7 +244,8 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes, DropOffPicku StopTimeUpdate.ScheduleRelationship.SKIPPED, pickDrop, null, - null + null, + NO_VALUE ); } @@ -237,7 +267,8 @@ private TripUpdateBuilder addStopTime( StopTimeUpdate.ScheduleRelationship scheduleRelationShip, @Nullable DropOffPickupType pickDrop, @Nullable StopTimeUpdate.StopTimeProperties.DropOffPickupType gtfsPickDrop, - @Nullable String headsign + @Nullable String headsign, + int scheduledMinutes ) { final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); stopTimeUpdateBuilder.setScheduleRelationship(scheduleRelationShip); @@ -279,6 +310,12 @@ private TripUpdateBuilder addStopTime( departureBuilder.setTime(epochSeconds); } + if (scheduledMinutes > NO_VALUE) { + var epochSeconds = midnight.plusHours(8).plusMinutes(scheduledMinutes).toEpochSecond(); + arrivalBuilder.setScheduledTime(epochSeconds); + departureBuilder.setScheduledTime(epochSeconds); + } + if (arrivalDelay != NO_DELAY) { arrivalBuilder.setDelay(arrivalDelay); } diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 8685a571bb0..ec01130e9bd 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -175,8 +175,8 @@ public void addedTripWithDelay() { // C1: scheduled 08:55:00 builder .addStopTime(STOP_A1_ID, 30) - .addStopTime(STOP_B1_ID, 45, 300) - .addStopTime(STOP_C1_ID, 55); + .addStopTimeWithDelay(STOP_B1_ID, 45, 300) + .addStopTimeWithScheduled(STOP_C1_ID, 55, 54); var tripUpdate = builder.build(); env.applyTripUpdate(tripUpdate); @@ -191,6 +191,8 @@ public void addedTripWithDelay() { assertEquals(30600, tripTimes.getDepartureTime(0)); // 08:30:00 assertEquals(300, tripTimes.getArrivalDelay(1)); assertEquals(31500, tripTimes.getArrivalTime(1)); // 08:45:00 + assertEquals(60, tripTimes.getArrivalDelay(2)); + assertEquals(32100, tripTimes.getArrivalTime(2)); // 08:55:00 } private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { From 513eca4d8c6f8eadb5c77f2ed27d28eba7f76c43 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 17:21:33 +0000 Subject: [PATCH 17/23] handle replacement trip headsign --- .../updater/trip/TimetableSnapshotSource.java | 37 ++++++++--- .../trip/RealtimeTestEnvironmentBuilder.java | 6 +- .../updater/trip/TripInput.java | 13 +++- .../trip/moduletests/addition/AddedTest.java | 26 +++++--- .../moduletests/addition/ReplacementTest.java | 61 +++++++++++++++++++ 5 files changed, 119 insertions(+), 24 deletions(-) create mode 100644 application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java diff --git a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index f36dea53ea4..1b8d6f7c58b 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -727,11 +727,9 @@ private Result handleAddedTrip( tripBuilder.withServiceId(serviceIds.iterator().next()); } - if (tripUpdate.hasTripProperties()) { - var tripProperties = tripUpdate.getTripProperties(); - if (tripProperties.hasTripHeadsign()) { - tripBuilder.withHeadsign(new NonLocalizedString(tripProperties.getTripHeadsign())); - } + var tripHeadsign = getTripHeadsign(tripUpdate); + if (tripHeadsign != null) { + tripBuilder.withHeadsign(new NonLocalizedString(tripHeadsign)); } return addTripToGraphAndBuffer( @@ -741,10 +739,22 @@ private Result handleAddedTrip( stops, serviceDate, RealTimeState.ADDED, - !routeExists + !routeExists, + tripHeadsign ); } + @Nullable + private static String getTripHeadsign(TripUpdate tripUpdate) { + if (tripUpdate.hasTripProperties()) { + var tripProperties = tripUpdate.getTripProperties(); + if (tripProperties.hasTripHeadsign()) { + return tripProperties.getTripHeadsign(); + } + } + return null; + } + private Route createRoute(TripDescriptor tripDescriptor, FeedScopedId tripId) { // the route in this update doesn't already exist, but the update contains the information so it will be created if ( @@ -826,7 +836,8 @@ private Result addTripToGraphAndBuffer( final List stops, final LocalDate serviceDate, final RealTimeState realTimeState, - final boolean isAddedRoute + final boolean isAddedRoute, + @Nullable final String tripHeadsign ) { // Preconditions Objects.requireNonNull(stops); @@ -888,7 +899,14 @@ private Result addTripToGraphAndBuffer( stopTime.setDropOffType(added.dropOff()); added .stopHeadsign() - .ifPresent(headsign -> stopTime.setStopHeadsign(new NonLocalizedString(headsign))); + .ifPresentOrElse( + headsign -> stopTime.setStopHeadsign(new NonLocalizedString(headsign)), + () -> { + if (tripHeadsign != null) { + stopTime.setStopHeadsign(new NonLocalizedString(tripHeadsign)); + } + } + ); // Add stop time to list stopTimes.add(stopTime); } @@ -1164,7 +1182,8 @@ private Result handleModifiedTrip( stops, serviceDate, RealTimeState.MODIFIED, - false + false, + getTripHeadsign(tripUpdate) ); } diff --git a/application/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java index eb31a555e1d..4f60d8de40d 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironmentBuilder.java @@ -58,11 +58,11 @@ public RealtimeTestEnvironment build() { return new RealtimeTestEnvironment(sourceType, timetableRepository); } - private Trip createTrip(TripInput tripInput) { + void createTrip(TripInput tripInput) { var trip = Trip .of(id(tripInput.id())) .withRoute(tripInput.route()) - .withHeadsign(I18NString.of("Headsign of %s".formatted(tripInput.id()))) + .withHeadsign(tripInput.headsign() == null ? null : I18NString.of(tripInput.headsign())) .withServiceId(SERVICE_ID) .build(); @@ -99,8 +99,6 @@ private Trip createTrip(TripInput tripInput) { .build(); timetableRepository.addTripPattern(pattern.getId(), pattern); - - return trip; } private static StopTime createStopTime( diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripInput.java b/application/src/test/java/org/opentripplanner/updater/trip/TripInput.java index eb4f3685659..18b45bc6c1d 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripInput.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripInput.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.utils.time.TimeUtils; @@ -10,7 +11,7 @@ * A simple data structure that is used by the {@link RealtimeTestEnvironment} to create * trips, trips on date and patterns. */ -public record TripInput(String id, Route route, List stops) { +public record TripInput(String id, Route route, List stops, @Nullable String headsign) { public static TripInputBuilder of(String id) { return new TripInputBuilder(id); } @@ -22,6 +23,9 @@ public static class TripInputBuilder implements RealtimeTestConstants { // can be made configurable if needed private Route route = ROUTE_1; + @Nullable + private String headsign; + TripInputBuilder(String id) { this.id = id; } @@ -34,13 +38,18 @@ public TripInputBuilder addStop(RegularStop stopId, String arrivalTime, String d } public TripInput build() { - return new TripInput(id, route, stops); + return new TripInput(id, route, stops, headsign); } public TripInputBuilder withRoute(Route route) { this.route = route; return this; } + + public TripInputBuilder withHeadsign(String headsign) { + this.headsign = headsign; + return this; + } } record StopCall(RegularStop stop, int arrivalTime, int departureTime) {} diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index ec01130e9bd..98feeb10fd7 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -12,8 +12,9 @@ import com.google.transit.realtime.GtfsRealtime; import de.mfdz.MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType; import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.Test; -import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.PickDrop; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; @@ -157,12 +158,14 @@ public void addedTripWithSkippedStop() { var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); var forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); - assertEquals(new NonLocalizedString("A loop"), tripTimes.getHeadsign(0)); + var trip = env.getTransitService().getTrip(TimetableRepositoryForTest.id(ADDED_TRIP_ID)); + assertEquals(I18NString.of("A loop"), Objects.requireNonNull(trip).getHeadsign()); + assertEquals(I18NString.of("A loop"), tripTimes.getHeadsign(0)); assertFalse(tripTimes.isCancelledStop(0)); assertTrue(tripTimes.isCancelledStop(1)); assertTrue(tripTimes.isCancelledStop(2)); assertFalse(tripTimes.isCancelledStop(3)); - assertEquals(new NonLocalizedString("A (non-stop)"), tripTimes.getHeadsign(3)); + assertEquals(I18NString.of("A (non-stop)"), tripTimes.getHeadsign(3)); } @Test @@ -195,11 +198,19 @@ public void addedTripWithDelay() { assertEquals(32100, tripTimes.getArrivalTime(2)); // 08:55:00 } - private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { + private static TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { + return assertAddedTrip(tripId, env, RealTimeState.ADDED); + } + + static TripPattern assertAddedTrip( + String tripId, + RealtimeTestEnvironment env, + RealTimeState realTimeState + ) { var snapshot = env.getTimetableSnapshot(); TransitService transitService = env.getTransitService(); - Trip trip = transitService.getTrip(TimetableRepositoryForTest.id(ADDED_TRIP_ID)); + Trip trip = transitService.getTrip(TimetableRepositoryForTest.id(tripId)); assertNotNull(trip); assertNotNull(transitService.findPattern(trip)); @@ -221,10 +232,7 @@ private TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) forTodayAddedTripIndex > -1, "Added trip should be found in time table for service date" ); - assertEquals( - RealTimeState.ADDED, - forToday.getTripTimes(forTodayAddedTripIndex).getRealTimeState() - ); + assertEquals(realTimeState, forToday.getTripTimes(forTodayAddedTripIndex).getRealTimeState()); final int scheduleTripIndex = schedule.getTripIndex(tripId); assertEquals(-1, scheduleTripIndex, "Added trip should not be found in scheduled time table"); diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java new file mode 100644 index 00000000000..ef44c81d157 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java @@ -0,0 +1,61 @@ +package org.opentripplanner.updater.trip.moduletests.addition; + +import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.REPLACEMENT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.updater.trip.moduletests.addition.AddedTest.assertAddedTrip; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.updater.trip.RealtimeTestConstants; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import org.opentripplanner.updater.trip.TripInput; +import org.opentripplanner.updater.trip.TripUpdateBuilder; + +public class ReplacementTest implements RealtimeTestConstants { + + @Test + void replacementTripShouldReplaceHeadsignIfSpecified() { + var TRIP_INPUT = TripInput + .of(TRIP_1_ID) + .addStop(STOP_A1, "8:30:00", "8:30:00") + .addStop(STOP_B1, "8:40:00", "8:40:00") + .withHeadsign("Original Headsign") + .build(); + var env = RealtimeTestEnvironment.gtfs().addTrip(TRIP_INPUT).build(); + var builder = new TripUpdateBuilder( + TRIP_1_ID, + SERVICE_DATE, + REPLACEMENT, + TIME_ZONE, + "New Headsign" + ); + builder + .addStopTime(STOP_A1_ID, 30) + .addStopTime(STOP_B1_ID, 45, "Changed Headsign") + .addStopTime(STOP_C1_ID, 60); + + var tripUpdate = builder.build(); + + env.applyTripUpdate(tripUpdate); + + // THEN + final TripPattern tripPattern = assertAddedTrip(TRIP_1_ID, env, RealTimeState.MODIFIED); + var snapshot = env.getTimetableSnapshot(); + var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); + var forTodayAddedTripIndex = forToday.getTripIndex(TRIP_1_ID); + var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); + + // We do not support trip headsign by service date + // TODO: I currently have no idea how TripOnServiceDate will behave, and will need to revisit this after #5393 is merged + assertEquals( + I18NString.of("Original Headsign"), + env.getTransitService().getTrip(TimetableRepositoryForTest.id(TRIP_1_ID)).getHeadsign() + ); + assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(0)); + assertEquals(I18NString.of("Changed Headsign"), tripTimes.getHeadsign(1)); + assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(2)); + } +} From f0fc986b6db7267fedb8228a5bced38312c2a5d3 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 17:31:17 +0000 Subject: [PATCH 18/23] move modified test into its own module --- .../trip/TimetableSnapshotSourceTest.java | 200 ------------------ .../moduletests/addition/ReplacementTest.java | 92 +++++++- 2 files changed, 90 insertions(+), 202 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java b/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java index d0e6f19a156..4d84e836604 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java @@ -81,206 +81,6 @@ public void testGetSnapshot() { assertSame(snapshot, updater.getTimetableSnapshot()); } - @Test - public void testHandleModifiedTrip() { - // GIVEN - - String modifiedTripId = "10.1"; - - TripUpdate tripUpdate; - { - final TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder(); - - tripDescriptorBuilder.setTripId(modifiedTripId); - tripDescriptorBuilder.setScheduleRelationship(ScheduleRelationship.REPLACEMENT); - tripDescriptorBuilder.setStartDate(ServiceDateUtils.asCompactString(SERVICE_DATE)); - - final long midnightSecondsSinceEpoch = ServiceDateUtils - .asStartOfService(SERVICE_DATE, transitService.getTimeZone()) - .toEpochSecond(); - - final TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder(); - - tripUpdateBuilder.setTrip(tripDescriptorBuilder); - - { // Stop O - final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); - stopTimeUpdateBuilder.setScheduleRelationship( - StopTimeUpdate.ScheduleRelationship.SCHEDULED - ); - stopTimeUpdateBuilder.setStopId("O"); - stopTimeUpdateBuilder.setStopSequence(10); - - { // Arrival - final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); - arrivalBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (30 * 60)); - arrivalBuilder.setDelay(0); - } - - { // Departure - final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder(); - departureBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (30 * 60)); - departureBuilder.setDelay(0); - } - } - - { // Stop C - final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); - stopTimeUpdateBuilder.setScheduleRelationship( - StopTimeUpdate.ScheduleRelationship.SCHEDULED - ); - stopTimeUpdateBuilder.setStopId("C"); - stopTimeUpdateBuilder.setStopSequence(30); - - { // Arrival - final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); - arrivalBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (40 * 60)); - arrivalBuilder.setDelay(0); - } - - { // Departure - final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder(); - departureBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (45 * 60)); - departureBuilder.setDelay(0); - } - } - - { // Stop D - final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); - stopTimeUpdateBuilder.setScheduleRelationship(SKIPPED); - stopTimeUpdateBuilder.setStopId("D"); - stopTimeUpdateBuilder.setStopSequence(40); - - { // Arrival - final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); - arrivalBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (50 * 60)); - arrivalBuilder.setDelay(0); - } - - { // Departure - final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder(); - departureBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (51 * 60)); - departureBuilder.setDelay(0); - } - } - - { // Stop P - final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); - stopTimeUpdateBuilder.setScheduleRelationship( - StopTimeUpdate.ScheduleRelationship.SCHEDULED - ); - stopTimeUpdateBuilder.setStopId("P"); - stopTimeUpdateBuilder.setStopSequence(50); - - { // Arrival - final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); - arrivalBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (55 * 60)); - arrivalBuilder.setDelay(0); - } - - { // Departure - final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder(); - departureBuilder.setTime(midnightSecondsSinceEpoch + (12 * 3600) + (55 * 60)); - departureBuilder.setDelay(0); - } - } - - tripUpdate = tripUpdateBuilder.build(); - } - - var updater = defaultUpdater(); - - // WHEN - updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - DIFFERENTIAL, - List.of(tripUpdate), - feedId - ); - updater.flushBuffer(); - - // THEN - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - - // Original trip pattern - { - final FeedScopedId tripId = new FeedScopedId(feedId, modifiedTripId); - final Trip trip = transitService.getTrip(tripId); - final TripPattern originalTripPattern = transitService.findPattern(trip); - - final Timetable originalTimetableForToday = snapshot.resolve( - originalTripPattern, - SERVICE_DATE - ); - final Timetable originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); - - assertNotSame(originalTimetableForToday, originalTimetableScheduled); - - final int originalTripIndexScheduled = originalTimetableScheduled.getTripIndex( - modifiedTripId - ); - assertTrue( - originalTripIndexScheduled > -1, - "Original trip should be found in scheduled time table" - ); - final TripTimes originalTripTimesScheduled = originalTimetableScheduled.getTripTimes( - originalTripIndexScheduled - ); - assertFalse( - originalTripTimesScheduled.isCanceledOrDeleted(), - "Original trip times should not be canceled in scheduled time table" - ); - assertEquals(RealTimeState.SCHEDULED, originalTripTimesScheduled.getRealTimeState()); - - final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(modifiedTripId); - assertTrue( - originalTripIndexForToday > -1, - "Original trip should be found in time table for service date" - ); - final TripTimes originalTripTimesForToday = originalTimetableForToday.getTripTimes( - originalTripIndexForToday - ); - assertTrue( - originalTripTimesForToday.isDeleted(), - "Original trip times should be deleted in time table for service date" - ); - assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState()); - } - - // New trip pattern - { - final TripPattern newTripPattern = snapshot.getNewTripPatternForModifiedTrip( - new FeedScopedId(feedId, modifiedTripId), - SERVICE_DATE - ); - assertNotNull(newTripPattern, "New trip pattern should be found"); - - final Timetable newTimetableForToday = snapshot.resolve(newTripPattern, SERVICE_DATE); - final Timetable newTimetableScheduled = snapshot.resolve(newTripPattern, null); - - assertNotSame(newTimetableForToday, newTimetableScheduled); - - final int newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex( - modifiedTripId - ); - assertTrue( - newTimetableForTodayModifiedTripIndex > -1, - "New trip should be found in time table for service date" - ); - assertEquals( - RealTimeState.MODIFIED, - newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex).getRealTimeState() - ); - - assertEquals( - -1, - newTimetableScheduled.getTripIndex(modifiedTripId), - "New trip should not be found in scheduled time table" - ); - } - } - private TimetableSnapshotSource defaultUpdater() { return new TimetableSnapshotSource( new TimetableSnapshotSourceParameters(Duration.ZERO, true), diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java index ef44c81d157..bc9cb6e62fa 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java @@ -2,13 +2,22 @@ import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.REPLACEMENT; 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.assertTrue; import static org.opentripplanner.updater.trip.moduletests.addition.AddedTest.assertAddedTrip; import org.junit.jupiter.api.Test; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.Timetable; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.trip.RealtimeTestConstants; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; import org.opentripplanner.updater.trip.TripInput; @@ -17,7 +26,7 @@ public class ReplacementTest implements RealtimeTestConstants { @Test - void replacementTripShouldReplaceHeadsignIfSpecified() { + void modifiedTrip() { var TRIP_INPUT = TripInput .of(TRIP_1_ID) .addStop(STOP_A1, "8:30:00", "8:30:00") @@ -47,12 +56,91 @@ void replacementTripShouldReplaceHeadsignIfSpecified() { var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); var forTodayAddedTripIndex = forToday.getTripIndex(TRIP_1_ID); var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); + final FeedScopedId tripId = TimetableRepositoryForTest.id(TRIP_1_ID); + + // Original trip pattern + TransitService transitService = env.getTransitService(); + { + final Trip trip = transitService.getTrip(tripId); + final TripPattern originalTripPattern = transitService.findPattern(trip); + + final Timetable originalTimetableForToday = snapshot.resolve( + originalTripPattern, + SERVICE_DATE + ); + final Timetable originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); + + assertNotSame(originalTimetableForToday, originalTimetableScheduled); + + final int originalTripIndexScheduled = originalTimetableScheduled.getTripIndex( + TRIP_1_ID + ); + assertTrue( + originalTripIndexScheduled > -1, + "Original trip should be found in scheduled time table" + ); + final TripTimes originalTripTimesScheduled = originalTimetableScheduled.getTripTimes( + originalTripIndexScheduled + ); + assertFalse( + originalTripTimesScheduled.isCanceledOrDeleted(), + "Original trip times should not be canceled in scheduled time table" + ); + assertEquals(RealTimeState.SCHEDULED, originalTripTimesScheduled.getRealTimeState()); + + final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(TRIP_1_ID); + assertTrue( + originalTripIndexForToday > -1, + "Original trip should be found in time table for service date" + ); + final TripTimes originalTripTimesForToday = originalTimetableForToday.getTripTimes( + originalTripIndexForToday + ); + assertTrue( + originalTripTimesForToday.isDeleted(), + "Original trip times should be deleted in time table for service date" + ); + assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState()); + } + + // New trip pattern + { + final TripPattern newTripPattern = snapshot.getNewTripPatternForModifiedTrip( + tripId, + SERVICE_DATE + ); + assertNotNull(newTripPattern, "New trip pattern should be found"); + + final Timetable newTimetableForToday = snapshot.resolve(newTripPattern, SERVICE_DATE); + final Timetable newTimetableScheduled = snapshot.resolve(newTripPattern, null); + + assertNotSame(newTimetableForToday, newTimetableScheduled); + + final int newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex( + TRIP_1_ID + ); + assertTrue( + newTimetableForTodayModifiedTripIndex > -1, + "New trip should be found in time table for service date" + ); + assertEquals( + RealTimeState.MODIFIED, + newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex).getRealTimeState() + ); + + assertEquals( + -1, + newTimetableScheduled.getTripIndex(TRIP_1_ID), + "New trip should not be found in scheduled time table" + ); + } + // We do not support trip headsign by service date // TODO: I currently have no idea how TripOnServiceDate will behave, and will need to revisit this after #5393 is merged assertEquals( I18NString.of("Original Headsign"), - env.getTransitService().getTrip(TimetableRepositoryForTest.id(TRIP_1_ID)).getHeadsign() + transitService.getTrip(TimetableRepositoryForTest.id(TRIP_1_ID)).getHeadsign() ); assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(0)); assertEquals(I18NString.of("Changed Headsign"), tripTimes.getHeadsign(1)); From 876539daacc54ddf950a098c5cefea953d8d41b4 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 17:37:17 +0000 Subject: [PATCH 19/23] integrate headsign test into the modified trip test --- .../trip/TimetableSnapshotSourceTest.java | 16 ---- .../moduletests/addition/ReplacementTest.java | 81 ++++++++----------- 2 files changed, 32 insertions(+), 65 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java b/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java index 4d84e836604..fe6a9abb7fa 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java @@ -1,21 +1,12 @@ package org.opentripplanner.updater.trip; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.CANCELED; -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.trip.BackwardsDelayPropagationType.REQUIRED_NO_DATA; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; -import com.google.transit.realtime.GtfsRealtime.TripDescriptor; -import com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship; import com.google.transit.realtime.GtfsRealtime.TripUpdate; -import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeEvent; -import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate; import java.time.Duration; import java.time.LocalDate; import java.util.List; @@ -24,19 +15,12 @@ import org.opentripplanner.ConstantsForTests; import org.opentripplanner.TestOtpModel; import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TimetableSnapshot; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.timetable.RealTimeState; -import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TimetableRepository; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.TimetableSnapshotSourceParameters; -import org.opentripplanner.utils.time.ServiceDateUtils; public class TimetableSnapshotSourceTest { diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java index bc9cb6e62fa..7ffcdc792c1 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java @@ -10,14 +10,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.model.Timetable; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.RealTimeState; -import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; -import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.trip.RealtimeTestConstants; import org.opentripplanner.updater.trip.RealtimeTestEnvironment; import org.opentripplanner.updater.trip.TripInput; @@ -51,35 +45,33 @@ void modifiedTrip() { env.applyTripUpdate(tripUpdate); // THEN - final TripPattern tripPattern = assertAddedTrip(TRIP_1_ID, env, RealTimeState.MODIFIED); var snapshot = env.getTimetableSnapshot(); - var forToday = snapshot.resolve(tripPattern, SERVICE_DATE); - var forTodayAddedTripIndex = forToday.getTripIndex(TRIP_1_ID); - var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); - final FeedScopedId tripId = TimetableRepositoryForTest.id(TRIP_1_ID); + var tripId = TimetableRepositoryForTest.id(TRIP_1_ID); + + var transitService = env.getTransitService(); + // We do not support trip headsign by service date + // TODO: I currently have no idea how TripOnServiceDate will behave, and will need to revisit this after #5393 is merged + assertEquals( + I18NString.of("Original Headsign"), + transitService.getTrip(TimetableRepositoryForTest.id(TRIP_1_ID)).getHeadsign() + ); // Original trip pattern - TransitService transitService = env.getTransitService(); { - final Trip trip = transitService.getTrip(tripId); - final TripPattern originalTripPattern = transitService.findPattern(trip); + var trip = transitService.getTrip(tripId); + var originalTripPattern = transitService.findPattern(trip); - final Timetable originalTimetableForToday = snapshot.resolve( - originalTripPattern, - SERVICE_DATE - ); - final Timetable originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); + var originalTimetableForToday = snapshot.resolve(originalTripPattern, SERVICE_DATE); + var originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); assertNotSame(originalTimetableForToday, originalTimetableScheduled); - final int originalTripIndexScheduled = originalTimetableScheduled.getTripIndex( - TRIP_1_ID - ); + var originalTripIndexScheduled = originalTimetableScheduled.getTripIndex(TRIP_1_ID); assertTrue( originalTripIndexScheduled > -1, "Original trip should be found in scheduled time table" ); - final TripTimes originalTripTimesScheduled = originalTimetableScheduled.getTripTimes( + var originalTripTimesScheduled = originalTimetableScheduled.getTripTimes( originalTripIndexScheduled ); assertFalse( @@ -88,12 +80,12 @@ void modifiedTrip() { ); assertEquals(RealTimeState.SCHEDULED, originalTripTimesScheduled.getRealTimeState()); - final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(TRIP_1_ID); + var originalTripIndexForToday = originalTimetableForToday.getTripIndex(TRIP_1_ID); assertTrue( originalTripIndexForToday > -1, "Original trip should be found in time table for service date" ); - final TripTimes originalTripTimesForToday = originalTimetableForToday.getTripTimes( + var originalTripTimesForToday = originalTimetableForToday.getTripTimes( originalTripIndexForToday ); assertTrue( @@ -101,49 +93,40 @@ void modifiedTrip() { "Original trip times should be deleted in time table for service date" ); assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState()); + assertEquals(I18NString.of("Original Headsign"), originalTripTimesScheduled.getHeadsign(0)); + assertEquals(I18NString.of("Original Headsign"), originalTripTimesScheduled.getHeadsign(1)); + assertEquals(I18NString.of("Original Headsign"), originalTripTimesForToday.getHeadsign(0)); + assertEquals(I18NString.of("Original Headsign"), originalTripTimesForToday.getHeadsign(1)); } // New trip pattern { - final TripPattern newTripPattern = snapshot.getNewTripPatternForModifiedTrip( - tripId, - SERVICE_DATE - ); + assertAddedTrip(TRIP_1_ID, env, RealTimeState.MODIFIED); + var newTripPattern = snapshot.getNewTripPatternForModifiedTrip(tripId, SERVICE_DATE); assertNotNull(newTripPattern, "New trip pattern should be found"); - final Timetable newTimetableForToday = snapshot.resolve(newTripPattern, SERVICE_DATE); - final Timetable newTimetableScheduled = snapshot.resolve(newTripPattern, null); + var newTimetableForToday = snapshot.resolve(newTripPattern, SERVICE_DATE); + var newTimetableScheduled = snapshot.resolve(newTripPattern, null); assertNotSame(newTimetableForToday, newTimetableScheduled); - final int newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex( - TRIP_1_ID - ); + var newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex(TRIP_1_ID); assertTrue( newTimetableForTodayModifiedTripIndex > -1, "New trip should be found in time table for service date" ); - assertEquals( - RealTimeState.MODIFIED, - newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex).getRealTimeState() - ); + var tripTimes = newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex); + assertEquals(RealTimeState.MODIFIED, tripTimes.getRealTimeState()); assertEquals( -1, newTimetableScheduled.getTripIndex(TRIP_1_ID), "New trip should not be found in scheduled time table" ); - } - - // We do not support trip headsign by service date - // TODO: I currently have no idea how TripOnServiceDate will behave, and will need to revisit this after #5393 is merged - assertEquals( - I18NString.of("Original Headsign"), - transitService.getTrip(TimetableRepositoryForTest.id(TRIP_1_ID)).getHeadsign() - ); - assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(0)); - assertEquals(I18NString.of("Changed Headsign"), tripTimes.getHeadsign(1)); - assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(2)); + assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(0)); + assertEquals(I18NString.of("Changed Headsign"), tripTimes.getHeadsign(1)); + assertEquals(I18NString.of("New Headsign"), tripTimes.getHeadsign(2)); + } } } From ee933f06a4c7d389642b31946eac87a37acad530 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 17:37:39 +0000 Subject: [PATCH 20/23] renamed ReplacementTest to ModifiedTest --- .../addition/{ReplacementTest.java => ModifiedTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/{ReplacementTest.java => ModifiedTest.java} (98%) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ModifiedTest.java similarity index 98% rename from application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java rename to application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ModifiedTest.java index 7ffcdc792c1..3ca93dbcae9 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ReplacementTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/ModifiedTest.java @@ -17,7 +17,7 @@ import org.opentripplanner.updater.trip.TripInput; import org.opentripplanner.updater.trip.TripUpdateBuilder; -public class ReplacementTest implements RealtimeTestConstants { +public class ModifiedTest implements RealtimeTestConstants { @Test void modifiedTrip() { From 16c4dc6e4e01b700b3ca963347649ce1bc0299d5 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 4 Dec 2024 18:09:32 +0000 Subject: [PATCH 21/23] use seconds from midnight instead of minutes for test --- .../updater/trip/TripUpdateBuilder.java | 56 +++++++++++-------- .../trip/moduletests/addition/AddedTest.java | 17 +++--- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java index add0fedf91b..3606d9f22ef 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/TripUpdateBuilder.java @@ -51,10 +51,10 @@ public TripUpdateBuilder( ); } - public TripUpdateBuilder addStopTime(String stopId, int minutes) { + public TripUpdateBuilder addStopTime(String stopId, int secondsFromMidnight) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -66,10 +66,10 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes) { ); } - public TripUpdateBuilder addStopTime(String stopId, int minutes, String headsign) { + public TripUpdateBuilder addStopTime(String stopId, int secondsFromMidnight, String headsign) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -81,10 +81,10 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, String headsign ); } - public TripUpdateBuilder addStopTimeWithDelay(String stopId, int minutes, int delay) { + public TripUpdateBuilder addStopTimeWithDelay(String stopId, int secondsFromMidnight, int delay) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, delay, delay, @@ -98,12 +98,12 @@ public TripUpdateBuilder addStopTimeWithDelay(String stopId, int minutes, int de public TripUpdateBuilder addStopTimeWithScheduled( String stopId, - int minutes, - int scheduledMinutes + int secondsFromMidnight, + int scheduledSeconds ) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -111,14 +111,18 @@ public TripUpdateBuilder addStopTimeWithScheduled( null, null, null, - scheduledMinutes + scheduledSeconds ); } - public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupType pickDrop) { + public TripUpdateBuilder addStopTime( + String stopId, + int secondsFromMidnight, + DropOffPickupType pickDrop + ) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -132,12 +136,12 @@ public TripUpdateBuilder addStopTime(String stopId, int minutes, DropOffPickupTy public TripUpdateBuilder addStopTime( String stopId, - int minutes, + int secondsFromMidnight, StopTimeUpdate.StopTimeProperties.DropOffPickupType pickDrop ) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -219,10 +223,10 @@ public TripUpdateBuilder addSkippedStop(int stopSequence) { ); } - public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { + public TripUpdateBuilder addSkippedStop(String stopId, int secondsFromMidnight) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -234,10 +238,14 @@ public TripUpdateBuilder addSkippedStop(String stopId, int minutes) { ); } - public TripUpdateBuilder addSkippedStop(String stopId, int minutes, DropOffPickupType pickDrop) { + public TripUpdateBuilder addSkippedStop( + String stopId, + int secondsFromMidnight, + DropOffPickupType pickDrop + ) { return addStopTime( stopId, - minutes, + secondsFromMidnight, NO_VALUE, NO_DELAY, NO_DELAY, @@ -260,7 +268,7 @@ public TripUpdateBuilder addRawStopTime(StopTimeUpdate stopTime) { private TripUpdateBuilder addStopTime( @Nullable String stopId, - int minutes, + int secondsFromMidnight, int stopSequence, int arrivalDelay, int departureDelay, @@ -268,7 +276,7 @@ private TripUpdateBuilder addStopTime( @Nullable DropOffPickupType pickDrop, @Nullable StopTimeUpdate.StopTimeProperties.DropOffPickupType gtfsPickDrop, @Nullable String headsign, - int scheduledMinutes + int scheduledSeconds ) { final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(); stopTimeUpdateBuilder.setScheduleRelationship(scheduleRelationShip); @@ -304,14 +312,14 @@ private TripUpdateBuilder addStopTime( final GtfsRealtime.TripUpdate.StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder(); final GtfsRealtime.TripUpdate.StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder(); - if (minutes > NO_VALUE) { - var epochSeconds = midnight.plusHours(8).plusMinutes(minutes).toEpochSecond(); + if (secondsFromMidnight > NO_VALUE) { + var epochSeconds = midnight.plusSeconds(secondsFromMidnight).toEpochSecond(); arrivalBuilder.setTime(epochSeconds); departureBuilder.setTime(epochSeconds); } - if (scheduledMinutes > NO_VALUE) { - var epochSeconds = midnight.plusHours(8).plusMinutes(scheduledMinutes).toEpochSecond(); + if (scheduledSeconds > NO_VALUE) { + var epochSeconds = midnight.plusSeconds(scheduledSeconds).toEpochSecond(); arrivalBuilder.setScheduledTime(epochSeconds); departureBuilder.setScheduledTime(epochSeconds); } diff --git a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java index 98feeb10fd7..2f3c11befc1 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/moduletests/addition/AddedTest.java @@ -173,13 +173,10 @@ public void addedTripWithDelay() { var env = RealtimeTestEnvironment.gtfs().build(); var builder = new TripUpdateBuilder(ADDED_TRIP_ID, SERVICE_DATE, ADDED, TIME_ZONE); - // A1: scheduled 08:30:00 - // B1: scheduled 08:40:00, delay 300 seconds (actual 08:45:00) - // C1: scheduled 08:55:00 builder - .addStopTime(STOP_A1_ID, 30) - .addStopTimeWithDelay(STOP_B1_ID, 45, 300) - .addStopTimeWithScheduled(STOP_C1_ID, 55, 54); + .addStopTime(STOP_A1_ID, 10000) + .addStopTimeWithDelay(STOP_B1_ID, 11300, 300) + .addStopTimeWithScheduled(STOP_C1_ID, 12500, 12000); var tripUpdate = builder.build(); env.applyTripUpdate(tripUpdate); @@ -191,11 +188,11 @@ public void addedTripWithDelay() { var forTodayAddedTripIndex = forToday.getTripIndex(ADDED_TRIP_ID); var tripTimes = forToday.getTripTimes(forTodayAddedTripIndex); assertEquals(0, tripTimes.getDepartureDelay(0)); - assertEquals(30600, tripTimes.getDepartureTime(0)); // 08:30:00 + assertEquals(10000, tripTimes.getDepartureTime(0)); assertEquals(300, tripTimes.getArrivalDelay(1)); - assertEquals(31500, tripTimes.getArrivalTime(1)); // 08:45:00 - assertEquals(60, tripTimes.getArrivalDelay(2)); - assertEquals(32100, tripTimes.getArrivalTime(2)); // 08:55:00 + assertEquals(11300, tripTimes.getArrivalTime(1)); + assertEquals(500, tripTimes.getArrivalDelay(2)); + assertEquals(12500, tripTimes.getArrivalTime(2)); } private static TripPattern assertAddedTrip(String tripId, RealtimeTestEnvironment env) { From c43c11cf2cdf2c3fcc98df73a3f5992790cdc7e2 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 21 Jan 2025 12:41:33 +0000 Subject: [PATCH 22/23] add NEW enum specified for google/transit#504 --- .../updater/trip/TimetableSnapshotSource.java | 35 +++++++-------- .../src/main/proto/gtfs-realtime.proto | 43 +++++++++++-------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 1b8d6f7c58b..6a821bcb4ca 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -249,7 +249,7 @@ public UpdateResult applyTripUpdates( serviceDate, backwardsDelayPropagationType ); - case ADDED -> validateAndHandleAddedTrip( + case NEW, ADDED -> validateAndHandleAddedTrip( tripUpdate, tripDescriptor, tripId, @@ -303,7 +303,7 @@ public UpdateResult applyTripUpdates( /** * Remove previous realtime updates for this trip. This is necessary to avoid previous stop * pattern modifications from persisting. If a trip was previously added with the - * ScheduleRelationship ADDED and is now cancelled or deleted, we still want to keep the realtime + * ScheduleRelationship NEW and is now cancelled or deleted, we still want to keep the realtime * added trip pattern. */ private void purgePatternModifications( @@ -324,7 +324,7 @@ private void purgePatternModifications( ) { // Remove previous realtime updates for this trip. This is necessary to avoid previous // stop pattern modifications from persisting. If a trip was previously added with the ScheduleRelationship - // ADDED and is now cancelled or deleted, we still want to keep the realtime added trip pattern. + // NEW and is now cancelled or deleted, we still want to keep the realtime added trip pattern. this.snapshotManager.revertTripToScheduledTripPattern(tripId, serviceDate); } } @@ -466,7 +466,7 @@ private Result handleScheduledTrip( } /** - * Validate and handle GTFS-RT TripUpdate message containing an ADDED trip. + * Validate and handle GTFS-RT TripUpdate message containing an NEW trip. * * @param tripUpdate GTFS-RT TripUpdate message * @param tripDescriptor GTFS-RT TripDescriptor @@ -490,9 +490,7 @@ private Result validateAndHandleAddedTrip( final Trip trip = transitEditorService.getScheduledTrip(tripId); if (trip != null) { - // TODO: should we support this and add a new instantiation of this trip (making it - // frequency based)? - debug(tripId, serviceDate, "Graph already contains trip id of ADDED trip, skipping."); + debug(tripId, serviceDate, "Graph already contains trip id of NEW trip, skipping."); return UpdateError.result(tripId, TRIP_ALREADY_EXISTS); } @@ -502,7 +500,7 @@ private Result validateAndHandleAddedTrip( debug( tripId, serviceDate, - "ADDED trip doesn't have a start date in TripDescriptor, skipping." + "NEW trip doesn't have a start date in TripDescriptor, skipping." ); return UpdateError.result(tripId, NO_START_DATE); } @@ -521,7 +519,7 @@ private Result validateAndHandleAddedTrip( // check if after filtering the stops we still have at least 2 if (stopTimeUpdates.size() < 2) { - debug(tripId, serviceDate, "ADDED trip has fewer than two known stops, skipping."); + debug(tripId, serviceDate, "NEW trip has fewer than two known stops, skipping."); return UpdateError.result(tripId, TOO_FEW_STOPS); } @@ -557,7 +555,7 @@ private List removeUnknownStops( debug( tripId, serviceDate, - "Stop '{}' not found in graph. Removing from ADDED trip.", + "Stop '{}' not found in graph. Removing from NEW trip.", st.getStopId() ); } @@ -567,7 +565,7 @@ private List removeUnknownStops( } /** - * Check stop time updates of trip update that results in a new trip (ADDED or MODIFIED) and find + * Check stop time updates of trip update that results in a new trip (NEW or REPLACEMENT) and find * all stops of that trip. * * @return stops when stop time updates are correct; null if there are errors @@ -602,7 +600,7 @@ private List checkNewStopTimeUpdatesAndFindStops( } previousStopSequence = stopSequence; } else { - // Allow missing stop sequences for ADDED and MODIFIED trips + // Allow missing stop sequences for NEW and REPLACEMENT trips } // Find stops @@ -669,7 +667,7 @@ private List checkNewStopTimeUpdatesAndFindStops( } /** - * Handle GTFS-RT TripUpdate message containing an ADDED trip. + * Handle GTFS-RT TripUpdate message containing an NEW trip. * * @param stopTimeUpdates GTFS-RT stop time updates * @param tripDescriptor GTFS-RT TripDescriptor @@ -718,7 +716,7 @@ private Result handleAddedTrip( debug( tripId, serviceDate, - "ADDED trip has service date {} for which no service id is available, skipping.", + "NEW trip has service date {} for which no service id is available, skipping.", serviceDate.toString() ); return UpdateError.result(tripId, NO_SERVICE_ON_DATE); @@ -870,7 +868,7 @@ private Result addTripToGraphAndBuffer( debug( trip.getId(), serviceDate, - "ADDED trip has invalid arrival time (compared to start date in " + + "NEW trip has invalid arrival time (compared to start date in " + "TripDescriptor), skipping." ); return UpdateError.result(trip.getId(), INVALID_ARRIVAL_TIME); @@ -886,7 +884,7 @@ private Result addTripToGraphAndBuffer( debug( trip.getId(), serviceDate, - "ADDED trip has invalid departure time (compared to start date in " + + "NEW trip has invalid departure time (compared to start date in " + "TripDescriptor), skipping." ); return UpdateError.result(trip.getId(), INVALID_DEPARTURE_TIME); @@ -1071,7 +1069,7 @@ private boolean cancelPreviouslyAddedTrip( } /** - * Validate and handle GTFS-RT TripUpdate message containing a MODIFIED trip. + * Validate and handle GTFS-RT TripUpdate message containing a REPLACEMENT trip. * * @param tripUpdate GTFS-RT TripUpdate message * @param tripDescriptor GTFS-RT TripDescriptor @@ -1095,8 +1093,7 @@ private Result validateAndHandleModifiedTrip( Trip trip = transitEditorService.getTrip(tripId); if (trip == null) { - // TODO: should we support this and consider it an ADDED trip? - debug(tripId, serviceDate, "Feed does not contain trip id of MODIFIED trip, skipping."); + debug(tripId, serviceDate, "Feed does not contain trip id of REPLACEMENT trip, skipping."); return UpdateError.result(tripId, TRIP_NOT_FOUND); } diff --git a/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto b/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto index 5d2adaa767d..bd610ae4243 100644 --- a/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto +++ b/gtfs-realtime-protobuf/src/main/proto/gtfs-realtime.proto @@ -190,7 +190,7 @@ message TripUpdate { // To specify a completely certain prediction, set its uncertainty to 0. optional int32 uncertainty = 3; - // Scheduled time for an added or replacement trip. + // Scheduled time for a new or replacement trip. // In Unix time (i.e., number of seconds since January 1st 1970 00:00:00 // UTC). optional int64 scheduled_time = 4; @@ -278,22 +278,9 @@ message TripUpdate { optional string assigned_stop_id = 1; // The updated headsign of the vehicle at the stop. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. optional string stop_headsign = 2; - // The updated pickup of the vehicle at the stop. - optional DropOffPickupType pickup_type = 3; - - // The updated drop off of the vehicle at the stop. - optional DropOffPickupType drop_off_type = 4; - - // The extensions namespace allows 3rd-party developers to extend the - // GTFS Realtime Specification in order to add and evaluate new features - // and modifications to the spec. - extensions 1000 to 1999; - - // The following extension IDs are reserved for private use by any organization. - extensions 9000 to 9999; - enum DropOffPickupType { // Regularly scheduled pickup/dropoff. REGULAR = 0; @@ -307,6 +294,22 @@ message TripUpdate { // Must coordinate with driver to arrange pickup/dropoff. COORDINATE_WITH_DRIVER = 3; } + + // The updated pickup of the vehicle at the stop. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + optional DropOffPickupType pickup_type = 3; + + // The updated drop off of the vehicle at the stop. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + optional DropOffPickupType drop_off_type = 4; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS Realtime Specification in order to add and evaluate new features + // and modifications to the spec. + extensions 1000 to 1999; + + // The following extension IDs are reserved for private use by any organization. + extensions 9000 to 9999; } // Realtime updates for certain properties defined within GTFS stop_times.txt @@ -829,8 +832,10 @@ message TripDescriptor { // enough to the scheduled trip to be associated with it. SCHEDULED = 0; - // An extra trip unrelated to any existing trips, for example, to respond to sudden passenger load. - ADDED = 1; + // This value has been deprecated as the behavior was unspecified. + // Use DUPLICATED for an extra trip that is the same as a scheduled trip except the start date or time, + // or NEW for an extra trip that is unrelated to an existing trip. + ADDED = 1 [deprecated = true]; // A trip that is running with no schedule associated to it (GTFS frequencies.txt exact_times=0). // Trips with ScheduleRelationship=UNSCHEDULED must also set all StopTimeUpdates.ScheduleRelationship=UNSCHEDULED. @@ -870,6 +875,10 @@ message TripDescriptor { // real-time predictions. // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. DELETED = 7; + + // An extra trip unrelated to any existing trips, for example, to respond to sudden passenger load. + // NOTE: This field is still experimental, and subject to change. It may be formally adopted in the future. + NEW = 8; } optional ScheduleRelationship schedule_relationship = 4; From 8fcd859f775dc334aff44e438dd12b017fed1162 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 21 Jan 2025 12:52:30 +0000 Subject: [PATCH 23/23] reformat code --- .../updater/trip/TimetableSnapshotSource.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 6a821bcb4ca..2bf523628d7 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -497,11 +497,7 @@ private Result validateAndHandleAddedTrip( // Check whether a start date exists if (!tripDescriptor.hasStartDate()) { // TODO: should we support this and apply update to all days? - debug( - tripId, - serviceDate, - "NEW trip doesn't have a start date in TripDescriptor, skipping." - ); + debug(tripId, serviceDate, "NEW trip doesn't have a start date in TripDescriptor, skipping."); return UpdateError.result(tripId, NO_START_DATE); }