Skip to content

Commit

Permalink
Merge pull request #4759 from entur/otp_deleted_trips
Browse files Browse the repository at this point in the history
Add support for deleted trips & including real-time cancelations in trip search
  • Loading branch information
hannesj committed Jan 31, 2023
2 parents 1d70594 + ea3a29b commit afc954a
Show file tree
Hide file tree
Showing 16 changed files with 357 additions and 69 deletions.
2 changes: 2 additions & 0 deletions src/ext/graphql/transmodelapi/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ type QueryType {
ignoreRealtimeUpdates: Boolean = false,
"When true, service journeys cancelled in scheduled route data will be included during this search."
includePlannedCancellations: Boolean = false,
"When true, service journeys cancelled by real-time updates will be included during this search."
includeRealtimeCancellations: Boolean = false,
"Configure the itinerary-filter-chain. NOTE! THESE PARAMETERS ARE USED FOR SERVER-SIDE TUNING AND IS AVAILABLE HERE FOR TESTING ONLY."
itineraryFilters: ItineraryFilters,
"The preferable language to use for text targeted the end user. Note! The data quality is limited, only stop and quay names are translates, and not in all places of the API."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -987,13 +987,13 @@ private Result<UpdateSuccess, List<UpdateError>> handleModifiedTrip(
if (tripTimes.getNumStops() == pattern.numberOfStops()) {
// All tripTimes should be handled the same way to always allow latest realtime-update to
// replace previous update regardless of realtimestate
cancelScheduledTrip(trip, serviceDate);
markScheduledTripAsDeleted(trip, serviceDate);

// Also check whether trip id has been used for previously ADDED/MODIFIED trip message and
// remove the previously created trip
removePreviousRealtimeUpdate(trip, serviceDate);

if (!tripTimes.isCanceled()) {
if (!tripTimes.isDeleted()) {
// Calculate modified stop-pattern
var modifiedStops = createModifiedStops(
pattern,
Expand All @@ -1008,6 +1008,7 @@ private Result<UpdateSuccess, List<UpdateError>> handleModifiedTrip(
);

if (modifiedStops != null && modifiedStops.isEmpty()) {
// Empty modified stops means that there is no calls for the trip, cancel it
tripTimes.cancelTrip();
} else {
// Add new trip
Expand Down Expand Up @@ -1167,26 +1168,26 @@ private void addTripOnServiceDateToBuffer(
}

/**
* Cancel scheduled trip in buffer given trip on service date
* Mark the scheduled trip in the buffer as deleted, given trip on service date
*
* @param serviceDate service date
* @return true if scheduled trip was cancelled
* @return true if scheduled trip was marked as deleted
*/
private boolean cancelScheduledTrip(Trip trip, final LocalDate serviceDate) {
private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDate) {
boolean success = false;

final TripPattern pattern = transitService.getPatternForTrip(trip);

if (pattern != null) {
// Cancel scheduled trip times for this trip in this pattern
// Mark scheduled trip times for this trip in this pattern as deleted
final Timetable timetable = pattern.getScheduledTimetable();
final TripTimes tripTimes = timetable.getTripTimes(trip);

if (tripTimes == null) {
LOG.warn("Could not cancel scheduled trip {}", trip.getId());
LOG.warn("Could not mark scheduled trip as deleted {}", trip.getId());
} else {
final TripTimes newTripTimes = new TripTimes(tripTimes);
newTripTimes.cancelTrip();
newTripTimes.deleteTrip();
buffer.update(pattern, newTripTimes, serviceDate);
success = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ private void mapPreferences(
});
callWith.argument("ignoreRealtimeUpdates", tr::setIgnoreRealtimeUpdates);
callWith.argument("includePlannedCancellations", tr::setIncludePlannedCancellations);
callWith.argument("includeRealtimeCancellations", tr::setIncludeRealtimeCancellations);
callWith.argument(
"relaxTransitSearchGeneralizedCostAtDestination",
(Double value) -> tr.withRaptor(it -> it.withRelaxGeneralizedCostAtDestination(value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ public static GraphQLFieldDefinition create(
.defaultValue(preferences.transit().includePlannedCancellations())
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("includeRealtimeCancellations")
.description(
"When true, service journeys cancelled by real-time updates will be included during this search."
)
.type(Scalars.GraphQLBoolean)
.defaultValue(preferences.transit().includeRealtimeCancellations())
.build()
)
.argument(
GraphQLArgument
.newArgument()
Expand Down
28 changes: 27 additions & 1 deletion src/main/java/org/opentripplanner/model/TripTimeOnDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
import org.opentripplanner.transit.model.timetable.StopTimeKey;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Represents a Trip at a specific stop index and on a specific service day. This is a read-only
* data transfer object used to pass information from the OTP internal model to the APIs.
*/
public class TripTimeOnDate {

private static final Logger LOG = LoggerFactory.getLogger(TripTimeOnDate.class);

public static final int UNDEFINED = -1;

private final TripTimes tripTimes;
Expand Down Expand Up @@ -169,7 +173,7 @@ public boolean isPredictionInaccurate() {
public boolean isCanceledEffectively() {
return (
isCancelledStop() ||
tripTimes.isCanceled() ||
tripTimes.isCanceledOrDeleted() ||
tripTimes.getTrip().getNetexAlteration().isCanceledOrReplaced()
);
}
Expand Down Expand Up @@ -213,12 +217,34 @@ public List<String> getHeadsignVias() {
}

public PickDrop getPickupType() {
if (tripTimes.isDeleted()) {
LOG.warn(
"Returning pickup type for a deleted trip {} on pattern {} on date {}. This indicates a bug.",
tripTimes.getTrip().getId(),
tripPattern.getId(),
serviceDate
);

return tripPattern.getBoardType(stopIndex);
}

return tripTimes.isCanceled() || tripTimes.isCancelledStop(stopIndex)
? PickDrop.CANCELLED
: tripPattern.getBoardType(stopIndex);
}

public PickDrop getDropoffType() {
if (tripTimes.isDeleted()) {
LOG.warn(
"Returning dropoff type for a deleted trip {} on pattern {} on date {}. This indicates a bug.",
tripTimes.getTrip().getId(),
tripPattern.getId(),
serviceDate
);

return tripPattern.getAlightType(stopIndex);
}

return tripTimes.isCanceled() || tripTimes.isCancelledStop(stopIndex)
? PickDrop.CANCELLED
: tripPattern.getAlightType(stopIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.opentripplanner.model.Timetable;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate;
import org.opentripplanner.transit.model.timetable.FrequencyEntry;
import org.opentripplanner.transit.model.timetable.RealTimeState;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -72,7 +71,7 @@ public TripPatternForDate map(Timetable timetable, LocalDate serviceDate) {
if (!serviceCodesRunning.contains(tripTimes.getServiceCode())) {
continue;
}
if (tripTimes.getRealTimeState() == RealTimeState.CANCELED) {
if (tripTimes.isDeleted()) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class RouteRequestTransitDataProviderFilter implements TransitDataProvide

private final boolean includePlannedCancellations;

private final boolean includeRealtimeCancellations;

private final List<TransitFilter> filters;

private final Set<FeedScopedId> bannedTrips;
Expand All @@ -46,6 +48,7 @@ public RouteRequestTransitDataProviderFilter(
request.wheelchair(),
request.preferences().wheelchair(),
request.preferences().transit().includePlannedCancellations(),
request.preferences().transit().includeRealtimeCancellations(),
Set.copyOf(request.journey().transit().bannedTrips()),
bannedRoutes(request.journey().transit().filters(), transitService.getAllRoutes()),
request.journey().transit().filters()
Expand All @@ -58,6 +61,7 @@ public RouteRequestTransitDataProviderFilter(
boolean wheelchairEnabled,
WheelchairPreferences wheelchairPreferences,
boolean includePlannedCancellations,
boolean includeRealtimeCancellations,
Set<FeedScopedId> bannedTrips,
Set<FeedScopedId> bannedRoutes,
List<TransitFilter> filters
Expand All @@ -66,6 +70,7 @@ public RouteRequestTransitDataProviderFilter(
this.wheelchairEnabled = wheelchairEnabled;
this.wheelchairPreferences = wheelchairPreferences;
this.includePlannedCancellations = includePlannedCancellations;
this.includeRealtimeCancellations = includeRealtimeCancellations;
this.bannedRoutes = Set.copyOf(bannedRoutes);
this.bannedTrips = bannedTrips;
this.filters = filters;
Expand Down Expand Up @@ -110,12 +115,17 @@ public boolean tripTimesPredicate(TripTimes tripTimes, boolean withFilters) {
}

if (!includePlannedCancellations) {
//noinspection RedundantIfStatement
if (trip.getNetexAlteration().isCanceledOrReplaced()) {
return false;
}
}

if (!includeRealtimeCancellations) {
if (tripTimes.isCanceled()) {
return false;
}
}

if (bannedTrips.contains(trip.getId())) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable {
private final DoubleAlgorithmFunction unpreferredCost;
private final boolean ignoreRealtimeUpdates;
private final boolean includePlannedCancellations;
private final boolean includeRealtimeCancellations;
private final RaptorPreferences raptor;

private TransitPreferences() {
Expand All @@ -40,6 +41,7 @@ private TransitPreferences() {
this.unpreferredCost = createLinearFunction(0.0, DEFAULT_ROUTE_RELUCTANCE);
this.ignoreRealtimeUpdates = false;
this.includePlannedCancellations = false;
this.includeRealtimeCancellations = false;
this.raptor = RaptorPreferences.DEFAULT;
}

Expand All @@ -51,6 +53,7 @@ private TransitPreferences(Builder builder) {
this.unpreferredCost = requireNonNull(builder.unpreferredCost);
this.ignoreRealtimeUpdates = builder.ignoreRealtimeUpdates;
this.includePlannedCancellations = builder.includePlannedCancellations;
this.includeRealtimeCancellations = builder.includeRealtimeCancellations;
this.raptor = requireNonNull(builder.raptor);
}

Expand Down Expand Up @@ -138,6 +141,13 @@ public boolean includePlannedCancellations() {
return includePlannedCancellations;
}

/**
* When true, trips cancelled in by real-time updates are included in this search.
*/
public boolean includeRealtimeCancellations() {
return includeRealtimeCancellations;
}

/**
* Set of options to use with Raptor. These are available here for testing purposes.
*/
Expand All @@ -154,6 +164,7 @@ public boolean equals(Object o) {
otherThanPreferredRoutesPenalty == that.otherThanPreferredRoutesPenalty &&
ignoreRealtimeUpdates == that.ignoreRealtimeUpdates &&
includePlannedCancellations == that.includePlannedCancellations &&
includeRealtimeCancellations == that.includeRealtimeCancellations &&
boardSlack.equals(that.boardSlack) &&
alightSlack.equals(that.alightSlack) &&
reluctanceForMode.equals(that.reluctanceForMode) &&
Expand All @@ -172,6 +183,7 @@ public int hashCode() {
unpreferredCost,
ignoreRealtimeUpdates,
includePlannedCancellations,
includeRealtimeCancellations,
raptor
);
}
Expand All @@ -197,6 +209,10 @@ public String toString() {
"includePlannedCancellations",
includePlannedCancellations != DEFAULT.includePlannedCancellations
)
.addBoolIfTrue(
"includeRealtimeCancellations",
includeRealtimeCancellations != DEFAULT.includeRealtimeCancellations
)
.addObj("raptor", raptor, DEFAULT.raptor)
.toString();
}
Expand All @@ -213,6 +229,7 @@ public static class Builder {
private DoubleAlgorithmFunction unpreferredCost;
private boolean ignoreRealtimeUpdates;
private boolean includePlannedCancellations;
private boolean includeRealtimeCancellations;
private RaptorPreferences raptor;

public Builder(TransitPreferences original) {
Expand All @@ -224,6 +241,7 @@ public Builder(TransitPreferences original) {
this.unpreferredCost = original.unpreferredCost;
this.ignoreRealtimeUpdates = original.ignoreRealtimeUpdates;
this.includePlannedCancellations = original.includePlannedCancellations;
this.includeRealtimeCancellations = original.includeRealtimeCancellations;
this.raptor = original.raptor;
}

Expand Down Expand Up @@ -279,6 +297,11 @@ public Builder setIncludePlannedCancellations(boolean includePlannedCancellation
return this;
}

public Builder setIncludeRealtimeCancellations(boolean includeRealtimeCancellations) {
this.includeRealtimeCancellations = includeRealtimeCancellations;
return this;
}

public Builder withRaptor(Consumer<RaptorPreferences.Builder> body) {
this.raptor = raptor.copyOf().apply(body).build();
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ private static boolean isReplacedByAnotherPattern(
}

public static boolean skipByTripCancellation(TripTimes tripTimes, boolean includeCancellations) {
if (tripTimes.isDeleted()) {
return true;
}

return (
(tripTimes.isCanceled() || tripTimes.getTrip().getNetexAlteration().isCanceledOrReplaced()) &&
!includeCancellations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ public enum RealTimeState {
* trip pattern of the scheduled trip.
*/
MODIFIED,

/**
* The trip should not be visible to the end user. Either it has been set as deleted in the
* real-time feed, or it has been replaced by another trip on another pattern.
*/
DELETED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -336,13 +336,27 @@ public boolean isScheduled() {
return realTimeState == RealTimeState.SCHEDULED;
}

/**
* @return true if this TripTimes is canceled or soft-deleted
*/
public boolean isCanceledOrDeleted() {
return isCanceled() || isDeleted();
}

/**
* @return true if this TripTimes is canceled
*/
public boolean isCanceled() {
return realTimeState == RealTimeState.CANCELED;
}

/**
* @return true if this TripTimes is soft-deleted, and should not be visible to the user
*/
public boolean isDeleted() {
return realTimeState == RealTimeState.DELETED;
}

/**
* @return the real-time state of this TripTimes
*/
Expand Down Expand Up @@ -384,6 +398,11 @@ public void cancelTrip() {
realTimeState = RealTimeState.CANCELED;
}

/** Soft delete the entire trip */
public void deleteTrip() {
realTimeState = RealTimeState.DELETED;
}

public void updateDepartureTime(final int stop, final int time) {
prepareForRealTimeUpdates();
departureTimes[stop] = time;
Expand Down
Loading

0 comments on commit afc954a

Please sign in to comment.