Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to specify delay or skipped stops on ADDED or REPLACEMENT trips in GTFS-RT #6028

Draft
wants to merge 32 commits into
base: dev-2.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fbcbe5d
implement delay and skipped stops on added / replacement trips
miklcct Aug 14, 2024
d7f506f
strengthen test case
miklcct Aug 31, 2024
7affd6c
Skipped stops should always have PickDrop = CANCELLED
miklcct Aug 31, 2024
4240da2
use "var" instead of specifying types
miklcct Sep 3, 2024
30e70fb
add overloads of TripUpdateBuilder.addSkippedStop to specify skipped …
miklcct Sep 3, 2024
979a656
move checks against StopTimeUpdate to AddedStopTime
miklcct Sep 3, 2024
dddd6fe
store the original StopTimeUpdate in AddedStopTime
miklcct Sep 4, 2024
ef9145e
Merge tag 'v2.6.0' into delay_on_added_trips
miklcct Sep 18, 2024
035fcca
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Sep 27, 2024
f34b149
Merge remote-tracking branch 'upstream/dev-2.x' into delay_on_added_t…
miklcct Sep 27, 2024
f097243
fix test cases
miklcct Sep 30, 2024
2168858
consistency in getter naming
miklcct Sep 30, 2024
86bc0e5
assume that the delay is 0 when it is missing
miklcct Sep 30, 2024
8c72fba
formatting
miklcct Oct 1, 2024
449f18d
Merge remote-tracking branch 'upstream/dev-2.x' into delay_on_added_t…
miklcct Oct 15, 2024
3dbb6ef
Merge remote-tracking branch 'upstream/dev-2.x' into delay_on_added_t…
miklcct Oct 21, 2024
6e21264
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Nov 5, 2024
f3792b1
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Nov 26, 2024
25c9d65
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Dec 4, 2024
b5c16aa
update proto file
miklcct Dec 4, 2024
fe83e7d
process pickup / drop off in StopTimeProperties
miklcct Dec 4, 2024
cafa318
process stop headsign
miklcct Dec 4, 2024
5236c7e
process trip headsign for added trips
miklcct Dec 4, 2024
18adc11
process scheduled time in added trips
miklcct Dec 4, 2024
513eca4
handle replacement trip headsign
miklcct Dec 4, 2024
f0fc986
move modified test into its own module
miklcct Dec 4, 2024
876539d
integrate headsign test into the modified trip test
miklcct Dec 4, 2024
ee933f0
renamed ReplacementTest to ModifiedTest
miklcct Dec 4, 2024
16c4dc6
use seconds from midnight instead of minutes for test
miklcct Dec 4, 2024
331b0c6
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Dec 17, 2024
f4860c5
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Jan 9, 2025
591f909
Merge branch 'dev-2.x' into delay_on_added_trips
miklcct Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 92 additions & 26 deletions src/main/java/org/opentripplanner/updater/trip/AddedStopTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.google.transit.realtime.GtfsRealtime;
import de.mfdz.MfdzRealtimeExtensions;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import javax.annotation.Nullable;
import org.opentripplanner.gtfs.mapping.PickDropMapper;
import org.opentripplanner.model.PickDrop;
Expand All @@ -13,41 +15,105 @@
*/
final class AddedStopTime {

@Nullable
private final PickDrop pickup;
private final GtfsRealtime.TripUpdate.StopTimeUpdate stopTimeUpdate;

@Nullable
private final PickDrop dropOff;

public static final PickDrop DEFAULT_PICK_DROP = PickDrop.SCHEDULED;

AddedStopTime(@Nullable PickDrop pickup, @Nullable PickDrop dropOff) {
this.pickup = pickup;
this.dropOff = dropOff;
AddedStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate stopTimeUpdate) {
this.stopTimeUpdate = stopTimeUpdate;
}

PickDrop pickup() {
return Objects.requireNonNullElse(pickup, DEFAULT_PICK_DROP);
return getPickDrop(
getStopTimePropertiesExtension()
.map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getPickupType)
.orElse(null)
);
}

PickDrop dropOff() {
return Objects.requireNonNullElse(dropOff, DEFAULT_PICK_DROP);
return getPickDrop(
getStopTimePropertiesExtension()
.map(MfdzRealtimeExtensions.StopTimePropertiesExtension::getDropoffType)
.orElse(null)
);
}

static AddedStopTime ofStopTime(GtfsRealtime.TripUpdate.StopTimeUpdate props) {
if (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);
private PickDrop getPickDrop(
@Nullable MfdzRealtimeExtensions.StopTimePropertiesExtension.DropOffPickupType extensionDropOffPickup
) {
if (isSkipped()) {
return PickDrop.CANCELLED;
}

if (extensionDropOffPickup == null) {
return toPickDrop(stopTimeUpdate.getScheduleRelationship());
}

return PickDropMapper.map(extensionDropOffPickup.getNumber());
}

private Optional<MfdzRealtimeExtensions.StopTimePropertiesExtension> getStopTimePropertiesExtension() {
return stopTimeUpdate
.getStopTimeProperties()
.hasExtension(MfdzRealtimeExtensions.stopTimeProperties)
? Optional.of(
stopTimeUpdate
.getStopTimeProperties()
.getExtension(MfdzRealtimeExtensions.stopTimeProperties)
)
: Optional.empty();
}

OptionalLong getArrivalTime() {
miklcct marked this conversation as resolved.
Show resolved Hide resolved
return stopTimeUpdate.hasArrival()
? getTime(stopTimeUpdate.getArrival())
: OptionalLong.empty();
}

OptionalLong getDepartureTime() {
return stopTimeUpdate.hasDeparture()
? getTime(stopTimeUpdate.getDeparture())
: OptionalLong.empty();
}

private OptionalLong getTime(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) {
return stopTimeEvent.hasTime()
? OptionalLong.of(stopTimeEvent.getTime())
: OptionalLong.empty();
}

OptionalInt getArrivalDelay() {
return stopTimeUpdate.hasArrival()
? getDelay(stopTimeUpdate.getArrival())
: OptionalInt.empty();
}

OptionalInt getDepartureDelay() {
return stopTimeUpdate.hasDeparture()
? getDelay(stopTimeUpdate.getDeparture())
: OptionalInt.empty();
}

private OptionalInt getDelay(GtfsRealtime.TripUpdate.StopTimeEvent stopTimeEvent) {
return stopTimeEvent.hasDelay()
? OptionalInt.of(stopTimeEvent.getDelay())
: OptionalInt.empty();
}

boolean isSkipped() {
return (
stopTimeUpdate.getScheduleRelationship() ==
GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED
);
}

OptionalInt getStopSequence() {
return stopTimeUpdate.hasStopSequence()
? OptionalInt.of(stopTimeUpdate.getStopSequence())
: OptionalInt.empty();
}

Optional<String> getStopId() {
return stopTimeUpdate.hasStopId() ? Optional.of(stopTimeUpdate.getStopId()) : Optional.empty();
}

private static PickDrop toPickDrop(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,11 +545,12 @@ private List<StopLocation> checkNewStopTimeUpdatesAndFindStops(
final List<StopLocation> stops = new ArrayList<>(stopTimeUpdates.size());

for (int index = 0; index < stopTimeUpdates.size(); ++index) {
final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(index);
final var addedStopTime = new AddedStopTime(stopTimeUpdates.get(index));

// Check stop sequence
if (stopTimeUpdate.hasStopSequence()) {
final Integer stopSequence = stopTimeUpdate.getStopSequence();
final var optionalStopSequence = addedStopTime.getStopSequence();
if (optionalStopSequence.isPresent()) {
final var stopSequence = optionalStopSequence.getAsInt();

// Check non-negative
if (stopSequence < 0) {
Expand All @@ -568,20 +569,18 @@ private List<StopLocation> checkNewStopTimeUpdatesAndFindStops(
}

// Find stops
if (stopTimeUpdate.hasStopId()) {
final var optionalStopId = addedStopTime.getStopId();
if (optionalStopId.isPresent()) {
final var stopId = optionalStopId.get();
// 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 {
Expand All @@ -590,9 +589,10 @@ private List<StopLocation> checkNewStopTimeUpdatesAndFindStops(
}

// Check arrival time
if (stopTimeUpdate.hasArrival() && stopTimeUpdate.getArrival().hasTime()) {
final var arrival = addedStopTime.getArrivalTime();
miklcct marked this conversation as resolved.
Show resolved Hide resolved
if (arrival.isPresent()) {
final var time = arrival.getAsLong();
// Check for increasing time
final Long time = stopTimeUpdate.getArrival().getTime();
if (previousTime != null && previousTime > time) {
debug(tripId, "Trip update contains decreasing times, skipping.");
return null;
Expand All @@ -604,9 +604,10 @@ private List<StopLocation> checkNewStopTimeUpdatesAndFindStops(
}

// Check departure time
if (stopTimeUpdate.hasDeparture() && stopTimeUpdate.getDeparture().hasTime()) {
final var departure = addedStopTime.getDepartureTime();
if (departure.isPresent()) {
final var time = departure.getAsLong();
// Check for increasing time
final Long time = stopTimeUpdate.getDeparture().getTime();
if (previousTime != null && previousTime > time) {
debug(tripId, "Trip update contains decreasing times, skipping.");
return null;
Expand Down Expand Up @@ -786,16 +787,18 @@ private Result<UpdateSuccess, UpdateError> addTripToGraphAndBuffer(
// Create StopTimes
final List<StopTime> stopTimes = new ArrayList<>(stopTimeUpdates.size());
for (int index = 0; index < stopTimeUpdates.size(); ++index) {
final StopTimeUpdate stopTimeUpdate = stopTimeUpdates.get(index);
final var added = new AddedStopTime(stopTimeUpdates.get(index));
final var stop = stops.get(index);

// Create stop time
final StopTime stopTime = new StopTime();
stopTime.setTrip(trip);
stopTime.setStop(stop);
// Set arrival time
if (stopTimeUpdate.hasArrival() && stopTimeUpdate.getArrival().hasTime()) {
final long arrivalTime = stopTimeUpdate.getArrival().getTime() - midnightSecondsSinceEpoch;
final var arrival = added.getArrivalTime();
if (arrival.isPresent()) {
final var delay = added.getArrivalDelay().orElse(0);
miklcct marked this conversation as resolved.
Show resolved Hide resolved
final var arrivalTime = arrival.getAsLong() - midnightSecondsSinceEpoch - delay;
if (arrivalTime < 0 || arrivalTime > MAX_ARRIVAL_DEPARTURE_TIME) {
debug(
trip.getId(),
Expand All @@ -807,9 +810,10 @@ private Result<UpdateSuccess, UpdateError> addTripToGraphAndBuffer(
stopTime.setArrivalTime((int) arrivalTime);
}
// Set departure time
if (stopTimeUpdate.hasDeparture() && stopTimeUpdate.getDeparture().hasTime()) {
final long departureTime =
stopTimeUpdate.getDeparture().getTime() - midnightSecondsSinceEpoch;
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(),
Expand All @@ -821,10 +825,7 @@ private Result<UpdateSuccess, UpdateError> addTripToGraphAndBuffer(
stopTime.setDepartureTime((int) departureTime);
}
stopTime.setTimepoint(1); // Exact time
if (stopTimeUpdate.hasStopSequence()) {
stopTime.setStopSequence(stopTimeUpdate.getStopSequence());
}
var added = AddedStopTime.ofStopTime(stopTimeUpdate);
added.getStopSequence().ifPresent(stopTime::setStopSequence);
stopTime.setPickupType(added.pickup());
stopTime.setDropOffType(added.dropOff());
// Add stop time to list
Expand Down Expand Up @@ -852,12 +853,23 @@ private Result<UpdateSuccess, UpdateError> 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
miklcct marked this conversation as resolved.
Show resolved Hide resolved
for (int stopIndex = 0; stopIndex < newTripTimes.getNumStops(); stopIndex++) {
newTripTimes.updateArrivalTime(stopIndex, newTripTimes.getScheduledArrivalTime(stopIndex));
final var addedStopTime = new AddedStopTime(stopTimeUpdates.get(stopIndex));

if (addedStopTime.isSkipped()) {
newTripTimes.setCancelled(stopIndex);
}

final int arrivalDelay = addedStopTime.getArrivalDelay().orElse(0);
final int departureDelay = addedStopTime.getDepartureDelay().orElse(0);
newTripTimes.updateArrivalTime(
stopIndex,
newTripTimes.getScheduledArrivalTime(stopIndex) + arrivalDelay
);
newTripTimes.updateDepartureTime(
stopIndex,
newTripTimes.getScheduledDepartureTime(stopIndex)
newTripTimes.getScheduledDepartureTime(stopIndex) + departureDelay
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface RealtimeTestConstants {
String STOP_A1_ID = "A1";
String STOP_B1_ID = "B1";
String STOP_C1_ID = "C1";
String STOP_D1_ID = "D1";
String TRIP_1_ID = "TestTrip1";
String TRIP_2_ID = "TestTrip2";
String OPERATOR_1_ID = "TestOperator1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ 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, DropOffPickupType pickDrop) {
return addStopTime(
stopId,
Expand Down Expand Up @@ -111,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.
Expand Down
Loading
Loading