From c19a0a424bb3ed646f998863f4f7f753b2df7e44 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 18 Sep 2023 22:49:04 +0300 Subject: [PATCH 001/120] Expand OccupancyStatus model to include all GTFS RT values --- .../ext/siri/mapper/OccupancyMapper.java | 2 +- .../mapping/OccupancyStatusMapper.java | 25 +++++++++++ .../ext/transmodelapi/model/EnumTypes.java | 4 +- .../model/siri/et/EstimatedCallType.java | 5 ++- .../model/timetable/OccupancyStatus.java | 43 ++++++++++++++----- .../transit/model/timetable/TripTimes.java | 4 +- 6 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java diff --git a/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java b/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java index d745ea3d473..61b7a15798a 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java @@ -10,7 +10,7 @@ public class OccupancyMapper { public static OccupancyStatus mapOccupancyStatus(OccupancyEnumeration occupancy) { if (occupancy == null) { - return OccupancyStatus.NO_DATA; + return OccupancyStatus.NO_DATA_AVAILABLE; } return switch (occupancy) { case SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java new file mode 100644 index 00000000000..8d1b74daa9e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java @@ -0,0 +1,25 @@ +package org.opentripplanner.ext.transmodelapi.mapping; + +import org.opentripplanner.transit.model.timetable.OccupancyStatus; + +/** + * Transmodel API supports a subset of {@link OccupancyStatus} and this mapper can be used to map + * any value to a value supported by the transmodel API. + */ +public class OccupancyStatusMapper { + + /** + * @return {@link OccupancyStatus} supported by the Transmodel API that is the closes match to the + * original. + */ + public static OccupancyStatus mapStatus(OccupancyStatus occupancyStatus) { + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE; + case MANY_SEATS_AVAILABLE, EMPTY -> OccupancyStatus.MANY_SEATS_AVAILABLE; + case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE; + case STANDING_ROOM_ONLY, CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY; + case FULL -> OccupancyStatus.FULL; + case NOT_ACCEPTING_PASSENGERS, NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS; + }; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index ff7a8002504..ac10b59f398 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -195,7 +195,7 @@ public class EnumTypes { .name("OccupancyStatus") .value( "noData", - OccupancyStatus.NO_DATA, + OccupancyStatus.NO_DATA_AVAILABLE, "The vehicle or carriage doesn't have any occupancy data available." ) .value( @@ -205,7 +205,7 @@ public class EnumTypes { ) .value( "fewSeatsAvailable", - OccupancyStatus.SEATS_AVAILABLE, + OccupancyStatus.FEW_SEATS_AVAILABLE, "The vehicle or carriage has a few seats available." ) .value( diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java index 3b7156854d9..7a39aee6cca 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java @@ -15,6 +15,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import org.opentripplanner.ext.transmodelapi.mapping.OccupancyStatusMapper; import org.opentripplanner.ext.transmodelapi.model.EnumTypes; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; @@ -202,7 +203,9 @@ public static GraphQLObjectType create( .name("occupancyStatus") .type(new GraphQLNonNull(EnumTypes.OCCUPANCY_STATUS)) .dataFetcher(environment -> - ((TripTimeOnDate) environment.getSource()).getOccupancyStatus() + OccupancyStatusMapper.mapStatus( + ((TripTimeOnDate) environment.getSource()).getOccupancyStatus() + ) ) .build() ) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java index 8be2f468b46..f47eb01baa2 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java @@ -2,34 +2,55 @@ /** * OccupancyStatus to be exposed in the API. The values are based on GTFS-RT - * (transit_realtime.VehiclePosition.OccupancyStatus), but is currently a subset that easily can - * be mapped to the Nordic SIRI-profile (SIRI 2.1) - * - * Descriptions are also based on the SIRI-profile + * (transit_realtime.VehiclePosition.OccupancyStatus) that can be easily be mapped to the Nordic + * SIRI-profile (SIRI 2.1) + *

+ * Descriptions are copied from the GTFS-RT specification. */ public enum OccupancyStatus { /** * Default. There is no occupancy-data on this departure */ - NO_DATA, + NO_DATA_AVAILABLE, /** - * More than ~50% of seats available + * The vehicle is considered empty by most measures, and has few or no passengers onboard, but is + * still accepting passengers. + */ + EMPTY, + /** + * The vehicle or carriage has a large number of seats available. The amount of free seats out of + * the total seats available to be considered large enough to fall into this category is + * determined at the discretion of the producer. */ MANY_SEATS_AVAILABLE, /** - * Less than ~50% of seats available + * The vehicle or carriage has a small number of seats available. The amount of free seats out of + * the total seats available to be considered small enough to fall into this category is + * determined at the discretion of the producer. */ - SEATS_AVAILABLE, + FEW_SEATS_AVAILABLE, /** - * Less than ~10% of seats available + * The vehicle or carriage can currently accommodate only standing passengers. */ STANDING_ROOM_ONLY, /** - * Close to or at full capacity + * The vehicle or carriage can currently accommodate only standing passengers and has limited + * space for them. + */ + CRUSHED_STANDING_ROOM_ONLY, + /** + * The vehicle is considered full by most measures, but may still be allowing passengers to + * board. */ FULL, /** - * If vehicle/carriage is not in use / unavailable, or passengers are only allowed to alight due to e.g. crowding + * The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts + * passengers for boarding. */ NOT_ACCEPTING_PASSENGERS, + /** + * The vehicle or carriage is not boardable and never accepts passengers. Useful for special + * vehicles or carriages (engine, maintenance carriage, etc…). + */ + NOT_BOARDABLE, } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 8b86a3b37b3..c6b4ea51b0f 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -324,7 +324,7 @@ public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { public OccupancyStatus getOccupancyStatus(int stop) { if (this.occupancyStatus == null) { - return OccupancyStatus.NO_DATA; + return OccupancyStatus.NO_DATA_AVAILABLE; } return this.occupancyStatus[stop]; } @@ -680,7 +680,7 @@ private void prepareForRealTimeUpdates() { arrivalTimes[i] += timeShift; departureTimes[i] += timeShift; stopRealTimeStates[i] = StopRealTimeState.DEFAULT; - occupancyStatus[i] = OccupancyStatus.NO_DATA; + occupancyStatus[i] = OccupancyStatus.NO_DATA_AVAILABLE; } // Update the real-time state From e84a514b15bef36ac81fb315d9436690c31229e5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 12:02:30 +0300 Subject: [PATCH 002/120] Load occupancy status from GTFS RT into positions --- .../model/RealtimeVehiclePosition.java | 8 +++++- .../model/RealtimeVehiclePositionBuilder.java | 10 +++++++- .../VehiclePositionPatternMatcher.java | 25 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java index 56c99c445c1..ffd76771f5d 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java @@ -4,6 +4,7 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; /** @@ -32,7 +33,12 @@ public record RealtimeVehiclePosition( * Status of the vehicle, ie. if approaching the next stop or if it is there already. */ StopRelationship stop, - Trip trip + Trip trip, + + /** + * How full the vehicle is and is it still accepting passengers. + */ + OccupancyStatus occupancyStatus ) { public static RealtimeVehiclePositionBuilder builder() { return new RealtimeVehiclePositionBuilder(); diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java index 00f2c3f7b51..809cfe5ab79 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java @@ -7,6 +7,7 @@ import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; public class RealtimeVehiclePositionBuilder { @@ -20,6 +21,7 @@ public class RealtimeVehiclePositionBuilder { private StopStatus stopStatus = StopStatus.IN_TRANSIT_TO; private StopLocation stop; private Trip trip; + private OccupancyStatus occupancyStatus; public RealtimeVehiclePositionBuilder setVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; @@ -66,6 +68,11 @@ public RealtimeVehiclePositionBuilder setTrip(Trip trip) { return this; } + public RealtimeVehiclePositionBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + this.occupancyStatus = occupancyStatus; + return this; + } + public RealtimeVehiclePosition build() { var stop = Optional .ofNullable(this.stop) @@ -79,7 +86,8 @@ public RealtimeVehiclePosition build() { heading, time, stop, - trip + trip, + occupancyStatus ); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java index 1fb05c9dcec..c181bebb6f8 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java @@ -37,6 +37,7 @@ import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.spi.ResultLogger; @@ -226,7 +227,7 @@ private RealtimeVehiclePosition mapVehiclePosition( } if (vehiclePosition.hasCurrentStatus()) { - newPosition.setStopStatus(toModel(vehiclePosition.getCurrentStatus())); + newPosition.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -259,6 +260,10 @@ else if (vehiclePosition.hasCurrentStopSequence()) { newPosition.setTrip(trip); + if (vehiclePosition.hasOccupancyStatus()) { + newPosition.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + } + return newPosition.build(); } @@ -271,7 +276,7 @@ private static boolean validStopIndex(int stopIndex, List stopsOnV private record TemporalDistance(LocalDate date, long distance) {} - private static StopStatus toModel(VehicleStopStatus currentStatus) { + private static StopStatus stopStatusToModel(VehicleStopStatus currentStatus) { return switch (currentStatus) { case IN_TRANSIT_TO -> StopStatus.IN_TRANSIT_TO; case INCOMING_AT -> StopStatus.INCOMING_AT; @@ -279,6 +284,22 @@ private static StopStatus toModel(VehicleStopStatus currentStatus) { }; } + private static OccupancyStatus occupancyStatusToModel( + VehiclePosition.OccupancyStatus occupancyStatus + ) { + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE; + case EMPTY -> OccupancyStatus.EMPTY; + case MANY_SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE; + case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE; + case STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY; + case CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY; + case FULL -> OccupancyStatus.FULL; + case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS; + case NOT_BOARDABLE -> OccupancyStatus.NOT_BOARDABLE; + }; + } + private static String toString(VehiclePosition vehiclePosition) { try { return JsonFormat.printer().omittingInsignificantWhitespace().print(vehiclePosition); From b494353a4ce348b2fb1b2db9fbc79016f78a494e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 23:45:43 +0300 Subject: [PATCH 003/120] Support fuzzy trip matching for vehicle positions --- docs/UpdaterConfig.md | 1 + .../config/framework/json/OtpVersion.java | 3 ++- .../VehiclePositionsUpdaterConfig.java | 15 ++++++++++++- .../PollingVehiclePositionUpdater.java | 8 ++++++- .../VehiclePositionPatternMatcher.java | 21 +++++++++++++++---- .../VehiclePositionsUpdaterParameters.java | 3 ++- .../VehiclePositionsMatcherTest.java | 12 +++++++---- 7 files changed, 51 insertions(+), 12 deletions(-) diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index cd3ee3f7035..12c6c919bde 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -228,6 +228,7 @@ The information is downloaded in a single HTTP request and polled regularly. | type = "vehicle-positions" | `enum` | The type of the updater. | *Required* | | 1.5 | | feedId | `string` | Feed ID to which the update should be applied. | *Required* | | 2.2 | | frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 | +| fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.5 | | url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 | | [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java index 9cfb7237c66..6caf9082cff 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java @@ -9,7 +9,8 @@ public enum OtpVersion { V2_1("2.1"), V2_2("2.2"), V2_3("2.3"), - V2_4("2.4"); + V2_4("2.4"), + V2_5("2.5"); private final String text; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java index 4090da7991a..da3b99d002d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java @@ -2,6 +2,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -25,7 +26,19 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd .since(V2_2) .summary("The URL of GTFS-RT protobuf HTTP resource to download the positions from.") .asUri(); + var fuzzyTripMatching = c + .of("fuzzyTripMatching") + .since(V2_5) + .summary("Whether to match trips fuzzily.") + .asBoolean(false); var headers = HttpHeadersConfig.headers(c, V2_3); - return new VehiclePositionsUpdaterParameters(updaterRef, feedId, url, frequency, headers); + return new VehiclePositionsUpdaterParameters( + updaterRef, + feedId, + url, + frequency, + headers, + fuzzyTripMatching + ); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index 31d94caeb00..ad525fe5104 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -8,7 +8,9 @@ import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.slf4j.Logger; @@ -42,6 +44,9 @@ public PollingVehiclePositionUpdater( this.vehiclePositionSource = new GtfsRealtimeHttpVehiclePositionSource(params.url(), params.headers()); var index = transitModel.getTransitModelIndex(); + var fuzzyTripMatcher = params.fuzzyTripMatching() + ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)) + : null; this.vehiclePositionPatternMatcher = new VehiclePositionPatternMatcher( params.feedId(), @@ -49,7 +54,8 @@ public PollingVehiclePositionUpdater( trip -> index.getPatternForTrip().get(trip), (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), vehiclePositionService, - transitModel.getTimeZone() + transitModel.getTimeZone(), + fuzzyTripMatcher ); LOG.info( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java index c181bebb6f8..5e7fb0c1dab 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java @@ -40,6 +40,7 @@ import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; @@ -62,6 +63,7 @@ public class VehiclePositionPatternMatcher { private final Function getTripForId; private final Function getStaticPattern; private final BiFunction getRealtimePattern; + private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher; private Set patternsInPreviousUpdate = Set.of(); @@ -71,7 +73,8 @@ public VehiclePositionPatternMatcher( Function getStaticPattern, BiFunction getRealtimePattern, VehiclePositionRepository repository, - ZoneId timeZoneId + ZoneId timeZoneId, + GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher ) { this.feedId = feedId; this.getTripForId = getTripForId; @@ -79,6 +82,7 @@ public VehiclePositionPatternMatcher( this.getRealtimePattern = getRealtimePattern; this.repository = repository; this.timeZoneId = timeZoneId; + this.fuzzyTripMatcher = fuzzyTripMatcher; } /** @@ -308,6 +312,11 @@ private static String toString(VehiclePosition vehiclePosition) { } } + private VehiclePosition fuzzilySetTrip(VehiclePosition vehiclePosition) { + var trip = fuzzyTripMatcher.match(feedId, vehiclePosition.getTrip()); + return vehiclePosition.toBuilder().setTrip(trip).build(); + } + private Result toRealtimeVehiclePosition( String feedId, VehiclePosition vehiclePosition @@ -320,7 +329,11 @@ private Result toRealtimeVehiclePosition return Result.failure(UpdateError.noTripId(INVALID_INPUT_STRUCTURE)); } - var tripId = vehiclePosition.getTrip().getTripId(); + var vehiclePositionWithTripId = fuzzyTripMatcher == null + ? vehiclePosition + : fuzzilySetTrip(vehiclePosition); + + var tripId = vehiclePositionWithTripId.getTrip().getTripId(); if (StringUtils.hasNoValue(tripId)) { return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.NO_TRIP_ID)); @@ -338,7 +351,7 @@ private Result toRealtimeVehiclePosition } var serviceDate = Optional - .of(vehiclePosition.getTrip().getStartDate()) + .of(vehiclePositionWithTripId.getTrip().getStartDate()) .map(Strings::emptyToNull) .flatMap(ServiceDateUtils::parseStringToOptional) .orElseGet(() -> inferServiceDate(trip)); @@ -359,7 +372,7 @@ private Result toRealtimeVehiclePosition // Add position to pattern var newPosition = mapVehiclePosition( - vehiclePosition, + vehiclePositionWithTripId, pattern.getStops(), trip, staticTripTimes::stopIndexOfGtfsSequence diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java index 7d08158903a..16db929c317 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java @@ -11,7 +11,8 @@ public record VehiclePositionsUpdaterParameters( String feedId, URI url, Duration frequency, - HttpHeaders headers + HttpHeaders headers, + boolean fuzzyTripMatching ) implements PollingGraphUpdaterParameters { public VehiclePositionsUpdaterParameters { diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java index 4a1a5d287bf..8cac79fc008 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java @@ -81,7 +81,8 @@ public void tripNotFoundInPattern() { ignored -> pattern, (id, time) -> pattern, service, - zoneId + zoneId, + null ); var positions = List.of(vehiclePosition(secondTripId)); @@ -112,7 +113,8 @@ public void sequenceId() { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var pos = VehiclePosition @@ -165,7 +167,8 @@ private void testVehiclePositions(VehiclePosition pos) { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var positions = List.of(pos); @@ -220,7 +223,8 @@ public void clearOldTrips() { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var pos1 = vehiclePosition(tripId1); From da163fd5f16c19483a4eb2949e9200670c6474c3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 23:52:17 +0300 Subject: [PATCH 004/120] Expose occupancy status for a trip through GTFS API --- .../gtfsgraphqlapi/datafetchers/TripImpl.java | 17 +++++ .../generated/GraphQLDataFetchers.java | 20 ++++- .../generated/GraphQLTypes.java | 13 ++++ .../generated/graphql-codegen.yml | 1 + .../gtfsgraphqlapi/model/TripOccupancy.java | 8 ++ .../resources/gtfsgraphqlapi/schema.graphqls | 76 +++++++++++++++++++ .../VehiclePositionService.java | 7 ++ .../DefaultVehiclePositionService.java | 16 ++++ 8 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java index a12cedff82c..437b96c5ec6 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java @@ -21,12 +21,14 @@ import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLWheelchairBoarding; +import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; +import org.opentripplanner.service.vehiclepositions.VehiclePositionService; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -372,6 +374,17 @@ public DataFetcher wheelchairAccessible() { return environment -> GraphQLUtils.toGraphQL(getSource(environment).getWheelchairBoarding()); } + @Override + public DataFetcher occupancy() { + return environment -> { + Trip trip = getSource(environment); + TripPattern pattern = getTransitService(environment).getPatternForTrip(trip); + return new TripOccupancy( + getVehiclePositionsService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) + ); + }; + } + private List getStops(DataFetchingEnvironment environment) { TripPattern tripPattern = getTripPattern(environment); if (tripPattern == null) { @@ -396,6 +409,10 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { return environment.getContext().transitService(); } + private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { + return environment.getContext().vehiclePositionService(); + } + private Trip getSource(DataFetchingEnvironment environment) { return environment.getSource(); } diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index 318c4fc2a11..adad8efc6d3 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -21,6 +21,7 @@ import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode; import org.opentripplanner.ext.gtfsgraphqlapi.model.RideHailingProvider; import org.opentripplanner.ext.gtfsgraphqlapi.model.StopPosition; +import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; import org.opentripplanner.model.SystemNotice; @@ -309,9 +310,12 @@ public interface GraphQLDefaultFareProduct { } /** - * Departure row is a location, which lists departures of a certain pattern from a - * stop. Departure rows are identified with the pattern, so querying departure rows - * will return only departures from one stop per pattern + * Departure row is a combination of a pattern and a stop of that pattern. + * + * They are de-duplicated so for each pattern there will only be a single departure row. + * + * This is useful if you want to show a list of stop/pattern combinations but want each pattern to be + * listed only once. */ public interface GraphQLDepartureRow { public DataFetcher id(); @@ -1029,6 +1033,8 @@ public interface GraphQLTrip { public DataFetcher id(); + public DataFetcher occupancy(); + public DataFetcher pattern(); public DataFetcher route(); @@ -1056,6 +1062,14 @@ public interface GraphQLTrip { public DataFetcher wheelchairAccessible(); } + /** + * Occupancy of a vehicle on a trip. This should include the most recent occupancy information + * available for a trip. Historic data might not be available. + */ + public interface GraphQLTripOccupancy { + public DataFetcher occupancyStatus(); + } + /** This is used for alert entities that we don't explicitly handle or they are missing. */ public interface GraphQLUnknown { public DataFetcher description(); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java index 3861f0e49ef..5fca60671b9 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java @@ -747,6 +747,19 @@ public enum GraphQLMode { WALK, } + /** Occupancy status of a vehicle. */ + public enum GraphQLOccupancyStatus { + CRUSHED_STANDING_ROOM_ONLY, + EMPTY, + FEW_SEATS_AVAILABLE, + FULL, + MANY_SEATS_AVAILABLE, + NOT_ACCEPTING_PASSENGERS, + NOT_BOARDABLE, + NO_DATA_AVAILABLE, + STANDING_ROOM_ONLY, + } + public static class GraphQLOpeningHoursDatesArgs { private List dates; diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index 7408e72cbca..aa9fac20d27 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -85,6 +85,7 @@ config: TicketType: org.opentripplanner.ext.fares.model.FareRuleSet#FareRuleSet TranslatedString: java.util.Map#Map.Entry Trip: org.opentripplanner.transit.model.timetable.Trip#Trip + TripOccupancy: org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy#TripOccupancy Unknown: org.opentripplanner.ext.gtfsgraphqlapi.model.UnknownModel#UnknownModel VehicleParking: org.opentripplanner.routing.vehicle_parking.VehicleParking#VehicleParking VehicleParkingSpaces: org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces#VehicleParkingSpaces diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java new file mode 100644 index 00000000000..3c48e727050 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java @@ -0,0 +1,8 @@ +package org.opentripplanner.ext.gtfsgraphqlapi.model; + +import org.opentripplanner.transit.model.timetable.OccupancyStatus; + +/** + * Record for holding trip occupancy information. + */ +public record TripOccupancy(OccupancyStatus occupancyStatus) {} diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index aaf312244d1..276a8ae6f53 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -3394,6 +3394,65 @@ enum AbsoluteDirection { NORTHWEST } +"""Occupancy status of a vehicle.""" +enum OccupancyStatus { + """Default. There is no occupancy-data on this departure.""" + NO_DATA_AVAILABLE + + """ + The vehicle is considered empty by most measures, and has few or no passengers onboard, but is + still accepting passengers. There isn't a big difference between this and MANY_SEATS_AVAILABLE + so it's possible to handle them as the same value, if one wants to limit the number of different + values. + """ + EMPTY + + """ + The vehicle or carriage has a large number of seats available. The amount of free seats out of + the total seats available to be considered large enough to fall into this category is + determined at the discretion of the producer. There isn't a big difference between this and + EMPTY so it's possible to handle them as the same value, if one wants to limit the number of + different values. + """ + MANY_SEATS_AVAILABLE + + """ + The vehicle or carriage has a small number of seats available. The amount of free seats out of + the total seats available to be considered small enough to fall into this category is + determined at the discretion of the producer. + """ + FEW_SEATS_AVAILABLE + + """The vehicle or carriage can currently accommodate only standing passengers.""" + STANDING_ROOM_ONLY + + """ + The vehicle or carriage can currently accommodate only standing passengers and has limited + space for them. There isn't a big difference between this and FULL so it's possible to handle + them as the same value, if one wants to limit the number of different values. + """ + CRUSHED_STANDING_ROOM_ONLY + + """ + The vehicle is considered full by most measures, but may still be allowing passengers to + board. + """ + FULL + + """ + The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts + passengers for boarding. + """ + NOT_ACCEPTING_PASSENGERS + + """ + The vehicle or carriage is not boardable and never accepts passengers. Useful for special + vehicles or carriages (engine, maintenance carriage, etc…). It might not make sense to show + these types of trips to passengers at all. + """ + NOT_BOARDABLE +} + type step { """The distance in meters that this step takes.""" distance: Float @@ -3972,6 +4031,12 @@ type Trip implements Node { """ types: [TripAlertType] ): [Alert] + + """ + The latest realtime occupancy information for the latest occurance of this + trip. + """ + occupancy: TripOccupancy } """Entities, which are relevant for a trip and can contain alerts""" @@ -3998,6 +4063,17 @@ enum TripAlertType { STOPS_ON_TRIP } +""" +Occupancy of a vehicle on a trip. This should include the most recent occupancy information +available for a trip. Historic data might not be available. +""" +type TripOccupancy { + """ + Occupancy information mapped to a limited set of descriptive states. + """ + occupancyStatus: OccupancyStatus +} + """ A system notice is used to tag elements with system information for debugging or other system related purpose. One use-case is to run a routing search with diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java index b4cbf1176ea..bc36668f294 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java @@ -2,11 +2,18 @@ import java.util.List; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; public interface VehiclePositionService { /** * Get the vehicle positions for a certain trip. */ List getVehiclePositions(TripPattern pattern); + + /** + * Get the latest occupancy status for a certain trip. + */ + OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId); } diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java index a9027f044d2..5c762a89009 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java @@ -2,13 +2,17 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; import org.opentripplanner.service.vehiclepositions.VehiclePositionService; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; @Singleton public class DefaultVehiclePositionService @@ -34,4 +38,16 @@ public List getVehiclePositions(TripPattern pattern) { // the list is made immutable during insertion, so we can safely return them return positions.getOrDefault(pattern, List.of()); } + + @Nonnull + @Override + public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) { + return positions + .getOrDefault(pattern, List.of()) + .stream() + .filter(vehicle -> tripId.equals(vehicle.trip().getId())) + .max(Comparator.comparing(vehicle -> vehicle.time())) + .map(vehicle -> vehicle.occupancyStatus()) + .orElse(OccupancyStatus.NO_DATA_AVAILABLE); + } } From 7946a9b2e058a604f192c9e2034597dc38ae18f8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 11:29:27 +0300 Subject: [PATCH 005/120] Rename VehiclePosition -> RealtimeVehicle apart from updater --- .../GraphQLIntegrationTest.java | 4 +- .../mapping/RouteRequestMapperTest.java | 4 +- .../mapping/TripRequestMapperTest.java | 5 +- .../gtfsgraphqlapi/GraphQLRequestContext.java | 6 +- .../datafetchers/PatternImpl.java | 12 +-- .../datafetchers/StopRelationshipImpl.java | 2 +- .../gtfsgraphqlapi/datafetchers/TripImpl.java | 8 +- .../datafetchers/VehiclePositionImpl.java | 6 +- .../generated/GraphQLDataFetchers.java | 6 +- .../generated/graphql-codegen.yml | 4 +- .../RealtimeVehicleRepository.java | 29 +++++++ .../RealtimeVehicleService.java} | 10 +-- .../RealtimeVehicleRepositoryModule.java | 16 ++++ .../RealtimeVehicleServiceModule.java | 16 ++++ .../DefaultRealtimeVehicleService.java} | 30 +++---- .../model/RealtimeVehicle.java} | 12 +-- .../model/RealtimeVehicleBuilder.java} | 32 ++++---- .../VehiclePositionRepository.java | 29 ------- .../VehiclePositionsRepositoryModule.java | 16 ---- .../VehiclePositionsServiceModule.java | 16 ---- .../api/OtpServerRequestContext.java | 4 +- .../configure/ConstructApplication.java | 8 +- .../ConstructApplicationFactory.java | 16 ++-- .../configure/ConstructApplicationModule.java | 6 +- .../server/DefaultServerRequestContext.java | 16 ++-- .../configure/UpdaterConfigurator.java | 18 ++-- .../PollingVehiclePositionUpdater.java | 18 ++-- ...ava => RealtimeVehiclePatternMatcher.java} | 82 +++++++++---------- .../VehiclePositionUpdaterRunnable.java | 4 +- .../opentripplanner/TestServerContext.java | 10 +-- .../transit/speed_test/SpeedTest.java | 6 +- ...t.java => RealtimeVehicleMatcherTest.java} | 70 ++++++++-------- 32 files changed, 260 insertions(+), 261 deletions(-) create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java rename src/main/java/org/opentripplanner/service/{vehiclepositions/VehiclePositionService.java => realtimevehicles/RealtimeVehicleService.java} (56%) create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java rename src/main/java/org/opentripplanner/service/{vehiclepositions/internal/DefaultVehiclePositionService.java => realtimevehicles/internal/DefaultRealtimeVehicleService.java} (50%) rename src/main/java/org/opentripplanner/service/{vehiclepositions/model/RealtimeVehiclePosition.java => realtimevehicles/model/RealtimeVehicle.java} (79%) rename src/main/java/org/opentripplanner/service/{vehiclepositions/model/RealtimeVehiclePositionBuilder.java => realtimevehicles/model/RealtimeVehicleBuilder.java} (57%) delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java rename src/main/java/org/opentripplanner/updater/vehicle_position/{VehiclePositionPatternMatcher.java => RealtimeVehiclePatternMatcher.java} (82%) rename src/test/java/org/opentripplanner/updater/vehicle_position/{VehiclePositionsMatcherTest.java => RealtimeVehicleMatcherTest.java} (81%) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index 56466db1e03..7f8226413c6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -68,7 +68,7 @@ import org.opentripplanner.routing.impl.TransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.standalone.config.framework.json.JsonSupport; import org.opentripplanner.test.support.FilePatternSource; @@ -222,7 +222,7 @@ public TransitAlertService getTransitAlertService() { new DefaultFareService(), graph.getVehicleParkingService(), new DefaultVehicleRentalService(), - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), GraphFinder.getInstance(graph, transitService::findRegularStop), new RouteRequest() ); diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java index c9ea4666b06..6b53f1acb56 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java @@ -29,7 +29,7 @@ import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.service.DefaultTransitService; @@ -51,7 +51,7 @@ class RouteRequestMapperTest implements PlanTestConstants { new DefaultFareService(), graph.getVehicleParkingService(), new DefaultVehicleRentalService(), - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), GraphFinder.getInstance(graph, transitService::findRegularStop), new RouteRequest() ); diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 2d8a03c5ce4..7a2d9a64465 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -11,7 +11,6 @@ import graphql.schema.DataFetchingEnvironmentImpl; import io.micrometer.core.instrument.Metrics; import java.time.Duration; -import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -29,7 +28,7 @@ import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; import org.opentripplanner.routing.core.BicycleOptimizeType; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeRepository; import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeService; @@ -74,7 +73,7 @@ public class TripRequestMapperTest implements PlanTestConstants { Metrics.globalRegistry, RouterConfig.DEFAULT.vectorTileLayers(), new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()), - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), new DefaultVehicleRentalService(), RouterConfig.DEFAULT.flexConfig(), List.of(), diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java index 166aa93fd35..414de1b43bd 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java @@ -6,7 +6,7 @@ import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.service.TransitService; @@ -17,7 +17,7 @@ public record GraphQLRequestContext( FareService fareService, VehicleParkingService vehicleParkingService, VehicleRentalService vehicleRentalService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { @@ -28,7 +28,7 @@ public static GraphQLRequestContext ofServerContext(OtpServerRequestContext cont context.graph().getFareService(), context.graph().getVehicleParkingService(), context.vehicleRentalService(), - context.vehiclePositionService(), + context.realtimeVehicleService(), context.graphFinder(), context.defaultRouteRequest() ); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java index f38a024b721..e165b7f7490 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java @@ -22,8 +22,8 @@ import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -227,9 +227,9 @@ public DataFetcher> tripsForDate() { } @Override - public DataFetcher> vehiclePositions() { + public DataFetcher> vehiclePositions() { return environment -> - getVehiclePositionsService(environment).getVehiclePositions(this.getSource(environment)); + getRealtimeVehiclesService(environment).getRealtimeVehicles(this.getSource(environment)); } private Agency getAgency(DataFetchingEnvironment environment) { @@ -252,8 +252,8 @@ private List getTrips(DataFetchingEnvironment environment) { return getSource(environment).scheduledTripsAsStream().collect(Collectors.toList()); } - private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { - return environment.getContext().vehiclePositionService(); + private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { + return environment.getContext().realtimeVehicleService(); } private TransitService getTransitService(DataFetchingEnvironment environment) { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java index 1fccf47e828..bdfdf993910 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java @@ -4,7 +4,7 @@ import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers.GraphQLStopRelationship; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLVehicleStopStatus; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship; public class StopRelationshipImpl implements GraphQLStopRelationship { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java index 437b96c5ec6..0c92ac28e8c 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java @@ -28,7 +28,7 @@ import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -380,7 +380,7 @@ public DataFetcher occupancy() { Trip trip = getSource(environment); TripPattern pattern = getTransitService(environment).getPatternForTrip(trip); return new TripOccupancy( - getVehiclePositionsService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) + getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) ); }; } @@ -409,8 +409,8 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { return environment.getContext().transitService(); } - private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { - return environment.getContext().vehiclePositionService(); + private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { + return environment.getContext().realtimeVehicleService(); } private Trip getSource(DataFetchingEnvironment environment) { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java index 861c4b61310..c64e15ce407 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java @@ -3,8 +3,8 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers.GraphQLVehiclePosition; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship; import org.opentripplanner.transit.model.timetable.Trip; public class VehiclePositionImpl implements GraphQLVehiclePosition { @@ -54,7 +54,7 @@ public DataFetcher vehicleId() { return env -> getSource(env).vehicleId().toString(); } - private RealtimeVehiclePosition getSource(DataFetchingEnvironment environment) { + private RealtimeVehicle getSource(DataFetchingEnvironment environment) { return environment.getSource(); } } diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index adad8efc6d3..95fe58cd894 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -44,8 +44,8 @@ import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; @@ -560,7 +560,7 @@ public interface GraphQLPattern { public DataFetcher> tripsForDate(); - public DataFetcher> vehiclePositions(); + public DataFetcher> vehiclePositions(); } public interface GraphQLPlace { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index aa9fac20d27..c71c0e8dfa2 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -92,8 +92,8 @@ config: VehicleParkingState: org.opentripplanner.routing.vehicle_parking.VehicleParkingState#VehicleParkingState VertexType: String SystemNotice: org.opentripplanner.model.SystemNotice#SystemNotice - VehiclePosition: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition#RealtimeVehiclePosition - StopRelationship: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship#StopRelationship + VehiclePosition: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle#RealtimeVehicle + StopRelationship: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship#StopRelationship WheelchairBoarding: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLWheelchairBoarding FormFactor: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLFormFactor PropulsionType: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLPropulsionType diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java new file mode 100644 index 00000000000..acf558cb2d1 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java @@ -0,0 +1,29 @@ +package org.opentripplanner.service.realtimevehicles; + +import java.util.List; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.transit.model.network.TripPattern; + +public interface RealtimeVehicleRepository { + /** + * For the given pattern set all realtime vehicles. + *

+ * The list is expected to be exhaustive: all existing vehicles will be overridden. + *

+ * This means that if there are two updaters providing vehicles for the same pattern they + * overwrite each other. + */ + void setRealtimeVehicles(TripPattern pattern, List updates); + + /** + * Remove all vehicles for a given pattern. + *

+ * This is useful to clear old vehicles for which there are no more updates and we assume that + * they have stopped their trip. + */ + void clearRealtimeVehicles(TripPattern pattern); + /** + * Get the vehicles for a certain trip. + */ + List getRealtimeVehicles(TripPattern pattern); +} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java similarity index 56% rename from src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java index bc36668f294..65d69db57cc 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java @@ -1,16 +1,16 @@ -package org.opentripplanner.service.vehiclepositions; +package org.opentripplanner.service.realtimevehicles; import java.util.List; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.OccupancyStatus; -public interface VehiclePositionService { +public interface RealtimeVehicleService { /** - * Get the vehicle positions for a certain trip. + * Get the realtime vehicles for a certain trip pattern. */ - List getVehiclePositions(TripPattern pattern); + List getRealtimeVehicles(TripPattern pattern); /** * Get the latest occupancy status for a certain trip. diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java new file mode 100644 index 00000000000..c247420c301 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java @@ -0,0 +1,16 @@ +package org.opentripplanner.service.realtimevehicles.configure; + +import dagger.Binds; +import dagger.Module; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; + +/** + * The repository is used during application loading phase, so we need to provide + * a module for the repository as well as the service. + */ +@Module +public interface RealtimeVehicleRepositoryModule { + @Binds + RealtimeVehicleRepository bindRepository(DefaultRealtimeVehicleService repository); +} diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java new file mode 100644 index 00000000000..625d3ee9510 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java @@ -0,0 +1,16 @@ +package org.opentripplanner.service.realtimevehicles.configure; + +import dagger.Binds; +import dagger.Module; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; + +/** + * The service is used during application serve phase, not loading, so we need to provide + * a module for the service without the repository, which is injected from the loading phase. + */ +@Module +public interface RealtimeVehicleServiceModule { + @Binds + RealtimeVehicleService bindService(DefaultRealtimeVehicleService service); +} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java similarity index 50% rename from src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 5c762a89009..5e59d931ebd 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -1,4 +1,4 @@ -package org.opentripplanner.service.vehiclepositions.internal; +package org.opentripplanner.service.realtimevehicles.internal; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -7,42 +7,42 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.OccupancyStatus; @Singleton -public class DefaultVehiclePositionService - implements VehiclePositionService, VehiclePositionRepository { +public class DefaultRealtimeVehicleService + implements RealtimeVehicleService, RealtimeVehicleRepository { - private final Map> positions = new ConcurrentHashMap<>(); + private final Map> vehicles = new ConcurrentHashMap<>(); @Inject - public DefaultVehiclePositionService() {} + public DefaultRealtimeVehicleService() {} @Override - public void setVehiclePositions(TripPattern pattern, List updates) { - positions.put(pattern, List.copyOf(updates)); + public void setRealtimeVehicles(TripPattern pattern, List updates) { + vehicles.put(pattern, List.copyOf(updates)); } @Override - public void clearVehiclePositions(TripPattern pattern) { - positions.remove(pattern); + public void clearRealtimeVehicles(TripPattern pattern) { + vehicles.remove(pattern); } @Override - public List getVehiclePositions(TripPattern pattern) { + public List getRealtimeVehicles(TripPattern pattern) { // the list is made immutable during insertion, so we can safely return them - return positions.getOrDefault(pattern, List.of()); + return vehicles.getOrDefault(pattern, List.of()); } @Nonnull @Override public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) { - return positions + return vehicles .getOrDefault(pattern, List.of()) .stream() .filter(vehicle -> tripId.equals(vehicle.trip().getId())) diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java similarity index 79% rename from src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java index ffd76771f5d..6cdbd7e1415 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java @@ -1,4 +1,4 @@ -package org.opentripplanner.service.vehiclepositions.model; +package org.opentripplanner.service.realtimevehicles.model; import java.time.Instant; import org.opentripplanner.framework.geometry.WgsCoordinate; @@ -8,9 +8,9 @@ import org.opentripplanner.transit.model.timetable.Trip; /** - * Internal model of a realtime vehicle position. + * Internal model of a realtime vehicle. */ -public record RealtimeVehiclePosition( +public record RealtimeVehicle( FeedScopedId vehicleId, String label, WgsCoordinate coordinates, @@ -25,7 +25,7 @@ public record RealtimeVehiclePosition( Double heading, /** - * When the realtime position was recorded + * When the realtime vehicle was recorded */ Instant time, @@ -40,8 +40,8 @@ public record RealtimeVehiclePosition( */ OccupancyStatus occupancyStatus ) { - public static RealtimeVehiclePositionBuilder builder() { - return new RealtimeVehiclePositionBuilder(); + public static RealtimeVehicleBuilder builder() { + return new RealtimeVehicleBuilder(); } public enum StopStatus { diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java similarity index 57% rename from src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java index 809cfe5ab79..3679ba43311 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java @@ -1,16 +1,16 @@ -package org.opentripplanner.service.vehiclepositions.model; +package org.opentripplanner.service.realtimevehicles.model; import java.time.Instant; import java.util.Optional; import org.opentripplanner.framework.geometry.WgsCoordinate; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; -public class RealtimeVehiclePositionBuilder { +public class RealtimeVehicleBuilder { private FeedScopedId vehicleId; private String label; @@ -23,62 +23,62 @@ public class RealtimeVehiclePositionBuilder { private Trip trip; private OccupancyStatus occupancyStatus; - public RealtimeVehiclePositionBuilder setVehicleId(FeedScopedId vehicleId) { + public RealtimeVehicleBuilder setVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; return this; } - public RealtimeVehiclePositionBuilder setLabel(String label) { + public RealtimeVehicleBuilder setLabel(String label) { this.label = label; return this; } - public RealtimeVehiclePositionBuilder setCoordinates(WgsCoordinate c) { + public RealtimeVehicleBuilder setCoordinates(WgsCoordinate c) { this.coordinates = c; return this; } - public RealtimeVehiclePositionBuilder setSpeed(double speed) { + public RealtimeVehicleBuilder setSpeed(double speed) { this.speed = speed; return this; } - public RealtimeVehiclePositionBuilder setHeading(double heading) { + public RealtimeVehicleBuilder setHeading(double heading) { this.heading = heading; return this; } - public RealtimeVehiclePositionBuilder setTime(Instant time) { + public RealtimeVehicleBuilder setTime(Instant time) { this.time = time; return this; } - public RealtimeVehiclePositionBuilder setStopStatus(StopStatus stopStatus) { + public RealtimeVehicleBuilder setStopStatus(StopStatus stopStatus) { this.stopStatus = stopStatus; return this; } - public RealtimeVehiclePositionBuilder setStop(StopLocation stop) { + public RealtimeVehicleBuilder setStop(StopLocation stop) { this.stop = stop; return this; } - public RealtimeVehiclePositionBuilder setTrip(Trip trip) { + public RealtimeVehicleBuilder setTrip(Trip trip) { this.trip = trip; return this; } - public RealtimeVehiclePositionBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + public RealtimeVehicleBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { this.occupancyStatus = occupancyStatus; return this; } - public RealtimeVehiclePosition build() { + public RealtimeVehicle build() { var stop = Optional .ofNullable(this.stop) .map(s -> new StopRelationship(s, stopStatus)) .orElse(null); - return new RealtimeVehiclePosition( + return new RealtimeVehicle( vehicleId, label, coordinates, diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java deleted file mode 100644 index a28e316ac37..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.opentripplanner.service.vehiclepositions; - -import java.util.List; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.transit.model.network.TripPattern; - -public interface VehiclePositionRepository { - /** - * For the given pattern set all realtime vehicle positions. - *

- * The list is expected to be exhaustive: all existing positions will be overridden. - *

- * This means that if there are two updaters providing positions for the same pattern they - * overwrite each other. - */ - void setVehiclePositions(TripPattern pattern, List updates); - - /** - * Remove all vehicle positions for a given pattern. - *

- * This is useful to clear old vehicles for which there are no more updates and we assume that - * they have stopped their trip. - */ - void clearVehiclePositions(TripPattern pattern); - /** - * Get the vehicle positions for a certain trip. - */ - List getVehiclePositions(TripPattern pattern); -} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java b/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java deleted file mode 100644 index d4820e1fe31..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opentripplanner.service.vehiclepositions.configure; - -import dagger.Binds; -import dagger.Module; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; - -/** - * The repository is used during application loading phase, so we need to provide - * a module for the repository as well as the service. - */ -@Module -public interface VehiclePositionsRepositoryModule { - @Binds - VehiclePositionRepository bindRepository(DefaultVehiclePositionService repository); -} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java b/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java deleted file mode 100644 index a8cfcbfbe4c..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opentripplanner.service.vehiclepositions.configure; - -import dagger.Binds; -import dagger.Module; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; - -/** - * The service is used during application serve phase, not loading, so we need to provide - * a module for the service without the repository, which is injected from the loading phase. - */ -@Module -public interface VehiclePositionsServiceModule { - @Binds - VehiclePositionService bindService(DefaultVehiclePositionService service); -} diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index a7effd16704..5c4b6ad0c1e 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -17,7 +17,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.config.sandbox.FlexConfig; @@ -82,7 +82,7 @@ public interface OtpServerRequestContext { */ WorldEnvelopeService worldEnvelopeService(); - VehiclePositionService vehiclePositionService(); + RealtimeVehicleService realtimeVehicleService(); VehicleRentalService vehicleRentalService(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index ee71aa3f542..e60fd159332 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -18,7 +18,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -149,7 +149,7 @@ private void setupTransitRoutingServer() { /* Create updater modules from JSON config. */ UpdaterConfigurator.configure( graph(), - vehiclePositionRepository(), + realtimeVehicleRepository(), vehicleRentalRepository(), transitModel(), routerConfig().updaterConfig() @@ -239,8 +239,8 @@ public DataImportIssueSummary dataImportIssueSummary() { return factory.dataImportIssueSummary(); } - public VehiclePositionRepository vehiclePositionRepository() { - return factory.vehiclePositionRepository(); + public RealtimeVehicleRepository realtimeVehicleRepository() { + return factory.realtimeVehicleRepository(); } public VehicleRentalRepository vehicleRentalRepository() { diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index f882bad3ffd..b776df1ae5d 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -9,10 +9,10 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.configure.VehiclePositionsRepositoryModule; -import org.opentripplanner.service.vehiclepositions.configure.VehiclePositionsServiceModule; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.configure.RealtimeVehicleRepositoryModule; +import org.opentripplanner.service.realtimevehicles.configure.RealtimeVehicleServiceModule; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.vehiclerental.configure.VehicleRentalRepositoryModule; @@ -38,8 +38,8 @@ ConfigModule.class, TransitModule.class, WorldEnvelopeServiceModule.class, - VehiclePositionsServiceModule.class, - VehiclePositionsRepositoryModule.class, + RealtimeVehicleServiceModule.class, + RealtimeVehicleRepositoryModule.class, VehicleRentalServiceModule.class, VehicleRentalRepositoryModule.class, ConstructApplicationModule.class, @@ -53,8 +53,8 @@ public interface ConstructApplicationFactory { TransitModel transitModel(); WorldEnvelopeRepository worldEnvelopeRepository(); WorldEnvelopeService worldEnvelopeService(); - VehiclePositionRepository vehiclePositionRepository(); - VehiclePositionService vehiclePositionService(); + RealtimeVehicleRepository realtimeVehicleRepository(); + RealtimeVehicleService realtimeVehicleService(); VehicleRentalRepository vehicleRentalRepository(); VehicleRentalService vehicleRentalService(); DataImportIssueSummary dataImportIssueSummary(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index bc677028296..3b43644e24a 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -10,7 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -29,7 +29,7 @@ OtpServerRequestContext providesServerContext( Graph graph, TransitService transitService, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, List rideHailingServices, @Nullable TraverseVisitor traverseVisitor @@ -43,7 +43,7 @@ OtpServerRequestContext providesServerContext( Metrics.globalRegistry, routerConfig.vectorTileLayers(), worldEnvelopeService, - vehiclePositionService, + realtimeVehicleService, vehicleRentalService, routerConfig.flexConfig(), rideHailingServices, diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index dad75d1f6f6..62b848fd5e9 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -16,7 +16,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.service.DefaultRoutingService; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.api.HttpRequestScoped; @@ -41,7 +41,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final FlexConfig flexConfig; private final TraverseVisitor traverseVisitor; private final WorldEnvelopeService worldEnvelopeService; - private final VehiclePositionService vehiclePositionService; + private final RealtimeVehicleService realtimeVehicleService; private final VehicleRentalService vehicleRentalService; /** @@ -57,7 +57,7 @@ private DefaultServerRequestContext( TileRendererManager tileRendererManager, VectorTilesResource.LayersParameters vectorTileLayers, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, List rideHailingServices, TraverseVisitor traverseVisitor, @@ -75,7 +75,7 @@ private DefaultServerRequestContext( this.traverseVisitor = traverseVisitor; this.routeRequestDefaults = routeRequestDefaults; this.worldEnvelopeService = worldEnvelopeService; - this.vehiclePositionService = vehiclePositionService; + this.realtimeVehicleService = realtimeVehicleService; this.rideHailingServices = rideHailingServices; } @@ -91,7 +91,7 @@ public static DefaultServerRequestContext create( MeterRegistry meterRegistry, VectorTilesResource.LayersParameters vectorTileLayers, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, FlexConfig flexConfig, List rideHailingServices, @@ -107,7 +107,7 @@ public static DefaultServerRequestContext create( new TileRendererManager(graph, routeRequestDefaults.preferences()), vectorTileLayers, worldEnvelopeService, - vehiclePositionService, + realtimeVehicleService, vehicleRentalService, rideHailingServices, traverseVisitor, @@ -158,8 +158,8 @@ public WorldEnvelopeService worldEnvelopeService() { } @Override - public VehiclePositionService vehiclePositionService() { - return vehiclePositionService; + public RealtimeVehicleService realtimeVehicleService() { + return realtimeVehicleService; } @Override diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index ea63561ccb3..037bd080f47 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -13,7 +13,7 @@ import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.GraphUpdaterManager; @@ -46,20 +46,20 @@ public class UpdaterConfigurator { private final Graph graph; private final TransitModel transitModel; private final UpdatersParameters updatersParameters; - private final VehiclePositionRepository vehiclePositionRepository; + private final RealtimeVehicleRepository realtimeVehicleRepository; private final VehicleRentalRepository vehicleRentalRepository; private SiriTimetableSnapshotSource siriTimetableSnapshotSource = null; private TimetableSnapshotSource gtfsTimetableSnapshotSource = null; private UpdaterConfigurator( Graph graph, - VehiclePositionRepository vehiclePositionRepository, + RealtimeVehicleRepository realtimeVehicleRepository, VehicleRentalRepository vehicleRentalRepository, TransitModel transitModel, UpdatersParameters updatersParameters ) { this.graph = graph; - this.vehiclePositionRepository = vehiclePositionRepository; + this.realtimeVehicleRepository = realtimeVehicleRepository; this.vehicleRentalRepository = vehicleRentalRepository; this.transitModel = transitModel; this.updatersParameters = updatersParameters; @@ -67,15 +67,15 @@ private UpdaterConfigurator( public static void configure( Graph graph, - VehiclePositionRepository vehiclePositionService, - VehicleRentalRepository vehicleRentalService, + RealtimeVehicleRepository realtimeVehicleRepository, + VehicleRentalRepository vehicleRentalRepository, TransitModel transitModel, UpdatersParameters updatersParameters ) { new UpdaterConfigurator( graph, - vehiclePositionService, - vehicleRentalService, + realtimeVehicleRepository, + vehicleRentalRepository, transitModel, updatersParameters ) @@ -163,7 +163,7 @@ private List createUpdatersFromConfig() { } for (var configItem : updatersParameters.getVehiclePositionsUpdaterParameters()) { updaters.add( - new PollingVehiclePositionUpdater(configItem, vehiclePositionRepository, transitModel) + new PollingVehiclePositionUpdater(configItem, realtimeVehicleRepository, transitModel) ); } for (var configItem : updatersParameters.getSiriETUpdaterParameters()) { diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index ad525fe5104..8489836d218 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Optional; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.DefaultTransitService; @@ -17,7 +17,9 @@ import org.slf4j.LoggerFactory; /** - * Add vehicle positions to OTP patterns via a GTFS-RT source. + * Map vehicle positions to + * {@link org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle} and add them to OTP + * patterns via a GTFS-RT source. */ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { @@ -28,7 +30,7 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { */ private final GtfsRealtimeHttpVehiclePositionSource vehiclePositionSource; - private final VehiclePositionPatternMatcher vehiclePositionPatternMatcher; + private final RealtimeVehiclePatternMatcher realtimeVehiclePatternMatcher; /** * Parent update manager. Is used to execute graph writer runnables. @@ -37,7 +39,7 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { public PollingVehiclePositionUpdater( VehiclePositionsUpdaterParameters params, - VehiclePositionRepository vehiclePositionService, + RealtimeVehicleRepository realtimeVehicleRepository, TransitModel transitModel ) { super(params); @@ -47,13 +49,13 @@ public PollingVehiclePositionUpdater( var fuzzyTripMatcher = params.fuzzyTripMatching() ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)) : null; - this.vehiclePositionPatternMatcher = - new VehiclePositionPatternMatcher( + this.realtimeVehiclePatternMatcher = + new RealtimeVehiclePatternMatcher( params.feedId(), tripId -> index.getTripForId().get(tripId), trip -> index.getPatternForTrip().get(trip), (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), - vehiclePositionService, + realtimeVehicleRepository, transitModel.getTimeZone(), fuzzyTripMatcher ); @@ -81,7 +83,7 @@ public void runPolling() { if (updates != null) { // Handle updating trip positions via graph writer runnable - var runnable = new VehiclePositionUpdaterRunnable(updates, vehiclePositionPatternMatcher); + var runnable = new VehiclePositionUpdaterRunnable(updates, realtimeVehiclePatternMatcher); saveResultOnGraph.execute(runnable); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java similarity index 82% rename from src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java rename to src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 5e7fb0c1dab..844fc41b075 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -30,9 +30,9 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; @@ -52,12 +52,12 @@ * Responsible for converting vehicle positions in memory to exportable ones, and associating each * position with a pattern. */ -public class VehiclePositionPatternMatcher { +public class RealtimeVehiclePatternMatcher { - private static final Logger LOG = LoggerFactory.getLogger(VehiclePositionPatternMatcher.class); + private static final Logger LOG = LoggerFactory.getLogger(RealtimeVehiclePatternMatcher.class); private final String feedId; - private final VehiclePositionRepository repository; + private final RealtimeVehicleRepository repository; private final ZoneId timeZoneId; private final Function getTripForId; @@ -67,12 +67,12 @@ public class VehiclePositionPatternMatcher { private Set patternsInPreviousUpdate = Set.of(); - public VehiclePositionPatternMatcher( + public RealtimeVehiclePatternMatcher( String feedId, Function getTripForId, Function getStaticPattern, BiFunction getRealtimePattern, - VehiclePositionRepository repository, + RealtimeVehicleRepository repository, ZoneId timeZoneId, GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher ) { @@ -86,25 +86,25 @@ public VehiclePositionPatternMatcher( } /** - * Attempts to match each vehicle position to a pattern, then adds each to a pattern + * Attempts to match each vehicle to a pattern, then adds each to a pattern * * @param vehiclePositions List of vehicle positions to match to patterns */ - public UpdateResult applyVehiclePositionUpdates(List vehiclePositions) { + public UpdateResult applyRealtimeVehicleUpdates(List vehiclePositions) { var matchResults = vehiclePositions .stream() - .map(vehiclePosition -> toRealtimeVehiclePosition(feedId, vehiclePosition)) + .map(vehiclePosition -> toRealtimeVehicle(feedId, vehiclePosition)) .toList(); - // we take the list of positions and out of them create a Map> - // that map makes it very easy to update the positions in the service - // it also enables the bookkeeping about which pattern previously had positions but no longer do + // we take the list of vehicles and out of them create a Map> + // that map makes it very easy to update the vehicles in the service + // it also enables the bookkeeping about which pattern previously had vehicles but no longer do // these need to be removed from the service as we assume that the vehicle has stopped - var positions = matchResults + var vehicles = matchResults .stream() .filter(Result::isSuccess) .map(Result::successValue) - .collect(Collectors.groupingBy(PatternAndVehiclePosition::pattern)) + .collect(Collectors.groupingBy(PatternAndRealtimeVehicle::pattern)) .entrySet() .stream() .collect( @@ -114,18 +114,18 @@ public UpdateResult applyVehiclePositionUpdates(List vehiclePos e .getValue() .stream() - .map(PatternAndVehiclePosition::position) + .map(PatternAndRealtimeVehicle::vehicle) .collect(Collectors.toList()) ) ); - positions.forEach(repository::setVehiclePositions); - Set patternsInCurrentUpdate = positions.keySet(); + vehicles.forEach(repository::setRealtimeVehicles); + Set patternsInCurrentUpdate = vehicles.keySet(); - // if there was a position in the previous update but not in the current one, we assume - // that the pattern has no more vehicle positions. + // if there was a vehicle in the previous update but not in the current one, we assume + // that the pattern has no more vehicles. var toDelete = Sets.difference(patternsInPreviousUpdate, patternsInCurrentUpdate); - toDelete.forEach(repository::clearVehiclePositions); + toDelete.forEach(repository::clearRealtimeVehicles); patternsInPreviousUpdate = patternsInCurrentUpdate; if (!vehiclePositions.isEmpty() && patternsInCurrentUpdate.isEmpty()) { @@ -190,48 +190,46 @@ protected static LocalDate inferServiceDate( } /** - * Converts GtfsRealtime vehicle position to the OTP RealtimeVehiclePosition which can be used by + * Converts GtfsRealtime vehicle position to the OTP RealtimeVehicle which can be used by * the API. * * @param stopIndexOfGtfsSequence A function that takes a GTFS stop_sequence and returns the index * of the stop in the trip. */ - private RealtimeVehiclePosition mapVehiclePosition( + private RealtimeVehicle mapRealtimeVehicle( VehiclePosition vehiclePosition, List stopsOnVehicleTrip, @Nonnull Trip trip, @Nonnull Function stopIndexOfGtfsSequence ) { - var newPosition = RealtimeVehiclePosition.builder(); + var newVehicle = RealtimeVehicle.builder(); if (vehiclePosition.hasPosition()) { var position = vehiclePosition.getPosition(); - newPosition.setCoordinates( - new WgsCoordinate(position.getLatitude(), position.getLongitude()) - ); + newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); if (position.hasSpeed()) { - newPosition.setSpeed(position.getSpeed()); + newVehicle.setSpeed(position.getSpeed()); } if (position.hasBearing()) { - newPosition.setHeading(position.getBearing()); + newVehicle.setHeading(position.getBearing()); } } if (vehiclePosition.hasVehicle()) { var vehicle = vehiclePosition.getVehicle(); var id = new FeedScopedId(feedId, vehicle.getId()); - newPosition + newVehicle .setVehicleId(id) .setLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); } if (vehiclePosition.hasTimestamp()) { - newPosition.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); + newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } if (vehiclePosition.hasCurrentStatus()) { - newPosition.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -241,7 +239,7 @@ private RealtimeVehiclePosition mapVehiclePosition( .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) .toList(); if (matchedStops.size() == 1) { - newPosition.setStop(matchedStops.get(0)); + newVehicle.setStop(matchedStops.get(0)); } else { LOG.warn( "Stop ID {} is not in trip {}. Not setting stopRelationship.", @@ -257,18 +255,18 @@ else if (vehiclePosition.hasCurrentStopSequence()) { .ifPresent(stopIndex -> { if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { var stop = stopsOnVehicleTrip.get(stopIndex); - newPosition.setStop(stop); + newVehicle.setStop(stop); } }); } - newPosition.setTrip(trip); + newVehicle.setTrip(trip); if (vehiclePosition.hasOccupancyStatus()) { - newPosition.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } - return newPosition.build(); + return newVehicle.build(); } /** @@ -317,7 +315,7 @@ private VehiclePosition fuzzilySetTrip(VehiclePosition vehiclePosition) { return vehiclePosition.toBuilder().setTrip(trip).build(); } - private Result toRealtimeVehiclePosition( + private Result toRealtimeVehicle( String feedId, VehiclePosition vehiclePosition ) { @@ -371,15 +369,15 @@ private Result toRealtimeVehiclePosition } // Add position to pattern - var newPosition = mapVehiclePosition( + var newVehicle = mapRealtimeVehicle( vehiclePositionWithTripId, pattern.getStops(), trip, staticTripTimes::stopIndexOfGtfsSequence ); - return Result.success(new PatternAndVehiclePosition(pattern, newPosition)); + return Result.success(new PatternAndRealtimeVehicle(pattern, newVehicle)); } - record PatternAndVehiclePosition(TripPattern pattern, RealtimeVehiclePosition position) {} + record PatternAndRealtimeVehicle(TripPattern pattern, RealtimeVehicle vehicle) {} } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java index 3bb5f349fb2..d1f923696b5 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java @@ -9,7 +9,7 @@ public record VehiclePositionUpdaterRunnable( List updates, - VehiclePositionPatternMatcher matcher + RealtimeVehiclePatternMatcher matcher ) implements GraphWriterRunnable { public VehiclePositionUpdaterRunnable { @@ -20,6 +20,6 @@ public record VehiclePositionUpdaterRunnable( @Override public void run(Graph graph, TransitModel transitModel) { // Apply new vehicle positions - matcher.applyVehiclePositionUpdates(updates); + matcher.applyRealtimeVehicleUpdates(updates); } } diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 969769f93fa..e6609a2b780 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -6,8 +6,8 @@ import java.util.List; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; @@ -39,7 +39,7 @@ public static OtpServerRequestContext createServerContext( Metrics.globalRegistry, routerConfig.vectorTileLayers(), createWorldEnvelopeService(), - createVehiclePositionService(), + createRealtimeVehicleService(), createVehicleRentalService(), routerConfig.flexConfig(), List.of(), @@ -54,8 +54,8 @@ public static WorldEnvelopeService createWorldEnvelopeService() { return new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()); } - public static VehiclePositionService createVehiclePositionService() { - return new DefaultVehiclePositionService(); + public static RealtimeVehicleService createRealtimeVehicleService() { + return new DefaultRealtimeVehicleService(); } public static VehicleRentalService createVehicleRentalService() { diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 5faffc6a606..9fa217903c1 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -22,7 +22,7 @@ import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.SerializedGraphObject; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.standalone.OtpStartupInfo; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -92,7 +92,7 @@ public SpeedTest( UpdaterConfigurator.configure( graph, - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), new DefaultVehicleRentalService(), transitModel, config.updatersConfig @@ -111,7 +111,7 @@ public SpeedTest( timer.getRegistry(), List::of, TestServerContext.createWorldEnvelopeService(), - TestServerContext.createVehiclePositionService(), + TestServerContext.createRealtimeVehicleService(), TestServerContext.createVehicleRentalService(), config.flexConfig, List.of(), diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java similarity index 81% rename from src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java rename to src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 8cac79fc008..b60d99da951 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -24,7 +24,7 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.StopTime; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -35,7 +35,7 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; -public class VehiclePositionsMatcherTest { +public class RealtimeVehicleMatcherTest { public static final Route ROUTE = TransitModelForTest.route("1").build(); ZoneId zoneId = ZoneIds.BERLIN; @@ -43,7 +43,7 @@ public class VehiclePositionsMatcherTest { FeedScopedId scopedTripId = TransitModelForTest.id(tripId); @Test - public void matchRealtimePositionsToTrip() { + public void matchRealtimeVehiclesToTrip() { var pos = vehiclePosition(tripId); testVehiclePositions(pos); } @@ -64,7 +64,7 @@ public void inferStartDate() { @Test public void tripNotFoundInPattern() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); final String secondTripId = "trip2"; @@ -75,7 +75,7 @@ public void tripNotFoundInPattern() { var pattern = tripPattern(trip1, stopTimes); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, ignored -> trip2, ignored -> pattern, @@ -86,7 +86,7 @@ public void tripNotFoundInPattern() { ); var positions = List.of(vehiclePosition(secondTripId)); - var result = matcher.applyVehiclePositionUpdates(positions); + var result = matcher.applyRealtimeVehicleUpdates(positions); assertEquals(1, result.failed()); assertEquals(Set.of(TRIP_NOT_FOUND_IN_PATTERN), result.failures().keySet()); @@ -94,7 +94,7 @@ public void tripNotFoundInPattern() { @Test public void sequenceId() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var tripId = "trip1"; var scopedTripId = TransitModelForTest.id(tripId); @@ -107,7 +107,7 @@ public void sequenceId() { var patternForTrip = Map.of(trip1, pattern1); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -126,11 +126,11 @@ public void sequenceId() { var positions = List.of(pos); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - assertEquals(1, service.getVehiclePositions(pattern1).size()); - var nextStop = service.getVehiclePositions(pattern1).get(0).stop().stop(); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); + var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop().stop(); assertEquals("F:stop-20", nextStop.getId().toString()); } @@ -148,7 +148,7 @@ void invalidStopSequence() { } private void testVehiclePositions(VehiclePosition pos) { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var trip = TransitModelForTest.trip(tripId).build(); var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2)); @@ -158,10 +158,10 @@ private void testVehiclePositions(VehiclePosition pos) { var patternForTrip = Map.of(trip, pattern); // an untouched pattern has no vehicle positions - assertEquals(0, service.getVehiclePositions(pattern).size()); + assertEquals(0, service.getRealtimeVehicles(pattern).size()); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -174,25 +174,25 @@ private void testVehiclePositions(VehiclePosition pos) { var positions = List.of(pos); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - var vehiclePositions = service.getVehiclePositions(pattern); - assertEquals(1, vehiclePositions.size()); + var realtimeVehicles = service.getRealtimeVehicles(pattern); + assertEquals(1, realtimeVehicles.size()); - var parsedPos = vehiclePositions.get(0); - assertEquals(tripId, parsedPos.trip().getId().getId()); - assertEquals(new WgsCoordinate(1, 1), parsedPos.coordinates()); - assertEquals(30, parsedPos.heading()); + var parsedVehicle = realtimeVehicles.get(0); + assertEquals(tripId, parsedVehicle.trip().getId().getId()); + assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates()); + assertEquals(30, parsedVehicle.heading()); // if we have an empty list of updates then clear the positions from the previous update - matcher.applyVehiclePositionUpdates(List.of()); - assertEquals(0, service.getVehiclePositions(pattern).size()); + matcher.applyRealtimeVehicleUpdates(List.of()); + assertEquals(0, service.getRealtimeVehicles(pattern).size()); } @Test public void clearOldTrips() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var tripId1 = "trip1"; var tripId2 = "trip2"; @@ -213,11 +213,11 @@ public void clearOldTrips() { var patternForTrip = Map.of(trip1, pattern1, trip2, pattern2); - // an untouched pattern has no vehicle positions - assertEquals(0, service.getVehiclePositions(pattern1).size()); + // an untouched pattern has no vehicles + assertEquals(0, service.getRealtimeVehicles(pattern1).size()); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -234,16 +234,16 @@ public void clearOldTrips() { var positions = List.of(pos1, pos2); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - assertEquals(1, service.getVehiclePositions(pattern1).size()); - assertEquals(1, service.getVehiclePositions(pattern2).size()); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); + assertEquals(1, service.getRealtimeVehicles(pattern2).size()); - matcher.applyVehiclePositionUpdates(List.of(pos1)); - assertEquals(1, service.getVehiclePositions(pattern1).size()); + matcher.applyRealtimeVehicleUpdates(List.of(pos1)); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); // because there are no more updates for pattern2 we remove all positions - assertEquals(0, service.getVehiclePositions(pattern2).size()); + assertEquals(0, service.getRealtimeVehicles(pattern2).size()); } static Stream inferenceTestCases = Stream.of( @@ -265,7 +265,7 @@ void inferServiceDayOfTripAt6(String time, String expectedDate) { var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); var instant = OffsetDateTime.parse(time).toInstant(); - var inferredDate = VehiclePositionPatternMatcher.inferServiceDate(tripTimes, zoneId, instant); + var inferredDate = RealtimeVehiclePatternMatcher.inferServiceDate(tripTimes, zoneId, instant); assertEquals(LocalDate.parse(expectedDate), inferredDate); } @@ -284,7 +284,7 @@ void inferServiceDateCloseToMidnight() { // because the trip "crosses" midnight and we are already on the next day, we infer the service date to be // yesterday - var inferredDate = VehiclePositionPatternMatcher.inferServiceDate(tripTimes, zoneId, time); + var inferredDate = RealtimeVehiclePatternMatcher.inferServiceDate(tripTimes, zoneId, time); assertEquals(LocalDate.parse("2022-04-04"), inferredDate); } From 0e145ea6d2b240f3f3982ad54e4ae0b1c2e5944d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 13:28:02 +0300 Subject: [PATCH 006/120] Make vehicle position features configurable --- docs/RouterConfiguration.md | 6 +- docs/UpdaterConfig.md | 31 +++++-- .../VehiclePositionsUpdaterConfig.java | 14 +++- .../PollingVehiclePositionUpdater.java | 3 +- .../RealtimeVehiclePatternMatcher.java | 82 ++++++++++++------- .../VehiclePositionsUpdaterParameters.java | 5 +- .../RealtimeVehicleMatcherTest.java | 25 +++++- .../standalone/config/router-config.json | 4 +- 8 files changed, 121 insertions(+), 49 deletions(-) diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 1e77d3513a2..42f6237ee34 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -730,7 +730,11 @@ HTTP headers to add to the request. Any header key, value can be inserted. "frequency" : "1m", "headers" : { "Header-Name" : "Header-Value" - } + }, + "fuzzyTripMatching" : false, + "features" : [ + "position" + ] }, { "type" : "websocket-gtfs-rt-updater" diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index 12c6c919bde..be9f9579c17 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -223,18 +223,27 @@ The information is downloaded in a single HTTP request and polled regularly. -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|----------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:| -| type = "vehicle-positions" | `enum` | The type of the updater. | *Required* | | 1.5 | -| feedId | `string` | Feed ID to which the update should be applied. | *Required* | | 2.2 | -| frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 | -| fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.5 | -| url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 | -| [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-----------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "vehicle-positions" | `enum` | The type of the updater. | *Required* | | 1.5 | +| feedId | `string` | Feed ID to which the update should be applied. | *Required* | | 2.2 | +| frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 | +| fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.5 | +| url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 | +| [features](#u__6__features) | `enum set` | Which features of GTFS RT vehicle positions should be loaded into OTP. | *Optional* | | 2.5 | +| [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | ##### Parameter details +

features

+ +**Since version:** `2.5` ∙ **Type:** `enum set` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[6] +**Enum values:** `position` | `stop-position` | `occupancy` + +Which features of GTFS RT vehicle positions should be loaded into OTP. +

headers

**Since version:** `2.3` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` @@ -257,7 +266,11 @@ HTTP headers to add to the request. Any header key, value can be inserted. "frequency" : "1m", "headers" : { "Header-Name" : "Header-Value" - } + }, + "fuzzyTripMatching" : false, + "features" : [ + "position" + ] } ] } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java index da3b99d002d..e41a14de79d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java @@ -31,6 +31,11 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd .since(V2_5) .summary("Whether to match trips fuzzily.") .asBoolean(false); + var features = c + .of("features") + .since(V2_5) + .summary("Which features of GTFS RT vehicle positions should be loaded into OTP.") + .asEnumSet(VehiclePositionFeature.class); var headers = HttpHeadersConfig.headers(c, V2_3); return new VehiclePositionsUpdaterParameters( updaterRef, @@ -38,7 +43,14 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd url, frequency, headers, - fuzzyTripMatching + fuzzyTripMatching, + features ); } + + public enum VehiclePositionFeature { + POSITION, + STOP_POSITION, + OCCUPANCY, + } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index 8489836d218..bb2e27c3ce8 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -57,7 +57,8 @@ public PollingVehiclePositionUpdater( (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), realtimeVehicleRepository, transitModel.getTimeZone(), - fuzzyTripMatcher + fuzzyTripMatcher, + params.vehiclePositionFeatures() ); LOG.info( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 844fc41b075..f24bbbe5365 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -33,6 +33,7 @@ import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; @@ -64,6 +65,7 @@ public class RealtimeVehiclePatternMatcher { private final Function getStaticPattern; private final BiFunction getRealtimePattern; private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher; + private final Set vehiclePositionFeatures; private Set patternsInPreviousUpdate = Set.of(); @@ -74,7 +76,8 @@ public RealtimeVehiclePatternMatcher( BiFunction getRealtimePattern, RealtimeVehicleRepository repository, ZoneId timeZoneId, - GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher + GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher, + Set vehiclePositionFeatures ) { this.feedId = feedId; this.getTripForId = getTripForId; @@ -83,6 +86,7 @@ public RealtimeVehiclePatternMatcher( this.repository = repository; this.timeZoneId = timeZoneId; this.fuzzyTripMatcher = fuzzyTripMatcher; + this.vehiclePositionFeatures = vehiclePositionFeatures; } /** @@ -204,7 +208,12 @@ private RealtimeVehicle mapRealtimeVehicle( ) { var newVehicle = RealtimeVehicle.builder(); - if (vehiclePosition.hasPosition()) { + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION + ) && + vehiclePosition.hasPosition() + ) { var position = vehiclePosition.getPosition(); newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); @@ -228,41 +237,52 @@ private RealtimeVehicle mapRealtimeVehicle( newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } - if (vehiclePosition.hasCurrentStatus()) { - newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); - } + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) + ) { + if (vehiclePosition.hasCurrentStatus()) { + newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + } - // we prefer the to get the current stop from the stop_id - if (vehiclePosition.hasStopId()) { - var matchedStops = stopsOnVehicleTrip - .stream() - .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) - .toList(); - if (matchedStops.size() == 1) { - newVehicle.setStop(matchedStops.get(0)); - } else { - LOG.warn( - "Stop ID {} is not in trip {}. Not setting stopRelationship.", - vehiclePosition.getStopId(), - trip.getId() - ); + // we prefer the to get the current stop from the stop_id + if (vehiclePosition.hasStopId()) { + var matchedStops = stopsOnVehicleTrip + .stream() + .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) + .toList(); + if (matchedStops.size() == 1) { + newVehicle.setStop(matchedStops.get(0)); + } else { + LOG.warn( + "Stop ID {} is not in trip {}. Not setting stopRelationship.", + vehiclePosition.getStopId(), + trip.getId() + ); + } + } + // but if stop_id isn't there we try current_stop_sequence + else if (vehiclePosition.hasCurrentStopSequence()) { + stopIndexOfGtfsSequence + .apply(vehiclePosition.getCurrentStopSequence()) + .ifPresent(stopIndex -> { + if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { + var stop = stopsOnVehicleTrip.get(stopIndex); + newVehicle.setStop(stop); + } + }); } - } - // but if stop_id isn't there we try current_stop_sequence - else if (vehiclePosition.hasCurrentStopSequence()) { - stopIndexOfGtfsSequence - .apply(vehiclePosition.getCurrentStopSequence()) - .ifPresent(stopIndex -> { - if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { - var stop = stopsOnVehicleTrip.get(stopIndex); - newVehicle.setStop(stop); - } - }); } newVehicle.setTrip(trip); - if (vehiclePosition.hasOccupancyStatus()) { + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY + ) && + vehiclePosition.hasOccupancyStatus() + ) { newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java index 16db929c317..14b3d3a04de 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java @@ -3,6 +3,8 @@ import java.net.URI; import java.time.Duration; import java.util.Objects; +import java.util.Set; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; @@ -12,7 +14,8 @@ public record VehiclePositionsUpdaterParameters( URI url, Duration frequency, HttpHeaders headers, - boolean fuzzyTripMatching + boolean fuzzyTripMatching, + Set vehiclePositionFeatures ) implements PollingGraphUpdaterParameters { public VehiclePositionsUpdaterParameters { diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index b60d99da951..cbd94f49322 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -25,6 +25,7 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.StopTime; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -82,7 +83,11 @@ public void tripNotFoundInPattern() { (id, time) -> pattern, service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var positions = List.of(vehiclePosition(secondTripId)); @@ -114,7 +119,11 @@ public void sequenceId() { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var pos = VehiclePosition @@ -168,7 +177,11 @@ private void testVehiclePositions(VehiclePosition pos) { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var positions = List.of(pos); @@ -224,7 +237,11 @@ public void clearOldTrips() { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var pos1 = vehiclePosition(tripId1); diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index ddfafff3eea..9cf87635680 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -291,7 +291,9 @@ "frequency": "1m", "headers": { "Header-Name": "Header-Value" - } + }, + "fuzzyTripMatching": false, + "features": ["position"] }, // Streaming differential GTFS-RT TripUpdates over websockets { From d962c97f868e220c48730333ae967b51d600c94c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 15:53:55 +0300 Subject: [PATCH 007/120] Fix npes in vehicle positions impl --- .../datafetchers/VehiclePositionImpl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java index c64e15ce407..4c2da6cb0ca 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java @@ -21,17 +21,19 @@ public DataFetcher label() { @Override public DataFetcher lastUpdated() { - return env -> getSource(env).time().getEpochSecond(); + return env -> getSource(env).time() != null ? getSource(env).time().getEpochSecond() : null; } @Override public DataFetcher lat() { - return env -> getSource(env).coordinates().latitude(); + return env -> + getSource(env).coordinates() != null ? getSource(env).coordinates().latitude() : null; } @Override public DataFetcher lon() { - return env -> getSource(env).coordinates().longitude(); + return env -> + getSource(env).coordinates() != null ? getSource(env).coordinates().longitude() : null; } @Override @@ -51,7 +53,7 @@ public DataFetcher trip() { @Override public DataFetcher vehicleId() { - return env -> getSource(env).vehicleId().toString(); + return env -> getSource(env).vehicleId() != null ? getSource(env).vehicleId().toString() : null; } private RealtimeVehicle getSource(DataFetchingEnvironment environment) { From abbc08b0e1dc0bd6c0c21a5b57a0662d48e12276 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 16:22:11 +0300 Subject: [PATCH 008/120] Add mapping from internal enum into API --- .../ext/gtfsgraphqlapi/GtfsGraphQLIndex.java | 2 ++ .../datafetchers/TripOccupancyImpl.java | 31 +++++++++++++++++++ .../generated/GraphQLDataFetchers.java | 2 +- .../generated/graphql-codegen.yml | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java index 9a8651a2389..50d6a9e45bf 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java @@ -73,6 +73,7 @@ import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TicketTypeImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TranslatedStringImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TripImpl; +import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TripOccupancyImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.UnknownImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.VehicleParkingImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.VehiclePositionImpl; @@ -172,6 +173,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(CurrencyImpl.class)) .type(typeWiring.build(FareProductUseImpl.class)) .type(typeWiring.build(DefaultFareProductImpl.class)) + .type(typeWiring.build(TripOccupancyImpl.class)) .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java new file mode 100644 index 00000000000..75543936918 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java @@ -0,0 +1,31 @@ +package org.opentripplanner.ext.gtfsgraphqlapi.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers; +import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy; + +public class TripOccupancyImpl implements GraphQLDataFetchers.GraphQLTripOccupancy { + + @Override + public DataFetcher occupancyStatus() { + return env -> { + var occupancyStatus = getSource(env).occupancyStatus(); + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> "NO_DATA_AVAILABLE"; + case EMPTY -> "EMPTY"; + case MANY_SEATS_AVAILABLE -> "MANY_SEATS_AVAILABLE"; + case FEW_SEATS_AVAILABLE -> "FEW_SEATS_AVAILABLE"; + case STANDING_ROOM_ONLY -> "STANDING_ROOM_ONLY"; + case CRUSHED_STANDING_ROOM_ONLY -> "CRUSHED_STANDING_ROOM_ONLY"; + case FULL -> "FULL"; + case NOT_ACCEPTING_PASSENGERS -> "NOT_ACCEPTING_PASSENGERS"; + case NOT_BOARDABLE -> "NOT_BOARDABLE"; + }; + }; + } + + private TripOccupancy getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index 95fe58cd894..279bae6cc53 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -1067,7 +1067,7 @@ public interface GraphQLTrip { * available for a trip. Historic data might not be available. */ public interface GraphQLTripOccupancy { - public DataFetcher occupancyStatus(); + public DataFetcher occupancyStatus(); } /** This is used for alert entities that we don't explicitly handle or they are missing. */ diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index c71c0e8dfa2..fcb1e840431 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -59,6 +59,7 @@ config: Leg: org.opentripplanner.model.plan.Leg#Leg Mode: String TransitMode: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode#GraphQLTransitMode + OccupancyStatus: String PageInfo: Object Pattern: org.opentripplanner.transit.model.network.TripPattern#TripPattern PickupDropoffType: String From 374f9616985271a1389185b1e0373d5b1ed2b3ae Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 16:27:30 +0300 Subject: [PATCH 009/120] Rename builder setter methods --- .../model/RealtimeVehicleBuilder.java | 20 ++++++++-------- .../RealtimeVehiclePatternMatcher.java | 24 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java index 3679ba43311..75dd64666de 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java @@ -23,52 +23,52 @@ public class RealtimeVehicleBuilder { private Trip trip; private OccupancyStatus occupancyStatus; - public RealtimeVehicleBuilder setVehicleId(FeedScopedId vehicleId) { + public RealtimeVehicleBuilder withVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; return this; } - public RealtimeVehicleBuilder setLabel(String label) { + public RealtimeVehicleBuilder withLabel(String label) { this.label = label; return this; } - public RealtimeVehicleBuilder setCoordinates(WgsCoordinate c) { + public RealtimeVehicleBuilder withCoordinates(WgsCoordinate c) { this.coordinates = c; return this; } - public RealtimeVehicleBuilder setSpeed(double speed) { + public RealtimeVehicleBuilder withSpeed(double speed) { this.speed = speed; return this; } - public RealtimeVehicleBuilder setHeading(double heading) { + public RealtimeVehicleBuilder withHeading(double heading) { this.heading = heading; return this; } - public RealtimeVehicleBuilder setTime(Instant time) { + public RealtimeVehicleBuilder withTime(Instant time) { this.time = time; return this; } - public RealtimeVehicleBuilder setStopStatus(StopStatus stopStatus) { + public RealtimeVehicleBuilder withStopStatus(StopStatus stopStatus) { this.stopStatus = stopStatus; return this; } - public RealtimeVehicleBuilder setStop(StopLocation stop) { + public RealtimeVehicleBuilder withStop(StopLocation stop) { this.stop = stop; return this; } - public RealtimeVehicleBuilder setTrip(Trip trip) { + public RealtimeVehicleBuilder withTrip(Trip trip) { this.trip = trip; return this; } - public RealtimeVehicleBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + public RealtimeVehicleBuilder withOccupancyStatus(OccupancyStatus occupancyStatus) { this.occupancyStatus = occupancyStatus; return this; } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index f24bbbe5365..0a461df1326 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -215,13 +215,15 @@ private RealtimeVehicle mapRealtimeVehicle( vehiclePosition.hasPosition() ) { var position = vehiclePosition.getPosition(); - newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); + newVehicle.withCoordinates( + new WgsCoordinate(position.getLatitude(), position.getLongitude()) + ); if (position.hasSpeed()) { - newVehicle.setSpeed(position.getSpeed()); + newVehicle.withSpeed(position.getSpeed()); } if (position.hasBearing()) { - newVehicle.setHeading(position.getBearing()); + newVehicle.withHeading(position.getBearing()); } } @@ -229,12 +231,12 @@ private RealtimeVehicle mapRealtimeVehicle( var vehicle = vehiclePosition.getVehicle(); var id = new FeedScopedId(feedId, vehicle.getId()); newVehicle - .setVehicleId(id) - .setLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); + .withVehicleId(id) + .withLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); } if (vehiclePosition.hasTimestamp()) { - newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); + newVehicle.withTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } if ( @@ -243,7 +245,7 @@ private RealtimeVehicle mapRealtimeVehicle( ) ) { if (vehiclePosition.hasCurrentStatus()) { - newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + newVehicle.withStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -253,7 +255,7 @@ private RealtimeVehicle mapRealtimeVehicle( .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) .toList(); if (matchedStops.size() == 1) { - newVehicle.setStop(matchedStops.get(0)); + newVehicle.withStop(matchedStops.get(0)); } else { LOG.warn( "Stop ID {} is not in trip {}. Not setting stopRelationship.", @@ -269,13 +271,13 @@ else if (vehiclePosition.hasCurrentStopSequence()) { .ifPresent(stopIndex -> { if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { var stop = stopsOnVehicleTrip.get(stopIndex); - newVehicle.setStop(stop); + newVehicle.withStop(stop); } }); } } - newVehicle.setTrip(trip); + newVehicle.withTrip(trip); if ( vehiclePositionFeatures.contains( @@ -283,7 +285,7 @@ else if (vehiclePosition.hasCurrentStopSequence()) { ) && vehiclePosition.hasOccupancyStatus() ) { - newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + newVehicle.withOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } return newVehicle.build(); From ea8baea22eefd76aa866131d10376efb32433505 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 21:14:54 +0300 Subject: [PATCH 010/120] Use static imports --- .../RealtimeVehiclePatternMatcher.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 0a461df1326..8b5833ad4e9 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -1,5 +1,8 @@ package org.opentripplanner.updater.vehicle_position; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.INVALID_INPUT_STRUCTURE; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_SERVICE_ON_DATE; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND; @@ -208,12 +211,7 @@ private RealtimeVehicle mapRealtimeVehicle( ) { var newVehicle = RealtimeVehicle.builder(); - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION - ) && - vehiclePosition.hasPosition() - ) { + if (vehiclePositionFeatures.contains(POSITION) && vehiclePosition.hasPosition()) { var position = vehiclePosition.getPosition(); newVehicle.withCoordinates( new WgsCoordinate(position.getLatitude(), position.getLongitude()) @@ -239,11 +237,7 @@ private RealtimeVehicle mapRealtimeVehicle( newVehicle.withTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION - ) - ) { + if (vehiclePositionFeatures.contains(STOP_POSITION)) { if (vehiclePosition.hasCurrentStatus()) { newVehicle.withStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } @@ -279,12 +273,7 @@ else if (vehiclePosition.hasCurrentStopSequence()) { newVehicle.withTrip(trip); - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY - ) && - vehiclePosition.hasOccupancyStatus() - ) { + if (vehiclePositionFeatures.contains(OCCUPANCY) && vehiclePosition.hasOccupancyStatus()) { newVehicle.withOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } From e9789751294c8bef3f197a37f2583d299212c1d7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 26 Sep 2023 13:16:04 +0300 Subject: [PATCH 011/120] Add default for config --- .../config/framework/json/ParameterBuilder.java | 14 ++++++++++++++ .../updaters/VehiclePositionsUpdaterConfig.java | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 3bc66ce6ea5..11672f99005 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -4,6 +4,7 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.COST_LINEAR_FUNCTION; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DOUBLE; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DURATION; +import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM; import static org.opentripplanner.standalone.config.framework.json.ConfigType.FEED_SCOPED_ID; import static org.opentripplanner.standalone.config.framework.json.ConfigType.INTEGER; import static org.opentripplanner.standalone.config.framework.json.ConfigType.LOCALE; @@ -236,6 +237,19 @@ public > Set asEnumSet(Class enumClass) { return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); } + public > Set asEnumSet(Class enumClass, Collection defaultValues) { + List dft = (defaultValues instanceof List) + ? (List) defaultValues + : List.copyOf(defaultValues); + info.withOptional(dft.toString()).withEnumSet(enumClass); + List> optionalList = buildAndListSimpleArrayElements( + List.of(), + it -> parseOptionalEnum(it.asText(), enumClass) + ); + List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); + return result.isEmpty() ? Set.copyOf(dft) : EnumSet.copyOf(result); + } + /** * Get a map of enum values listed in the config like this: (This example has Boolean values) *
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
index e41a14de79d..502f0ea456e 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
@@ -3,8 +3,12 @@
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION;
 
 import java.time.Duration;
+import java.util.List;
 import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
 import org.opentripplanner.updater.vehicle_position.VehiclePositionsUpdaterParameters;
 
@@ -35,7 +39,7 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd
       .of("features")
       .since(V2_5)
       .summary("Which features of GTFS RT vehicle positions should be loaded into OTP.")
-      .asEnumSet(VehiclePositionFeature.class);
+      .asEnumSet(VehiclePositionFeature.class, List.of(POSITION, STOP_POSITION, OCCUPANCY));
     var headers = HttpHeadersConfig.headers(c, V2_3);
     return new VehiclePositionsUpdaterParameters(
       updaterRef,

From bb0e0bddb2540eda77da3ccaf8b490c54b487a05 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Tue, 26 Sep 2023 18:05:19 +0300
Subject: [PATCH 012/120] Include more types into transmodel API and less to
 GTFS

---
 src/ext/graphql/transmodelapi/schema.graphql  | 13 +++++++++
 .../datafetchers/TripOccupancyImpl.java       | 29 ++++++++++++-------
 .../generated/GraphQLDataFetchers.java        |  3 +-
 .../generated/GraphQLTypes.java               |  1 -
 .../generated/graphql-codegen.yml             |  2 +-
 .../mapping/OccupancyStatusMapper.java        |  8 +++--
 .../ext/transmodelapi/model/EnumTypes.java    | 19 ++++++++++++
 .../resources/gtfsgraphqlapi/schema.graphqls  | 10 +------
 .../model/timetable/OccupancyStatus.java      |  8 +----
 .../RealtimeVehiclePatternMatcher.java        |  2 +-
 10 files changed, 62 insertions(+), 33 deletions(-)

diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql
index f568c46f7fc..652e01920c1 100644
--- a/src/ext/graphql/transmodelapi/schema.graphql
+++ b/src/ext/graphql/transmodelapi/schema.graphql
@@ -1523,6 +1523,19 @@ enum MultiModalMode {
 }
 
 enum OccupancyStatus {
+  """
+  The vehicle or carriage can currently accommodate only standing passengers and has limited
+  space for them. There isn't a big difference between this and `full` so it's possible to handle
+  them as the same value, if one wants to limit the number of different values.
+  """
+  crushedStandingRoomOnly
+  """
+  The vehicle is considered empty by most measures, and has few or no passengers onboard, but is
+  still accepting passengers. There isn't a big difference between this and `manySeatsAvailable`
+  so it's possible to handle them as the same value, if one wants to limit the number of different
+  values.
+  """
+  empty
   "The vehicle or carriage has a few seats available."
   fewSeatsAvailable
   "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board."
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
index 75543936918..6818d3e4420 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
@@ -1,26 +1,35 @@
 package org.opentripplanner.ext.gtfsgraphqlapi.datafetchers;
 
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.FULL;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
+
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers;
+import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes;
 import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy;
 
 public class TripOccupancyImpl implements GraphQLDataFetchers.GraphQLTripOccupancy {
 
   @Override
-  public DataFetcher occupancyStatus() {
+  public DataFetcher occupancyStatus() {
     return env -> {
       var occupancyStatus = getSource(env).occupancyStatus();
       return switch (occupancyStatus) {
-        case NO_DATA_AVAILABLE -> "NO_DATA_AVAILABLE";
-        case EMPTY -> "EMPTY";
-        case MANY_SEATS_AVAILABLE -> "MANY_SEATS_AVAILABLE";
-        case FEW_SEATS_AVAILABLE -> "FEW_SEATS_AVAILABLE";
-        case STANDING_ROOM_ONLY -> "STANDING_ROOM_ONLY";
-        case CRUSHED_STANDING_ROOM_ONLY -> "CRUSHED_STANDING_ROOM_ONLY";
-        case FULL -> "FULL";
-        case NOT_ACCEPTING_PASSENGERS -> "NOT_ACCEPTING_PASSENGERS";
-        case NOT_BOARDABLE -> "NOT_BOARDABLE";
+        case NO_DATA_AVAILABLE -> NO_DATA_AVAILABLE;
+        case EMPTY -> EMPTY;
+        case MANY_SEATS_AVAILABLE -> MANY_SEATS_AVAILABLE;
+        case FEW_SEATS_AVAILABLE -> FEW_SEATS_AVAILABLE;
+        case STANDING_ROOM_ONLY -> STANDING_ROOM_ONLY;
+        case CRUSHED_STANDING_ROOM_ONLY -> CRUSHED_STANDING_ROOM_ONLY;
+        case FULL -> FULL;
+        case NOT_ACCEPTING_PASSENGERS -> NOT_ACCEPTING_PASSENGERS;
       };
     };
   }
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
index 279bae6cc53..83c823adde2 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
@@ -16,6 +16,7 @@
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLAlertEffectType;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLAlertSeverityLevelType;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLInputField;
+import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLRelativeDirection;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLRoutingErrorCode;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode;
@@ -1067,7 +1068,7 @@ public interface GraphQLTrip {
    * available for a trip. Historic data might not be available.
    */
   public interface GraphQLTripOccupancy {
-    public DataFetcher occupancyStatus();
+    public DataFetcher occupancyStatus();
   }
 
   /** This is used for alert entities that we don't explicitly handle or they are missing. */
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
index 5fca60671b9..b27fee0cfef 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
@@ -755,7 +755,6 @@ public enum GraphQLOccupancyStatus {
     FULL,
     MANY_SEATS_AVAILABLE,
     NOT_ACCEPTING_PASSENGERS,
-    NOT_BOARDABLE,
     NO_DATA_AVAILABLE,
     STANDING_ROOM_ONLY,
   }
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
index fcb1e840431..ff51e2df9b7 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
@@ -59,7 +59,7 @@ config:
     Leg: org.opentripplanner.model.plan.Leg#Leg
     Mode: String
     TransitMode: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode#GraphQLTransitMode
-    OccupancyStatus: String
+    OccupancyStatus: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus#GraphQLOccupancyStatus
     PageInfo: Object
     Pattern: org.opentripplanner.transit.model.network.TripPattern#TripPattern
     PickupDropoffType: String
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
index 8d1b74daa9e..978f8d46c2c 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
@@ -15,11 +15,13 @@ public class OccupancyStatusMapper {
   public static OccupancyStatus mapStatus(OccupancyStatus occupancyStatus) {
     return switch (occupancyStatus) {
       case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE;
-      case MANY_SEATS_AVAILABLE, EMPTY -> OccupancyStatus.MANY_SEATS_AVAILABLE;
+      case EMPTY -> OccupancyStatus.EMPTY;
+      case MANY_SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE;
       case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE;
-      case STANDING_ROOM_ONLY, CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY;
+      case STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY;
+      case CRUSHED_STANDING_ROOM_ONLY -> occupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
       case FULL -> OccupancyStatus.FULL;
-      case NOT_ACCEPTING_PASSENGERS, NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
+      case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
     };
   }
 }
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
index ac10b59f398..3fa3ed67cc2 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
@@ -198,6 +198,16 @@ public class EnumTypes {
       OccupancyStatus.NO_DATA_AVAILABLE,
       "The vehicle or carriage doesn't have any occupancy data available."
     )
+    .value(
+      "empty",
+      OccupancyStatus.EMPTY,
+      """
+      The vehicle is considered empty by most measures, and has few or no passengers onboard, but is
+      still accepting passengers. There isn't a big difference between this and `manySeatsAvailable`
+      so it's possible to handle them as the same value, if one wants to limit the number of different
+      values.
+      """
+    )
     .value(
       "manySeatsAvailable",
       OccupancyStatus.MANY_SEATS_AVAILABLE,
@@ -213,6 +223,15 @@ public class EnumTypes {
       OccupancyStatus.STANDING_ROOM_ONLY,
       "The vehicle or carriage only has standing room available."
     )
+    .value(
+      "crushedStandingRoomOnly",
+      OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY,
+      """
+        The vehicle or carriage can currently accommodate only standing passengers and has limited
+        space for them. There isn't a big difference between this and `full` so it's possible to handle
+        them as the same value, if one wants to limit the number of different values.
+        """
+    )
     .value(
       "full",
       OccupancyStatus.FULL,
diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls
index 276a8ae6f53..7117acd8902 100644
--- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls
+++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls
@@ -3440,17 +3440,9 @@ enum OccupancyStatus {
   FULL
 
   """
-  The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
-  passengers for boarding.
+  The vehicle or carriage is not accepting passengers.
   """
   NOT_ACCEPTING_PASSENGERS
-
-  """
-  The vehicle or carriage is not boardable and never accepts passengers. Useful for special
-  vehicles or carriages (engine, maintenance carriage, etc…). It might not make sense to show
-  these types of trips to passengers at all.
-  """
-  NOT_BOARDABLE
 }
 
 type step {
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
index f47eb01baa2..53ef887e347 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
@@ -44,13 +44,7 @@ public enum OccupancyStatus {
    */
   FULL,
   /**
-   * The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
-   * passengers for boarding.
+   * The vehicle or carriage is not accepting passengers.
    */
   NOT_ACCEPTING_PASSENGERS,
-  /**
-   * The vehicle or carriage is not boardable and never accepts passengers. Useful for special
-   * vehicles or carriages (engine, maintenance carriage, etc…).
-   */
-  NOT_BOARDABLE,
 }
diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
index 8b5833ad4e9..fe29ef35b97 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
+++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
@@ -309,7 +309,7 @@ private static OccupancyStatus occupancyStatusToModel(
       case CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
       case FULL -> OccupancyStatus.FULL;
       case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
-      case NOT_BOARDABLE -> OccupancyStatus.NOT_BOARDABLE;
+      case NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
     };
   }
 

From 2ed9ccf3413b28497daac450000c2c4ce8fbcc5c Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Thu, 28 Sep 2023 15:21:20 +0300
Subject: [PATCH 013/120] Refactor RealtimeVehicle to return optionals

---
 .../datafetchers/VehiclePositionImpl.java     | 16 ++--
 .../DefaultRealtimeVehicleService.java        |  9 +-
 .../model/RealtimeVehicle.java                | 82 ++++++++++++++++---
 .../model/RealtimeVehicleBuilder.java         | 58 +++++++++----
 4 files changed, 127 insertions(+), 38 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
index a4c9f6c36fe..7c2c0a7172a 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
@@ -11,39 +11,39 @@ public class VehiclePositionImpl implements GraphQLDataFetchers.GraphQLVehiclePo
 
   @Override
   public DataFetcher heading() {
-    return env -> getSource(env).heading();
+    return env -> getSource(env).heading().orElse(null);
   }
 
   @Override
   public DataFetcher label() {
-    return env -> getSource(env).label();
+    return env -> getSource(env).label().orElse(null);
   }
 
   @Override
   public DataFetcher lastUpdated() {
-    return env -> getSource(env).time() != null ? getSource(env).time().getEpochSecond() : null;
+    return env -> getSource(env).time().map(time -> time.getEpochSecond()).orElse(null);
   }
 
   @Override
   public DataFetcher lat() {
     return env ->
-      getSource(env).coordinates() != null ? getSource(env).coordinates().latitude() : null;
+      getSource(env).coordinates().map(coordinates -> coordinates.latitude()).orElse(null);
   }
 
   @Override
   public DataFetcher lon() {
     return env ->
-      getSource(env).coordinates() != null ? getSource(env).coordinates().longitude() : null;
+      getSource(env).coordinates().map(coordinates -> coordinates.longitude()).orElse(null);
   }
 
   @Override
   public DataFetcher speed() {
-    return env -> getSource(env).speed();
+    return env -> getSource(env).speed().orElse(null);
   }
 
   @Override
   public DataFetcher stopRelationship() {
-    return env -> getSource(env).stop();
+    return env -> getSource(env).stop().orElse(null);
   }
 
   @Override
@@ -53,7 +53,7 @@ public DataFetcher trip() {
 
   @Override
   public DataFetcher vehicleId() {
-    return env -> getSource(env).vehicleId() != null ? getSource(env).vehicleId().toString() : null;
+    return env -> getSource(env).vehicleId().map(vehicleId -> vehicleId.toString()).orElse(null);
   }
 
   private RealtimeVehicle getSource(DataFetchingEnvironment environment) {
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 5e59d931ebd..40485cd70ae 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -1,10 +1,14 @@
 package org.opentripplanner.service.realtimevehicles.internal;
 
+import static org.opentripplanner.transit.model.timetable.OccupancyStatus.NO_DATA_AVAILABLE;
+
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
+import java.time.Instant;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nonnull;
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
@@ -46,8 +50,9 @@ public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScoped
       .getOrDefault(pattern, List.of())
       .stream()
       .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
-      .max(Comparator.comparing(vehicle -> vehicle.time()))
+      .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
       .map(vehicle -> vehicle.occupancyStatus())
-      .orElse(OccupancyStatus.NO_DATA_AVAILABLE);
+      .orElse(Optional.empty())
+      .orElse(NO_DATA_AVAILABLE);
   }
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
index 6cdbd7e1415..98cfc6300f7 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
@@ -1,6 +1,8 @@
 package org.opentripplanner.service.realtimevehicles.model;
 
 import java.time.Instant;
+import java.util.Optional;
+import javax.annotation.Nonnull;
 import org.opentripplanner.framework.geometry.WgsCoordinate;
 import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.site.StopLocation;
@@ -10,36 +12,94 @@
 /**
  * Internal model of a realtime vehicle.
  */
-public record RealtimeVehicle(
-  FeedScopedId vehicleId,
-  String label,
-  WgsCoordinate coordinates,
+public class RealtimeVehicle {
+
+  private final FeedScopedId vehicleId;
+
+  private final String label;
+
+  private final WgsCoordinate coordinates;
+
   /**
    * Speed in meters per second
    */
-  Double speed,
+  private final Double speed;
   /**
    * Bearing, in degrees, clockwise from North, i.e., 0 is North and 90 is East. This can be the
    * compass bearing, or the direction towards the next stop or intermediate location.
    */
-  Double heading,
+  private final Double heading;
 
   /**
    * When the realtime vehicle was recorded
    */
-  Instant time,
+  private final Instant time;
 
   /**
    * Status of the vehicle, ie. if approaching the next stop or if it is there already.
    */
-  StopRelationship stop,
-  Trip trip,
+  private final StopRelationship stop;
+
+  private final Trip trip;
 
   /**
    * How full the vehicle is and is it still accepting passengers.
    */
-  OccupancyStatus occupancyStatus
-) {
+  private final OccupancyStatus occupancyStatus;
+
+  RealtimeVehicle(RealtimeVehicleBuilder builder) {
+    var stopRelationship = Optional
+      .ofNullable(builder.stop())
+      .map(s -> new StopRelationship(s, builder.stopStatus()))
+      .orElse(null);
+    vehicleId = builder.vehicleId();
+    label = builder().label();
+    coordinates = builder.coordinates();
+    speed = builder().speed();
+    heading = builder().heading();
+    time = builder.time();
+    stop = stopRelationship;
+    trip = builder.trip();
+    occupancyStatus = builder.occupancyStatus();
+  }
+
+  public Optional vehicleId() {
+    return Optional.ofNullable(vehicleId);
+  }
+
+  public Optional label() {
+    return Optional.ofNullable(label);
+  }
+
+  public Optional coordinates() {
+    return Optional.ofNullable(coordinates);
+  }
+
+  public Optional speed() {
+    return Optional.ofNullable(speed);
+  }
+
+  public Optional heading() {
+    return Optional.ofNullable(heading);
+  }
+
+  public Optional time() {
+    return Optional.ofNullable(time);
+  }
+
+  public Optional stop() {
+    return Optional.ofNullable(stop);
+  }
+
+  @Nonnull
+  public Trip trip() {
+    return trip;
+  }
+
+  public Optional occupancyStatus() {
+    return Optional.ofNullable(occupancyStatus);
+  }
+
   public static RealtimeVehicleBuilder builder() {
     return new RealtimeVehicleBuilder();
   }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
index 75dd64666de..cc68919f41d 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
@@ -1,9 +1,7 @@
 package org.opentripplanner.service.realtimevehicles.model;
 
 import java.time.Instant;
-import java.util.Optional;
 import org.opentripplanner.framework.geometry.WgsCoordinate;
-import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus;
 import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.site.StopLocation;
@@ -23,71 +21,97 @@ public class RealtimeVehicleBuilder {
   private Trip trip;
   private OccupancyStatus occupancyStatus;
 
+  public FeedScopedId vehicleId() {
+    return vehicleId;
+  }
+
   public RealtimeVehicleBuilder withVehicleId(FeedScopedId vehicleId) {
     this.vehicleId = vehicleId;
     return this;
   }
 
+  public String label() {
+    return label;
+  }
+
   public RealtimeVehicleBuilder withLabel(String label) {
     this.label = label;
     return this;
   }
 
+  public WgsCoordinate coordinates() {
+    return coordinates;
+  }
+
   public RealtimeVehicleBuilder withCoordinates(WgsCoordinate c) {
     this.coordinates = c;
     return this;
   }
 
+  public Double speed() {
+    return speed;
+  }
+
   public RealtimeVehicleBuilder withSpeed(double speed) {
     this.speed = speed;
     return this;
   }
 
+  public Double heading() {
+    return heading;
+  }
+
   public RealtimeVehicleBuilder withHeading(double heading) {
     this.heading = heading;
     return this;
   }
 
+  public Instant time() {
+    return time;
+  }
+
   public RealtimeVehicleBuilder withTime(Instant time) {
     this.time = time;
     return this;
   }
 
+  public StopStatus stopStatus() {
+    return stopStatus;
+  }
+
   public RealtimeVehicleBuilder withStopStatus(StopStatus stopStatus) {
     this.stopStatus = stopStatus;
     return this;
   }
 
+  public StopLocation stop() {
+    return stop;
+  }
+
   public RealtimeVehicleBuilder withStop(StopLocation stop) {
     this.stop = stop;
     return this;
   }
 
+  public Trip trip() {
+    return trip;
+  }
+
   public RealtimeVehicleBuilder withTrip(Trip trip) {
     this.trip = trip;
     return this;
   }
 
+  public OccupancyStatus occupancyStatus() {
+    return occupancyStatus;
+  }
+
   public RealtimeVehicleBuilder withOccupancyStatus(OccupancyStatus occupancyStatus) {
     this.occupancyStatus = occupancyStatus;
     return this;
   }
 
   public RealtimeVehicle build() {
-    var stop = Optional
-      .ofNullable(this.stop)
-      .map(s -> new StopRelationship(s, stopStatus))
-      .orElse(null);
-    return new RealtimeVehicle(
-      vehicleId,
-      label,
-      coordinates,
-      speed,
-      heading,
-      time,
-      stop,
-      trip,
-      occupancyStatus
-    );
+    return new RealtimeVehicle(this);
   }
 }

From 865947f94a588c05820ab3ab680d35cc706db07d Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Thu, 28 Sep 2023 15:52:33 +0300
Subject: [PATCH 014/120] Include transit service in realtime vehicle service

---
 .../mapping/TripRequestMapperTest.java             |  2 +-
 .../apis/gtfs/datafetchers/TripImpl.java           |  3 +--
 .../realtimevehicles/RealtimeVehicleService.java   |  4 ++--
 .../internal/DefaultRealtimeVehicleService.java    | 14 ++++++++++----
 .../org/opentripplanner/TestServerContext.java     |  8 +++++---
 .../apis/gtfs/GraphQLIntegrationTest.java          |  2 +-
 .../apis/gtfs/mapping/RouteRequestMapperTest.java  |  2 +-
 .../transit/speed_test/SpeedTest.java              |  6 ++++--
 8 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
index 7a2d9a64465..6034fbcd3a3 100644
--- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
@@ -73,7 +73,7 @@ public class TripRequestMapperTest implements PlanTestConstants {
           Metrics.globalRegistry,
           RouterConfig.DEFAULT.vectorTileLayers(),
           new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()),
-          new DefaultRealtimeVehicleService(),
+          new DefaultRealtimeVehicleService(transitService),
           new DefaultVehicleRentalService(),
           RouterConfig.DEFAULT.flexConfig(),
           List.of(),
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
index 45be3238c4e..78255ce7787 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
@@ -374,9 +374,8 @@ public DataFetcher wheelchairAccessible(
   public DataFetcher occupancy() {
     return environment -> {
       Trip trip = getSource(environment);
-      TripPattern pattern = getTransitService(environment).getPatternForTrip(trip);
       return new TripOccupancy(
-        getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(pattern, trip.getId())
+        getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(trip)
       );
     };
   }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
index 65d69db57cc..6424b182bf3 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
@@ -2,9 +2,9 @@
 
 import java.util.List;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+import org.opentripplanner.transit.model.timetable.Trip;
 
 public interface RealtimeVehicleService {
   /**
@@ -15,5 +15,5 @@ public interface RealtimeVehicleService {
   /**
    * Get the latest occupancy status for a certain trip.
    */
-  OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId);
+  OccupancyStatus getVehicleOccupancyStatus(Trip trip);
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 40485cd70ae..8539367affe 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -14,9 +14,10 @@
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.service.TransitService;
 
 @Singleton
 public class DefaultRealtimeVehicleService
@@ -24,8 +25,12 @@ public class DefaultRealtimeVehicleService
 
   private final Map> vehicles = new ConcurrentHashMap<>();
 
+  private final TransitService transitService;
+
   @Inject
-  public DefaultRealtimeVehicleService() {}
+  public DefaultRealtimeVehicleService(TransitService transitService) {
+    this.transitService = transitService;
+  }
 
   @Override
   public void setRealtimeVehicles(TripPattern pattern, List updates) {
@@ -45,11 +50,12 @@ public List getRealtimeVehicles(TripPattern pattern) {
 
   @Nonnull
   @Override
-  public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) {
+  public OccupancyStatus getVehicleOccupancyStatus(Trip trip) {
+    TripPattern pattern = transitService.getPatternForTrip(trip);
     return vehicles
       .getOrDefault(pattern, List.of())
       .stream()
-      .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
+      .filter(vehicle -> trip.getId().equals(vehicle.trip().getId()))
       .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
       .map(vehicle -> vehicle.occupancyStatus())
       .orElse(Optional.empty())
diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java
index e6609a2b780..8e68fc7c3a0 100644
--- a/src/test/java/org/opentripplanner/TestServerContext.java
+++ b/src/test/java/org/opentripplanner/TestServerContext.java
@@ -18,6 +18,7 @@
 import org.opentripplanner.standalone.server.DefaultServerRequestContext;
 import org.opentripplanner.transit.service.DefaultTransitService;
 import org.opentripplanner.transit.service.TransitModel;
+import org.opentripplanner.transit.service.TransitService;
 
 public class TestServerContext {
 
@@ -30,6 +31,7 @@ public static OtpServerRequestContext createServerContext(
   ) {
     transitModel.index();
     final RouterConfig routerConfig = RouterConfig.DEFAULT;
+    var transitService = new DefaultTransitService(transitModel);
     DefaultServerRequestContext context = DefaultServerRequestContext.create(
       routerConfig.transitTuningConfig(),
       routerConfig.routingRequestDefaults(),
@@ -39,7 +41,7 @@ public static OtpServerRequestContext createServerContext(
       Metrics.globalRegistry,
       routerConfig.vectorTileLayers(),
       createWorldEnvelopeService(),
-      createRealtimeVehicleService(),
+      createRealtimeVehicleService(transitService),
       createVehicleRentalService(),
       routerConfig.flexConfig(),
       List.of(),
@@ -54,8 +56,8 @@ public static WorldEnvelopeService createWorldEnvelopeService() {
     return new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository());
   }
 
-  public static RealtimeVehicleService createRealtimeVehicleService() {
-    return new DefaultRealtimeVehicleService();
+  public static RealtimeVehicleService createRealtimeVehicleService(TransitService transitService) {
+    return new DefaultRealtimeVehicleService(transitService);
   }
 
   public static VehicleRentalService createVehicleRentalService() {
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index fbe047dc837..2b026fada72 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -223,7 +223,7 @@ public TransitAlertService getTransitAlertService() {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(),
+        new DefaultRealtimeVehicleService(transitService),
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
index 5b703eb8302..af6352839fb 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
@@ -51,7 +51,7 @@ class RouteRequestMapperTest implements PlanTestConstants {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(),
+        new DefaultRealtimeVehicleService(transitService),
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
index 9fa217903c1..519514ab9ed 100644
--- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
+++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
@@ -90,9 +90,11 @@ public SpeedTest(
     this.testCaseDefinitions = tcIO.readTestCaseDefinitions();
     this.expectedResultsByTcId = tcIO.readExpectedResults();
 
+    var transitService = new DefaultTransitService(transitModel);
+
     UpdaterConfigurator.configure(
       graph,
-      new DefaultRealtimeVehicleService(),
+      new DefaultRealtimeVehicleService(transitService),
       new DefaultVehicleRentalService(),
       transitModel,
       config.updatersConfig
@@ -111,7 +113,7 @@ public SpeedTest(
         timer.getRegistry(),
         List::of,
         TestServerContext.createWorldEnvelopeService(),
-        TestServerContext.createRealtimeVehicleService(),
+        TestServerContext.createRealtimeVehicleService(transitService),
         TestServerContext.createVehicleRentalService(),
         config.flexConfig,
         List.of(),

From 4ea77e8909e4094e97c06bc65812de2af6b66fe0 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 14:51:11 +0300
Subject: [PATCH 015/120] Fix constructor

---
 .../model/RealtimeVehicle.java                 | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
index 98cfc6300f7..e06ae5ebaa7 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
@@ -52,15 +52,15 @@ public class RealtimeVehicle {
       .ofNullable(builder.stop())
       .map(s -> new StopRelationship(s, builder.stopStatus()))
       .orElse(null);
-    vehicleId = builder.vehicleId();
-    label = builder().label();
-    coordinates = builder.coordinates();
-    speed = builder().speed();
-    heading = builder().heading();
-    time = builder.time();
-    stop = stopRelationship;
-    trip = builder.trip();
-    occupancyStatus = builder.occupancyStatus();
+    this.vehicleId = builder.vehicleId();
+    this.label = builder.label();
+    this.coordinates = builder.coordinates();
+    this.speed = builder.speed();
+    this.heading = builder.heading();
+    this.time = builder.time();
+    this.stop = stopRelationship;
+    this.trip = builder.trip();
+    this.occupancyStatus = builder.occupancyStatus();
   }
 
   public Optional vehicleId() {

From 00083dc8d0d54e87bdb5f645e6b3357f46525018 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 14:53:45 +0300
Subject: [PATCH 016/120] Add graphql tests

---
 .../apis/gtfs/GraphQLIntegrationTest.java     | 27 ++++++++++++-
 .../apis/gtfs/expectations/patterns.json      | 38 ++++++++++++++++++-
 .../apis/gtfs/queries/patterns.graphql        | 21 ++++++++++
 3 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index 2b026fada72..c1e0d0bbccb 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -14,10 +14,12 @@
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_30;
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_50;
 import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary;
+import static org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus.IN_TRANSIT_TO;
 import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson;
 import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
 import static org.opentripplanner.transit.model.basic.TransitMode.BUS;
 import static org.opentripplanner.transit.model.basic.TransitMode.FERRY;
+import static org.opentripplanner.transit.model.timetable.OccupancyStatus.FEW_SEATS_AVAILABLE;
 
 import jakarta.ws.rs.core.Response;
 import java.io.IOException;
@@ -69,6 +71,7 @@
 import org.opentripplanner.routing.services.TransitAlertService;
 import org.opentripplanner.routing.vehicle_parking.VehicleParking;
 import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
+import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
 import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
 import org.opentripplanner.standalone.config.framework.json.JsonSupport;
 import org.opentripplanner.test.support.FilePatternSource;
@@ -216,6 +219,28 @@ public TransitAlertService getTransitAlertService() {
     var alerts = ListUtils.combine(List.of(alert), getTransitAlert(entitySelector));
     transitService.getTransitAlertService().setAlerts(alerts);
 
+    var realtimeVehicleService = new DefaultRealtimeVehicleService(transitService);
+    var occypancyVehicle = RealtimeVehicle
+      .builder()
+      .withTrip(trip)
+      .withTime(Instant.MAX)
+      .withVehicleId(id("vehicle-1"))
+      .withOccupancyStatus(FEW_SEATS_AVAILABLE)
+      .build();
+    var positionVehicle = RealtimeVehicle
+      .builder()
+      .withTrip(trip)
+      .withTime(Instant.MIN)
+      .withVehicleId(id("vehicle-2"))
+      .withLabel("vehicle2")
+      .withCoordinates(new WgsCoordinate(60.0, 80.0))
+      .withHeading(80.0)
+      .withSpeed(10.2)
+      .withStop(pattern.getStop(0))
+      .withStopStatus(IN_TRANSIT_TO)
+      .build();
+    realtimeVehicleService.setRealtimeVehicles(pattern, List.of(occypancyVehicle, positionVehicle));
+
     context =
       new GraphQLRequestContext(
         new TestRoutingService(List.of(i1)),
@@ -223,7 +248,7 @@ public TransitAlertService getTransitAlertService() {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(transitService),
+        realtimeVehicleService,
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
index 5f2a0e7d746..b23ced4f954 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
@@ -47,7 +47,43 @@
                 "pickupType" : "SCHEDULED",
                 "dropoffType" : "SCHEDULED"
               }
-            ]
+            ],
+            "occupancy" : {
+              "occupancyStatus" : "FEW_SEATS_AVAILABLE"
+            }
+          }
+        ],
+        "vehiclePositions" : [
+          {
+            "vehicleId" : "F:vehicle-1",
+            "label" : null,
+            "lat" : null,
+            "lon" : null,
+            "stopRelationship" : null,
+            "speed" : null,
+            "heading" : null,
+            "lastUpdated" : 31556889864403199,
+            "trip" : {
+              "gtfsId" : "F:123"
+            }
+          },
+          {
+            "vehicleId" : "F:vehicle-2",
+            "label" : "vehicle2",
+            "lat" : 60.0,
+            "lon" : 80.0,
+            "stopRelationship" : {
+              "status" : "IN_TRANSIT_TO",
+              "stop" : {
+                "gtfsId" : "F:Stop_0"
+              }
+            },
+            "speed" : 10.2,
+            "heading" : 80.0,
+            "lastUpdated" : -31557014167219200,
+            "trip" : {
+              "gtfsId" : "F:123"
+            }
           }
         ]
       }
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
index d4feaf2c8f6..32be856274e 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
@@ -17,6 +17,27 @@
                 pickupType
                 dropoffType
             }
+            occupancy {
+                occupancyStatus
+            }
+        }
+        vehiclePositions {
+            vehicleId
+            label
+            lat
+            lon
+            stopRelationship {
+                status
+                stop {
+                    gtfsId
+                }
+            }
+            speed
+            heading
+            lastUpdated
+            trip {
+                gtfsId
+            }
         }
     }
 }
\ No newline at end of file

From 72baea8044e907eb0c9293c6a9ffeb2f3d12611c Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:33:34 +0300
Subject: [PATCH 017/120] Improve service testability and docs

---
 .../RealtimeVehicleService.java               | 11 ++++++----
 .../DefaultRealtimeVehicleService.java        | 20 ++++++++++++++-----
 2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
index 6424b182bf3..949b196faeb 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
@@ -1,6 +1,7 @@
 package org.opentripplanner.service.realtimevehicles;
 
 import java.util.List;
+import javax.annotation.Nonnull;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
@@ -8,12 +9,14 @@
 
 public interface RealtimeVehicleService {
   /**
-   * Get the realtime vehicles for a certain trip pattern.
+   * Get the realtime vehicles for a certain trip pattern. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data.
    */
-  List getRealtimeVehicles(TripPattern pattern);
+  List getRealtimeVehicles(@Nonnull TripPattern pattern);
 
   /**
-   * Get the latest occupancy status for a certain trip.
+   * Get the latest occupancy status for a certain trip. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data.
    */
-  OccupancyStatus getVehicleOccupancyStatus(Trip trip);
+  OccupancyStatus getVehicleOccupancyStatus(@Nonnull Trip trip);
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 8539367affe..401092b6058 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -14,6 +14,7 @@
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
 import org.opentripplanner.transit.model.timetable.Trip;
@@ -43,21 +44,30 @@ public void clearRealtimeVehicles(TripPattern pattern) {
   }
 
   @Override
-  public List getRealtimeVehicles(TripPattern pattern) {
+  public List getRealtimeVehicles(@Nonnull TripPattern pattern) {
     // the list is made immutable during insertion, so we can safely return them
     return vehicles.getOrDefault(pattern, List.of());
   }
 
   @Nonnull
   @Override
-  public OccupancyStatus getVehicleOccupancyStatus(Trip trip) {
-    TripPattern pattern = transitService.getPatternForTrip(trip);
+  public OccupancyStatus getVehicleOccupancyStatus(@Nonnull Trip trip) {
+    return getOccupancyStatus(trip.getId(), transitService.getPatternForTrip(trip));
+  }
+
+  /**
+   * Get the latest occupancy status for a certain trip. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data. This method is an alternative to
+   * {@link #getVehicleOccupancyStatus(Trip)} and works even when {@link TransitService} is not
+   * provided to the service.
+   */
+  public OccupancyStatus getOccupancyStatus(FeedScopedId tripId, TripPattern pattern) {
     return vehicles
       .getOrDefault(pattern, List.of())
       .stream()
-      .filter(vehicle -> trip.getId().equals(vehicle.trip().getId()))
+      .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
       .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
-      .map(vehicle -> vehicle.occupancyStatus())
+      .map(RealtimeVehicle::occupancyStatus)
       .orElse(Optional.empty())
       .orElse(NO_DATA_AVAILABLE);
   }

From 91d1fb9d255f5b4971c46469a450ef1b00b917ab Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:36:27 +0300
Subject: [PATCH 018/120] Update tests

---
 .../RealtimeVehicleMatcherTest.java           | 94 ++++++++++++++-----
 1 file changed, 68 insertions(+), 26 deletions(-)

diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
index cbd94f49322..2ddc4860679 100644
--- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
+++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
@@ -2,6 +2,9 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_00;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION;
 import static org.opentripplanner.transit.model._data.TransitModelForTest.stopTime;
 import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND_IN_PATTERN;
 
@@ -33,12 +36,18 @@
 import org.opentripplanner.transit.model.network.Route;
 import org.opentripplanner.transit.model.network.StopPattern;
 import org.opentripplanner.transit.model.network.TripPattern;
+import org.opentripplanner.transit.model.timetable.OccupancyStatus;
 import org.opentripplanner.transit.model.timetable.Trip;
 import org.opentripplanner.transit.model.timetable.TripTimes;
 
 public class RealtimeVehicleMatcherTest {
 
-  public static final Route ROUTE = TransitModelForTest.route("1").build();
+  private static final Route ROUTE = TransitModelForTest.route("1").build();
+  private static final Set FEATURES = Set.of(
+    POSITION,
+    STOP_POSITION,
+    OCCUPANCY
+  );
   ZoneId zoneId = ZoneIds.BERLIN;
   String tripId = "trip1";
   FeedScopedId scopedTripId = TransitModelForTest.id(tripId);
@@ -49,6 +58,16 @@ public void matchRealtimeVehiclesToTrip() {
     testVehiclePositions(pos);
   }
 
+  @Test
+  public void testOccupancy() {
+    var pos = vehiclePosition(tripId);
+    var posWithOccupancy = pos
+      .toBuilder()
+      .setOccupancyStatus(VehiclePosition.OccupancyStatus.FEW_SEATS_AVAILABLE)
+      .build();
+    testVehiclePositionOccupancy(posWithOccupancy);
+  }
+
   @Test
   @DisplayName("If the vehicle position has no start_date we need to guess the service day")
   public void inferStartDate() {
@@ -65,7 +84,7 @@ public void inferStartDate() {
 
   @Test
   public void tripNotFoundInPattern() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     final String secondTripId = "trip2";
 
@@ -84,10 +103,7 @@ public void tripNotFoundInPattern() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var positions = List.of(vehiclePosition(secondTripId));
@@ -99,7 +115,7 @@ public void tripNotFoundInPattern() {
 
   @Test
   public void sequenceId() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     var tripId = "trip1";
     var scopedTripId = TransitModelForTest.id(tripId);
@@ -120,10 +136,7 @@ public void sequenceId() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var pos = VehiclePosition
@@ -139,8 +152,8 @@ public void sequenceId() {
 
     // ensure that gtfs-rt was matched to an OTP pattern correctly
     assertEquals(1, service.getRealtimeVehicles(pattern1).size());
-    var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop().stop();
-    assertEquals("F:stop-20", nextStop.getId().toString());
+    var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop();
+    assertEquals("F:stop-20", nextStop.get().stop().getId().toString());
   }
 
   @Test
@@ -157,7 +170,7 @@ void invalidStopSequence() {
   }
 
   private void testVehiclePositions(VehiclePosition pos) {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
     var trip = TransitModelForTest.trip(tripId).build();
     var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2));
 
@@ -170,7 +183,7 @@ private void testVehiclePositions(VehiclePosition pos) {
     assertEquals(0, service.getRealtimeVehicles(pattern).size());
 
     // Map positions to trips in feed
-    RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher(
+    var matcher = new RealtimeVehiclePatternMatcher(
       TransitModelForTest.FEED_ID,
       tripForId::get,
       patternForTrip::get,
@@ -178,10 +191,7 @@ private void testVehiclePositions(VehiclePosition pos) {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var positions = List.of(pos);
@@ -195,17 +205,52 @@ private void testVehiclePositions(VehiclePosition pos) {
 
     var parsedVehicle = realtimeVehicles.get(0);
     assertEquals(tripId, parsedVehicle.trip().getId().getId());
-    assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates());
-    assertEquals(30, parsedVehicle.heading());
+    assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates().get());
+    assertEquals(30, parsedVehicle.heading().get());
 
     // if we have an empty list of updates then clear the positions from the previous update
     matcher.applyRealtimeVehicleUpdates(List.of());
     assertEquals(0, service.getRealtimeVehicles(pattern).size());
   }
 
+  private void testVehiclePositionOccupancy(VehiclePosition pos) {
+    var service = new DefaultRealtimeVehicleService(null);
+    var trip = TransitModelForTest.trip(tripId).build();
+    var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2));
+
+    TripPattern pattern = tripPattern(trip, stopTimes);
+
+    var tripForId = Map.of(scopedTripId, trip);
+    var patternForTrip = Map.of(trip, pattern);
+
+    // an untouched pattern has no vehicle positions
+    assertEquals(0, service.getRealtimeVehicles(pattern).size());
+
+    // Map positions to trips in feed
+    RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher(
+      TransitModelForTest.FEED_ID,
+      tripForId::get,
+      patternForTrip::get,
+      (id, time) -> patternForTrip.get(id),
+      service,
+      zoneId,
+      null,
+      FEATURES
+    );
+
+    var positions = List.of(pos);
+
+    // Execute the same match-to-pattern step as the runner
+    matcher.applyRealtimeVehicleUpdates(positions);
+
+    // Check that occupancy for the trip is as set in original position
+    var occupancy = service.getOccupancyStatus(trip.getId(), pattern);
+    assertEquals(OccupancyStatus.FEW_SEATS_AVAILABLE, occupancy);
+  }
+
   @Test
   public void clearOldTrips() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     var tripId1 = "trip1";
     var tripId2 = "trip2";
@@ -238,10 +283,7 @@ public void clearOldTrips() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var pos1 = vehiclePosition(tripId1);

From 292f2501ae5adc0f03296b616280bb71da3b3641 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:52:52 +0300
Subject: [PATCH 019/120] Don't use static import when mapping from one enum to
 another to avoid confusion

---
 .../gtfs/datafetchers/TripOccupancyImpl.java  | 25 ++++++-------------
 1 file changed, 8 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
index 8a623d7b0e2..b8b01eda755 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
@@ -1,14 +1,5 @@
 package org.opentripplanner.apis.gtfs.datafetchers;
 
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.FULL;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
-
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
 import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
@@ -22,14 +13,14 @@ public DataFetcher occupancyStatus() {
     return env -> {
       var occupancyStatus = getSource(env).occupancyStatus();
       return switch (occupancyStatus) {
-        case NO_DATA_AVAILABLE -> NO_DATA_AVAILABLE;
-        case EMPTY -> EMPTY;
-        case MANY_SEATS_AVAILABLE -> MANY_SEATS_AVAILABLE;
-        case FEW_SEATS_AVAILABLE -> FEW_SEATS_AVAILABLE;
-        case STANDING_ROOM_ONLY -> STANDING_ROOM_ONLY;
-        case CRUSHED_STANDING_ROOM_ONLY -> CRUSHED_STANDING_ROOM_ONLY;
-        case FULL -> FULL;
-        case NOT_ACCEPTING_PASSENGERS -> NOT_ACCEPTING_PASSENGERS;
+        case NO_DATA_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
+        case EMPTY -> GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
+        case MANY_SEATS_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
+        case FEW_SEATS_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
+        case STANDING_ROOM_ONLY -> GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
+        case CRUSHED_STANDING_ROOM_ONLY -> GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
+        case FULL -> GraphQLTypes.GraphQLOccupancyStatus.FULL;
+        case NOT_ACCEPTING_PASSENGERS -> GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
       };
     };
   }

From 4b1044bb746d6d5e2268dc02e17ccbd0199068db Mon Sep 17 00:00:00 2001
From: Thomas Gran 
Date: Thu, 14 Sep 2023 18:46:41 +0200
Subject: [PATCH 020/120] refactor: Small cleanup in Raptor, fix bug in
 MultiCriteriaRequest

The McRequest failed to copy all parameters in the constructor, which caused
the factory methods to not work as expected.
---
 .../framework/lang/IntUtils.java              |  12 ++
 .../api/request/MultiCriteriaRequest.java     |  21 ++--
 .../raptor/api/request/PassThroughPoint.java  |  17 ++-
 .../raptor/api/request/PassThroughPoints.java |  44 --------
 .../BitSetPassThroughPointsService.java       |   3 +-
 .../transit/mappers/RaptorRequestMapper.java  |  29 ++---
 .../framework/lang/IntUtilsTest.java          |   8 ++
 .../api/request/MultiCriteriaRequestTest.java | 104 ++++++++++++++++++
 .../api/request/PassThroughPointTest.java     |  11 +-
 .../api/request/PassThroughPointsTest.java    |  58 ----------
 .../raptor/api/request/SearchParamsTest.java  |   2 +-
 .../moduletests/J01_PassThroughTest.java      |  72 +++++-------
 .../BitSetPassThroughPointsServiceTest.java   |   5 +-
 13 files changed, 196 insertions(+), 190 deletions(-)
 delete mode 100644 src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoints.java
 create mode 100644 src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java
 delete mode 100644 src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java

diff --git a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java
index 86bb31f41ad..c1c2406f462 100644
--- a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java
+++ b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java
@@ -21,6 +21,18 @@ public static String intToString(int value, int notSetValue) {
     return value == notSetValue ? "" : Integer.toString(value);
   }
 
+  /**
+   * Convert an integer to a String, if the value equals the {@code notSetValue} parameter an empty
+   * string is returned.
+   */
+  public static String intArrayToString(int... values) {
+    var buf = new StringBuilder();
+    for (int it : values) {
+      buf.append(", ").append(it);
+    }
+    return buf.isEmpty() ? "" : buf.substring(2);
+  }
+
   /**
    * Round a given value from a 64 bit double to an 32 bit int - potential overflow is
    * ignored. ONLY USE THIS FUNCTION IF THE DOUBLE IS GUARANTEED TO BE LESS THAN THE
diff --git a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
index 27ff1db1efb..0593eb72fc2 100644
--- a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
+++ b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java
@@ -1,5 +1,6 @@
 package org.opentripplanner.raptor.api.request;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import javax.annotation.Nullable;
@@ -21,7 +22,7 @@ public class MultiCriteriaRequest {
   private final RaptorTransitPriorityGroupCalculator transitPriorityCalculator;
 
   @Nullable
-  private final PassThroughPoints passThroughPoints;
+  private final List passThroughPoints;
 
   @Nullable
   private final Double relaxCostAtDestination;
@@ -76,7 +77,7 @@ public Optional transitPriorityCalculator(
     return Optional.ofNullable(transitPriorityCalculator);
   }
 
-  public Optional passThroughPoints() {
+  public Optional> passThroughPoints() {
     return Optional.ofNullable(passThroughPoints);
   }
 
@@ -144,13 +145,16 @@ public static class Builder {
 
     private final MultiCriteriaRequest original;
     private RelaxFunction relaxC1;
-    private RaptorTransitPriorityGroupCalculator transitPriorityCalculator = null;
-    private PassThroughPoints passThroughPoints = null;
-    private Double relaxCostAtDestination = null;
+    private RaptorTransitPriorityGroupCalculator transitPriorityCalculator;
+    private List passThroughPoints;
+    private Double relaxCostAtDestination;
 
     public Builder(MultiCriteriaRequest original) {
       this.original = original;
       this.relaxC1 = original.relaxC1;
+      this.passThroughPoints = original.passThroughPoints;
+      this.transitPriorityCalculator = original.transitPriorityCalculator;
+      this.relaxCostAtDestination = original.relaxCostAtDestination;
     }
 
     @Nullable
@@ -174,13 +178,14 @@ public Builder withTransitPriorityCalculator(RaptorTransitPriorityGroupCalcul
     }
 
     @Nullable
-    public PassThroughPoints passThroughPoints() {
+    public List passThroughPoints() {
       return passThroughPoints;
     }
 
     @Nullable
-    public Builder withPassThroughPoints(PassThroughPoints value) {
-      passThroughPoints = value;
+    public Builder withPassThroughPoints(List points) {
+      // Prevent setting this to an empty list - here we use null to represent NOT_SET
+      passThroughPoints = (points == null || points.isEmpty()) ? null : points;
       return this;
     }
 
diff --git a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java
index 18e2966e9c5..724cc649243 100644
--- a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java
+++ b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java
@@ -5,22 +5,24 @@
 import java.util.Objects;
 import java.util.stream.IntStream;
 import javax.annotation.Nullable;
+import org.opentripplanner.framework.lang.IntUtils;
+import org.opentripplanner.framework.lang.StringUtils;
 
 /**
  * A collection of stop indexes used to define a pass through-point.
  */
 public class PassThroughPoint {
 
-  private final int[] stops;
   private final String name;
+  private final int[] stops;
 
-  public PassThroughPoint(int[] stops, @Nullable String name) {
+  public PassThroughPoint(@Nullable String name, int... stops) {
     Objects.requireNonNull(stops);
     if (stops.length == 0) {
       throw new IllegalArgumentException("At least one stop is required");
     }
+    this.name = StringUtils.hasNoValue(name) ? null : name;
     this.stops = Arrays.copyOf(stops, stops.length);
-    this.name = name;
   }
 
   /**
@@ -44,10 +46,17 @@ public int hashCode() {
     return Arrays.hashCode(stops);
   }
 
+  /**
+   * We want the {@code toString()} to be as easy to read as possible for value objects, so the
+   * format for this class is:
+   * 

+ * {@code "(, )"}, for example: + * {@code "(PT-Name, 1, 12, 123)"} or without the name {@code "(1, 12, 123)"} + */ @Override public String toString() { return ( - (name == null ? "(" : "(name: '" + name + "', ") + "stops: " + Arrays.toString(stops) + ")" + "(" + (name == null ? "" : name + ", ") + "stops: " + IntUtils.intArrayToString(stops) + ")" ); } } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoints.java b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoints.java deleted file mode 100644 index 697cc56318e..00000000000 --- a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoints.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.opentripplanner.raptor.api.request; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; -import org.opentripplanner.framework.tostring.ToStringBuilder; - -/** - * An ordered set of pass through points. - */ -public class PassThroughPoints { - - private final List points; - - public PassThroughPoints(List points) { - this.points = List.copyOf(Objects.requireNonNull(points)); - } - - public Stream stream() { - return points.stream(); - } - - public boolean isEmpty() { - return points.isEmpty(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PassThroughPoints that = (PassThroughPoints) o; - return Objects.equals(points, that.points); - } - - @Override - public int hashCode() { - return Objects.hash(points); - } - - @Override - public String toString() { - return ToStringBuilder.of(PassThroughPoints.class).addCol("points", points).toString(); - } -} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java index d85c51e1f97..19289a44f63 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java @@ -10,7 +10,6 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.api.request.PassThroughPoints; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; /** @@ -32,7 +31,7 @@ private BitSetPassThroughPointsService(final List passThroughPoints) { this.expectedC2ValueAtDestination = passThroughPoints.size(); } - public static PassThroughPointsService of(PassThroughPoints points) { + public static PassThroughPointsService of(List points) { return points .stream() .map(PassThroughPoint::asBitSet) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index e938471d808..8e6121edffe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -1,8 +1,5 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; -import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; import static org.opentripplanner.raptor.api.request.Optimization.PARALLEL; import io.micrometer.core.instrument.MeterRegistry; @@ -11,13 +8,11 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; -import java.util.Optional; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.api.request.PassThroughPoints; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; @@ -114,24 +109,13 @@ private RaptorRequest doMap() { searchParams.numberOfAdditionalTransfers(preferences.transfer().maxAdditionalTransfers()); } - final Optional passThroughPoints = request - .getPassThroughPoints() - .stream() - .map(p -> { - final int[] stops = p.stopLocations().stream().mapToInt(StopLocation::getIndex).toArray(); - return new PassThroughPoint(stops, p.name()); - }) - .collect(collectingAndThen(toList(), Optional::ofNullable)) - .filter(not(List::isEmpty)) - .map(PassThroughPoints::new); - builder.withMultiCriteria(mcBuilder -> { preferences .transit() .raptor() .relaxGeneralizedCostAtDestination() .ifPresent(mcBuilder::withRelaxCostAtDestination); - passThroughPoints.ifPresent(pt -> mcBuilder.withPassThroughPoints(pt)); + mcBuilder.withPassThroughPoints(mapPassThroughPoints()); }); for (Optimization optimization : preferences.transit().raptor().optimizations()) { @@ -190,6 +174,17 @@ private RaptorRequest doMap() { return builder.build(); } + private List mapPassThroughPoints() { + return request + .getPassThroughPoints() + .stream() + .map(p -> { + final int[] stops = p.stopLocations().stream().mapToInt(StopLocation::getIndex).toArray(); + return new PassThroughPoint(p.name(), stops); + }) + .toList(); + } + private int relativeTime(Instant time) { if (time == null) { return RaptorConstants.TIME_NOT_SET; diff --git a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java index 781ef315254..09de5ab62ad 100644 --- a/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/lang/IntUtilsTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.framework.lang.IntUtils.concat; import static org.opentripplanner.framework.lang.IntUtils.intArray; +import static org.opentripplanner.framework.lang.IntUtils.intArrayToString; import static org.opentripplanner.framework.lang.IntUtils.intToString; import static org.opentripplanner.framework.lang.IntUtils.requireInRange; import static org.opentripplanner.framework.lang.IntUtils.requireNotNegative; @@ -22,6 +23,13 @@ void testIntToString() { assertEquals("", intToString(-1, -1)); } + @SuppressWarnings("RedundantArrayCreation") + @Test + void testIntToString2() { + assertEquals("7, -1", intArrayToString(7, -1)); + assertEquals("", intArrayToString(new int[0])); + } + @Test void testIntArray() { assertArrayEquals(new int[] { 5, 5, 5 }, intArray(3, 5)); diff --git a/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java b/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java new file mode 100644 index 00000000000..26d17aec022 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java @@ -0,0 +1,104 @@ +package org.opentripplanner.raptor.api.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.model.RelaxFunction; + +class MultiCriteriaRequestTest { + + private static final RelaxFunction RELAX_C1 = GeneralizedCostRelaxFunction.of(2.0, 600); + + private static final List PASS_THROUGH_POINTS = List.of( + new PassThroughPoint(null, 7, 13) + ); + + private final MultiCriteriaRequest subject = MultiCriteriaRequest + .of() + .withRelaxC1(RELAX_C1) + .withPassThroughPoints(PASS_THROUGH_POINTS) + .build(); + + @Test + void copyOf() { + assertSame(subject, subject.copyOf().build()); + + // Change a filed - build - make a new copy and set same value => should be equal + assertEquals( + subject, + subject + .copyOf() + .withPassThroughPoints(null) + .build() + .copyOf() + .withPassThroughPoints(PASS_THROUGH_POINTS) + .build() + ); + // Change another filed - build - make a new copy and set same value => should be equal + assertEquals( + subject, + subject + .copyOf() + .withRelaxC1(RelaxFunction.NORMAL) + .build() + .copyOf() + .withRelaxC1(RELAX_C1) + .build() + ); + } + + @Test + void relaxC1() { + assertEquals(RELAX_C1, subject.relaxC1()); + } + + @Test + void passThroughPoints() { + assertEquals(PASS_THROUGH_POINTS, subject.passThroughPoints().orElseThrow()); + } + + @Test + void testEqualsAndHashCode() { + var eq = MultiCriteriaRequest + .of() + .withRelaxC1(RELAX_C1) + .withPassThroughPoints(PASS_THROUGH_POINTS) + .build(); + var noRelaxC1 = subject.copyOf().withRelaxC1(RelaxFunction.NORMAL).build(); + var noPassThroughPoint = subject.copyOf().withPassThroughPoints(null).build(); + + assertEquals(subject, subject); + assertEquals(subject, eq); + assertNotEquals(subject, noRelaxC1); + assertNotEquals(subject, noPassThroughPoint); + + assertEquals(subject.hashCode(), eq.hashCode()); + assertNotEquals(subject.hashCode(), noRelaxC1.hashCode()); + assertNotEquals(subject.hashCode(), noPassThroughPoint.hashCode()); + } + + @Test + void testToString() { + assertEquals( + "MultiCriteriaRequest{relaxC1: f(x) = 2.00 * x + 6.0, passThroughPoints: [(stops: 7, 13)]}", + subject.toString() + ); + } + + @Test + void includeC2() { + assertTrue(subject.includeC2()); + assertFalse(MultiCriteriaRequest.of().build().includeC2()); + assertTrue( + MultiCriteriaRequest.of().withPassThroughPoints(PASS_THROUGH_POINTS).build().includeC2() + ); + assertFalse(MultiCriteriaRequest.of().withRelaxC1(RELAX_C1).build().includeC2()); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java index 0938b58240d..155e122a7b6 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java @@ -11,7 +11,7 @@ class PassThroughPointTest { private static final int[] STOPS = { 2, 7, 13 }; - private final PassThroughPoint subject = new PassThroughPoint(STOPS, "PT1"); + private final PassThroughPoint subject = new PassThroughPoint("PT1", STOPS); @Test void asBitSet() { @@ -29,8 +29,8 @@ void asBitSet() { @Test void testEqualsAndHashCode() { - var same = new PassThroughPoint(STOPS, "PT1"); - var other = new PassThroughPoint(new int[] { 2, 7 }, "PT2"); + var same = new PassThroughPoint("PT1", STOPS); + var other = new PassThroughPoint("PT2", 2, 7); assertEquals(subject, subject); assertEquals(same, subject); @@ -42,7 +42,8 @@ void testEqualsAndHashCode() { @Test void testToString() { - assertEquals("(name: 'PT1', stops: [2, 7, 13])", subject.toString()); - assertEquals("(stops: [2, 7, 13])", new PassThroughPoint(STOPS, null).toString()); + assertEquals("(PT1, stops: 2, 7, 13)", subject.toString()); + assertEquals("(stops: 2, 7, 13)", new PassThroughPoint(" ", STOPS).toString()); + assertEquals("(stops: 2, 7, 13)", new PassThroughPoint(null, STOPS).toString()); } } diff --git a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java deleted file mode 100644 index 83f4dda4879..00000000000 --- a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.opentripplanner.raptor.api.request; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Test; - -class PassThroughPointsTest { - - private static final int[] STOPS_1 = { 2, 7, 13 }; - private static final int[] STOPS_2 = { 12 }; - - private final PassThroughPoints subject = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) - ); - - @Test - void stream() { - assertEquals( - "(name: 'PT1', stops: [2, 7, 13]), (name: 'PT2', stops: [12])", - subject.stream().map(Objects::toString).collect(Collectors.joining(", ")) - ); - } - - @Test - void isEmpty() { - assertFalse(subject.isEmpty()); - assertTrue(new PassThroughPoints(List.of()).isEmpty()); - } - - @Test - void testEqualsAndHashCode() { - var same = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) - ); - var other = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_1, "PT2")) - ); - assertEquals(same, subject); - assertNotEquals(other, subject); - - assertEquals(same.hashCode(), subject.hashCode()); - assertNotEquals(other.hashCode(), subject.hashCode()); - } - - @Test - void testToString() { - assertEquals( - "PassThroughPoints{points: [(name: 'PT1', stops: [2, 7, 13]), (name: 'PT2', stops: [12])]}", - subject.toString() - ); - } -} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/SearchParamsTest.java b/src/test/java/org/opentripplanner/raptor/api/request/SearchParamsTest.java index d2919cd10d9..0a27c5a1575 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/SearchParamsTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/SearchParamsTest.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor._data.transit.TestAccessEgress; import org.opentripplanner.raptor._data.transit.TestTripSchedule; -public class SearchParamsTest { +class SearchParamsTest { @Test public void earliestDepartureTimeOrLatestArrivalTimeIsRequired() { diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java index 350fcc72cbd..4cc776674da 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java @@ -24,7 +24,6 @@ import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.api.request.PassThroughPoints; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.configure.RaptorConfig; @@ -47,6 +46,21 @@ */ public class J01_PassThroughTest { + public static final List PASS_THROUGH_STOP_A = List.of(point("A", STOP_A)); + public static final List PASS_THROUGH_STOP_C = List.of(point("C", STOP_C)); + public static final List PASS_THROUGH_STOP_D = List.of(point("D", STOP_D)); + public static final List PASS_THROUGH_STOP_B_OR_C = List.of( + point("B&C", STOP_B, STOP_C) + ); + public static final List PASS_THROUGH_STOP_B_THEN_C = List.of( + point("B", STOP_B), + point("C", STOP_C) + ); + public static final List PASS_THROUGH_STOP_B_THEN_D = List.of( + point("B", STOP_B), + point("D", STOP_D) + ); + private final RaptorService raptorService = new RaptorService<>( RaptorConfig.defaultConfigForTest() ); @@ -88,12 +102,7 @@ public void passThroughPointOnEgress() { var requestBuilder = prepareRequest(); requestBuilder - .withMultiCriteria(mc -> - mc.withPassThroughPoints( - // Include desired pass-through point in the request - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_D }, "PT1"))) - ) - ) + .withMultiCriteria(mc -> mc.withPassThroughPoints(PASS_THROUGH_STOP_D)) .searchParams() .addAccessPaths(TestAccessEgress.walk(STOP_A, D30s)) .addEgressPaths(TestAccessEgress.walk(STOP_D, D30s)) @@ -126,9 +135,7 @@ public void passThroughPointOnAccess() { requestBuilder .withMultiCriteria(mc -> // Include desired pass-through point in the request - mc.withPassThroughPoints( - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_A }, "PT1"))) - ) + mc.withPassThroughPoints(PASS_THROUGH_STOP_A) ) .searchParams() .addAccessPaths(TestAccessEgress.walk(STOP_A, D30s)) @@ -161,12 +168,7 @@ public void passThroughPointInTheMiddle() { var requestBuilder = prepareRequest(); requestBuilder - .withMultiCriteria(mc -> - // Include desired pass-through point in the request - mc.withPassThroughPoints( - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_C }, "PT1"))) - ) - ) + .withMultiCriteria(mc -> mc.withPassThroughPoints(PASS_THROUGH_STOP_C)) .searchParams() .addAccessPaths(TestAccessEgress.walk(STOP_A, D30s)) .addEgressPaths(TestAccessEgress.walk(STOP_D, D30s)); @@ -198,17 +200,7 @@ public void multiplePassThroughPoints() { var requestBuilder = prepareRequest(); requestBuilder - .withMultiCriteria(mc -> - mc.withPassThroughPoints( - // Include desired pass-through point in the request - new PassThroughPoints( - List.of( - new PassThroughPoint(new int[] { STOP_B }, "PT1"), - new PassThroughPoint(new int[] { STOP_D }, "PT2") - ) - ) - ) - ) + .withMultiCriteria(mc -> mc.withPassThroughPoints(PASS_THROUGH_STOP_B_THEN_D)) .searchParams() .addAccessPaths(TestAccessEgress.walk(STOP_A, D30s)) .addEgressPaths(TestAccessEgress.walk(STOP_F, D30s)); @@ -238,17 +230,7 @@ public void passThroughOrder() { var requestBuilder = prepareRequest(); requestBuilder - .withMultiCriteria(mc -> - mc.withPassThroughPoints( - // Include desired pass-through point in the request - new PassThroughPoints( - List.of( - new PassThroughPoint(new int[] { STOP_B }, "PT1"), - new PassThroughPoint(new int[] { STOP_C }, "PT2") - ) - ) - ) - ) + .withMultiCriteria(mc -> mc.withPassThroughPoints(PASS_THROUGH_STOP_B_THEN_C)) .searchParams() .addAccessPaths(TestAccessEgress.walk(STOP_A, D30s)) .addEgressPaths(TestAccessEgress.walk(STOP_D, D30s)); @@ -277,15 +259,7 @@ public void passThroughGroup() { var requestBuilder = prepareRequest(); requestBuilder - .withMultiCriteria(mc -> - mc.withPassThroughPoints( - // Include desired pass-through point in the request - new PassThroughPoints( - // Both STOP_B and STOP_C is a valid pass-through point - List.of(new PassThroughPoint(new int[] { STOP_B, STOP_C }, "PT1")) - ) - ) - ) + .withMultiCriteria(mc -> mc.withPassThroughPoints(PASS_THROUGH_STOP_B_OR_C)) .searchParams() // Both routes are pareto optimal. // Route 2 is faster but it contains more walk @@ -301,4 +275,8 @@ public void passThroughGroup() { pathsToString(raptorService.route(requestBuilder.build(), data)) ); } + + private static PassThroughPoint point(String name, int... stops) { + return new PassThroughPoint(name, stops); + } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java index bfe4853a3d8..1d443cd2f9b 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java @@ -13,7 +13,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.api.request.PassThroughPoints; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; class BitSetPassThroughPointsServiceTest { @@ -32,9 +31,7 @@ class BitSetPassThroughPointsServiceTest { private static final int STOP_31 = 6; private static final PassThroughPointsService SUBJECT = BitSetPassThroughPointsService.of( - new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) - ) + List.of(new PassThroughPoint("PT1", STOPS_1), new PassThroughPoint("PT2", STOPS_2)) ); /** * We expect the c2 value at the destination to be the same as the number of pass-through From 3aa86e5fa4841e54232df7eaacb881b8f3fb117a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 6 Oct 2023 15:17:03 +0300 Subject: [PATCH 021/120] Include SIRI nordic profile docs --- src/ext/graphql/transmodelapi/schema.graphql | 21 +++++++++++++++---- .../ext/transmodelapi/model/EnumTypes.java | 21 +++++++++++++++---- .../apis/gtfs/generated/GraphQLTypes.java | 8 +++---- .../model/timetable/OccupancyStatus.java | 7 ++++++- .../opentripplanner/apis/gtfs/schema.graphqls | 9 +++++++- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 71aed3a2242..c62356e5dfa 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1538,17 +1538,30 @@ enum OccupancyStatus { values. """ empty - "The vehicle or carriage has a few seats available." + """ + The vehicle or carriage has a few seats available. + SIRI nordic profile: less than ~50% of seats available. + """ fewSeatsAvailable "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board." full - "The vehicle or carriage has a large number of seats available." + """ + The vehicle or carriage has a large number of seats available. + SIRI nordic profile: more than ~50% of seats available. + """ manySeatsAvailable "The vehicle or carriage doesn't have any occupancy data available." noData - "The vehicle or carriage has no seats or standing room available." + """ + The vehicle or carriage has no seats or standing room available. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed + to alight due to e.g. crowding. + """ notAcceptingPassengers - "The vehicle or carriage only has standing room available." + """ + The vehicle or carriage only has standing room available. + SIRI nordic profile: less than ~10% of seats available. + """ standingRoomOnly } diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 3fa3ed67cc2..21befce4150 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -211,17 +211,26 @@ public class EnumTypes { .value( "manySeatsAvailable", OccupancyStatus.MANY_SEATS_AVAILABLE, - "The vehicle or carriage has a large number of seats available." + """ + The vehicle or carriage has a large number of seats available. + SIRI nordic profile: more than ~50% of seats available. + """ ) .value( "fewSeatsAvailable", OccupancyStatus.FEW_SEATS_AVAILABLE, - "The vehicle or carriage has a few seats available." + """ + The vehicle or carriage has a few seats available. + SIRI nordic profile: less than ~50% of seats available. + """ ) .value( "standingRoomOnly", OccupancyStatus.STANDING_ROOM_ONLY, - "The vehicle or carriage only has standing room available." + """ + The vehicle or carriage only has standing room available. + SIRI nordic profile: less than ~10% of seats available. + """ ) .value( "crushedStandingRoomOnly", @@ -240,7 +249,11 @@ public class EnumTypes { .value( "notAcceptingPassengers", OccupancyStatus.NOT_ACCEPTING_PASSENGERS, - "The vehicle or carriage has no seats or standing room available." + """ + The vehicle or carriage has no seats or standing room available. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed + to alight due to e.g. crowding. + """ ) .build(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index cba63739dd5..9ecf488452d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -917,8 +917,8 @@ public enum GraphQLPropulsionType { } /** - * Additional qualifier for a transport mode. - * Note that qualifiers can only be used with certain transport modes. + * Additional qualifier for a transport mode. Note that qualifiers can only be used with certain + * transport modes. */ public enum GraphQLQualifier { ACCESS, @@ -3373,8 +3373,8 @@ public void setGraphQLUnpreferredCost(Integer unpreferredCost) { } /** - * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that they - * may be represented differently to the user. + * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that + * they may be represented differently to the user. */ public enum GraphQLVehicleParkingState { CLOSED, diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java index 53ef887e347..cd4abd27ced 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java @@ -9,7 +9,7 @@ */ public enum OccupancyStatus { /** - * Default. There is no occupancy-data on this departure + * Default. There is no occupancy-data on this departure, */ NO_DATA_AVAILABLE, /** @@ -27,15 +27,18 @@ public enum OccupancyStatus { * The vehicle or carriage has a small number of seats available. The amount of free seats out of * the total seats available to be considered small enough to fall into this category is * determined at the discretion of the producer. + * SIRI nordic profile: more than ~50% of seats available. */ FEW_SEATS_AVAILABLE, /** * The vehicle or carriage can currently accommodate only standing passengers. + * SIRI nordic profile: less than ~50% of seats available. */ STANDING_ROOM_ONLY, /** * The vehicle or carriage can currently accommodate only standing passengers and has limited * space for them. + * SIRI nordic profile: less than ~10% of seats available. */ CRUSHED_STANDING_ROOM_ONLY, /** @@ -45,6 +48,8 @@ public enum OccupancyStatus { FULL, /** * The vehicle or carriage is not accepting passengers. + * SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only + * allowed to alight due to e.g. crowding. */ NOT_ACCEPTING_PASSENGERS, } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 7117acd8902..a249adaafbb 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3413,6 +3413,7 @@ enum OccupancyStatus { determined at the discretion of the producer. There isn't a big difference between this and EMPTY so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: more than ~50% of seats available. """ MANY_SEATS_AVAILABLE @@ -3420,10 +3421,14 @@ enum OccupancyStatus { The vehicle or carriage has a small number of seats available. The amount of free seats out of the total seats available to be considered small enough to fall into this category is determined at the discretion of the producer. + SIRI nordic profile: less than ~50% of seats available. """ FEW_SEATS_AVAILABLE - """The vehicle or carriage can currently accommodate only standing passengers.""" + """ + The vehicle or carriage can currently accommodate only standing passengers. + SIRI nordic profile: less than ~10% of seats available. + """ STANDING_ROOM_ONLY """ @@ -3441,6 +3446,8 @@ enum OccupancyStatus { """ The vehicle or carriage is not accepting passengers. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed + to alight due to e.g. crowding. """ NOT_ACCEPTING_PASSENGERS } From 356f4e876407ece7c2e19c43811344dceb46847a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 14:31:02 +0200 Subject: [PATCH 022/120] refactor: Remove Optional from Optional> passThroughPoints() --- .../raptor/api/request/MultiCriteriaRequest.java | 16 +++++++++------- .../configure/McRangeRaptorConfig.java | 9 ++++----- .../api/request/MultiCriteriaRequestTest.java | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java index 0593eb72fc2..bf1747a5daf 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequest.java @@ -21,7 +21,6 @@ public class MultiCriteriaRequest { @Nullable private final RaptorTransitPriorityGroupCalculator transitPriorityCalculator; - @Nullable private final List passThroughPoints; @Nullable @@ -30,7 +29,7 @@ public class MultiCriteriaRequest { private MultiCriteriaRequest() { this.relaxC1 = RelaxFunction.NORMAL; this.transitPriorityCalculator = null; - this.passThroughPoints = null; + this.passThroughPoints = List.of(); this.relaxCostAtDestination = null; } @@ -77,8 +76,12 @@ public Optional transitPriorityCalculator( return Optional.ofNullable(transitPriorityCalculator); } - public Optional> passThroughPoints() { - return Optional.ofNullable(passThroughPoints); + public boolean hasPassThroughPoints() { + return !passThroughPoints.isEmpty(); + } + + public List passThroughPoints() { + return passThroughPoints; } /** @@ -138,7 +141,7 @@ public String toString() { } public boolean includeC2() { - return passThroughPoints != null || transitPriorityCalculator != null; + return hasPassThroughPoints() || transitPriorityCalculator != null; } public static class Builder { @@ -177,7 +180,6 @@ public Builder withTransitPriorityCalculator(RaptorTransitPriorityGroupCalcul return this; } - @Nullable public List passThroughPoints() { return passThroughPoints; } @@ -185,7 +187,7 @@ public List passThroughPoints() { @Nullable public Builder withPassThroughPoints(List points) { // Prevent setting this to an empty list - here we use null to represent NOT_SET - passThroughPoints = (points == null || points.isEmpty()) ? null : points; + passThroughPoints = (points == null || points.isEmpty()) ? List.of() : points; return this; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index 66dff861626..56679f8148b 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -63,10 +63,9 @@ public McRangeRaptorConfig( public static PassThroughPointsService passThroughPointsService( MultiCriteriaRequest multiCriteriaRequest ) { - return multiCriteriaRequest - .passThroughPoints() - .map(BitSetPassThroughPointsService::of) - .orElse(PassThroughPointsService.NOOP); + return multiCriteriaRequest.hasPassThroughPoints() + ? BitSetPassThroughPointsService.of(multiCriteriaRequest.passThroughPoints()) + : PassThroughPointsService.NOOP; } /** @@ -206,7 +205,7 @@ private RaptorTransitPriorityGroupCalculator getTransitPriorityGroupCalculator() } private boolean isPassThrough() { - return mcRequest().passThroughPoints().isPresent(); + return mcRequest().hasPassThroughPoints(); } private boolean isTransitPriority() { diff --git a/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java b/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java index 26d17aec022..8468af54401 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/MultiCriteriaRequestTest.java @@ -61,7 +61,7 @@ void relaxC1() { @Test void passThroughPoints() { - assertEquals(PASS_THROUGH_POINTS, subject.passThroughPoints().orElseThrow()); + assertEquals(PASS_THROUGH_POINTS, subject.passThroughPoints()); } @Test From 8b8f02032fe3dd9a18a9c2b721dbbacf809e4c48 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 6 Oct 2023 15:16:00 +0200 Subject: [PATCH 023/120] Add validation for missing calls in SIRI update --- .../ext/siri/AddedTripBuilderTest.java | 43 ++++++++++++++++--- .../ext/siri/AddedTripBuilder.java | 5 +++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java index 1da6f30101c..4332776dc5d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java @@ -38,7 +38,7 @@ import org.opentripplanner.updater.spi.UpdateError; import uk.org.siri.siri20.VehicleModesEnumeration; -public class AddedTripBuilderTest { +class AddedTripBuilderTest { private static final Agency AGENCY = TransitModelForTest.AGENCY; private static final ZoneId TIME_ZONE = AGENCY.getTimezone(); @@ -105,7 +105,7 @@ void setUp() { } @Test - public void testAddedTrip() { + void testAddedTrip() { var addedTrip = new AddedTripBuilder( TRANSIT_MODEL, ENTITY_RESOLVER, @@ -370,7 +370,7 @@ void testAddedTripWithoutReplacedRoute() { } @Test - public void testAddedTripFailOnMissingServiceId() { + void testAddedTripFailOnMissingServiceId() { var addedTrip = new AddedTripBuilder( TRANSIT_MODEL, ENTITY_RESOLVER, @@ -382,7 +382,7 @@ public void testAddedTripFailOnMissingServiceId() { null, TRANSIT_MODE, SUB_MODE, - List.of(), + getCalls(0), false, null, false, @@ -400,7 +400,7 @@ public void testAddedTripFailOnMissingServiceId() { } @Test - public void testAddedTripFailOnNonIncreasingDwellTime() { + void testAddedTripFailOnNonIncreasingDwellTime() { List calls = List.of( TestCall .of() @@ -453,6 +453,37 @@ public void testAddedTripFailOnNonIncreasingDwellTime() { ); } + @Test + void testAddedTripFailOnTooFewCalls() { + List calls = List.of(); + var addedTrip = new AddedTripBuilder( + TRANSIT_MODEL, + ENTITY_RESOLVER, + AbstractTransitEntity::getId, + TRIP_ID, + OPERATOR, + LINE_REF, + REPLACED_ROUTE, + SERVICE_DATE, + TRANSIT_MODE, + SUB_MODE, + calls, + false, + null, + false, + SHORT_NAME, + HEADSIGN + ) + .build(); + + assertTrue(addedTrip.isFailure(), "Trip creation should fail"); + assertEquals( + UpdateError.UpdateErrorType.TOO_FEW_STOPS, + addedTrip.failureValue().errorType(), + "Trip creation should fail with too few stops" + ); + } + @ParameterizedTest @CsvSource( { @@ -462,7 +493,7 @@ public void testAddedTripFailOnNonIncreasingDwellTime() { "ferry,FERRY,RAIL,", } ) - public void testGetTransportMode( + void testGetTransportMode( String siriMode, String internalMode, String replacedRouteMode, diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index b4f797d9ec1..c8e47134e06 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -3,6 +3,7 @@ import static java.lang.Boolean.TRUE; import static org.opentripplanner.ext.siri.mapper.SiriTransportModeMapper.mapTransitMainMode; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_START_DATE; +import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TOO_FEW_STOPS; import java.time.LocalDate; import java.time.ZoneId; @@ -144,6 +145,10 @@ class AddedTripBuilder { } Result build() { + if (calls.isEmpty()) { + return UpdateError.result(tripId, TOO_FEW_STOPS); + } + if (serviceDate == null) { return UpdateError.result(tripId, NO_START_DATE); } From 5b8fa6b6c19ecc81fb97f0d7fedb6eae58cae7c0 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 6 Oct 2023 16:52:40 +0200 Subject: [PATCH 024/120] Add validation for unknown stop in SIRI update --- .../ext/siri/AddedTripBuilderTest.java | 57 ++++++++++++++++++- .../ext/siri/AddedTripBuilder.java | 19 ++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java index 4332776dc5d..ed1e165343d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/AddedTripBuilderTest.java @@ -455,7 +455,14 @@ void testAddedTripFailOnNonIncreasingDwellTime() { @Test void testAddedTripFailOnTooFewCalls() { - List calls = List.of(); + List calls = List.of( + TestCall + .of() + .withStopPointRef(STOP_A.getId().getId()) + .withAimedDepartureTime(zonedDateTime(10, 20)) + .withExpectedDepartureTime(zonedDateTime(10, 20)) + .build() + ); var addedTrip = new AddedTripBuilder( TRANSIT_MODEL, ENTITY_RESOLVER, @@ -480,7 +487,53 @@ void testAddedTripFailOnTooFewCalls() { assertEquals( UpdateError.UpdateErrorType.TOO_FEW_STOPS, addedTrip.failureValue().errorType(), - "Trip creation should fail with too few stops" + "Trip creation should fail with too few calls" + ); + } + + @Test + void testAddedTripFailOnUnknownStop() { + List calls = List.of( + TestCall + .of() + .withStopPointRef("UNKNOWN_STOP_REF") + .withAimedDepartureTime(zonedDateTime(10, 20)) + .withExpectedDepartureTime(zonedDateTime(10, 20)) + .build(), + TestCall + .of() + .withStopPointRef(STOP_B.getId().getId()) + .withAimedArrivalTime(zonedDateTime(10, 30)) + .withExpectedArrivalTime(zonedDateTime(10, 31)) + .withAimedDepartureTime(zonedDateTime(10, 30)) + .withExpectedDepartureTime(zonedDateTime(10, 29)) + .build() + ); + var addedTrip = new AddedTripBuilder( + TRANSIT_MODEL, + ENTITY_RESOLVER, + AbstractTransitEntity::getId, + TRIP_ID, + OPERATOR, + LINE_REF, + REPLACED_ROUTE, + SERVICE_DATE, + TRANSIT_MODE, + SUB_MODE, + calls, + false, + null, + false, + SHORT_NAME, + HEADSIGN + ) + .build(); + + assertTrue(addedTrip.isFailure(), "Trip creation should fail"); + assertEquals( + UpdateError.UpdateErrorType.NO_VALID_STOPS, + addedTrip.failureValue().errorType(), + "Trip creation should fail with call referring to unknown stop" ); } diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index c8e47134e06..23d7cbb1705 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -3,6 +3,7 @@ import static java.lang.Boolean.TRUE; import static org.opentripplanner.ext.siri.mapper.SiriTransportModeMapper.mapTransitMainMode; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_START_DATE; +import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_VALID_STOPS; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TOO_FEW_STOPS; import java.time.LocalDate; @@ -24,6 +25,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; +import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; @@ -145,7 +147,7 @@ class AddedTripBuilder { } Result build() { - if (calls.isEmpty()) { + if (calls.size() < 2) { return UpdateError.result(tripId, TOO_FEW_STOPS); } @@ -181,6 +183,11 @@ Result build() { stopSequence == (calls.size() - 1) ); + // Drop this update if the call refers to an unknown stop (not present in the stop model). + if (stopTime == null) { + return UpdateError.result(tripId, NO_VALID_STOPS); + } + aimedStopTimes.add(stopTime); } @@ -284,6 +291,9 @@ private Trip createTrip(Route route, FeedScopedId calServiceId) { return tripBuilder.build(); } + /** + * Map the call to a StopTime or return null if the stop cannot be found in the stop model. + */ private StopTime createStopTime( Trip trip, ZonedDateTime departureDate, @@ -292,10 +302,15 @@ private StopTime createStopTime( boolean isFirstStop, boolean isLastStop ) { + RegularStop stop = entityResolver.resolveQuay(call.getStopPointRef()); + if (stop == null) { + return null; + } + StopTime stopTime = new StopTime(); stopTime.setStopSequence(stopSequence); stopTime.setTrip(trip); - stopTime.setStop(entityResolver.resolveQuay(call.getStopPointRef())); + stopTime.setStop(stop); // Fallback to other time, if one doesn't exist var aimedArrivalTime = call.getAimedArrivalTime() != null From 6f79131c8366e61098c6c043cf325c38517d6ef8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 20 Sep 2023 16:33:22 +0200 Subject: [PATCH 025/120] Improve geocoder fuzziness --- .../ext/geocoder/LuceneIndexTest.java | 41 +++++++++++++++++-- .../ext/geocoder/LuceneIndex.java | 29 ++++++++----- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 97f151493e1..f85cdfae353 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -145,9 +145,24 @@ void stopLocationGroupsWithSpace() { @Nested class StopClusters { - @Test - void stopClusters() { - var result1 = index.queryStopClusters("alex").toList(); + @ParameterizedTest + @ValueSource( + strings = { + "Alexanderplatz", + "alex", + "Alexnderplatz", + "Alexnaderplatz", + "alexnaderplaz", + "Alexanderplat", + "alexanderplat", + "alexand", + "alexander platz", + "alexander-platz", + "alexander", + } + ) + void stopClustersWithTypos(String searchTerm) { + var result1 = index.queryStopClusters(searchTerm).toList(); assertEquals(List.of(mapper.map(ALEXANDERPLATZ_STATION)), result1); } @@ -167,7 +182,25 @@ void deduplicatedStopClusters() { @ParameterizedTest @ValueSource( strings = { - "five", "five ", "five p", "five po", "five poi", "five poin", "five point", "five points", + "five", + "five ", + "five p", + "five po", + "five poi", + "five poin", + "five point", + "five points", + "fife point", + "five poits", + "fife", + "points", + "the five points", + "five @ points", + "five@points", + "five at points", + "five&points", + "five & points", + "five and points", } ) void stopClustersWithSpace(String query) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 62eeb977c8d..2ddbbf89069 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -10,7 +10,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.core.SimpleAnalyzer; +import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.codecs.Codec; @@ -24,9 +24,12 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.FuzzyQuery; +import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.suggest.document.Completion90PostingsFormat; import org.apache.lucene.search.suggest.document.CompletionAnalyzer; @@ -70,7 +73,7 @@ public LuceneIndex(Graph graph, TransitService transitService) { this.analyzer = new PerFieldAnalyzerWrapper( new StandardAnalyzer(), - Map.of(NAME, new SimpleAnalyzer(), SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())) + Map.of(NAME, new EnglishAnalyzer(), SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())) ); var directory = new ByteBuffersDirectory(); @@ -194,7 +197,7 @@ public Stream queryStreetVertices(String query, boolean autocomple * one of those is chosen at random and returned. */ public Stream queryStopClusters(String query) { - return matchingDocuments(StopCluster.class, query, true).map(LuceneIndex::toStopCluster); + return matchingDocuments(StopCluster.class, query, false).map(LuceneIndex::toStopCluster); } private static StopCluster toStopCluster(Document document) { @@ -279,6 +282,7 @@ private Stream matchingDocuments( 3 ); var query = new ContextQuery(completionQuery); + query.addContext(type.getSimpleName()); var topDocs = searcher.suggest(query, 25, true); @@ -293,8 +297,12 @@ private Stream matchingDocuments( } }); } else { - var parser = new QueryParser(CODE, analyzer); - var nameQuery = parser.createPhraseQuery(NAME, searchTerms); + var parser = new QueryParser(NAME, analyzer); + var nameQuery = parser.parse(searchTerms); + var fuzzyNameQuery = new FuzzyQuery(new Term(NAME, analyzer.normalize(NAME, searchTerms))); + var prefixNameQuery = new PrefixQuery( + new Term(NAME, analyzer.normalize(NAME, searchTerms)) + ); var codeQuery = new TermQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); var typeQuery = new TermQuery( new Term(TYPE, analyzer.normalize(TYPE, type.getSimpleName())) @@ -303,11 +311,10 @@ private Stream matchingDocuments( var builder = new BooleanQuery.Builder() .setMinimumNumberShouldMatch(1) .add(typeQuery, Occur.MUST) - .add(codeQuery, Occur.SHOULD); - - if (nameQuery != null) { - builder.add(nameQuery, Occur.SHOULD); - } + .add(codeQuery, Occur.SHOULD) + .add(nameQuery, Occur.SHOULD) + .add(fuzzyNameQuery, Occur.SHOULD) + .add(prefixNameQuery, Occur.SHOULD); var query = builder.build(); @@ -323,7 +330,7 @@ private Stream matchingDocuments( } }); } - } catch (IOException ex) { + } catch (IOException | ParseException ex) { throw new RuntimeException(ex); } } From 30754257a3b83360631f6cef209cc1c0136818aa Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 20 Sep 2023 17:43:00 +0200 Subject: [PATCH 026/120] Allow prefix queries for stop code --- .../ext/geocoder/LuceneIndexTest.java | 33 +++++++++++++++++-- .../ext/geocoder/LuceneIndex.java | 31 ++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index f85cdfae353..76ad7b005c0 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -9,9 +9,15 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -149,6 +155,7 @@ class StopClusters { @ValueSource( strings = { "Alexanderplatz", + "Alexa", "alex", "Alexnderplatz", "Alexnaderplatz", @@ -201,6 +208,8 @@ void deduplicatedStopClusters() { "five&points", "five & points", "five and points", + "points five", + "points fife", } ) void stopClustersWithSpace(String query) { @@ -208,13 +217,31 @@ void stopClustersWithSpace(String query) { assertEquals(List.of(mapper.map(FIVE_POINTS_STATION)), result); } - @Test - void stopCode() { - var result = index.queryStopClusters(ARTS_CENTER.getCode()).toList(); + @ParameterizedTest + @ValueSource(strings = { "4456", "445", "#445" }) + void fuzzyStopCode(String query) { + var result = index.queryStopClusters(query).toList(); assertEquals(1, result.size()); assertEquals(ARTS_CENTER.getName().toString(), result.get(0).name()); } + @Test + void analyzer() throws IOException { + var x = analyze("#444", new StandardAnalyzer()); + assertEquals(x, "444"); + } + + public List analyze(String text, Analyzer analyzer) throws IOException { + List result = new ArrayList(); + TokenStream tokenStream = analyzer.tokenStream("code", text); + CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); + tokenStream.reset(); + while (tokenStream.incrementToken()) { + result.add(attr.toString()); + } + return result; + } + @Test void modes() { var result = index.queryStopClusters("westh").toList(); diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 2ddbbf89069..fe210b6db22 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.geocoder; +import static java.util.Map.entry; + import java.io.IOException; import java.io.Serializable; import java.util.Arrays; @@ -10,9 +12,14 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.CharArraySet; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.core.LowerCaseFilter; +import org.apache.lucene.analysis.core.StopFilter; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.analysis.standard.StandardTokenizer; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.PostingsFormat; import org.apache.lucene.codecs.lucene95.Lucene95Codec; @@ -67,13 +74,31 @@ public class LuceneIndex implements Serializable { private final Analyzer analyzer; private final SuggestIndexSearcher searcher; + public static class MyCustomAnalyzer extends Analyzer { + + static final CharArraySet CODE_STOP_WORDS = new CharArraySet(Set.of("#"), true); + + @Override + protected TokenStreamComponents createComponents(String fieldName) { + StandardTokenizer src = new StandardTokenizer(); + TokenStream result = new LowerCaseFilter(src); + result = new StopFilter(result, CODE_STOP_WORDS); + return new TokenStreamComponents(src, result); + } + } + public LuceneIndex(Graph graph, TransitService transitService) { this.graph = graph; this.transitService = transitService; + this.analyzer = new PerFieldAnalyzerWrapper( new StandardAnalyzer(), - Map.of(NAME, new EnglishAnalyzer(), SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())) + Map.ofEntries( + entry(NAME, new EnglishAnalyzer()), + entry(SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())), + entry(CODE, new MyCustomAnalyzer()) + ) ); var directory = new ByteBuffersDirectory(); @@ -304,6 +329,9 @@ private Stream matchingDocuments( new Term(NAME, analyzer.normalize(NAME, searchTerms)) ); var codeQuery = new TermQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); + var prefixCodeQuery = new PrefixQuery( + new Term(CODE, analyzer.normalize(CODE, searchTerms)) + ); var typeQuery = new TermQuery( new Term(TYPE, analyzer.normalize(TYPE, type.getSimpleName())) ); @@ -312,6 +340,7 @@ private Stream matchingDocuments( .setMinimumNumberShouldMatch(1) .add(typeQuery, Occur.MUST) .add(codeQuery, Occur.SHOULD) + .add(prefixCodeQuery, Occur.SHOULD) .add(nameQuery, Occur.SHOULD) .add(fuzzyNameQuery, Occur.SHOULD) .add(prefixNameQuery, Occur.SHOULD); From d87d9fb7f0f24a36f261cabe6871a622f5a68464 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 20 Sep 2023 23:20:07 +0200 Subject: [PATCH 027/120] Allow prefix queries for stop codes --- .../ext/geocoder/LuceneIndexTest.java | 32 ++++--------------- .../ext/geocoder/LuceneIndex.java | 31 ++++-------------- 2 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 76ad7b005c0..d6502318726 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -9,15 +9,9 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; -import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.standard.StandardAnalyzer; -import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -157,6 +151,8 @@ class StopClusters { "Alexanderplatz", "Alexa", "alex", + "aleyanderplazt", + "alexnderplazt", "Alexnderplatz", "Alexnaderplatz", "alexnaderplaz", @@ -203,13 +199,14 @@ void deduplicatedStopClusters() { "points", "the five points", "five @ points", + "five @ the points", "five@points", "five at points", "five&points", "five & points", - "five and points", + "five and the points", "points five", - "points fife", + "points fife" } ) void stopClustersWithSpace(String query) { @@ -218,30 +215,13 @@ void stopClustersWithSpace(String query) { } @ParameterizedTest - @ValueSource(strings = { "4456", "445", "#445" }) + @ValueSource(strings = { "4456", "445" }) void fuzzyStopCode(String query) { var result = index.queryStopClusters(query).toList(); assertEquals(1, result.size()); assertEquals(ARTS_CENTER.getName().toString(), result.get(0).name()); } - @Test - void analyzer() throws IOException { - var x = analyze("#444", new StandardAnalyzer()); - assertEquals(x, "444"); - } - - public List analyze(String text, Analyzer analyzer) throws IOException { - List result = new ArrayList(); - TokenStream tokenStream = analyzer.tokenStream("code", text); - CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); - tokenStream.reset(); - while (tokenStream.incrementToken()) { - result.add(attr.toString()); - } - return result; - } - @Test void modes() { var result = index.queryStopClusters("westh").toList(); diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index fe210b6db22..3e2bb1c5289 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -12,14 +12,9 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.CharArraySet; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.core.LowerCaseFilter; -import org.apache.lucene.analysis.core.StopFilter; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; -import org.apache.lucene.analysis.standard.StandardTokenizer; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.PostingsFormat; import org.apache.lucene.codecs.lucene95.Lucene95Codec; @@ -74,19 +69,6 @@ public class LuceneIndex implements Serializable { private final Analyzer analyzer; private final SuggestIndexSearcher searcher; - public static class MyCustomAnalyzer extends Analyzer { - - static final CharArraySet CODE_STOP_WORDS = new CharArraySet(Set.of("#"), true); - - @Override - protected TokenStreamComponents createComponents(String fieldName) { - StandardTokenizer src = new StandardTokenizer(); - TokenStream result = new LowerCaseFilter(src); - result = new StopFilter(result, CODE_STOP_WORDS); - return new TokenStreamComponents(src, result); - } - } - public LuceneIndex(Graph graph, TransitService transitService) { this.graph = graph; this.transitService = transitService; @@ -96,8 +78,7 @@ public LuceneIndex(Graph graph, TransitService transitService) { new StandardAnalyzer(), Map.ofEntries( entry(NAME, new EnglishAnalyzer()), - entry(SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())), - entry(CODE, new MyCustomAnalyzer()) + entry(SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())) ) ); @@ -322,16 +303,16 @@ private Stream matchingDocuments( } }); } else { - var parser = new QueryParser(NAME, analyzer); - var nameQuery = parser.parse(searchTerms); + var nameParser = new QueryParser(NAME, analyzer); + var nameQuery = nameParser.parse(searchTerms); var fuzzyNameQuery = new FuzzyQuery(new Term(NAME, analyzer.normalize(NAME, searchTerms))); var prefixNameQuery = new PrefixQuery( new Term(NAME, analyzer.normalize(NAME, searchTerms)) ); var codeQuery = new TermQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); - var prefixCodeQuery = new PrefixQuery( - new Term(CODE, analyzer.normalize(CODE, searchTerms)) - ); + + var prefixCodeQuery = new PrefixQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); + var typeQuery = new TermQuery( new Term(TYPE, analyzer.normalize(TYPE, type.getSimpleName())) ); From a84810b88bc6b685095440c8d02713f383443260 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 22 Sep 2023 13:30:37 +0200 Subject: [PATCH 028/120] Build NGram index for matching middle parts of names --- .../geocoder/EnglishNgramAnalyzerTest.java | 96 +++++++++++++++++++ .../ext/geocoder/LuceneIndexTest.java | 5 +- .../ext/geocoder/EnglishNGramAnalyzer.java | 34 +++++++ .../ext/geocoder/LuceneIndex.java | 15 ++- 4 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java create mode 100644 src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java new file mode 100644 index 00000000000..9bf7ef73da5 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -0,0 +1,96 @@ +package org.opentripplanner.ext.geocoder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; +import org.junit.jupiter.api.Test; + +class EnglishNgramAnalyzerTest { + + @Test + void ngram() throws IOException { + var analyzer = new EnglishNGramAnalyzer(); + List result = analyze("Alexanderplatz", analyzer); + + System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); + assertEquals( + List.of( + "Alex", + "Alexa", + "Alexan", + "Alexand", + "Alexande", + "Alexander", + "Alexanderp", + "lexa", + "lexan", + "lexand", + "lexande", + "lexander", + "lexanderp", + "lexanderpl", + "exan", + "exand", + "exande", + "exander", + "exanderp", + "exanderpl", + "exanderpla", + "xand", + "xande", + "xander", + "xanderp", + "xanderpl", + "xanderpla", + "xanderplat", + "ande", + "ander", + "anderp", + "anderpl", + "anderpla", + "anderplat", + "anderplatz", + "nder", + "nderp", + "nderpl", + "nderpla", + "nderplat", + "nderplatz", + "derp", + "derpl", + "derpla", + "derplat", + "derplatz", + "erpl", + "erpla", + "erplat", + "erplatz", + "rpla", + "rplat", + "rplatz", + "plat", + "platz", + "latz", + "Alexanderplatz" + ), + result + ); + } + + public List analyze(String text, Analyzer analyzer) throws IOException { + List result = new ArrayList<>(); + TokenStream tokenStream = analyzer.tokenStream("name", text); + CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); + tokenStream.reset(); + while (tokenStream.incrementToken()) { + result.add(attr.toString()); + } + return result; + } +} diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index d6502318726..80cce649e04 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -155,6 +155,8 @@ class StopClusters { "alexnderplazt", "Alexnderplatz", "Alexnaderplatz", + "xande", + "xanderpla", "alexnaderplaz", "Alexanderplat", "alexanderplat", @@ -197,6 +199,7 @@ void deduplicatedStopClusters() { "five poits", "fife", "points", + "ife points", "the five points", "five @ points", "five @ the points", @@ -206,7 +209,7 @@ void deduplicatedStopClusters() { "five & points", "five and the points", "points five", - "points fife" + "points fife", } ) void stopClustersWithSpace(String query) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java new file mode 100644 index 00000000000..af2156fd40e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -0,0 +1,34 @@ +package org.opentripplanner.ext.geocoder; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.core.LowerCaseFilter; +import org.apache.lucene.analysis.core.StopFilter; +import org.apache.lucene.analysis.en.EnglishAnalyzer; +import org.apache.lucene.analysis.en.EnglishPossessiveFilter; +import org.apache.lucene.analysis.en.PorterStemFilter; +import org.apache.lucene.analysis.miscellaneous.CapitalizationFilter; +import org.apache.lucene.analysis.ngram.NGramTokenFilter; +import org.apache.lucene.analysis.standard.StandardTokenizer; + +/** + * A custom analyzer for stop names. It removes english stop words (at,the...) and splits + * the input into ing NGrams (https://en.wikipedia.org/wiki/N-gram) so that the middle + * of a stop name can be matched efficiently. + *

+ * For example the query of "exanderpl" will match the stop name "Alexanderplatz". + */ +class EnglishNGramAnalyzer extends Analyzer { + + @Override + protected TokenStreamComponents createComponents(String fieldName) { + StandardTokenizer src = new StandardTokenizer(); + TokenStream result = new EnglishPossessiveFilter(src); + result = new LowerCaseFilter(result); + result = new StopFilter(result, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); + result = new PorterStemFilter(result); + result = new CapitalizationFilter(result); + result = new NGramTokenFilter(result, 4, 10, true); + return new TokenStreamComponents(src, result); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 3e2bb1c5289..ca8a9d96df9 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -58,6 +58,7 @@ public class LuceneIndex implements Serializable { private static final String ID = "id"; private static final String SUGGEST = "suggest"; private static final String NAME = "name"; + private static final String NAME_NGRAM = "name_ngram"; private static final String CODE = "code"; private static final String LAT = "latitude"; private static final String LON = "longitude"; @@ -78,6 +79,7 @@ public LuceneIndex(Graph graph, TransitService transitService) { new StandardAnalyzer(), Map.ofEntries( entry(NAME, new EnglishAnalyzer()), + entry(NAME_NGRAM, new EnglishNGramAnalyzer()), entry(SUGGEST, new CompletionAnalyzer(new StandardAnalyzer())) ) ); @@ -249,6 +251,7 @@ private static void addToIndex( document.add(new StoredField(ID, id)); document.add(new TextField(TYPE, typeName, Store.YES)); document.add(new TextField(NAME, Objects.toString(name), Store.YES)); + document.add(new TextField(NAME_NGRAM, Objects.toString(name), Store.YES)); document.add(new ContextSuggestField(SUGGEST, Objects.toString(name), 1, typeName)); document.add(new StoredField(LAT, latitude)); document.add(new StoredField(LON, longitude)); @@ -305,13 +308,20 @@ private Stream matchingDocuments( } else { var nameParser = new QueryParser(NAME, analyzer); var nameQuery = nameParser.parse(searchTerms); + + var ngramNameQuery = new TermQuery( + new Term(NAME_NGRAM, analyzer.normalize(NAME_NGRAM, searchTerms)) + ); + var fuzzyNameQuery = new FuzzyQuery(new Term(NAME, analyzer.normalize(NAME, searchTerms))); var prefixNameQuery = new PrefixQuery( new Term(NAME, analyzer.normalize(NAME, searchTerms)) ); var codeQuery = new TermQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); - var prefixCodeQuery = new PrefixQuery(new Term(CODE, analyzer.normalize(CODE, searchTerms))); + var prefixCodeQuery = new PrefixQuery( + new Term(CODE, analyzer.normalize(CODE, searchTerms)) + ); var typeQuery = new TermQuery( new Term(TYPE, analyzer.normalize(TYPE, type.getSimpleName())) @@ -324,7 +334,8 @@ private Stream matchingDocuments( .add(prefixCodeQuery, Occur.SHOULD) .add(nameQuery, Occur.SHOULD) .add(fuzzyNameQuery, Occur.SHOULD) - .add(prefixNameQuery, Occur.SHOULD); + .add(prefixNameQuery, Occur.SHOULD) + .add(ngramNameQuery, Occur.SHOULD); var query = builder.build(); From 63ab2a83b70c4b125603fff62bfa3c77cd67975c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 22 Sep 2023 14:00:56 +0200 Subject: [PATCH 029/120] Remove street corners from Lucene indexing --- .../ext/geocoder/LuceneIndexTest.java | 2 +- .../ext/geocoder/GeocoderResource.java | 28 ++-------------- .../ext/geocoder/LuceneIndex.java | 33 ++----------------- 3 files changed, 6 insertions(+), 57 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 80cce649e04..881df8b65af 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -113,7 +113,7 @@ public List getModesOfStopLocation(StopLocation stop) { } } }; - index = new LuceneIndex(graph, transitService); + index = new LuceneIndex(transitService); mapper = new StopClusterMapper(transitService); } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index 70e41d136c6..d2eafc91e5c 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -16,7 +16,6 @@ import java.util.stream.Collectors; import org.opentripplanner.api.mapping.FeedScopedIdMapper; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.transit.model.site.StopLocation; /** @@ -47,7 +46,6 @@ public GeocoderResource(@Context OtpServerRequestContext requestContext) { * @param autocomplete Whether we should use the query string to do a prefix match * @param stops Search for stops, either by name or stop code * @param clusters Search for clusters by their name - * @param corners Search for street corners using at least one of the street names * @return list of results in the format expected by GeocoderBuiltin.js in the OTP Leaflet * client */ @@ -56,12 +54,11 @@ public Response textSearch( @QueryParam("query") String query, @QueryParam("autocomplete") @DefaultValue("false") boolean autocomplete, @QueryParam("stops") @DefaultValue("true") boolean stops, - @QueryParam("clusters") @DefaultValue("false") boolean clusters, - @QueryParam("corners") @DefaultValue("true") boolean corners + @QueryParam("clusters") @DefaultValue("false") boolean clusters ) { return Response .status(Response.Status.OK) - .entity(query(query, autocomplete, stops, clusters, corners)) + .entity(query(query, autocomplete, stops, clusters)) .build(); } @@ -77,8 +74,7 @@ private List query( String query, boolean autocomplete, boolean stops, - boolean clusters, - boolean corners + boolean clusters ) { List results = new ArrayList<>(); @@ -90,10 +86,6 @@ private List query( results.addAll(queryStations(query, autocomplete)); } - if (corners) { - results.addAll(queryCorners(query, autocomplete)); - } - return results; } @@ -127,20 +119,6 @@ private Collection queryStations(String query, boolean a .collect(Collectors.toList()); } - private Collection queryCorners(String query, boolean autocomplete) { - return LuceneIndex - .forServer(serverContext) - .queryStreetVertices(query, autocomplete) - .map(v -> - new SearchResult(v.getLat(), v.getLon(), stringifyStreetVertex(v), v.getLabelString()) - ) - .collect(Collectors.toList()); - } - - private String stringifyStreetVertex(StreetVertex v) { - return String.format("%s (%s)", v.getIntersectionName(), v.getLabel()); - } - private String stringifyStopLocation(StopLocation sl) { return sl.getCode() != null ? String.format("%s (%s)", sl.getName(), sl.getCode()) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index ca8a9d96df9..7037fd19361 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -43,10 +43,7 @@ import org.opentripplanner.ext.geocoder.StopCluster.Coordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; -import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.street.model.vertex.StreetVertex; -import org.opentripplanner.street.model.vertex.VertexLabel; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; @@ -64,14 +61,11 @@ public class LuceneIndex implements Serializable { private static final String LON = "longitude"; private static final String MODE = "mode"; - private final Graph graph; - private final TransitService transitService; private final Analyzer analyzer; private final SuggestIndexSearcher searcher; - public LuceneIndex(Graph graph, TransitService transitService) { - this.graph = graph; + public LuceneIndex(TransitService transitService) { this.transitService = transitService; this.analyzer = @@ -141,24 +135,6 @@ public LuceneIndex(Graph graph, TransitService transitService) { stopCluster.modes() ) ); - - graph - .getVertices() - .stream() - .filter(v -> v instanceof StreetVertex) - .map(v -> (StreetVertex) v) - .forEach(streetVertex -> - addToIndex( - directoryWriter, - StreetVertex.class, - streetVertex.getLabelString(), - streetVertex.getIntersectionName(), - streetVertex.getLabelString(), - streetVertex.getLat(), - streetVertex.getLon(), - Set.of() - ) - ); } DirectoryReader indexReader = DirectoryReader.open(directory); @@ -175,7 +151,7 @@ public static synchronized LuceneIndex forServer(OtpServerRequestContext serverC return existingIndex; } - var newIndex = new LuceneIndex(graph, serverContext.transitService()); + var newIndex = new LuceneIndex(serverContext.transitService()); graph.setLuceneIndex(newIndex); return newIndex; } @@ -190,11 +166,6 @@ public Stream queryStopLocationGroups(String query, boolean .map(document -> transitService.getStopLocationsGroup(FeedScopedId.parse(document.get(ID)))); } - public Stream queryStreetVertices(String query, boolean autocomplete) { - return matchingDocuments(StreetVertex.class, query, autocomplete) - .map(document -> (StreetVertex) graph.getVertex(VertexLabel.string(document.get(ID)))); - } - /** * Return all "stop clusters" for a given query. *

From ea0f449e723dd257b9a00dc89e68da6ed72342a2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 29 Sep 2023 11:34:35 +0200 Subject: [PATCH 030/120] Round to 100m instead of 10m --- .../org/opentripplanner/ext/geocoder/LuceneIndexTest.java | 3 --- .../java/org/opentripplanner/ext/geocoder/LuceneIndex.java | 1 + .../org/opentripplanner/ext/geocoder/StopClusterMapper.java | 2 +- .../opentripplanner/framework/geometry/WgsCoordinate.java | 6 ++++++ .../opentripplanner/openstreetmap/model/OSMWithTags.java | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 881df8b65af..0ef9b17e2fd 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.site.RegularStop; @@ -29,8 +28,6 @@ class LuceneIndexTest { - static Graph graph = new Graph(); - // Berlin static Station BERLIN_HAUPTBAHNHOF_STATION = station("Hauptbahnhof") .withCoordinate(52.52495, 13.36952) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 7037fd19361..ad7fd151764 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -33,6 +33,7 @@ import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.search.suggest.document.Completion90PostingsFormat; import org.apache.lucene.search.suggest.document.CompletionAnalyzer; import org.apache.lucene.search.suggest.document.ContextQuery; diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index f375b24fefa..6ec3e650eb6 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -42,7 +42,7 @@ Stream generateStopClusters( // if they are very close to each other and have the same name, only one is chosen (at random) .filter( PredicateUtils.distinctByKey(sl -> - new DeduplicationKey(sl.getName(), sl.getCoordinate().roundToApproximate10m()) + new DeduplicationKey(sl.getName(), sl.getCoordinate().roundToApproximate100m()) ) ) .flatMap(sl -> this.map(sl).stream()); diff --git a/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java b/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java index c1fb3619a04..0f2b5ebc160 100644 --- a/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java +++ b/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java @@ -146,6 +146,12 @@ public WgsCoordinate roundToApproximate10m() { return new WgsCoordinate(lat, lng); } + public WgsCoordinate roundToApproximate100m() { + var lat = DoubleUtils.roundTo3Decimals(latitude); + var lng = DoubleUtils.roundTo3Decimals(longitude); + return new WgsCoordinate(lat, lng); + } + /** * Return a string on the form: {@code "(60.12345, 11.12345)"}. Up to 5 digits are used after the * period(.), even if the coordinate is specified with a higher precision. diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 4b4883544d4..04815d8820b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -438,7 +438,7 @@ public boolean isParkAndRide() { } /** - * Is this a public transport boarding location where passengers wait for transti and that can be + * Is this a public transport boarding location where passengers wait for transit and that can be * linked to a transit stop vertex later on. *

* This intentionally excludes railway=stop and public_transport=stop because these are supposed From 302a49c7e8f6452322996f6b45c91d757f930785 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 6 Oct 2023 12:38:34 +0200 Subject: [PATCH 031/120] Add Javadoc --- docs/sandbox/GeocoderAPI.md | 1 - .../ext/geocoder/EnglishNgramAnalyzerTest.java | 3 +-- .../framework/geometry/WgsCoordinate.java | 11 +++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/sandbox/GeocoderAPI.md b/docs/sandbox/GeocoderAPI.md index 5a90f13573b..0405724fff6 100644 --- a/docs/sandbox/GeocoderAPI.md +++ b/docs/sandbox/GeocoderAPI.md @@ -36,7 +36,6 @@ It supports the following URL parameters: | `autocomplete` | Whether we should use the query string to do a prefix match | | `stops` | Search for stops, either by name or stop code | | `clusters` | Search for clusters by their name | -| `corners` | Search for street corners using at least one of the street names | #### Stop clusters diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index 9bf7ef73da5..615ef90cbbd 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; @@ -18,7 +17,7 @@ void ngram() throws IOException { var analyzer = new EnglishNGramAnalyzer(); List result = analyze("Alexanderplatz", analyzer); - System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); + //System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( "Alex", diff --git a/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java b/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java index 0f2b5ebc160..e818d50c1f6 100644 --- a/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java +++ b/src/main/java/org/opentripplanner/framework/geometry/WgsCoordinate.java @@ -146,6 +146,17 @@ public WgsCoordinate roundToApproximate10m() { return new WgsCoordinate(lat, lng); } + /** + * Return a new version of this coordinate where latitude/longitude are rounded to 3 decimal + * places which at the equator has ~100 meter precision. + *

+ * See https://wiki.openstreetmap.org/wiki/Precision_of_coordinates + *

+ * This is useful when you want to cache coordinate-based computations but don't need absolute + * precision. + *

+ * DO NOT USE THIS IN ROUTING (USE AT LEAST 7 DECIMALS)! + */ public WgsCoordinate roundToApproximate100m() { var lat = DoubleUtils.roundTo3Decimals(latitude); var lng = DoubleUtils.roundTo3Decimals(longitude); From 3dcbc984e0c2e10325bd4d70741c6a95936ad286 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 10 Oct 2023 12:26:35 +0200 Subject: [PATCH 032/120] Add tests for rounding --- .../framework/geometry/WgsCoordinateTest.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/opentripplanner/framework/geometry/WgsCoordinateTest.java b/src/test/java/org/opentripplanner/framework/geometry/WgsCoordinateTest.java index 78c92cea174..05051ab7dff 100644 --- a/src/test/java/org/opentripplanner/framework/geometry/WgsCoordinateTest.java +++ b/src/test/java/org/opentripplanner/framework/geometry/WgsCoordinateTest.java @@ -9,25 +9,26 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner._support.geometry.Coordinates; public class WgsCoordinateTest { @Test - public void normalize() { + void normalize() { WgsCoordinate c = new WgsCoordinate(1.123456789, 2.987654321); assertEquals(1.1234568, c.latitude()); assertEquals(2.9876543, c.longitude()); } @Test - public void testToString() { + void testToString() { WgsCoordinate c = new WgsCoordinate(1.123456789, 2.987654321); assertEquals("(1.12346, 2.98765)", c.toString()); assertEquals("(1.123, 2.9)", new WgsCoordinate(1.123, 2.9).toString()); } @Test - public void testCoordinateEquals() { + void testCoordinateEquals() { WgsCoordinate a = new WgsCoordinate(5.000_000_3, 3.0); // Test latitude @@ -50,7 +51,7 @@ public void testCoordinateEquals() { } @Test - public void asJtsCoordinate() { + void asJtsCoordinate() { // Given a well known location in Oslo double latitude = 59.9110583; double longitude = 10.7502691; @@ -65,7 +66,7 @@ public void asJtsCoordinate() { } @Test - public void mean() { + void mean() { var c1 = new WgsCoordinate(10.0, 5.0); var c2 = new WgsCoordinate(20.0, -5.0); @@ -79,7 +80,7 @@ public void mean() { } @Test - public void validCoordinates() { + void validCoordinates() { // Edge cases should NOT throw exceptions new WgsCoordinate(90d, 1d); new WgsCoordinate(-90d, 1d); @@ -94,13 +95,29 @@ public void validCoordinates() { } @Test - public void add() { + void add() { assertEquals(new WgsCoordinate(12d, 5d), new WgsCoordinate(9d, 1d).add(3d, 4d)); } @Test - public void testGreenwich() { + void testGreenwich() { assertEquals(51.48d, WgsCoordinate.GREENWICH.latitude()); assertEquals(0d, WgsCoordinate.GREENWICH.longitude()); } + + @Test + void roundingTo10m() { + var hamburg = new WgsCoordinate(Coordinates.HAMBURG); + var rounded = hamburg.roundToApproximate10m(); + assertEquals(10.0003, rounded.latitude()); + assertEquals(53.5566, rounded.longitude()); + } + + @Test + void roundingTo100m() { + var hamburg = new WgsCoordinate(Coordinates.HAMBURG); + var rounded = hamburg.roundToApproximate100m(); + assertEquals(10, rounded.latitude()); + assertEquals(53.557, rounded.longitude()); + } } From 043739736215bea8e8875f88350bb10e6adc60c3 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 10 Oct 2023 16:06:49 +0200 Subject: [PATCH 033/120] Update issue templates --- .github/ISSUE_TEMPLATE/feature_request.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..6f40bf8f78a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: new feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Why is this a problem?** +Describe the context, the goal or use-case this is part of. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 6dbd6569b3bcd711d91191703396f664331b3e97 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 10 Oct 2023 18:32:59 +0200 Subject: [PATCH 034/120] Fix stop times helper when requesting zero departure --- .../routing/stoptimes/StopTimesHelper.java | 4 ++++ .../stoptimes/StopTimesHelperTest.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java b/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java index 4426122d84d..109bf7f53c0 100644 --- a/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java +++ b/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java @@ -52,6 +52,10 @@ public static List stopTimesForStop( ArrivalDeparture arrivalDeparture, boolean includeCancelledTrips ) { + if (numberOfDepartures <= 0) { + return List.of(); + } + List result = new ArrayList<>(); // Fetch all patterns, including those from realtime sources diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index f9cf1898a19..916e1736a74 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -41,6 +41,25 @@ public static void setUp() throws Exception { tt.getTripTimes(0).cancelTrip(); } + /** + * Case 0, requested number of departure = 0 + */ + @Test + void stopTimesForStop_zeroRequestedNumberOfDeparture() { + // Case 1, should find first departure for each pattern + var result = StopTimesHelper.stopTimesForStop( + transitService, + transitService.getRegularStop(stopId), + serviceDate.atStartOfDay(transitService.getTimeZone()).toInstant(), + Duration.ofHours(24), + 0, + ArrivalDeparture.BOTH, + true + ); + + assertTrue(result.isEmpty()); + } + /** * Case 1, should find first departure for each pattern when numberOfDepartures is one */ From 14fd58bc14f74b1265b5a6fccdacd62f0941133f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 11 Oct 2023 09:18:04 +0200 Subject: [PATCH 035/120] Fix handling of null start time in Transmodel API --- .../ext/transmodelapi/model/stop/QuayType.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java index b68bc528f6c..d2fb70fe151 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java @@ -322,8 +322,9 @@ public static GraphQLObjectType create( JourneyWhiteListed whiteListed = new JourneyWhiteListed(environment); Collection transitModes = environment.getArgument("whiteListedModes"); - Instant startTime = environment.containsArgument("startTime") - ? Instant.ofEpochMilli(environment.getArgument("startTime")) + Integer startTimeInput = environment.getArgument("startTime"); + Instant startTime = startTimeInput != null + ? Instant.ofEpochMilli(startTimeInput) : Instant.now(); return StopPlaceType From 927a1c141271bc9ec4501aca97410c1b1fc8b33c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 15:45:53 +0200 Subject: [PATCH 036/120] Remove duplicate method --- .../ext/flex/trip/UnscheduledTripTest.java | 12 ++++++------ .../ext/flex/trip/ScheduledDeviatedTrip.java | 16 ++++------------ .../ext/flex/trip/UnscheduledTrip.java | 18 +++++------------- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 030c9de3398..fe006450791 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -191,8 +191,8 @@ void testUnscheduledTrip() { assertEquals(T11_00, trip.earliestDepartureTime(STOP_B)); assertEquals(T15_00, trip.latestArrivalTime(STOP_B)); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } @Test @@ -216,8 +216,8 @@ void testUnscheduledFeederTripFromScheduledStop() { assertEquals(T10_00, trip.earliestDepartureTime(STOP_B)); assertEquals(T14_00, trip.latestArrivalTime(STOP_B)); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } @Test @@ -236,8 +236,8 @@ void testUnscheduledFeederTripToScheduledStop() { .withStopTimes(List.of(fromStopTime, toStopTime)) .build(); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } static Stream testRegularStopToAreaEarliestDepartureTimeTestCases() { diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index 06bbd11fac7..3148634f945 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -92,7 +92,7 @@ public Stream getFlexAccessTemplates( ArrayList res = new ArrayList<>(); for (int toIndex = fromIndex; toIndex < stopTimes.length; toIndex++) { - if (getDropOffType(toIndex).isNotRoutable()) { + if (getAlightRule(toIndex).isNotRoutable()) { continue; } for (StopLocation stop : expandStops(stopTimes[toIndex].stop)) { @@ -132,7 +132,7 @@ public Stream getFlexEgressTemplates( ArrayList res = new ArrayList<>(); for (int fromIndex = toIndex; fromIndex >= 0; fromIndex--) { - if (getPickupType(fromIndex).isNotRoutable()) { + if (getBoardRule(fromIndex).isNotRoutable()) { continue; } for (StopLocation stop : expandStops(stopTimes[fromIndex].stop)) { @@ -230,14 +230,6 @@ public boolean isAlightingPossible(NearbyStop stop) { return getToIndex(stop) != -1; } - public PickDrop getPickupType(int i) { - return stopTimes[i].pickupType; - } - - public PickDrop getDropOffType(int i) { - return stopTimes[i].dropOffType; - } - @Override public boolean sameAs(@Nonnull ScheduledDeviatedTrip other) { return ( @@ -262,7 +254,7 @@ private Collection expandStops(StopLocation stop) { private int getFromIndex(NearbyStop accessEgress) { for (int i = 0; i < stopTimes.length; i++) { - if (getPickupType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; @@ -281,7 +273,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getDropOffType(i).isNotRoutable()) { + if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index b54c6173387..c2ff03fd29e 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -38,7 +38,7 @@ public class UnscheduledTrip extends FlexTrip { // unscheduled trips can contain one or two stop_times - private static final Set N_STOPS = Set.of(1, 2); + private static final Set N_STOPS = Set.of(1, 2, 3); private final StopTimeWindow[] stopTimes; @@ -94,7 +94,7 @@ public Stream getFlexAccessTemplates( int toIndex = stopTimes.length - 1; // Check if trip is possible - if (fromIndex == -1 || fromIndex > toIndex || getDropOffType(toIndex).isNotRoutable()) { + if (fromIndex == -1 || fromIndex > toIndex || getBoardRule(toIndex).isNotRoutable()) { return Stream.empty(); } @@ -123,7 +123,7 @@ public Stream getFlexEgressTemplates( int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == -1 || fromIndex > toIndex || getPickupType(fromIndex).isNotRoutable()) { + if (toIndex == -1 || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable()) { return Stream.empty(); } @@ -229,14 +229,6 @@ public boolean isAlightingPossible(NearbyStop stop) { return getToIndex(stop) != -1; } - public PickDrop getPickupType(int i) { - return stopTimes[i].pickupType(); - } - - public PickDrop getDropOffType(int i) { - return stopTimes[i].dropOffType(); - } - @Override public boolean sameAs(@Nonnull UnscheduledTrip other) { return ( @@ -261,7 +253,7 @@ private Collection expandStops(StopLocation stop) { private int getFromIndex(NearbyStop accessEgress) { for (int i = 0; i < stopTimes.length; i++) { - if (getPickupType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); @@ -280,7 +272,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getDropOffType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); From e2bb1c57bb4019a3d10b888728e29479f0587221 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 17:51:57 +0200 Subject: [PATCH 037/120] Clean up some flex code --- .../opentripplanner/ext/flex/FlexRouter.java | 8 ++-- .../ext/flex/trip/ScheduledDeviatedTrip.java | 2 +- .../ext/flex/trip/UnscheduledTrip.java | 44 ++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java index 970adf4a267..c84cb8823a7 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java +++ b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java @@ -153,7 +153,7 @@ public Collection createFlexAccesses() { return this.flexAccessTemplates.stream() .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService)) - .collect(Collectors.toList()); + .toList(); } public Collection createFlexEgresses() { @@ -162,7 +162,7 @@ public Collection createFlexEgresses() { return this.flexEgressTemplates.stream() .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService)) - .collect(Collectors.toList()); + .toList(); } private void calculateFlexAccessTemplates() { @@ -186,7 +186,7 @@ private void calculateFlexAccessTemplates() { .getFlexAccessTemplates(it.accessEgress(), date, accessFlexPathCalculator, config) ) ) - .collect(Collectors.toList()); + .toList(); } private void calculateFlexEgressTemplates() { @@ -210,7 +210,7 @@ private void calculateFlexEgressTemplates() { .getFlexEgressTemplates(it.accessEgress(), date, egressFlexPathCalculator, config) ) ) - .collect(Collectors.toList()); + .toList(); } private Stream getClosestFlexTrips( diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index 3148634f945..edff0860933 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -31,7 +31,7 @@ import org.opentripplanner.transit.model.site.StopLocation; /** - * A scheduled deviated trip is similar to a regular scheduled trip, except that is continues stop + * A scheduled deviated trip is similar to a regular scheduled trip, except that it contains stop * locations, which are not stops, but other types, such as groups of stops or location areas. */ public class ScheduledDeviatedTrip diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c2ff03fd29e..c204c9b34d5 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -3,10 +3,7 @@ import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -39,6 +36,7 @@ public class UnscheduledTrip extends FlexTrip N_STOPS = Set.of(1, 2, 3); + private static final int INDEX_NOT_FOUND = -1; private final StopTimeWindow[] stopTimes; @@ -94,19 +92,16 @@ public Stream getFlexAccessTemplates( int toIndex = stopTimes.length - 1; // Check if trip is possible - if (fromIndex == -1 || fromIndex > toIndex || getBoardRule(toIndex).isNotRoutable()) { + if ( + fromIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getAlightRule(toIndex).isNotRoutable() + ) { return Stream.empty(); } - ArrayList res = new ArrayList<>(); - - for (StopLocation stop : expandStops(stopTimes[toIndex].stop())) { - res.add( + return expandStops(stopTimes[toIndex].stop()) + .map(stop -> new FlexAccessTemplate(access, this, fromIndex, toIndex, stop, date, calculator, config) ); - } - - return res.stream(); } @Override @@ -123,19 +118,16 @@ public Stream getFlexEgressTemplates( int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == -1 || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable()) { + if ( + toIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable() + ) { return Stream.empty(); } - ArrayList res = new ArrayList<>(); - - for (StopLocation stop : expandStops(stopTimes[fromIndex].stop())) { - res.add( + return expandStops(stopTimes[fromIndex].stop()) + .map(stop -> new FlexEgressTemplate(egress, this, fromIndex, toIndex, stop, date, calculator, config) ); - } - - return res.stream(); } @Override @@ -221,12 +213,12 @@ public PickDrop getAlightRule(int i) { @Override public boolean isBoardingPossible(NearbyStop stop) { - return getFromIndex(stop) != -1; + return getFromIndex(stop) != INDEX_NOT_FOUND; } @Override public boolean isAlightingPossible(NearbyStop stop) { - return getToIndex(stop) != -1; + return getToIndex(stop) != INDEX_NOT_FOUND; } @Override @@ -245,10 +237,10 @@ public TransitBuilder copy() { return new UnscheduledTripBuilder(this); } - private Collection expandStops(StopLocation stop) { + private Stream expandStops(StopLocation stop) { return stop instanceof GroupStop groupStop - ? groupStop.getLocations() - : Collections.singleton(stop); + ? groupStop.getLocations().stream() + : Stream.of(stop); } private int getFromIndex(NearbyStop accessEgress) { @@ -267,7 +259,7 @@ private int getFromIndex(NearbyStop accessEgress) { } } } - return -1; + return INDEX_NOT_FOUND; } private int getToIndex(NearbyStop accessEgress) { @@ -286,7 +278,7 @@ private int getToIndex(NearbyStop accessEgress) { } } } - return -1; + return INDEX_NOT_FOUND; } private Optional departureTimeWindow( From 1a4c3df6fc349974cbdc07bf0c8ab290d85531fe Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:09:58 +0200 Subject: [PATCH 038/120] Take mulitple AreaStops into account --- .../ext/flex/trip/UnscheduledTrip.java | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c204c9b34d5..9b58a2c00fa 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nonnull; import org.opentripplanner.ext.flex.FlexServiceDate; @@ -85,22 +86,43 @@ public Stream getFlexAccessTemplates( FlexPathCalculator calculator, FlexConfig config ) { - // Find boarding index - int fromIndex = getFromIndex(access); + // Find boarding index, also check if it's boardable + final int fromIndex = getFromIndex(access); // Alighting is always at the last stop for unscheduled trips - int toIndex = stopTimes.length - 1; + final int lastIndexInTrip = stopTimes.length - 1; // Check if trip is possible - if ( - fromIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getAlightRule(toIndex).isNotRoutable() - ) { + if (fromIndex == INDEX_NOT_FOUND || fromIndex > lastIndexInTrip) { return Stream.empty(); } - return expandStops(stopTimes[toIndex].stop()) - .map(stop -> - new FlexAccessTemplate(access, this, fromIndex, toIndex, stop, date, calculator, config) + IntStream indices; + if (stopTimes.length == 1) { + indices = IntStream.of(fromIndex); + } else { + indices = IntStream.range(fromIndex + 1, lastIndexInTrip); + } + // check for every stop after fromIndex if you can alight, if so return a template + return indices + // if you cannot alight at a certain index, the trip is not possible + .filter(alightIndex -> getAlightRule(alightIndex).isRoutable()) + // expand GroupStops and build IndexedStopLocations + .mapToObj(this::expandStops) + // flatten stream of streams + .flatMap(s -> s) + // create template + .map(alightStop -> + new FlexAccessTemplate( + access, + this, + fromIndex, + alightStop.index, + alightStop.stop, + date, + calculator, + config + ) ); } @@ -124,9 +146,18 @@ public Stream getFlexEgressTemplates( return Stream.empty(); } - return expandStops(stopTimes[fromIndex].stop()) - .map(stop -> - new FlexEgressTemplate(egress, this, fromIndex, toIndex, stop, date, calculator, config) + return expandStops(fromIndex) + .map(indexedStop -> + new FlexEgressTemplate( + egress, + this, + fromIndex, + toIndex, + indexedStop.stop, + date, + calculator, + config + ) ); } @@ -237,10 +268,11 @@ public TransitBuilder copy() { return new UnscheduledTripBuilder(this); } - private Stream expandStops(StopLocation stop) { + private Stream expandStops(int index) { + var stop = stopTimes[index].stop(); return stop instanceof GroupStop groupStop - ? groupStop.getLocations().stream() - : Stream.of(stop); + ? groupStop.getLocations().stream().map(s -> new IndexedStopLocation(index, s)) + : Stream.of(new IndexedStopLocation(index, stop)); } private int getFromIndex(NearbyStop accessEgress) { @@ -308,4 +340,6 @@ private Optional arrivalTimeWindow( // requested-arrival-time must be within return toTime.intersect(fromTimeShifted); } + + private record IndexedStopLocation(int index, StopLocation stop) {} } From 378b8dedceb3469b990fe7e2c09883961f770d91 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:15:16 +0200 Subject: [PATCH 039/120] Fix tests --- .../opentripplanner/ext/flex/trip/UnscheduledTripTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index fe006450791..d7708b53d30 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -192,7 +192,7 @@ void testUnscheduledTrip() { assertEquals(T15_00, trip.latestArrivalTime(STOP_B)); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } @Test @@ -217,7 +217,7 @@ void testUnscheduledFeederTripFromScheduledStop() { assertEquals(T14_00, trip.latestArrivalTime(STOP_B)); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } @Test @@ -237,7 +237,7 @@ void testUnscheduledFeederTripToScheduledStop() { .build(); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } static Stream testRegularStopToAreaEarliestDepartureTimeTestCases() { From 71fa02724941dc9585baf45b5268c2dbe0c243d0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:59:00 +0200 Subject: [PATCH 040/120] Fix computation of boarding/alighting --- .../opentripplanner/ext/flex/trip/UnscheduledTrip.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 9b58a2c00fa..51db4d8b3dd 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -101,16 +102,16 @@ public Stream getFlexAccessTemplates( if (stopTimes.length == 1) { indices = IntStream.of(fromIndex); } else { - indices = IntStream.range(fromIndex + 1, lastIndexInTrip); + indices = IntStream.range(fromIndex + 1, lastIndexInTrip + 1); } // check for every stop after fromIndex if you can alight, if so return a template return indices - // if you cannot alight at a certain index, the trip is not possible + // if you cannot alight at an index, the trip is not possible .filter(alightIndex -> getAlightRule(alightIndex).isRoutable()) // expand GroupStops and build IndexedStopLocations .mapToObj(this::expandStops) // flatten stream of streams - .flatMap(s -> s) + .flatMap(Function.identity()) // create template .map(alightStop -> new FlexAccessTemplate( @@ -296,7 +297,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getBoardRule(i).isNotRoutable()) { + if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); From 4bcbf57ac61fd1ca3057a1a5a2eef46ff5629b55 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 14:28:55 +0200 Subject: [PATCH 041/120] Also compute several egress templates --- .../ext/flex/trip/UnscheduledTripTest.java | 2 +- .../ext/flex/trip/UnscheduledTrip.java | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d7708b53d30..6c3d9a6cef7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -108,7 +108,7 @@ void testIsUnscheduledTrip() { isUnscheduledTrip(List.of(scheduledStop, scheduledStop)), "Two scheduled stop times is not unscheduled" ); - assertFalse( + assertTrue( isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop, unscheduledStop)), "Three unscheduled stop times is not unscheduled" ); diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 51db4d8b3dd..403ae84618a 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -90,7 +90,7 @@ public Stream getFlexAccessTemplates( // Find boarding index, also check if it's boardable final int fromIndex = getFromIndex(access); - // Alighting is always at the last stop for unscheduled trips + // templates will be generated from the boardingIndex to the end of the trip final int lastIndexInTrip = stopTimes.length - 1; // Check if trip is possible @@ -134,27 +134,39 @@ public Stream getFlexEgressTemplates( FlexPathCalculator calculator, FlexConfig config ) { - // Boarding is always at the first stop for unscheduled trips - int fromIndex = 0; + // templates will be generated from the first index to the toIndex + int firstIndexInStop = 0; - // Find alighting index + // Find alighting index, also check if alighting is allowed int toIndex = getToIndex(egress); // Check if trip is possible - if ( - toIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable() - ) { + if (toIndex == INDEX_NOT_FOUND || firstIndexInStop > toIndex) { return Stream.empty(); } - return expandStops(fromIndex) - .map(indexedStop -> + IntStream indices; + if (stopTimes.length == 1) { + indices = IntStream.of(toIndex); + } else { + indices = IntStream.range(firstIndexInStop, toIndex + 1); + } + // check for every stop after fromIndex if you can alight, if so return a template + return indices + // if you cannot board at this index, the trip is not possible + .filter(boardIndex -> getBoardRule(boardIndex).isRoutable()) + // expand GroupStops and build IndexedStopLocations + .mapToObj(this::expandStops) + // flatten stream of streams + .flatMap(Function.identity()) + // create template + .map(boardStop -> new FlexEgressTemplate( egress, this, - fromIndex, toIndex, - indexedStop.stop, + boardStop.index, + boardStop.stop, date, calculator, config From 8cf74b56e6bfe63d90380736ab56f040ac70b46d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 17:24:11 +0200 Subject: [PATCH 042/120] Reorganise tests --- .../ext/flex/GtfsFlexTest.java | 98 +++++++++++++++++ .../ext/flex/trip/UnscheduledTripTest.java | 103 +----------------- 2 files changed, 102 insertions(+), 99 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java new file mode 100644 index 00000000000..5065068d4a2 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java @@ -0,0 +1,98 @@ +package org.opentripplanner.ext.flex; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.TestOtpModel; +import org.opentripplanner.ext.flex.trip.FlexTrip; +import org.opentripplanner.ext.flex.trip.UnscheduledTrip; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.standalone.config.sandbox.FlexConfig; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; + +/** + * This test makes sure that one of the example feeds in the GTFS-Flex repo works. It's the City of + * Aspen Downtown taxi service which is a completely unscheduled trip that takes you door-to-door in + * the city. + *

+ * It only contains a single stop time which in GTFS static would not work but is valid in GTFS + * Flex. + */ +public class GtfsFlexTest extends FlexTest { + + private static TransitModel transitModel; + + @BeforeAll + static void setup() { + TestOtpModel model = FlexTest.buildFlexGraph(ASPEN_GTFS); + transitModel = model.transitModel(); + } + + @Test + void parseAspenTaxiAsUnscheduledTrip() { + var flexTrips = transitModel.getAllFlexTrips(); + assertFalse(flexTrips.isEmpty()); + assertEquals( + Set.of("t_1289262_b_29084_tn_0", "t_1289257_b_28352_tn_0"), + flexTrips.stream().map(FlexTrip::getId).map(FeedScopedId::getId).collect(Collectors.toSet()) + ); + + assertEquals( + Set.of(UnscheduledTrip.class), + flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet()) + ); + } + + @Test + void calculateAccessTemplate() { + var trip = getFlexTrip(); + var nearbyStop = getNearbyStop(trip); + + var accesses = trip + .getFlexAccessTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) + .toList(); + + assertEquals(1, accesses.size()); + + var access = accesses.get(0); + assertEquals(0, access.fromStopIndex); + assertEquals(0, access.toStopIndex); + } + + @Test + void calculateEgressTemplate() { + var trip = getFlexTrip(); + var nearbyStop = getNearbyStop(trip); + var egresses = trip + .getFlexEgressTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) + .toList(); + + assertEquals(1, egresses.size()); + + var egress = egresses.get(0); + assertEquals(0, egress.fromStopIndex); + assertEquals(0, egress.toStopIndex); + } + + @Test + void shouldGeneratePatternForFlexTripWithSingleStop() { + assertFalse(transitModel.getAllTripPatterns().isEmpty()); + } + private static NearbyStop getNearbyStop(FlexTrip trip) { + assertEquals(1, trip.getStops().size()); + var stopLocation = trip.getStops().iterator().next(); + return new NearbyStop(stopLocation, 0, List.of(), null); + } + + private static FlexTrip getFlexTrip() { + var flexTrips = transitModel.getAllFlexTrips(); + return flexTrips.iterator().next(); + } + +} diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 6c3d9a6cef7..1241a1e5d9e 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -9,39 +9,21 @@ import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.locationtech.jts.geom.Coordinate; -import org.opentripplanner.TestOtpModel; -import org.opentripplanner.ext.flex.FlexTest; -import org.opentripplanner.framework.geometry.GeometryUtils; +import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; -import org.opentripplanner.routing.graphfinder.NearbyStop; -import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.transit.model._data.TransitModelForTest; -import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.service.TransitModel; - -/** - * This test makes sure that one of the example feeds in the GTFS-Flex repo works. It's the City of - * Aspen Downtown taxi service which is a completely unscheduled trip that takes you door-to-door in - * the city. - *

- * It only contains a single stop time which in GTFS static would not work but is valid in GTFS - * Flex. - */ -public class UnscheduledTripTest extends FlexTest { + +public class UnscheduledTripTest { private static final int STOP_A = 0; private static final int STOP_B = 1; @@ -53,25 +35,8 @@ public class UnscheduledTripTest extends FlexTest { private static final StopLocation AREA_STOP = TransitModelForTest.areaStopForTest( "area", - GeometryUtils - .getGeometryFactory() - .createPolygon( - new Coordinate[] { - new Coordinate(11.0, 63.0), - new Coordinate(11.5, 63.0), - new Coordinate(11.5, 63.5), - new Coordinate(11.0, 63.5), - new Coordinate(11.0, 63.0), - } - ) + Polygons.BERLIN ); - static TransitModel transitModel; - - @BeforeAll - static void setup() { - TestOtpModel model = FlexTest.buildFlexGraph(ASPEN_GTFS); - transitModel = model.transitModel(); - } @Test void testIsUnscheduledTrip() { @@ -118,57 +83,6 @@ void testIsUnscheduledTrip() { ); } - @Test - void parseAspenTaxiAsUnscheduledTrip() { - var flexTrips = transitModel.getAllFlexTrips(); - assertFalse(flexTrips.isEmpty()); - assertEquals( - Set.of("t_1289262_b_29084_tn_0", "t_1289257_b_28352_tn_0"), - flexTrips.stream().map(FlexTrip::getId).map(FeedScopedId::getId).collect(Collectors.toSet()) - ); - - assertEquals( - Set.of(UnscheduledTrip.class), - flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet()) - ); - } - - @Test - void calculateAccessTemplate() { - var trip = getFlexTrip(); - var nearbyStop = getNearbyStop(trip); - - var accesses = trip - .getFlexAccessTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) - .toList(); - - assertEquals(1, accesses.size()); - - var access = accesses.get(0); - assertEquals(0, access.fromStopIndex); - assertEquals(0, access.toStopIndex); - } - - @Test - void calculateEgressTemplate() { - var trip = getFlexTrip(); - var nearbyStop = getNearbyStop(trip); - var egresses = trip - .getFlexEgressTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) - .toList(); - - assertEquals(1, egresses.size()); - - var egress = egresses.get(0); - assertEquals(0, egress.fromStopIndex); - assertEquals(0, egress.toStopIndex); - } - - @Test - void shouldGeneratePatternForFlexTripWithSingleStop() { - assertFalse(transitModel.getAllTripPatterns().isEmpty()); - } - @Test void testUnscheduledTrip() { var fromStopTime = new StopTime(); @@ -558,16 +472,7 @@ private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } - private static NearbyStop getNearbyStop(FlexTrip trip) { - assertEquals(1, trip.getStops().size()); - var stopLocation = trip.getStops().iterator().next(); - return new NearbyStop(stopLocation, 0, List.of(), null); - } - private static FlexTrip getFlexTrip() { - var flexTrips = transitModel.getAllFlexTrips(); - return flexTrips.iterator().next(); - } private static StopTime area(String startTime, String endTime) { var stopTime = new StopTime(); From 23a690a3d07ab4e02d2455fb4a709d82483adcfe Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 17:48:40 +0200 Subject: [PATCH 043/120] Refactor Flex tests --- .../ext/flex/GtfsFlexTest.java | 2 +- .../ext/flex/trip/UnscheduledTripTest.java | 96 ++++++++++--------- .../support/VariableArgumentsProvider.java | 14 ++- 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java index 5065068d4a2..78c4a2f3ddc 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java @@ -84,6 +84,7 @@ void calculateEgressTemplate() { void shouldGeneratePatternForFlexTripWithSingleStop() { assertFalse(transitModel.getAllTripPatterns().isEmpty()); } + private static NearbyStop getNearbyStop(FlexTrip trip) { assertEquals(1, trip.getStops().size()); var stopLocation = trip.getStops().iterator().next(); @@ -94,5 +95,4 @@ private static NearbyStop getNearbyStop(FlexTrip trip) { var flexTrips = transitModel.getAllFlexTrips(); return flexTrips.iterator().next(); } - } diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 1241a1e5d9e..e0fcfdd00b6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,10 +8,14 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; @@ -19,6 +23,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; +import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; @@ -38,49 +43,52 @@ public class UnscheduledTripTest { Polygons.BERLIN ); - @Test - void testIsUnscheduledTrip() { - var scheduledStop = new StopTime(); - scheduledStop.setArrivalTime(30); - scheduledStop.setDepartureTime(60); - - var unscheduledStop = new StopTime(); - unscheduledStop.setFlexWindowStart(30); - unscheduledStop.setFlexWindowEnd(300); - - assertFalse(isUnscheduledTrip(List.of()), "Empty stop times is not a unscheduled trip"); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop)), - "Single scheduled stop time is not unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop)), - "Single unscheduled stop time is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop)), - "Two unscheduled stop times is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, scheduledStop)), - "Unscheduled + scheduled stop times is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(scheduledStop, unscheduledStop)), - "Scheduled + unscheduled stop times is unscheduled" - ); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop, scheduledStop)), - "Two scheduled stop times is not unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop, unscheduledStop)), - "Three unscheduled stop times is not unscheduled" - ); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop, scheduledStop, scheduledStop)), - "Three scheduled stop times is not unscheduled" - ); + @Nested + class IsUnscheduledTrip { + + private static final StopTime SCHEDULED_STOP = new StopTime(); + private static final StopTime UNSCHEDULED_STOP = new StopTime(); + + static { + SCHEDULED_STOP.setArrivalTime(30); + SCHEDULED_STOP.setDepartureTime(60); + + UNSCHEDULED_STOP.setFlexWindowStart(30); + UNSCHEDULED_STOP.setFlexWindowEnd(300); + } + + static Collection notUnscheduled = Stream + .of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + ) + .map(Arguments::of) + .toList(); + + @ParameterizedTest + @VariableSource("notUnscheduled") + void isNotUnscheduled(List stopTimes) { + assertFalse(isUnscheduledTrip(stopTimes)); + } + + static Collection unscheduled = Stream + .of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) + ) + .map(Arguments::of) + .collect(Collectors.toList()); + + @ParameterizedTest + @VariableSource("unscheduled") + void isUnscheduled(List stopTimes) { + assertTrue(isUnscheduledTrip(stopTimes)); + } } @Test @@ -472,8 +480,6 @@ private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } - - private static StopTime area(String startTime, String endTime) { var stopTime = new StopTime(); stopTime.setStop(AREA_STOP); diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 1ae9fb2af84..85e2c455da6 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -1,6 +1,8 @@ package org.opentripplanner.test.support; import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; @@ -50,6 +52,16 @@ private Stream getValue(Field field) { field.setAccessible(accessible); - return value == null ? null : (Stream) value; + if (value == null) { + return null; + } else if (value instanceof Collection collection) { + return (Stream) collection.stream(); + } else if (value instanceof Stream stream) { + return stream; + } else { + throw new IllegalArgumentException( + "Cannot convert %s to stream.".formatted(value.getClass()) + ); + } } } From e3b78c20acd4c2c0b83eeb3d5f446c547d026df5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 20:43:25 +0200 Subject: [PATCH 044/120] Move test cases, cleanup StopTime --- .../ext/flex/trip/UnscheduledTripTest.java | 4 +- .../org/opentripplanner/model/StopTime.java | 47 ++++--------------- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index e0fcfdd00b6..d8ccee70236 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -61,8 +61,6 @@ class IsUnscheduledTrip { .of( List.of(), List.of(SCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) ) @@ -78,6 +76,8 @@ void isNotUnscheduled(List stopTimes) { static Collection unscheduled = Stream .of( List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) ) diff --git a/src/main/java/org/opentripplanner/model/StopTime.java b/src/main/java/org/opentripplanner/model/StopTime.java index 0ea5dfc15c0..957cc56285d 100644 --- a/src/main/java/org/opentripplanner/model/StopTime.java +++ b/src/main/java/org/opentripplanner/model/StopTime.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.StopTimeKey; import org.opentripplanner.transit.model.timetable.Trip; @@ -136,10 +136,6 @@ public void setArrivalTime(int arrivalTime) { this.arrivalTime = arrivalTime; } - public void clearArrivalTime() { - this.arrivalTime = MISSING_VALUE; - } - public boolean isDepartureTimeSet() { return departureTime != MISSING_VALUE; } @@ -155,10 +151,6 @@ public void setDepartureTime(int departureTime) { this.departureTime = departureTime; } - public void clearDepartureTime() { - this.departureTime = MISSING_VALUE; - } - public boolean isTimepointSet() { return timepoint != MISSING_VALUE; } @@ -174,10 +166,6 @@ public void setTimepoint(int timepoint) { this.timepoint = timepoint; } - public void clearTimepoint() { - this.timepoint = MISSING_VALUE; - } - public I18NString getStopHeadsign() { return stopHeadsign; } @@ -222,10 +210,6 @@ public void setShapeDistTraveled(double shapeDistTraveled) { this.shapeDistTraveled = shapeDistTraveled; } - public void clearShapeDistTraveled() { - this.shapeDistTraveled = MISSING_VALUE; - } - public String getFarePeriodId() { return farePeriodId; } @@ -309,29 +293,16 @@ public void cancel() { dropOffType = PickDrop.CANCELLED; } - public void cancelDropOff() { - dropOffType = PickDrop.CANCELLED; - } - - public void cancelPickup() { - pickupType = PickDrop.CANCELLED; - } - @Override public String toString() { - return ( - "StopTime(seq=" + - getStopSequence() + - " stop=" + - getStop().getId() + - " trip=" + - getTrip().getId() + - " times=" + - TimeUtils.timeToStrLong(getArrivalTime()) + - "-" + - TimeUtils.timeToStrLong(getDepartureTime()) + - ")" - ); + return ToStringBuilder + .of(this.getClass()) + .addNum("seq", stopSequence) + .addObj("stop", stop) + .addObj("trip", trip) + .addServiceTime("arrival", arrivalTime) + .addServiceTime("departure", departureTime) + .toString(); } private static int getAvailableTime(int... times) { From cf47a0442a07b64546a75fb570406c2ef3c89d0c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 15 Sep 2023 15:56:06 +0200 Subject: [PATCH 045/120] Update test --- .../opentripplanner/model/impl/OtpTransitServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 9ecdbbeb451..7a464995ba6 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -117,7 +117,7 @@ public void testGetAllStopTimes() { assertEquals(88, stopTimes.size()); assertEquals( - "StopTime(seq=1 stop=F:A trip=agency:1.1 times=00:00:00-00:00:00)", + "StopTime{seq: 1, stop: RegularStop{F:A A}, trip: Trip{agency:1.1 1}, arrival: 0:00, departure: 0:00}", first(stopTimes).toString() ); } From 1f1db518e98dfc1dfa1f701730928a5aa03d75e7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 20:38:53 +0200 Subject: [PATCH 046/120] Improve isUnscheduledTrip check --- .../ext/flex/trip/UnscheduledTripTest.java | 16 +++++----- .../org/opentripplanner/ext/flex/README.md | 4 +-- .../ext/flex/trip/UnscheduledTrip.java | 29 ++++++++++++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d8ccee70236..1bedecb5435 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,9 +8,8 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -57,15 +56,14 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static Collection notUnscheduled = Stream + static Stream notUnscheduled = Stream .of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) ) - .map(Arguments::of) - .toList(); + .map(Arguments::of); @ParameterizedTest @VariableSource("notUnscheduled") @@ -73,16 +71,16 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static Collection unscheduled = Stream + static Stream unscheduled = Stream .of( List.of(UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) ) - .map(Arguments::of) - .collect(Collectors.toList()); + .map(Arguments::of); @ParameterizedTest @VariableSource("unscheduled") diff --git a/src/ext/java/org/opentripplanner/ext/flex/README.md b/src/ext/java/org/opentripplanner/ext/flex/README.md index 8f099279450..a6d161c3088 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/README.md +++ b/src/ext/java/org/opentripplanner/ext/flex/README.md @@ -14,7 +14,7 @@ The algorithm runs in two phases. 1. Flex access/egress templates are generated from the places reachable by the access/egress mode. The templates contain information about how to reach the flex service and the service itself. -1. From these templates, flex accesses are generated by alighting at all places after the boarding. +2. From these templates, flex accesses are generated by alighting at all places after the boarding. If the alighting is not at a stop, transfers are generated to all stops reachable by walking distance. For egresses the flow is the opposite. @@ -29,7 +29,7 @@ FlexTrip class, with the required methods. #### UnscheduledTrip -This type of trip consists of one or two service areas, with a set operating window. The trip is +This type of trip consists any number of service areas, with a set operating window. The trip is possible at any time within the set window, and the class can represent multiple potential trips. #### ScheduledDeviatedTrip diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 403ae84618a..c72148dcc98 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -36,8 +36,7 @@ */ public class UnscheduledTrip extends FlexTrip { - // unscheduled trips can contain one or two stop_times - private static final Set N_STOPS = Set.of(1, 2, 3); + private static final Set N_STOPS = Set.of(1, 2); private static final int INDEX_NOT_FOUND = -1; private final StopTimeWindow[] stopTimes; @@ -68,16 +67,32 @@ public static UnscheduledTripBuilder of(FeedScopedId id) { return new UnscheduledTripBuilder(id); } + /** + * Tests if the stop times constitute an {@link UnscheduledTrip}. + *

+ * Returns true for the following cases: + * - A single fixed scheduled stop followed by a flexible one + * - One or more stop times with a flexible time window but no fixed stop in between them + */ public static boolean isUnscheduledTrip(List stopTimes) { Predicate hasFlexWindow = st -> st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; Predicate notContinuousStop = stopTime -> stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; - return ( - N_STOPS.contains(stopTimes.size()) && - stopTimes.stream().anyMatch(hasFlexWindow) && - stopTimes.stream().allMatch(notContinuousStop) - ); + if (stopTimes.isEmpty()) { + return false; + } + if (N_STOPS.contains(stopTimes.size())) { + return ( + N_STOPS.contains(stopTimes.size()) && + stopTimes.stream().anyMatch(hasFlexWindow) && + stopTimes.stream().allMatch(notContinuousStop) + ); + } else { + return ( + stopTimes.stream().allMatch(hasFlexWindow) && stopTimes.stream().allMatch(notContinuousStop) + ); + } } @Override From 04da7a179c22e40909b1e183c4d24a66269577a0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 20:58:37 +0200 Subject: [PATCH 047/120] Automatically convert to Arguments --- .../ext/flex/trip/UnscheduledTripTest.java | 33 ++++++++----------- .../ext/flex/trip/UnscheduledTrip.java | 10 +++--- .../support/VariableArgumentsProvider.java | 18 ++++++++-- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 1bedecb5435..3b8819fb31b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; @@ -56,14 +55,12 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static Stream notUnscheduled = Stream - .of( - List.of(), - List.of(SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) - ) - .map(Arguments::of); + static List> notUnscheduled = List.of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + ); @ParameterizedTest @VariableSource("notUnscheduled") @@ -71,16 +68,14 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static Stream unscheduled = Stream - .of( - List.of(UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), - Collections.nCopies(10, UNSCHEDULED_STOP) - ) - .map(Arguments::of); + static List> unscheduled = List.of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) + ); @ParameterizedTest @VariableSource("unscheduled") diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c72148dcc98..d3a749204c9 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -79,19 +79,17 @@ public static boolean isUnscheduledTrip(List stopTimes) { st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; Predicate notContinuousStop = stopTime -> stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; + boolean noContinuousStops = stopTimes.stream().allMatch(notContinuousStop); if (stopTimes.isEmpty()) { return false; - } - if (N_STOPS.contains(stopTimes.size())) { + } else if (N_STOPS.contains(stopTimes.size())) { return ( N_STOPS.contains(stopTimes.size()) && stopTimes.stream().anyMatch(hasFlexWindow) && - stopTimes.stream().allMatch(notContinuousStop) + noContinuousStops ); } else { - return ( - stopTimes.stream().allMatch(hasFlexWindow) && stopTimes.stream().allMatch(notContinuousStop) - ); + return stopTimes.stream().allMatch(hasFlexWindow) && noContinuousStops; } } diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 85e2c455da6..0d4ba1071a3 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -2,8 +2,9 @@ import java.lang.reflect.Field; import java.util.Collection; -import java.util.List; +import java.util.function.Function; import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; @@ -55,13 +56,24 @@ private Stream getValue(Field field) { if (value == null) { return null; } else if (value instanceof Collection collection) { - return (Stream) collection.stream(); + return collection.stream().map(toArguments()); } else if (value instanceof Stream stream) { - return stream; + return stream.map(toArguments()); } else { throw new IllegalArgumentException( "Cannot convert %s to stream.".formatted(value.getClass()) ); } } + + @Nonnull + private static Function toArguments() { + return val -> { + if (val instanceof Arguments arguments) { + return arguments; + } else { + return Arguments.of(val); + } + }; + } } From 105cc5fc7d0003d918f360f6bf09e8bfa6b31907 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 21:51:39 +0200 Subject: [PATCH 048/120] Add test case for computing access templates --- .../ext/flex/trip/UnscheduledTripTest.java | 97 ++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 3b8819fb31b..8be6aa294fa 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,19 +8,26 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; +import gnu.trove.set.hash.TIntHashSet; +import java.time.LocalDate; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; +import org.opentripplanner.ext.flex.FlexServiceDate; +import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.RegularStop; @@ -30,6 +37,7 @@ public class UnscheduledTripTest { private static final int STOP_A = 0; private static final int STOP_B = 1; + private static final int STOP_C = 2; private static final int T10_00 = TimeUtils.hm2time(10, 0); private static final int T11_00 = TimeUtils.hm2time(11, 0); private static final int T14_00 = TimeUtils.hm2time(14, 0); @@ -55,7 +63,7 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static List> notUnscheduled = List.of( + static final List> notUnscheduled = List.of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), @@ -68,7 +76,7 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static List> unscheduled = List.of( + static final List> unscheduled = List.of( List.of(UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), @@ -469,6 +477,81 @@ void testAreaToAreaLatestArrivalTime(TestCase tc) { ); } + static Stream multipleAreasEarliestDepartureTimeTestCases() { + var from = area("10:00", "10:05"); + var middle = area("10:10", "10:15"); + var to = area("10:20", "10:25"); + + var tc = new TestCase.Builder(from, to).withStopTimes(List.of(from, middle, to)); + + return Stream.of( + tc + .expected( + "Requested departure time is after flex service departure window start, duration 21m", + "10:01" + ) + .request("10:01", "21m") + .build(), + tc + .expected( + "Requested departure time is before flex service departure window start, duration 1h", + "10:00" + ) + .request("09:50", "24m") + .build() + ); + } + + @ParameterizedTest + @MethodSource("multipleAreasEarliestDepartureTimeTestCases") + void testMultipleAreasEarliestDepartureTime(TestCase tc) { + assertEquals( + timeToString(tc.expectedTime), + timeToString( + tc.trip().earliestDepartureTime(tc.requestedTime, STOP_A, STOP_C, tc.tripDuration) + ) + ); + } + + @Nested + class FlexTemplates { + + private static final DirectFlexPathCalculator CALCULATOR = new DirectFlexPathCalculator(); + + @Test + void templates() { + var from = area("10:00", "10:05"); + var middle = area("10:10", "10:15"); + var to = area("10:20", "10:25"); + + var trip = new TestCase.Builder(from, to) + .withStopTimes(List.of(from, middle, to)) + .build() + .trip(); + + var nearbyStop = new NearbyStop(to.getStop(), 100, List.of(), null); + var flexServiceDate = new FlexServiceDate( + LocalDate.of(2023, 9, 16), + to.getFlexWindowStart(), + new TIntHashSet() + ); + + var templates = trip + .getFlexAccessTemplates(nearbyStop, flexServiceDate, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + + assertEquals(2, templates.size()); + + var first = templates.get(0); + assertEquals(first.fromStopIndex, 0); + assertEquals(first.toStopIndex, 1); + + var second = templates.get(1); + assertEquals(second.fromStopIndex, 0); + assertEquals(second.toStopIndex, 2); + } + } + private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } @@ -500,6 +583,7 @@ private static StopTime regularStopTime(int arrivalTime, int departureTime) { record TestCase( StopTime from, StopTime to, + List stopTimes, String expectedDescription, int expectedTime, int requestedTime, @@ -510,7 +594,7 @@ static Builder tc(StopTime start, StopTime end) { } UnscheduledTrip trip() { - return UnscheduledTrip.of(id("UNSCHEDULED")).withStopTimes(List.of(from, to)).build(); + return UnscheduledTrip.of(id("UNSCHEDULED")).withStopTimes(stopTimes).build(); } @Override @@ -549,6 +633,7 @@ private static class Builder { private final StopTime from; private final StopTime to; private String expectedDescription; + private List stopTimes; private int expectedTime; private int requestedTime; private int tripDuration; @@ -558,6 +643,11 @@ public Builder(StopTime from, StopTime to) { this.to = to; } + public Builder withStopTimes(List stopTimes) { + this.stopTimes = stopTimes; + return this; + } + Builder expected(String expectedDescription, String expectedTime) { this.expectedDescription = expectedDescription; this.expectedTime = TimeUtils.time(expectedTime); @@ -580,6 +670,7 @@ TestCase build() { return new TestCase( from, to, + Objects.requireNonNullElse(stopTimes, List.of(from, to)), expectedDescription, expectedTime, requestedTime, From adef349d25069b8773f19780947c4bda150bd98c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 18 Sep 2023 10:00:00 +0200 Subject: [PATCH 049/120] Improve tests --- .../ext/flex/trip/UnscheduledTripTest.java | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 8be6aa294fa..f13449cb727 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -517,38 +518,63 @@ void testMultipleAreasEarliestDepartureTime(TestCase tc) { class FlexTemplates { private static final DirectFlexPathCalculator CALCULATOR = new DirectFlexPathCalculator(); + static final StopTime FIRST = area("10:00", "10:05"); + static final StopTime SECOND = area("10:10", "10:15"); + static final StopTime THIRD = area("10:20", "10:25"); + static final StopTime FOURTH = area("10:30", "10:35"); + private static final FlexServiceDate FLEX_SERVICE_DATE = new FlexServiceDate( + LocalDate.of(2023, 9, 16), + 0, + new TIntHashSet() + ); @Test - void templates() { - var from = area("10:00", "10:05"); - var middle = area("10:10", "10:15"); - var to = area("10:20", "10:25"); + void accessTemplates() { + var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); - var trip = new TestCase.Builder(from, to) - .withStopTimes(List.of(from, middle, to)) - .build() - .trip(); + var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - var nearbyStop = new NearbyStop(to.getStop(), 100, List.of(), null); - var flexServiceDate = new FlexServiceDate( - LocalDate.of(2023, 9, 16), - to.getFlexWindowStart(), - new TIntHashSet() - ); + var templates = trip + .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + + assertEquals(3, templates.size()); + + List + .of(0, 1, 2) + .forEach(index -> { + var template = templates.get(index); + assertEquals(template.fromStopIndex, 0); + assertEquals(template.toStopIndex, index + 1); + }); + } + + @Test + void accessTemplatesNoAlighting() { + var second = area("10:10", "10:15"); + second.setDropOffType(PickDrop.NONE); + + var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); + + var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); var templates = trip - .getFlexAccessTemplates(nearbyStop, flexServiceDate, CALCULATOR, FlexConfig.DEFAULT) + .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) .toList(); assertEquals(2, templates.size()); + List + .of(0, 1) + .forEach(index -> { + var template = templates.get(index); + assertEquals(template.fromStopIndex, 0); + assertEquals(template.toStopIndex, index + 2); + }); + } - var first = templates.get(0); - assertEquals(first.fromStopIndex, 0); - assertEquals(first.toStopIndex, 1); - - var second = templates.get(1); - assertEquals(second.fromStopIndex, 0); - assertEquals(second.toStopIndex, 2); + @Nonnull + private static UnscheduledTrip trip(List stopTimes) { + return new TestCase.Builder(FIRST, THIRD).withStopTimes(stopTimes).build().trip(); } } From 2d70974b553f617657a56e41a3cc59c3f4ed1063 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 18 Sep 2023 12:09:36 +0200 Subject: [PATCH 050/120] Add tests for egress --- .../ext/flex/trip/UnscheduledTripTest.java | 50 ++++++++++++++----- .../ext/flex/trip/UnscheduledTrip.java | 6 +-- .../org/opentripplanner/model/plan/Leg.java | 1 - 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index f13449cb727..065daa652ab 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -22,6 +22,8 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.ext.flex.FlexServiceDate; import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator; +import org.opentripplanner.ext.flex.template.FlexAccessTemplate; +import org.opentripplanner.ext.flex.template.FlexEgressTemplate; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; @@ -527,16 +529,18 @@ class FlexTemplates { 0, new TIntHashSet() ); + private static final NearbyStop NEARBY_STOP = new NearbyStop( + FOURTH.getStop(), + 100, + List.of(), + null + ); @Test void accessTemplates() { var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); - var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - - var templates = trip - .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) - .toList(); + var templates = accessTemplates(trip); assertEquals(3, templates.size()); @@ -544,8 +548,8 @@ void accessTemplates() { .of(0, 1, 2) .forEach(index -> { var template = templates.get(index); - assertEquals(template.fromStopIndex, 0); - assertEquals(template.toStopIndex, index + 1); + assertEquals(0, template.fromStopIndex); + assertEquals(index + 1, template.toStopIndex); }); } @@ -556,11 +560,7 @@ void accessTemplatesNoAlighting() { var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); - var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - - var templates = trip - .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) - .toList(); + var templates = accessTemplates(trip); assertEquals(2, templates.size()); List @@ -572,10 +572,36 @@ void accessTemplatesNoAlighting() { }); } + @Test + void egressTemplates() { + var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); + + var templates = egressTemplates(trip); + + assertEquals(4, templates.size()); + var template = templates.get(0); + assertEquals(template.fromStopIndex, 3); + assertEquals(template.toStopIndex, 0); + } + @Nonnull private static UnscheduledTrip trip(List stopTimes) { return new TestCase.Builder(FIRST, THIRD).withStopTimes(stopTimes).build().trip(); } + + @Nonnull + private static List accessTemplates(UnscheduledTrip trip) { + return trip + .getFlexAccessTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + } + + @Nonnull + private static List egressTemplates(UnscheduledTrip trip) { + return trip + .getFlexEgressTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + } } private static String timeToString(int time) { diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index d3a749204c9..9871e796633 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -148,13 +148,13 @@ public Stream getFlexEgressTemplates( FlexConfig config ) { // templates will be generated from the first index to the toIndex - int firstIndexInStop = 0; + int firstIndexInTrip = 0; // Find alighting index, also check if alighting is allowed int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == INDEX_NOT_FOUND || firstIndexInStop > toIndex) { + if (toIndex == INDEX_NOT_FOUND || firstIndexInTrip > toIndex) { return Stream.empty(); } @@ -162,7 +162,7 @@ public Stream getFlexEgressTemplates( if (stopTimes.length == 1) { indices = IntStream.of(toIndex); } else { - indices = IntStream.range(firstIndexInStop, toIndex + 1); + indices = IntStream.range(firstIndexInTrip, toIndex + 1); } // check for every stop after fromIndex if you can alight, if so return a template return indices diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index aa9949dae24..f561f2713e1 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -19,7 +19,6 @@ import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.street.model.note.StreetNote; import org.opentripplanner.transit.model.basic.Accessibility; -import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; From 8df5c71cab8bbf478f330cb6b10200e29eff7481 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 26 Sep 2023 12:38:46 +0200 Subject: [PATCH 051/120] Add test for boarding and alighting --- .../ext/flex/trip/UnscheduledTripTest.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 065daa652ab..87b1fd6e2b5 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.ext.flex.trip.UnscheduledTrip.isUnscheduledTrip; import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc; +import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; @@ -33,6 +34,7 @@ import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; @@ -516,6 +518,31 @@ void testMultipleAreasEarliestDepartureTime(TestCase tc) { ); } + @Test + void boardingAlighting() { + var AREA_STOP1 = TransitModelForTest.areaStopForTest("area-1", Polygons.BERLIN); + var AREA_STOP2 = TransitModelForTest.areaStopForTest("area-2", Polygons.BERLIN); + var AREA_STOP3 = TransitModelForTest.areaStopForTest("area-3", Polygons.BERLIN); + + var first = area(AREA_STOP1, "10:00", "10:05"); + first.setDropOffType(NONE); + var second = area(AREA_STOP2, "10:10", "10:15"); + second.setPickupType(NONE); + var third = area(AREA_STOP3, "10:20", "10:25"); + + var trip = TestCase + .tc(first, third) + .withStopTimes(List.of(first, second, third)) + .build() + .trip(); + + assertTrue(trip.isBoardingPossible(nearbyStop(AREA_STOP1))); + assertFalse(trip.isAlightingPossible(nearbyStop(AREA_STOP1))); + + assertFalse(trip.isBoardingPossible(nearbyStop(AREA_STOP2))); + assertTrue(trip.isAlightingPossible(nearbyStop(AREA_STOP2))); + } + @Nested class FlexTemplates { @@ -556,7 +583,7 @@ void accessTemplates() { @Test void accessTemplatesNoAlighting() { var second = area("10:10", "10:15"); - second.setDropOffType(PickDrop.NONE); + second.setDropOffType(NONE); var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); @@ -609,8 +636,13 @@ private static String timeToString(int time) { } private static StopTime area(String startTime, String endTime) { + return area(AREA_STOP, endTime, startTime); + } + + @Nonnull + private static StopTime area(StopLocation areaStop, String endTime, String startTime) { var stopTime = new StopTime(); - stopTime.setStop(AREA_STOP); + stopTime.setStop(areaStop); stopTime.setFlexWindowStart(TimeUtils.time(startTime)); stopTime.setFlexWindowEnd(TimeUtils.time(endTime)); return stopTime; @@ -632,6 +664,11 @@ private static StopTime regularStopTime(int arrivalTime, int departureTime) { return stopTime; } + @Nonnull + private static NearbyStop nearbyStop(AreaStop stop) { + return new NearbyStop(stop, 1000, List.of(), null); + } + record TestCase( StopTime from, StopTime to, From bbaa303bcf05ed12e2a74e72998f9c3d62236a3e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 5 Oct 2023 14:39:13 +0200 Subject: [PATCH 052/120] Add Javadoc to FlexAccessEgressTemplate --- .../ext/flex/template/FlexAccessEgressTemplate.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java index d9f08f512ae..68f490f775f 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java +++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java @@ -27,6 +27,12 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; +/** + * A container for a few pieces of information that can be used to calculate flex accesses, egresses, + * direct flex itineraries or polylines. + *

+ * Please also see Flex.svg for an illustration of how the flex concepts relate to each other. + */ public abstract class FlexAccessEgressTemplate { /** From dd2b2ffad869c520e51134594b51944cd6de0e42 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 11 Oct 2023 12:19:07 +0200 Subject: [PATCH 053/120] Update documentation --- .../opentripplanner/ext/flex/trip/UnscheduledTrip.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 9871e796633..372d102a235 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -29,10 +29,14 @@ import org.opentripplanner.transit.model.site.StopLocation; /** - * This type of FlexTrip is used when a taxi-type service is modeled, which operates in one or - * between two areas/groups of stops without a set schedule. The travel times are calculated based + * This type of FlexTrip is used when a taxi-type service is modeled, which operates in any number + * od areas/groups of stops without a set schedule. The travel times are calculated based * on the driving time between the stops, with the schedule times being used just for deciding if a * trip is possible. + *

+ * An unscheduled flex trip may visit/drive from one flex stops(areas/group of stop locations) to + * any other stop in the pattern without driving through the stops in between. Only the times in the + * two stops used need to match the path. */ public class UnscheduledTrip extends FlexTrip { From 80c9769133c6c74ddb2f3eb40c4272189678cfed Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 13 Oct 2023 11:26:27 +0200 Subject: [PATCH 054/120] Apply review suggestion --- .../opentripplanner/routing/stoptimes/StopTimesHelperTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java index 916e1736a74..54194696e71 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/StopTimesHelperTest.java @@ -46,7 +46,6 @@ public static void setUp() throws Exception { */ @Test void stopTimesForStop_zeroRequestedNumberOfDeparture() { - // Case 1, should find first departure for each pattern var result = StopTimesHelper.stopTimesForStop( transitService, transitService.getRegularStop(stopId), @@ -65,7 +64,6 @@ void stopTimesForStop_zeroRequestedNumberOfDeparture() { */ @Test void stopTimesForStop_oneDeparture() { - // Case 1, should find first departure for each pattern var result = StopTimesHelper.stopTimesForStop( transitService, transitService.getRegularStop(stopId), From b1193cad71fcead3f52f10cff0f5dba436aba55c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 13 Oct 2023 11:51:01 +0200 Subject: [PATCH 055/120] review: Fix JavaDoc on IntUtils#intArrayToString --- .../java/org/opentripplanner/framework/lang/IntUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java index c1c2406f462..01040bf0f93 100644 --- a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java @@ -22,8 +22,8 @@ public static String intToString(int value, int notSetValue) { } /** - * Convert an integer to a String, if the value equals the {@code notSetValue} parameter an empty - * string is returned. + * Convert an integer array to a string of comma separated numbers. If the array is empty, then + * an empty string is returned. */ public static String intArrayToString(int... values) { var buf = new StringBuilder(); From d371ba2dd6f3ee8e97a3d30aa30e3ac97c635ed6 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 13 Oct 2023 12:08:20 +0200 Subject: [PATCH 056/120] refactor: Make the creation of passThroughPointsService a bit safer --- .../multicriteria/configure/McRangeRaptorConfig.java | 4 +--- .../passthrough/BitSetPassThroughPointsService.java | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java index 56679f8148b..b05b0aeb5b0 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/configure/McRangeRaptorConfig.java @@ -63,9 +63,7 @@ public McRangeRaptorConfig( public static PassThroughPointsService passThroughPointsService( MultiCriteriaRequest multiCriteriaRequest ) { - return multiCriteriaRequest.hasPassThroughPoints() - ? BitSetPassThroughPointsService.of(multiCriteriaRequest.passThroughPoints()) - : PassThroughPointsService.NOOP; + return BitSetPassThroughPointsService.of(multiCriteriaRequest.passThroughPoints()); } /** diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java index 19289a44f63..8780980f790 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsService.java @@ -32,10 +32,12 @@ private BitSetPassThroughPointsService(final List passThroughPoints) { } public static PassThroughPointsService of(List points) { - return points - .stream() - .map(PassThroughPoint::asBitSet) - .collect(collectingAndThen(toList(), BitSetPassThroughPointsService::new)); + return points == null || points.isEmpty() + ? NOOP + : points + .stream() + .map(PassThroughPoint::asBitSet) + .collect(collectingAndThen(toList(), BitSetPassThroughPointsService::new)); } @Override From a6311d5a3aef451a378bb9e3808fce0599f01513 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 13 Oct 2023 22:12:23 +0300 Subject: [PATCH 057/120] Apply suggestions from code review Co-authored-by: Thomas Gran --- .../org/opentripplanner/ext/transmodelapi/model/EnumTypes.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 21befce4150..65966744bdc 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -206,6 +206,7 @@ public class EnumTypes { still accepting passengers. There isn't a big difference between this and `manySeatsAvailable` so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge these into `manySeatsAvailable`. """ ) .value( @@ -239,6 +240,7 @@ public class EnumTypes { The vehicle or carriage can currently accommodate only standing passengers and has limited space for them. There isn't a big difference between this and `full` so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge into `standingRoomOnly`. """ ) .value( From 832f037b64f945a290aae63d7907c24f4b2b74b0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 13 Oct 2023 22:43:20 +0300 Subject: [PATCH 058/120] Update and refactor documentation --- src/ext/graphql/transmodelapi/schema.graphql | 3 + .../ext/transmodelapi/model/EnumTypes.java | 79 +++------------- .../apis/gtfs/generated/GraphQLTypes.java | 8 +- .../model/timetable/OccupancyStatus.java | 90 +++++++++++-------- .../opentripplanner/apis/gtfs/schema.graphqls | 2 + 5 files changed, 72 insertions(+), 110 deletions(-) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index c62356e5dfa..5f17dd5c16d 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1524,11 +1524,13 @@ enum MultiModalMode { parent } +"OccupancyStatus to be exposed in the API. The values are based on GTFS-RT" enum OccupancyStatus { """ The vehicle or carriage can currently accommodate only standing passengers and has limited space for them. There isn't a big difference between this and `full` so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge into `standingRoomOnly`. """ crushedStandingRoomOnly """ @@ -1536,6 +1538,7 @@ enum OccupancyStatus { still accepting passengers. There isn't a big difference between this and `manySeatsAvailable` so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge these into `manySeatsAvailable`. """ empty """ diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index 65966744bdc..2b6942d7b8a 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -190,74 +190,19 @@ public class EnumTypes { .value("all", "all", "Both multiModal parents and their mono modal child stop places.") .build(); - public static final GraphQLEnumType OCCUPANCY_STATUS = GraphQLEnumType - .newEnum() - .name("OccupancyStatus") - .value( - "noData", - OccupancyStatus.NO_DATA_AVAILABLE, - "The vehicle or carriage doesn't have any occupancy data available." - ) - .value( - "empty", - OccupancyStatus.EMPTY, - """ - The vehicle is considered empty by most measures, and has few or no passengers onboard, but is - still accepting passengers. There isn't a big difference between this and `manySeatsAvailable` - so it's possible to handle them as the same value, if one wants to limit the number of different - values. - SIRI nordic profile: merge these into `manySeatsAvailable`. - """ - ) - .value( - "manySeatsAvailable", - OccupancyStatus.MANY_SEATS_AVAILABLE, - """ - The vehicle or carriage has a large number of seats available. - SIRI nordic profile: more than ~50% of seats available. - """ - ) - .value( - "fewSeatsAvailable", - OccupancyStatus.FEW_SEATS_AVAILABLE, - """ - The vehicle or carriage has a few seats available. - SIRI nordic profile: less than ~50% of seats available. - """ - ) - .value( - "standingRoomOnly", - OccupancyStatus.STANDING_ROOM_ONLY, - """ - The vehicle or carriage only has standing room available. - SIRI nordic profile: less than ~10% of seats available. - """ - ) - .value( - "crushedStandingRoomOnly", - OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY, - """ - The vehicle or carriage can currently accommodate only standing passengers and has limited - space for them. There isn't a big difference between this and `full` so it's possible to handle - them as the same value, if one wants to limit the number of different values. - SIRI nordic profile: merge into `standingRoomOnly`. - """ - ) - .value( - "full", - OccupancyStatus.FULL, - "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board." - ) - .value( - "notAcceptingPassengers", - OccupancyStatus.NOT_ACCEPTING_PASSENGERS, - """ - The vehicle or carriage has no seats or standing room available. - SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed - to alight due to e.g. crowding. - """ + public static final GraphQLEnumType OCCUPANCY_STATUS = createFromDocumentedEnum( + "OccupancyStatus", + List.of( + map("noData", OccupancyStatus.NO_DATA_AVAILABLE), + map("empty", OccupancyStatus.EMPTY), + map("manySeatsAvailable", OccupancyStatus.MANY_SEATS_AVAILABLE), + map("fewSeatsAvailable", OccupancyStatus.FEW_SEATS_AVAILABLE), + map("standingRoomOnly", OccupancyStatus.STANDING_ROOM_ONLY), + map("crushedStandingRoomOnly", OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY), + map("full", OccupancyStatus.FULL), + map("notAcceptingPassengers", OccupancyStatus.NOT_ACCEPTING_PASSENGERS) ) - .build(); + ); public static final GraphQLEnumType PURCHASE_WHEN = GraphQLEnumType .newEnum() diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 9ecf488452d..cba63739dd5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -917,8 +917,8 @@ public enum GraphQLPropulsionType { } /** - * Additional qualifier for a transport mode. Note that qualifiers can only be used with certain - * transport modes. + * Additional qualifier for a transport mode. + * Note that qualifiers can only be used with certain transport modes. */ public enum GraphQLQualifier { ACCESS, @@ -3373,8 +3373,8 @@ public void setGraphQLUnpreferredCost(Integer unpreferredCost) { } /** - * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that - * they may be represented differently to the user. + * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that they + * may be represented differently to the user. */ public enum GraphQLVehicleParkingState { CLOSED, diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java index cd4abd27ced..9c32ca39394 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java @@ -1,55 +1,67 @@ package org.opentripplanner.transit.model.timetable; +import org.opentripplanner.framework.doc.DocumentedEnum; + /** * OccupancyStatus to be exposed in the API. The values are based on GTFS-RT * (transit_realtime.VehiclePosition.OccupancyStatus) that can be easily be mapped to the Nordic * SIRI-profile (SIRI 2.1) *

- * Descriptions are copied from the GTFS-RT specification. + * Descriptions are copied from the GTFS-RT specification with additions of SIRI nordic profile documentation. */ -public enum OccupancyStatus { - /** - * Default. There is no occupancy-data on this departure, - */ +public enum OccupancyStatus implements DocumentedEnum { NO_DATA_AVAILABLE, - /** - * The vehicle is considered empty by most measures, and has few or no passengers onboard, but is - * still accepting passengers. - */ EMPTY, - /** - * The vehicle or carriage has a large number of seats available. The amount of free seats out of - * the total seats available to be considered large enough to fall into this category is - * determined at the discretion of the producer. - */ MANY_SEATS_AVAILABLE, - /** - * The vehicle or carriage has a small number of seats available. The amount of free seats out of - * the total seats available to be considered small enough to fall into this category is - * determined at the discretion of the producer. - * SIRI nordic profile: more than ~50% of seats available. - */ FEW_SEATS_AVAILABLE, - /** - * The vehicle or carriage can currently accommodate only standing passengers. - * SIRI nordic profile: less than ~50% of seats available. - */ STANDING_ROOM_ONLY, - /** - * The vehicle or carriage can currently accommodate only standing passengers and has limited - * space for them. - * SIRI nordic profile: less than ~10% of seats available. - */ CRUSHED_STANDING_ROOM_ONLY, - /** - * The vehicle is considered full by most measures, but may still be allowing passengers to - * board. - */ FULL, - /** - * The vehicle or carriage is not accepting passengers. - * SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only - * allowed to alight due to e.g. crowding. - */ - NOT_ACCEPTING_PASSENGERS, + NOT_ACCEPTING_PASSENGERS; + + @Override + public String typeDescription() { + return "OccupancyStatus to be exposed in the API. The values are based on GTFS-RT"; + } + + @Override + public String enumValueDescription() { + return switch (this) { + case NO_DATA_AVAILABLE -> "The vehicle or carriage doesn't have any occupancy data available."; + case EMPTY -> """ + The vehicle is considered empty by most measures, and has few or no passengers onboard, but is + still accepting passengers. There isn't a big difference between this and `manySeatsAvailable` + so it's possible to handle them as the same value, if one wants to limit the number of different + values. + SIRI nordic profile: merge these into `manySeatsAvailable`. + """; + case MANY_SEATS_AVAILABLE -> """ + The vehicle or carriage has a large number of seats available. + SIRI nordic profile: more than ~50% of seats available. + """; + case FEW_SEATS_AVAILABLE -> """ + The vehicle or carriage has a few seats available. + SIRI nordic profile: less than ~50% of seats available. + """; + case STANDING_ROOM_ONLY -> """ + The vehicle or carriage only has standing room available. + SIRI nordic profile: less than ~10% of seats available. + """; + case CRUSHED_STANDING_ROOM_ONLY -> """ + The vehicle or carriage can currently accommodate only standing passengers and has limited + space for them. There isn't a big difference between this and `full` so it's possible to + handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge into `standingRoomOnly`. + """; + case FULL -> """ + The vehicle or carriage is considered full by most measures, but may still be allowing + passengers to board. + """; + case NOT_ACCEPTING_PASSENGERS -> """ + The vehicle or carriage has no seats or standing room available. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only + allowed to alight due to e.g. crowding. + """; + }; + } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a249adaafbb..046e581af30 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3404,6 +3404,7 @@ enum OccupancyStatus { still accepting passengers. There isn't a big difference between this and MANY_SEATS_AVAILABLE so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge these into `MANY_SEATS_AVAILABLE`. """ EMPTY @@ -3435,6 +3436,7 @@ enum OccupancyStatus { The vehicle or carriage can currently accommodate only standing passengers and has limited space for them. There isn't a big difference between this and FULL so it's possible to handle them as the same value, if one wants to limit the number of different values. + SIRI nordic profile: merge into `STANDING_ROOM_ONLY`. """ CRUSHED_STANDING_ROOM_ONLY From 0b724bb94079523de57742ca9f5ca1c8833f5cdd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 13 Oct 2023 22:51:33 +0300 Subject: [PATCH 059/120] Update src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java Co-authored-by: Thomas Gran --- .../standalone/config/framework/json/ParameterBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 11672f99005..cd5e9a202b2 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -247,7 +247,7 @@ public > Set asEnumSet(Class enumClass, Collection de it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? Set.copyOf(dft) : EnumSet.copyOf(result); + return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); } /** From bb38a6c3d62ebcf7b64db26a93dd9f48967a3404 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 13 Oct 2023 23:24:49 +0200 Subject: [PATCH 060/120] Apply review feedback --- .../ext/flex/trip/UnscheduledTripTest.java | 25 ++++++++++++++++++- .../org/opentripplanner/ext/flex/README.md | 12 ++++++++- .../ext/flex/trip/UnscheduledTrip.java | 17 ++++++------- .../org/opentripplanner/model/StopTime.java | 23 ++++++++++------- .../model/impl/OtpTransitServiceImplTest.java | 2 +- 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 87b1fd6e2b5..a9a486aef25 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -59,20 +59,43 @@ class IsUnscheduledTrip { private static final StopTime SCHEDULED_STOP = new StopTime(); private static final StopTime UNSCHEDULED_STOP = new StopTime(); + private static final StopTime CONTINUOUS_PICKUP_STOP = new StopTime(); + private static final StopTime CONTINUOUS_DROP_OFF_STOP = new StopTime(); static { + var trip = TransitModelForTest.trip("flex").build(); SCHEDULED_STOP.setArrivalTime(30); SCHEDULED_STOP.setDepartureTime(60); + SCHEDULED_STOP.setStop(AREA_STOP); + SCHEDULED_STOP.setTrip(trip); UNSCHEDULED_STOP.setFlexWindowStart(30); UNSCHEDULED_STOP.setFlexWindowEnd(300); + UNSCHEDULED_STOP.setStop(AREA_STOP); + UNSCHEDULED_STOP.setTrip(trip); + + CONTINUOUS_PICKUP_STOP.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER); + CONTINUOUS_PICKUP_STOP.setFlexWindowStart(30); + CONTINUOUS_PICKUP_STOP.setFlexWindowEnd(300); + CONTINUOUS_PICKUP_STOP.setStop(AREA_STOP); + CONTINUOUS_PICKUP_STOP.setTrip(trip); + + CONTINUOUS_DROP_OFF_STOP.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER); + CONTINUOUS_DROP_OFF_STOP.setFlexWindowStart(100); + CONTINUOUS_DROP_OFF_STOP.setFlexWindowEnd(200); + CONTINUOUS_DROP_OFF_STOP.setStop(AREA_STOP); + CONTINUOUS_DROP_OFF_STOP.setTrip(trip); } static final List> notUnscheduled = List.of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), + List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) ); @ParameterizedTest diff --git a/src/ext/java/org/opentripplanner/ext/flex/README.md b/src/ext/java/org/opentripplanner/ext/flex/README.md index a6d161c3088..f6db9fba84c 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/README.md +++ b/src/ext/java/org/opentripplanner/ext/flex/README.md @@ -14,7 +14,7 @@ The algorithm runs in two phases. 1. Flex access/egress templates are generated from the places reachable by the access/egress mode. The templates contain information about how to reach the flex service and the service itself. -2. From these templates, flex accesses are generated by alighting at all places after the boarding. +1. From these templates, flex accesses are generated by alighting at all places after the boarding. If the alighting is not at a stop, transfers are generated to all stops reachable by walking distance. For egresses the flow is the opposite. @@ -32,6 +32,16 @@ FlexTrip class, with the required methods. This type of trip consists any number of service areas, with a set operating window. The trip is possible at any time within the set window, and the class can represent multiple potential trips. +You can travel from a stop time to any other stop time that comes later in the order of stop times +but not the other way around (even when the time windows would otherwise allow this). If you want +this to happen then you need to create a trip going the opposite direction. + +Note, that in a case of three or more flex zones the GTFS Flex spec doesn't exactly specify what +the driving duration is to go from the first to the third zone or if indeed the second zone needs to +be passed through. + +This topic is discussed in https://github.com/MobilityData/gtfs-flex/issues/76 + #### ScheduledDeviatedTrip A scheduled deviated trip represents a single trip, which operates not only via stops, but also via diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 372d102a235..f3a050bb224 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -37,6 +37,8 @@ * An unscheduled flex trip may visit/drive from one flex stops(areas/group of stop locations) to * any other stop in the pattern without driving through the stops in between. Only the times in the * two stops used need to match the path. + *

+ * For a discussion of this behaviour see https://github.com/MobilityData/gtfs-flex/issues/76 */ public class UnscheduledTrip extends FlexTrip { @@ -81,19 +83,16 @@ public static UnscheduledTripBuilder of(FeedScopedId id) { public static boolean isUnscheduledTrip(List stopTimes) { Predicate hasFlexWindow = st -> st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; - Predicate notContinuousStop = stopTime -> - stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; - boolean noContinuousStops = stopTimes.stream().allMatch(notContinuousStop); + Predicate hasContinuousStops = stopTime -> + stopTime.getFlexContinuousDropOff() != NONE || stopTime.getFlexContinuousPickup() != NONE; if (stopTimes.isEmpty()) { return false; + } else if (stopTimes.stream().anyMatch(hasContinuousStops)) { + return false; } else if (N_STOPS.contains(stopTimes.size())) { - return ( - N_STOPS.contains(stopTimes.size()) && - stopTimes.stream().anyMatch(hasFlexWindow) && - noContinuousStops - ); + return stopTimes.stream().anyMatch(hasFlexWindow); } else { - return stopTimes.stream().allMatch(hasFlexWindow) && noContinuousStops; + return stopTimes.stream().allMatch(hasFlexWindow); } } diff --git a/src/main/java/org/opentripplanner/model/StopTime.java b/src/main/java/org/opentripplanner/model/StopTime.java index 957cc56285d..2ae04484426 100644 --- a/src/main/java/org/opentripplanner/model/StopTime.java +++ b/src/main/java/org/opentripplanner/model/StopTime.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.StopTimeKey; import org.opentripplanner.transit.model.timetable.Trip; @@ -295,14 +295,19 @@ public void cancel() { @Override public String toString() { - return ToStringBuilder - .of(this.getClass()) - .addNum("seq", stopSequence) - .addObj("stop", stop) - .addObj("trip", trip) - .addServiceTime("arrival", arrivalTime) - .addServiceTime("departure", departureTime) - .toString(); + return ( + "StopTime(seq=" + + getStopSequence() + + " stop=" + + getStop().getId() + + " trip=" + + getTrip().getId() + + " times=" + + TimeUtils.timeToStrLong(getArrivalTime()) + + "-" + + TimeUtils.timeToStrLong(getDepartureTime()) + + ")" + ); } private static int getAvailableTime(int... times) { diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 7a464995ba6..9ecdbbeb451 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -117,7 +117,7 @@ public void testGetAllStopTimes() { assertEquals(88, stopTimes.size()); assertEquals( - "StopTime{seq: 1, stop: RegularStop{F:A A}, trip: Trip{agency:1.1 1}, arrival: 0:00, departure: 0:00}", + "StopTime(seq=1 stop=F:A trip=agency:1.1 times=00:00:00-00:00:00)", first(stopTimes).toString() ); } From 2ba658bdb78d9cd69fd967c0229d945f6d396889 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 13 Oct 2023 23:33:54 +0200 Subject: [PATCH 061/120] Just use @MethodSource as it doesn't need the Arguments wrapper --- .../ext/flex/trip/UnscheduledTripTest.java | 45 ++++++++++--------- .../ext/flex/trip/UnscheduledTrip.java | 2 +- .../support/VariableArgumentsProvider.java | 26 +---------- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index a9a486aef25..47b9fda6568 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -32,7 +32,6 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.RegularStop; @@ -87,34 +86,38 @@ class IsUnscheduledTrip { CONTINUOUS_DROP_OFF_STOP.setTrip(trip); } - static final List> notUnscheduled = List.of( - List.of(), - List.of(SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), - List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), - List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) - ); + static List> notUnscheduled() { + return List.of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), + List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) + ); + } @ParameterizedTest - @VariableSource("notUnscheduled") + @MethodSource("notUnscheduled") void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static final List> unscheduled = List.of( - List.of(UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), - Collections.nCopies(10, UNSCHEDULED_STOP) - ); + static List> unscheduled() { + return List.of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) + ); + } @ParameterizedTest - @VariableSource("unscheduled") + @MethodSource("unscheduled") void isUnscheduled(List stopTimes) { assertTrue(isUnscheduledTrip(stopTimes)); } diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index f3a050bb224..6b32917e83e 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -30,7 +30,7 @@ /** * This type of FlexTrip is used when a taxi-type service is modeled, which operates in any number - * od areas/groups of stops without a set schedule. The travel times are calculated based + * of areas/groups of stops without a set schedule. The travel times are calculated based * on the driving time between the stops, with the schedule times being used just for deciding if a * trip is possible. *

diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 0d4ba1071a3..1ae9fb2af84 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -1,10 +1,7 @@ package org.opentripplanner.test.support; import java.lang.reflect.Field; -import java.util.Collection; -import java.util.function.Function; import java.util.stream.Stream; -import javax.annotation.Nonnull; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; @@ -53,27 +50,6 @@ private Stream getValue(Field field) { field.setAccessible(accessible); - if (value == null) { - return null; - } else if (value instanceof Collection collection) { - return collection.stream().map(toArguments()); - } else if (value instanceof Stream stream) { - return stream.map(toArguments()); - } else { - throw new IllegalArgumentException( - "Cannot convert %s to stream.".formatted(value.getClass()) - ); - } - } - - @Nonnull - private static Function toArguments() { - return val -> { - if (val instanceof Arguments arguments) { - return arguments; - } else { - return Arguments.of(val); - } - }; + return value == null ? null : (Stream) value; } } From 8533c2160197df7c9d58fb5f643587feec4f0428 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 10:50:22 +0300 Subject: [PATCH 062/120] Update src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java Co-authored-by: Leonard Ehrenfried --- .../internal/DefaultRealtimeVehicleService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 401092b6058..07fa48fa84f 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -67,8 +67,7 @@ public OccupancyStatus getOccupancyStatus(FeedScopedId tripId, TripPattern patte .stream() .filter(vehicle -> tripId.equals(vehicle.trip().getId())) .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN))) - .map(RealtimeVehicle::occupancyStatus) - .orElse(Optional.empty()) + .flatMap(RealtimeVehicle::occupancyStatus) .orElse(NO_DATA_AVAILABLE); } } From 130983f8e22341c8cfa9d0d6b8a904798cb04b52 Mon Sep 17 00:00:00 2001 From: bartosz Date: Fri, 1 Sep 2023 15:39:55 +0200 Subject: [PATCH 063/120] Create storage for c2 values in PathBuilderLeg --- .../raptor/path/PathBuilderLeg.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index d4375c97aaf..bdc61c87dda 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -1,5 +1,6 @@ package org.opentripplanner.raptor.path; +import java.util.Arrays; import java.util.function.Predicate; import javax.annotation.Nullable; import org.opentripplanner.framework.time.TimeUtils; @@ -44,6 +45,11 @@ public class PathBuilderLeg { private PathBuilderLeg prev = null; private PathBuilderLeg next = null; + // TODO: 2023-09-01 We need some storage space to keep track of c2 value per stop + // when we are filtering out possible transfers. + // I choose to include it here because it is practical but maybe we should have it somewhere else? + private final int[] c2PerStopPosition; + /** * Copy-constructor - do a deep copy with the exception of immutable types. Always start with the * desired head. The constructor will recursively copy the entire path (all legs until {@code @@ -54,6 +60,9 @@ private PathBuilderLeg(PathBuilderLeg other) { this.fromTime = other.fromTime; this.toTime = other.toTime; this.leg = other.leg; + // TODO: 2023-09-01 We have to copy over values here so that they are present + // in the next filter loop cycle + this.c2PerStopPosition = other.c2PerStopPosition.clone(); // Mutable fields if (other.next != null) { @@ -69,6 +78,10 @@ private PathBuilderLeg(MyLeg leg) { var transit = (MyTransitLeg) leg; this.fromTime = transit.fromTime(); this.toTime = transit.toTime(); + c2PerStopPosition = new int[transit.trip.pattern().numberOfStopsInPattern()]; + Arrays.fill(c2PerStopPosition, NOT_SET); + } else { + c2PerStopPosition = new int[0]; } } @@ -104,6 +117,27 @@ public int durationInSec() { return toTime - fromTime; } + /** + * Get c2 value associate with given stop position in a pattern. + * This works only for transit legs and if c2 is already set + */ + public int c2ForStopPosition(int pos) { + var c2 = c2PerStopPosition[pos]; + if (c2 == NOT_SET) { + throw new IllegalArgumentException("C2 for stop position " + pos + " not set"); + } + + return c2; + } + + /** + * Set c2 value on a given stop position in a transit leg + */ + public void setC2OnStopPosition(int pos, int c2) { + c2PerStopPosition[pos] = c2; + } + + @Nullable public RaptorConstrainedTransfer constrainedTransferAfterLeg() { return isTransit() ? asTransitLeg().constrainedTransferAfterLeg : null; From da285e515bd2a8200a1cb81b12ee6337ef2763db Mon Sep 17 00:00:00 2001 From: bartosz Date: Fri, 1 Sep 2023 15:40:29 +0200 Subject: [PATCH 064/120] Add support for pass-through-point searches to OptimizeTransferService --- .../raptor/path/PathBuilderLeg.java | 1 - .../raptoradapter/router/TransitRouter.java | 5 +- ...ansferOptimizationServiceConfigurator.java | 16 +- .../model/MinCostFilterChain.java | 3 +- ...OptimizeTransfersCostAndC2FilterChain.java | 35 ++++ .../model/OptimizeTransfersFilterChain.java | 7 + .../services/OptimizePathDomainService.java | 184 +++++++++++++++++- .../services/TransitPathLegSelector.java | 5 +- .../OptimizePathDomainServiceTest.java | 4 +- 9 files changed, 242 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index bdc61c87dda..6756863f795 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -137,7 +137,6 @@ public void setC2OnStopPosition(int pos, int c2) { c2PerStopPosition[pos] = c2; } - @Nullable public RaptorConstrainedTransfer constrainedTransferAfterLeg() { return isTransit() ? asTransitLeg().constrainedTransferAfterLeg : null; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e8a4c9e3311..bfa9e9ad267 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -150,7 +150,10 @@ private TransitRouterResult route() { serverContext.transitService().getTransferService(), requestTransitDataProvider, transitLayer.getStopBoardAlightCosts(), - request.preferences().transfer().optimization() + request.preferences().transfer().optimization(), + // TODO: 2023-09-01 Here we are injecting pass through points into TransferOptimizationService + // we have to think about what's the best solution + raptorRequest.multiCriteria().passThroughPoints() ) .optimize(transitResponse.paths()); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 231abf74555..bbccdfcf21d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -1,9 +1,11 @@ package org.opentripplanner.routing.algorithm.transferoptimization.configure; +import java.util.List; import java.util.function.IntFunction; import org.opentripplanner.model.transfer.TransferService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; +import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService; @@ -29,6 +31,7 @@ public class TransferOptimizationServiceConfigurator transitDataProvider; private final int[] stopBoardAlightCosts; private final TransferOptimizationParameters config; + private final List passThroughPoints; private TransferOptimizationServiceConfigurator( IntFunction stopLookup, @@ -36,7 +39,8 @@ private TransferOptimizationServiceConfigurator( TransferService transferService, RaptorTransitDataProvider transitDataProvider, int[] stopBoardAlightCosts, - TransferOptimizationParameters config + TransferOptimizationParameters config, + List passthroughPoints ) { this.stopLookup = stopLookup; this.stopNameResolver = stopNameResolver; @@ -44,6 +48,7 @@ private TransferOptimizationServiceConfigurator( this.transitDataProvider = transitDataProvider; this.stopBoardAlightCosts = stopBoardAlightCosts; this.config = config; + this.passThroughPoints = passthroughPoints; } /** @@ -57,7 +62,8 @@ > OptimizeTransferService createOptimizeTransferService( TransferService transferService, RaptorTransitDataProvider transitDataProvider, int[] stopBoardAlightCosts, - TransferOptimizationParameters config + TransferOptimizationParameters config, + List passthroughPoints ) { return new TransferOptimizationServiceConfigurator( stopLookup, @@ -65,7 +71,8 @@ > OptimizeTransferService createOptimizeTransferService( transferService, transitDataProvider, stopBoardAlightCosts, - config + config, + passthroughPoints ) .createOptimizeTransferService(); } @@ -117,7 +124,8 @@ private OptimizePathDomainService createOptimizePathService( stopBoardAlightCosts, config.extraStopBoardAlightCostsFactor(), transferPointFilter, - stopNameResolver + stopNameResolver, + passThroughPoints ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java index 554790ccb4c..f4c3ae9f3ac 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java @@ -17,7 +17,7 @@ * * @param The element type of the cost-functions and the filtered list */ -public class MinCostFilterChain { +public class MinCostFilterChain implements OptimizeTransfersFilterChain { private final List> costFunctions; @@ -25,6 +25,7 @@ public MinCostFilterChain(List> costFunctions) { this.costFunctions = costFunctions; } + @Override public Set filter(Set elements) { for (ToIntFunction costFunction : costFunctions) { elements = filter(elements, costFunction); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java new file mode 100644 index 00000000000..c3e2f3431bd --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java @@ -0,0 +1,35 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model; + +import static java.util.stream.Collectors.toSet; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class OptimizeTransfersCostAndC2FilterChain implements OptimizeTransfersFilterChain { + + private final MinCostFilterChain filterChain; + private final Function getC2; + + public OptimizeTransfersCostAndC2FilterChain( + MinCostFilterChain filterChain, + Function getC2 + ) { + this.filterChain = filterChain; + this.getC2 = getC2; + } + + @Override + public Set filter(Set elements) { + Map> elementsByC2Value = elements + .stream() + .collect(Collectors.groupingBy(getC2, toSet())); + Set result = new HashSet<>(); + for (Integer c2 : elementsByC2Value.keySet()) { + result.addAll(filterChain.filter(elementsByC2Value.get(c2))); + } + return result; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java new file mode 100644 index 00000000000..2eb8283c271 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java @@ -0,0 +1,7 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model; + +import java.util.Set; + +public interface OptimizeTransfersFilterChain { + Set filter(Set elements); +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 7798f3342b8..e896987f2f8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -1,8 +1,12 @@ package org.opentripplanner.routing.algorithm.transferoptimization.services; +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -11,10 +15,14 @@ import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransferPathLeg; import org.opentripplanner.raptor.api.path.TransitPathLeg; +import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersCostAndC2FilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; @@ -71,6 +79,7 @@ public class OptimizePathDomainService { private final RaptorSlackProvider slackProvider; private final MinCostFilterChain> minCostFilterChain; private final RaptorStopNameResolver stopNameTranslator; + private final List passThroughPoints; @Nullable private final TransferWaitTimeCostCalculator waitTimeCostCalculator; @@ -88,7 +97,12 @@ public OptimizePathDomainService( int[] stopBoardAlightCosts, double extraStopBoardAlightCostsFactor, MinCostFilterChain> minCostFilterChain, - RaptorStopNameResolver stopNameTranslator + RaptorStopNameResolver stopNameTranslator, + // TODO: 2023-09-01 Here I' including PassThroughPoints because I need a way to determine + // which filter we should use. I don't like this approach and would like to make it more generic + // but I do not know how to solve it because c2 filter depends on possibleTransfers that are generated + // in this class. + List passThroughPoints ) { this.transferGenerator = transferGenerator; this.costCalculator = costCalculator; @@ -98,20 +112,174 @@ public OptimizePathDomainService( this.extraStopBoardAlightCostsFactor = extraStopBoardAlightCostsFactor; this.minCostFilterChain = minCostFilterChain; this.stopNameTranslator = stopNameTranslator; + this.passThroughPoints = passThroughPoints; } public Set> findBestTransitPath(RaptorPath originalPath) { + if (!passThroughPoints.isEmpty()) { + return findBestTransitPathWithPassThroughPoints(originalPath, passThroughPoints); + } else { + return findBestTransitPathWithMinCost(originalPath); + } + } + + private List>> findPossibleTransfers(RaptorPath originalPath) { List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); // Find all possible transfers between each pair of transit legs, and sort on arrival time - var possibleTransfers = sortTransfersOnArrivalTimeInDecOrder( + return sortTransfersOnArrivalTimeInDecOrder( transferGenerator.findAllPossibleTransfers(transitLegs) ); + } + + /** + * Create a filer chain function and find the best transfers combination for journey that also + * includes all pass-through points + * Algorithm starts with the last trip in the journey, then goes backwards looping through all + * possible transfers for each transit leg. + * For each possible transfer stop position C2 value is calculated. Filter chain function is going + * to use c2 value and cost function to determine whether tail should be included or excluded from + * the collection. + * + * Example: + * Let's say we have a trip with 2 transit legs and the pass-through point is (B) + * There are 3 possible transfer combination with first and second transit + * + * Iteration 1 (initial c2 value is 1 since we have one pass-through point): + * (?) Transit 2 -> (Egress) | C2 = 1 + * + * Iteration 2 (create all possible journey combinations with transfers and calculate c2): + * (?) Transit 1 -> (A) Transit 2 -> (Egress) | C2 = 0 <- C2 is 0 since we will pass through (B) if we board here + * (?) Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 + * (?) Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 1 <- C2 is 1 since we will not pass through (B) if we board here + * + * Iteration 3 (insert access and filter out all combinations where c2 != 0) + * (Access) -> Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 + * (Access) -> Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 0 + * + * Then we're going to use minCostFilterChain to determine which of those 2 possibilities is better + */ + private Set> findBestTransitPathWithPassThroughPoints( + RaptorPath originalPath, + List passThroughPoints + ) { + var possibleTransfers = findPossibleTransfers(originalPath); + List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); + var filter = createC2FilterChain(minCostFilterChain, possibleTransfers, passThroughPoints); + + // We need to filter out all path without passThrough points + return findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter) + .stream() + // TODO: 2023-09-01 here we need to do one more filter to exclude all invalid paths + // (if a path is missing any pass-through points) + // Ideally this should be done "inside" the algorithm but I'm not sure how to do it + .filter(tail -> calculateC2(tail, possibleTransfers, passThroughPoints) == 0) + .map(OptimizedPathTail::build) + .collect(toSet()); + } + + private Set> findBestTransitPathWithMinCost(RaptorPath originalPath) { + var possibleTransfers = findPossibleTransfers(originalPath); + + List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); // Combine transit legs and transfers - var tails = findBestTransferOption(originalPath, transitLegs, possibleTransfers); + return findBestTransferOption(originalPath, transitLegs, possibleTransfers, minCostFilterChain) + .stream() + .map(OptimizedPathTail::build) + .collect(toSet()); + } + + private OptimizeTransfersFilterChain> createC2FilterChain( + MinCostFilterChain> minCostFilterChain, + List>> possibleTransfers, + List passThroughPoints + ) { + return new OptimizeTransfersCostAndC2FilterChain<>( + minCostFilterChain, + tail -> this.calculateC2(tail, possibleTransfers, passThroughPoints) + ); + } + + // TODO: 2023-09-01 I don't really like that this method is here. + // but since we need a list of possible transfers between paths it is hard to move it + // some else in the code. + /** + * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 + * value given position. + * + * @param tail Current OptimizedPathTail + * @param possibleTransfers List all possible transfers for the whole journey + * @param passThroughPoints Pass-through points for the search + * @return c2 value for the earliest possible boarding position. + */ + private int calculateC2( + OptimizedPathTail tail, + List>> possibleTransfers, + List passThroughPoints + ) { + var legs = new ArrayList<>(tail.legsAsStream().toList()); + + var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); + + int c2; + if (transits.size() == 1) { + // That's the last transit in the journey we are checking. There can't be any via stops yet. + // This should be via.size + // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can + // set initial C2 value. That's why I added size() method to PassThroughPoints + c2 = passThroughPoints.size(); + } else { + // Check c2 value on board position from previous transit leg. + var previousTransit = transits.get(1); + c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); + } + + var transitLeg = transits.get(0); + int fromStopPosition; + if (possibleTransfers.size() + 1 == transits.size()) { + // Here we reached the first transit in the journey. + // We do not have to check possible transfers. + fromStopPosition = transitLeg.fromStopPos(); + } else { + // This should be the earliest possible board stop position for a leg. + // We can verify that with transfers. + fromStopPosition = + possibleTransfers + .get(possibleTransfers.size() - transits.size()) + .stream() + .map(t -> t.to().stopPosition()) + .reduce((first, second) -> first < second ? first : second) + .get(); + } + + // TODO PT: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. + // The value is gonna persist into the next loop so that we know what c2 value we had + // on previous (next) leg + // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting + // the whole filter logic. + // We already visited all via stops. + // Don't have to check anything. Just set on all stop positions. + if (c2 == 0) { + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + transitLeg.setC2OnStopPosition(pos, 0); + } + } else { + // Loop through all possible boarding position and calculate potential c2 value + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + var stopIndex = transitLeg.trip().pattern().stopIndex(pos); + // TODO PT: 2023-09-01 We need to check here whether point is a pass through for a given + // c2 value. That's why I need to include this method in PassThroughPoint class. + // This might not be the best solution. What are the other options we have? + if (passThroughPoints.get(c2 - 1).asBitSet().get(stopIndex)) { + c2--; + } + + transitLeg.setC2OnStopPosition(pos, c2); + } + } - return tails.stream().map(OptimizedPathTail::build).collect(Collectors.toSet()); + return transitLeg.c2ForStopPosition(fromStopPosition); } private static T last(List list) { @@ -121,7 +289,8 @@ private static T last(List list) { private Set> findBestTransferOption( RaptorPath originalPath, List> originalTransitLegs, - List>> possibleTransfers + List>> possibleTransfers, + OptimizeTransfersFilterChain> filterChain ) { final int iterationDepartureTime = originalPath.rangeRaptorIterationDepartureTime(); // Create a set of tails with the last transit leg in it (one element) @@ -158,7 +327,7 @@ private Set> findBestTransferOption( // create a tailSelector for the tails produced in the last round and use it to filter them // based on the transfer-arrival-time and given filter - var tailSelector = new TransitPathLegSelector<>(minCostFilterChain, tails); + var tailSelector = new TransitPathLegSelector<>(filterChain, tails); // Reset the result set to an empty set tails = new HashSet<>(); @@ -183,8 +352,7 @@ private Set> findBestTransferOption( // Filter tails one final time tails = - new TransitPathLegSelector<>(minCostFilterChain, tails) - .next(originalPath.accessLeg().toTime()); + new TransitPathLegSelector<>(filterChain, tails).next(originalPath.accessLeg().toTime()); // Insert the access leg and the following transfer insertAccess(originalPath, tails); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index b3fc002a87c..076f4e89ad7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -5,6 +5,7 @@ import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; /** @@ -28,14 +29,14 @@ */ class TransitPathLegSelector { - private final MinCostFilterChain> filter; + private final OptimizeTransfersFilterChain> filter; private Set> remindingLegs; private Set> selectedLegs; private int lastLimit = Integer.MAX_VALUE; TransitPathLegSelector( - final MinCostFilterChain> filter, + final OptimizeTransfersFilterChain> filter, final Set> legs ) { this.filter = filter; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index 192cdb84f11..3dc0474f129 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; import org.opentripplanner.raptor._data.RaptorTestConstants; @@ -295,7 +296,8 @@ static OptimizePathDomainService subject( null, 0.0, TransferOptimizedFilterFactory.filter(true, waitTimeCalculator != null), - (new RaptorTestConstants() {})::stopIndexToName + (new RaptorTestConstants() {})::stopIndexToName, + List.of() ); } From 5e15178db526bc60b569bcaaec9a4d945bce783d Mon Sep 17 00:00:00 2001 From: bartosz Date: Tue, 19 Sep 2023 16:40:17 +0200 Subject: [PATCH 065/120] change to filter factory --- .../raptoradapter/router/TransitRouter.java | 24 +-- ...ansferOptimizationServiceConfigurator.java | 56 +++--- .../model/MinCostFilterChain.java | 5 + ...OptimizeTransfersCostAndC2FilterChain.java | 5 + .../model/OptimizeTransfersFilterChain.java | 1 + .../services/FilterFactory.java | 13 ++ .../services/OptimizePathDomainService.java | 181 ++---------------- ...timizeTransfersCostAndC2FilterFactory.java | 158 +++++++++++++++ .../TransferOptimizedFilterFactory.java | 22 ++- .../OptimizePathDomainServiceTest.java | 5 +- 10 files changed, 248 insertions(+), 222 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index bfa9e9ad267..b9f9685341c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -142,20 +142,16 @@ private TransitRouterResult route() { Collection> paths = transitResponse.paths(); if (OTPFeature.OptimizeTransfers.isOn() && !transitResponse.containsUnknownPaths()) { - paths = - TransferOptimizationServiceConfigurator - .createOptimizeTransferService( - transitLayer::getStopByIndex, - requestTransitDataProvider.stopNameResolver(), - serverContext.transitService().getTransferService(), - requestTransitDataProvider, - transitLayer.getStopBoardAlightCosts(), - request.preferences().transfer().optimization(), - // TODO: 2023-09-01 Here we are injecting pass through points into TransferOptimizationService - // we have to think about what's the best solution - raptorRequest.multiCriteria().passThroughPoints() - ) - .optimize(transitResponse.paths()); + var service = TransferOptimizationServiceConfigurator.createOptimizeTransferService( + transitLayer::getStopByIndex, + requestTransitDataProvider.stopNameResolver(), + serverContext.transitService().getTransferService(), + requestTransitDataProvider, + transitLayer.getStopBoardAlightCosts(), + request.preferences().transfer().optimization(), + raptorRequest.multiCriteria() + ); + paths = service.optimize(transitResponse.paths()); } // Create itineraries diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index bbccdfcf21d..9b36a0c1573 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -1,20 +1,19 @@ package org.opentripplanner.routing.algorithm.transferoptimization.configure; -import java.util.List; import java.util.function.IntFunction; import org.opentripplanner.model.transfer.TransferService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; -import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService; import org.opentripplanner.routing.algorithm.transferoptimization.api.TransferOptimizationParameters; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.services.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; +import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizeTransfersCostAndC2FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferServiceAdaptor; @@ -31,7 +30,7 @@ public class TransferOptimizationServiceConfigurator transitDataProvider; private final int[] stopBoardAlightCosts; private final TransferOptimizationParameters config; - private final List passThroughPoints; + private final MultiCriteriaRequest multiCriteriaRequest; private TransferOptimizationServiceConfigurator( IntFunction stopLookup, @@ -40,7 +39,7 @@ private TransferOptimizationServiceConfigurator( RaptorTransitDataProvider transitDataProvider, int[] stopBoardAlightCosts, TransferOptimizationParameters config, - List passthroughPoints + MultiCriteriaRequest multiCriteriaRequest ) { this.stopLookup = stopLookup; this.stopNameResolver = stopNameResolver; @@ -48,7 +47,7 @@ private TransferOptimizationServiceConfigurator( this.transitDataProvider = transitDataProvider; this.stopBoardAlightCosts = stopBoardAlightCosts; this.config = config; - this.passThroughPoints = passthroughPoints; + this.multiCriteriaRequest = multiCriteriaRequest; } /** @@ -63,7 +62,7 @@ > OptimizeTransferService createOptimizeTransferService( RaptorTransitDataProvider transitDataProvider, int[] stopBoardAlightCosts, TransferOptimizationParameters config, - List passthroughPoints + MultiCriteriaRequest multiCriteriaRequest ) { return new TransferOptimizationServiceConfigurator( stopLookup, @@ -72,24 +71,22 @@ > OptimizeTransferService createOptimizeTransferService( transitDataProvider, stopBoardAlightCosts, config, - passthroughPoints + multiCriteriaRequest ) .createOptimizeTransferService(); } private OptimizeTransferService createOptimizeTransferService() { var pathTransferGenerator = createTransferGenerator(config.optimizeTransferPriority()); - var filter = createTransferOptimizedFilter( - config.optimizeTransferPriority(), - config.optimizeTransferWaitTime() - ); + + var filterFactory = createFilterFactory(); if (config.optimizeTransferWaitTime()) { var transferWaitTimeCalculator = createTransferWaitTimeCalculator(); var transfersPermutationService = createOptimizePathService( pathTransferGenerator, - filter, + filterFactory, transferWaitTimeCalculator, transitDataProvider.multiCriteriaCostCalculator() ); @@ -102,7 +99,7 @@ private OptimizeTransferService createOptimizeTransferService() { } else { var transfersPermutationService = createOptimizePathService( pathTransferGenerator, - filter, + filterFactory, null, transitDataProvider.multiCriteriaCostCalculator() ); @@ -110,9 +107,24 @@ private OptimizeTransferService createOptimizeTransferService() { } } + private FilterFactory createFilterFactory() { + if (multiCriteriaRequest.hasPassThroughPoints()) { + return new OptimizeTransfersCostAndC2FilterFactory<>( + config.optimizeTransferPriority(), + config.optimizeTransferWaitTime(), + multiCriteriaRequest.passThroughPoints() + ); + } else { + return new TransferOptimizedFilterFactory<>( + config.optimizeTransferPriority(), + config.optimizeTransferWaitTime() + ); + } + } + private OptimizePathDomainService createOptimizePathService( TransferGenerator transferGenerator, - MinCostFilterChain> transferPointFilter, + FilterFactory filterFactory, TransferWaitTimeCostCalculator transferWaitTimeCostCalculator, RaptorCostCalculator costCalculator ) { @@ -123,9 +135,8 @@ private OptimizePathDomainService createOptimizePathService( transferWaitTimeCostCalculator, stopBoardAlightCosts, config.extraStopBoardAlightCostsFactor(), - transferPointFilter, - stopNameResolver, - passThroughPoints + filterFactory, + stopNameResolver ); } @@ -147,11 +158,4 @@ private TransferWaitTimeCostCalculator createTransferWaitTimeCalculator() { config.minSafeWaitTimeFactor() ); } - - private MinCostFilterChain> createTransferOptimizedFilter( - boolean transferPriority, - boolean optimizeWaitTime - ) { - return TransferOptimizedFilterFactory.filter(transferPriority, optimizeWaitTime); - } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java index f4c3ae9f3ac..67a203b1a2d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java @@ -50,4 +50,9 @@ private Set filter(Set elements, ToIntFunction costFunction) { } return result; } + + @Override + public Set finalizeFilter(Set elements) { + return elements; + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java index c3e2f3431bd..3c727650290 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java @@ -32,4 +32,9 @@ public Set filter(Set elements) { } return result; } + + @Override + public Set finalizeFilter(Set elements) { + return elements.stream().filter(tail -> getC2.apply(tail) == 0).collect(toSet()); + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java index 2eb8283c271..78760d5a867 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java @@ -4,4 +4,5 @@ public interface OptimizeTransfersFilterChain { Set filter(Set elements); + Set finalizeFilter(Set elements); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java new file mode 100644 index 00000000000..c9215c3b485 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java @@ -0,0 +1,13 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.services; + +import java.util.List; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; + +public interface FilterFactory { + OptimizeTransfersFilterChain> createFilter( + List>> possibleTransfers + ); +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index e896987f2f8..9bfa569fd87 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -2,11 +2,9 @@ import static java.util.stream.Collectors.toSet; -import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -15,13 +13,9 @@ import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransferPathLeg; import org.opentripplanner.raptor.api.path.TransitPathLeg; -import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersCostAndC2FilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; @@ -77,9 +71,8 @@ public class OptimizePathDomainService { private final TransferGenerator transferGenerator; private final RaptorCostCalculator costCalculator; private final RaptorSlackProvider slackProvider; - private final MinCostFilterChain> minCostFilterChain; + private final FilterFactory filterFactory; private final RaptorStopNameResolver stopNameTranslator; - private final List passThroughPoints; @Nullable private final TransferWaitTimeCostCalculator waitTimeCostCalculator; @@ -96,13 +89,8 @@ public OptimizePathDomainService( @Nullable TransferWaitTimeCostCalculator waitTimeCostCalculator, int[] stopBoardAlightCosts, double extraStopBoardAlightCostsFactor, - MinCostFilterChain> minCostFilterChain, - RaptorStopNameResolver stopNameTranslator, - // TODO: 2023-09-01 Here I' including PassThroughPoints because I need a way to determine - // which filter we should use. I don't like this approach and would like to make it more generic - // but I do not know how to solve it because c2 filter depends on possibleTransfers that are generated - // in this class. - List passThroughPoints + FilterFactory filterFactory, + RaptorStopNameResolver stopNameTranslator ) { this.transferGenerator = transferGenerator; this.costCalculator = costCalculator; @@ -110,178 +98,31 @@ public OptimizePathDomainService( this.waitTimeCostCalculator = waitTimeCostCalculator; this.stopBoardAlightCosts = stopBoardAlightCosts; this.extraStopBoardAlightCostsFactor = extraStopBoardAlightCostsFactor; - this.minCostFilterChain = minCostFilterChain; + this.filterFactory = filterFactory; this.stopNameTranslator = stopNameTranslator; - this.passThroughPoints = passThroughPoints; } public Set> findBestTransitPath(RaptorPath originalPath) { - if (!passThroughPoints.isEmpty()) { - return findBestTransitPathWithPassThroughPoints(originalPath, passThroughPoints); - } else { - return findBestTransitPathWithMinCost(originalPath); - } - } - - private List>> findPossibleTransfers(RaptorPath originalPath) { - List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - - // Find all possible transfers between each pair of transit legs, and sort on arrival time - return sortTransfersOnArrivalTimeInDecOrder( - transferGenerator.findAllPossibleTransfers(transitLegs) - ); - } - - /** - * Create a filer chain function and find the best transfers combination for journey that also - * includes all pass-through points - * Algorithm starts with the last trip in the journey, then goes backwards looping through all - * possible transfers for each transit leg. - * For each possible transfer stop position C2 value is calculated. Filter chain function is going - * to use c2 value and cost function to determine whether tail should be included or excluded from - * the collection. - * - * Example: - * Let's say we have a trip with 2 transit legs and the pass-through point is (B) - * There are 3 possible transfer combination with first and second transit - * - * Iteration 1 (initial c2 value is 1 since we have one pass-through point): - * (?) Transit 2 -> (Egress) | C2 = 1 - * - * Iteration 2 (create all possible journey combinations with transfers and calculate c2): - * (?) Transit 1 -> (A) Transit 2 -> (Egress) | C2 = 0 <- C2 is 0 since we will pass through (B) if we board here - * (?) Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 - * (?) Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 1 <- C2 is 1 since we will not pass through (B) if we board here - * - * Iteration 3 (insert access and filter out all combinations where c2 != 0) - * (Access) -> Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 - * (Access) -> Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 0 - * - * Then we're going to use minCostFilterChain to determine which of those 2 possibilities is better - */ - private Set> findBestTransitPathWithPassThroughPoints( - RaptorPath originalPath, - List passThroughPoints - ) { var possibleTransfers = findPossibleTransfers(originalPath); List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - var filter = createC2FilterChain(minCostFilterChain, possibleTransfers, passThroughPoints); + var filter = filterFactory.createFilter(possibleTransfers); - // We need to filter out all path without passThrough points - return findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter) + return filter + .finalizeFilter(findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter)) .stream() - // TODO: 2023-09-01 here we need to do one more filter to exclude all invalid paths - // (if a path is missing any pass-through points) - // Ideally this should be done "inside" the algorithm but I'm not sure how to do it - .filter(tail -> calculateC2(tail, possibleTransfers, passThroughPoints) == 0) .map(OptimizedPathTail::build) .collect(toSet()); } - private Set> findBestTransitPathWithMinCost(RaptorPath originalPath) { - var possibleTransfers = findPossibleTransfers(originalPath); - + private List>> findPossibleTransfers(RaptorPath originalPath) { List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - // Combine transit legs and transfers - return findBestTransferOption(originalPath, transitLegs, possibleTransfers, minCostFilterChain) - .stream() - .map(OptimizedPathTail::build) - .collect(toSet()); - } - - private OptimizeTransfersFilterChain> createC2FilterChain( - MinCostFilterChain> minCostFilterChain, - List>> possibleTransfers, - List passThroughPoints - ) { - return new OptimizeTransfersCostAndC2FilterChain<>( - minCostFilterChain, - tail -> this.calculateC2(tail, possibleTransfers, passThroughPoints) + // Find all possible transfers between each pair of transit legs, and sort on arrival time + return sortTransfersOnArrivalTimeInDecOrder( + transferGenerator.findAllPossibleTransfers(transitLegs) ); } - // TODO: 2023-09-01 I don't really like that this method is here. - // but since we need a list of possible transfers between paths it is hard to move it - // some else in the code. - /** - * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 - * value given position. - * - * @param tail Current OptimizedPathTail - * @param possibleTransfers List all possible transfers for the whole journey - * @param passThroughPoints Pass-through points for the search - * @return c2 value for the earliest possible boarding position. - */ - private int calculateC2( - OptimizedPathTail tail, - List>> possibleTransfers, - List passThroughPoints - ) { - var legs = new ArrayList<>(tail.legsAsStream().toList()); - - var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); - - int c2; - if (transits.size() == 1) { - // That's the last transit in the journey we are checking. There can't be any via stops yet. - // This should be via.size - // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can - // set initial C2 value. That's why I added size() method to PassThroughPoints - c2 = passThroughPoints.size(); - } else { - // Check c2 value on board position from previous transit leg. - var previousTransit = transits.get(1); - c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); - } - - var transitLeg = transits.get(0); - int fromStopPosition; - if (possibleTransfers.size() + 1 == transits.size()) { - // Here we reached the first transit in the journey. - // We do not have to check possible transfers. - fromStopPosition = transitLeg.fromStopPos(); - } else { - // This should be the earliest possible board stop position for a leg. - // We can verify that with transfers. - fromStopPosition = - possibleTransfers - .get(possibleTransfers.size() - transits.size()) - .stream() - .map(t -> t.to().stopPosition()) - .reduce((first, second) -> first < second ? first : second) - .get(); - } - - // TODO PT: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. - // The value is gonna persist into the next loop so that we know what c2 value we had - // on previous (next) leg - // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting - // the whole filter logic. - // We already visited all via stops. - // Don't have to check anything. Just set on all stop positions. - if (c2 == 0) { - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - transitLeg.setC2OnStopPosition(pos, 0); - } - } else { - // Loop through all possible boarding position and calculate potential c2 value - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - var stopIndex = transitLeg.trip().pattern().stopIndex(pos); - // TODO PT: 2023-09-01 We need to check here whether point is a pass through for a given - // c2 value. That's why I need to include this method in PassThroughPoint class. - // This might not be the best solution. What are the other options we have? - if (passThroughPoints.get(c2 - 1).asBitSet().get(stopIndex)) { - c2--; - } - - transitLeg.setC2OnStopPosition(pos, c2); - } - } - - return transitLeg.c2ForStopPosition(fromStopPosition); - } - private static T last(List list) { return list.get(list.size() - 1); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java new file mode 100644 index 00000000000..e0cf458c6f1 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java @@ -0,0 +1,158 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.services; + +import java.util.ArrayList; +import java.util.List; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.path.PathBuilderLeg; +import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersCostAndC2FilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; + +public class OptimizeTransfersCostAndC2FilterFactory + implements FilterFactory { + + private final boolean transferPriority; + private final boolean optimizeWaitTime; + private final List passThroughPoints; + + public OptimizeTransfersCostAndC2FilterFactory( + boolean transferPriority, + boolean optimizeWaitTime, + List passThroughPoints + ) { + this.transferPriority = transferPriority; + this.optimizeWaitTime = optimizeWaitTime; + this.passThroughPoints = passThroughPoints; + } + + /** + * Create a filer chain function and find the best transfers combination for journey that also + * includes all pass-through points + * Algorithm starts with the last trip in the journey, then goes backwards looping through all + * possible transfers for each transit leg. + * For each possible transfer stop position C2 value is calculated. Filter chain function is going + * to use c2 value and cost function to determine whether tail should be included or excluded from + * the collection. + * + * Example: + * Let's say we have a trip with 2 transit legs and the pass-through point is (B) + * There are 3 possible transfer combination with first and second transit + * + * Iteration 1 (initial c2 value is 1 since we have one pass-through point): + * (?) Transit 2 -> (Egress) | C2 = 1 + * + * Iteration 2 (create all possible journey combinations with transfers and calculate c2): + * (?) Transit 1 -> (A) Transit 2 -> (Egress) | C2 = 0 <- C2 is 0 since we will pass through (B) if we board here + * (?) Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 + * (?) Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 1 <- C2 is 1 since we will not pass through (B) if we board here + * + * Iteration 3 (insert access and filter out all combinations where c2 != 0) + * (Access) -> Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 + * (Access) -> Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 0 + * + * Then we're going to use minCostFilterChain to determine which of those 2 possibilities is better + */ + @Override + public OptimizeTransfersFilterChain> createFilter( + List>> possibleTransfers + ) { + return new OptimizeTransfersCostAndC2FilterChain<>( + (MinCostFilterChain>) new TransferOptimizedFilterFactory( + transferPriority, + optimizeWaitTime + ) + .createFilter(possibleTransfers), + tail -> this.calculateC2(tail, possibleTransfers, passThroughPoints) + ); + } + + /** + * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 + * value given position. + * + * @param tail Current OptimizedPathTail + * @param possibleTransfers List all possible transfers for the whole journey + * @param passThroughPoints Pass-through points for the search + * @return c2 value for the earliest possible boarding position. + */ + private int calculateC2( + OptimizedPathTail tail, + List>> possibleTransfers, + List passThroughPoints + ) { + var legs = new ArrayList<>(tail.legsAsStream().toList()); + + var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); + + int c2; + if (transits.size() == 1) { + // That's the last transit in the journey we are checking. There can't be any via stops yet. + // This should be via.size + // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can + // set initial C2 value. That's why I added size() method to PassThroughPoints + c2 = passThroughPoints.size(); + } else { + // Check c2 value on board position from previous transit leg. + var previousTransit = transits.get(1); + c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); + } + + var transitLeg = transits.get(0); + int fromStopPosition; + if (possibleTransfers.size() + 1 == transits.size()) { + // Here we reached the first transit in the journey. + // We do not have to check possible transfers. + fromStopPosition = transitLeg.fromStopPos(); + } else { + // This should be the earliest possible board stop position for a leg. + // We can verify that with transfers. + fromStopPosition = + possibleTransfers + .get(possibleTransfers.size() - transits.size()) + .stream() + .map(t -> t.to().stopPosition()) + .reduce((first, second) -> first < second ? first : second) + .get(); + } + + // TODO: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. + // The value is gonna persist into the next loop so that we know what c2 value we had + // on previous (next) leg + // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting + // the whole filter logic. + // We already visited all via stops. + // Don't have to check anything. Just set on all stop positions. + if (c2 == 0) { + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + transitLeg.setC2OnStopPosition(pos, 0); + } + } else { + // Loop through all possible boarding position and calculate potential c2 value + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + var stopIndex = transitLeg.trip().pattern().stopIndex(pos); + // TODO: 2023-09-01 We need to check here whether point is a pass through for a given + // c2 value. That's why I need to include this method in PassThroughPoint class. + // This might not be the best solution. What are the other options we have? + if (isPassThroughPoint(passThroughPoints, c2, stopIndex)) { + c2--; + } + + transitLeg.setC2OnStopPosition(pos, c2); + } + } + + return transitLeg.c2ForStopPosition(fromStopPosition); + } + + private boolean isPassThroughPoint( + List passThroughPoints, + int c2, + int stopIndex + ) { + var point = passThroughPoints.get(c2 - 1); + return point != null && point.asBitSet().get(stopIndex); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java index 9b950a2663e..d82be7a5b3e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java @@ -5,20 +5,24 @@ import java.util.function.ToIntFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -public class TransferOptimizedFilterFactory { +public class TransferOptimizedFilterFactory + implements FilterFactory { - public static MinCostFilterChain> filter( - boolean transferPriority, - boolean optimizeWaitTime - ) { - return new TransferOptimizedFilterFactory().create(transferPriority, optimizeWaitTime); + private final boolean transferPriority; + private final boolean optimizeWaitTime; + + public TransferOptimizedFilterFactory(boolean transferPriority, boolean optimizeWaitTime) { + this.transferPriority = transferPriority; + this.optimizeWaitTime = optimizeWaitTime; } - private MinCostFilterChain> create( - boolean transferPriority, - boolean optimizeWaitTime + @Override + public OptimizeTransfersFilterChain> createFilter( + List>> possibleTransfers ) { List>> filters = new ArrayList<>(3); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index 3dc0474f129..1aee0cdeb5e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -295,9 +295,8 @@ static OptimizePathDomainService subject( waitTimeCalculator, null, 0.0, - TransferOptimizedFilterFactory.filter(true, waitTimeCalculator != null), - (new RaptorTestConstants() {})::stopIndexToName, - List.of() + new TransferOptimizedFilterFactory<>(true, waitTimeCalculator != null), + (new RaptorTestConstants() {})::stopIndexToName ); } From 839c9f9e57b4ad9759c021a0c080c5dd34acb8f0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 16:37:57 +0200 Subject: [PATCH 066/120] refactor: Clean code --- .../model/MinCostFilterChain.java | 2 +- .../model/OptimizeTransfersFilterChain.java | 9 ++++++++ .../services/OptimizePathDomainService.java | 22 ++++++++++--------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java index 67a203b1a2d..639f23cda27 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java @@ -27,7 +27,7 @@ public MinCostFilterChain(List> costFunctions) { @Override public Set filter(Set elements) { - for (ToIntFunction costFunction : costFunctions) { + for (var costFunction : costFunctions) { elements = filter(elements, costFunction); } return elements; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java index 78760d5a867..ccc376360da 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java @@ -2,7 +2,16 @@ import java.util.Set; +/** + * TODO PT - This need JavaDoc + */ public interface OptimizeTransfersFilterChain { + /** + * TODO PT - This need JavaDoc + */ Set filter(Set elements); + /** + * TODO PT - This need JavaDoc + */ Set finalizeFilter(Set elements); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 9bfa569fd87..09e6b25f484 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -107,17 +107,20 @@ public Set> findBestTransitPath(RaptorPath originalPath) { List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); var filter = filterFactory.createFilter(possibleTransfers); - return filter - .finalizeFilter(findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter)) - .stream() - .map(OptimizedPathTail::build) - .collect(toSet()); + // Combine transit legs and transfers + var tails = findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter); + + var filteredTails = filter.finalizeFilter(tails); + + return filteredTails.stream().map(OptimizedPathTail::build).collect(toSet()); } + /** + * Find all possible transfers between each pair of transit legs, and sort on arrival time. + */ private List>> findPossibleTransfers(RaptorPath originalPath) { List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - // Find all possible transfers between each pair of transit legs, and sort on arrival time return sortTransfersOnArrivalTimeInDecOrder( transferGenerator.findAllPossibleTransfers(transitLegs) ); @@ -131,7 +134,7 @@ private Set> findBestTransferOption( RaptorPath originalPath, List> originalTransitLegs, List>> possibleTransfers, - OptimizeTransfersFilterChain> filterChain + OptimizeTransfersFilterChain> filter ) { final int iterationDepartureTime = originalPath.rangeRaptorIterationDepartureTime(); // Create a set of tails with the last transit leg in it (one element) @@ -168,7 +171,7 @@ private Set> findBestTransferOption( // create a tailSelector for the tails produced in the last round and use it to filter them // based on the transfer-arrival-time and given filter - var tailSelector = new TransitPathLegSelector<>(filterChain, tails); + var tailSelector = new TransitPathLegSelector<>(filter, tails); // Reset the result set to an empty set tails = new HashSet<>(); @@ -192,8 +195,7 @@ private Set> findBestTransferOption( } // Filter tails one final time - tails = - new TransitPathLegSelector<>(filterChain, tails).next(originalPath.accessLeg().toTime()); + tails = new TransitPathLegSelector<>(filter, tails).next(originalPath.accessLeg().toTime()); // Insert the access leg and the following transfer insertAccess(originalPath, tails); From fb7ca7b4aa845bd86858698f80898de8a356f587 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 16:59:54 +0200 Subject: [PATCH 067/120] refactor: Move FilterFactory to model --- .../configure/TransferOptimizationServiceConfigurator.java | 2 +- .../{services => model}/FilterFactory.java | 5 +---- .../services/OptimizePathDomainService.java | 1 + .../services/OptimizeTransfersCostAndC2FilterFactory.java | 1 + .../services/TransferOptimizedFilterFactory.java | 1 + 5 files changed, 5 insertions(+), 5 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/{services => model}/FilterFactory.java (53%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 9b36a0c1573..484a9991316 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -9,9 +9,9 @@ import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService; import org.opentripplanner.routing.algorithm.transferoptimization.api.TransferOptimizationParameters; +import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.services.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizeTransfersCostAndC2FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java similarity index 53% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java index c9215c3b485..2d37a0d73d0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java @@ -1,10 +1,7 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.services; +package org.opentripplanner.routing.algorithm.transferoptimization.model; import java.util.List; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; -import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public interface FilterFactory { OptimizeTransfersFilterChain> createFilter( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 09e6b25f484..6ee44d186d9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -16,6 +16,7 @@ import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; +import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java index e0cf458c6f1..01650b80a5e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java @@ -5,6 +5,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.path.PathBuilderLeg; +import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersCostAndC2FilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java index d82be7a5b3e..35b58c53d3f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.function.ToIntFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; From 13199986d68a660333bc2e96c10cf31ce3fb377f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:05:43 +0200 Subject: [PATCH 068/120] refactor: Rename OptimizeTransfersFilterChain to PathTailFilter --- .../transferoptimization/model/FilterFactory.java | 2 +- ...stFilterChain.java => MinCostPathTailFilter.java} | 4 ++-- ...lterChain.java => PassThroughPathTailFilter.java} | 8 ++++---- ...TransfersFilterChain.java => PathTailFilter.java} | 2 +- .../services/OptimizePathDomainService.java | 4 ++-- .../OptimizeTransfersCostAndC2FilterFactory.java | 12 ++++++------ .../services/TransferOptimizedFilterFactory.java | 8 ++++---- .../services/TransitPathLegSelector.java | 7 +++---- ...ChainTest.java => MinCostPathTailFilterTest.java} | 6 +++--- .../services/TransitPathLegSelectorTest.java | 4 ++-- 10 files changed, 28 insertions(+), 29 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{MinCostFilterChain.java => MinCostPathTailFilter.java} (90%) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{OptimizeTransfersCostAndC2FilterChain.java => PassThroughPathTailFilter.java} (79%) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{OptimizeTransfersFilterChain.java => PathTailFilter.java} (85%) rename src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{MinCostFilterChainTest.java => MinCostPathTailFilterTest.java} (90%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java index 2d37a0d73d0..d145dbc0544 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java @@ -4,7 +4,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; public interface FilterFactory { - OptimizeTransfersFilterChain> createFilter( + PathTailFilter> createFilter( List>> possibleTransfers ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java similarity index 90% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java index 639f23cda27..68971227c13 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java @@ -17,11 +17,11 @@ * * @param The element type of the cost-functions and the filtered list */ -public class MinCostFilterChain implements OptimizeTransfersFilterChain { +public class MinCostPathTailFilter implements PathTailFilter { private final List> costFunctions; - public MinCostFilterChain(List> costFunctions) { + public MinCostPathTailFilter(List> costFunctions) { this.costFunctions = costFunctions; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java similarity index 79% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java index 3c727650290..13e79470f8d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersCostAndC2FilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java @@ -8,13 +8,13 @@ import java.util.function.Function; import java.util.stream.Collectors; -public class OptimizeTransfersCostAndC2FilterChain implements OptimizeTransfersFilterChain { +public class PassThroughPathTailFilter implements PathTailFilter { - private final MinCostFilterChain filterChain; + private final MinCostPathTailFilter filterChain; private final Function getC2; - public OptimizeTransfersCostAndC2FilterChain( - MinCostFilterChain filterChain, + public PassThroughPathTailFilter( + MinCostPathTailFilter filterChain, Function getC2 ) { this.filterChain = filterChain; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java similarity index 85% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index ccc376360da..1fee07d1de9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizeTransfersFilterChain.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -5,7 +5,7 @@ /** * TODO PT - This need JavaDoc */ -public interface OptimizeTransfersFilterChain { +public interface PathTailFilter { /** * TODO PT - This need JavaDoc */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 6ee44d186d9..0678403a73b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -17,8 +17,8 @@ import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; @@ -135,7 +135,7 @@ private Set> findBestTransferOption( RaptorPath originalPath, List> originalTransitLegs, List>> possibleTransfers, - OptimizeTransfersFilterChain> filter + PathTailFilter> filter ) { final int iterationDepartureTime = originalPath.rangeRaptorIterationDepartureTime(); // Create a set of tails with the last transit leg in it (one element) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java index 01650b80a5e..e8368725351 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java @@ -6,10 +6,10 @@ import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersCostAndC2FilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PassThroughPathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public class OptimizeTransfersCostAndC2FilterFactory @@ -57,11 +57,11 @@ public OptimizeTransfersCostAndC2FilterFactory( * Then we're going to use minCostFilterChain to determine which of those 2 possibilities is better */ @Override - public OptimizeTransfersFilterChain> createFilter( + public PathTailFilter> createFilter( List>> possibleTransfers ) { - return new OptimizeTransfersCostAndC2FilterChain<>( - (MinCostFilterChain>) new TransferOptimizedFilterFactory( + return new PassThroughPathTailFilter<>( + (MinCostPathTailFilter>) new TransferOptimizedFilterFactory( transferPriority, optimizeWaitTime ) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java index 35b58c53d3f..34a5b4b0855 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java @@ -5,9 +5,9 @@ import java.util.function.ToIntFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public class TransferOptimizedFilterFactory @@ -22,7 +22,7 @@ public TransferOptimizedFilterFactory(boolean transferPriority, boolean optimize } @Override - public OptimizeTransfersFilterChain> createFilter( + public PathTailFilter> createFilter( List>> possibleTransfers ) { List>> filters = new ArrayList<>(3); @@ -39,6 +39,6 @@ public OptimizeTransfersFilterChain> createFilter( filters.add(OptimizedPathTail::breakTieCost); - return new MinCostFilterChain<>(filters); + return new MinCostPathTailFilter<>(filters); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index 076f4e89ad7..10187f0f19f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -4,9 +4,8 @@ import java.util.Set; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizeTransfersFilterChain; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; /** * This class takes a list of transit legs and returns the best leg based on the {@link @@ -29,14 +28,14 @@ */ class TransitPathLegSelector { - private final OptimizeTransfersFilterChain> filter; + private final PathTailFilter> filter; private Set> remindingLegs; private Set> selectedLegs; private int lastLimit = Integer.MAX_VALUE; TransitPathLegSelector( - final OptimizeTransfersFilterChain> filter, + final PathTailFilter> filter, final Set> legs ) { this.filter = filter; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java similarity index 90% rename from src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChainTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java index a9aacbf67b3..6ec6f3c296f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; -class MinCostFilterChainTest { +class MinCostPathTailFilterTest { private final A v01 = new A("A", 0, 1); private final A v10 = new A("B", 1, 0); @@ -22,7 +22,7 @@ static Set setOf(A... as) { @Test void filterEmptySet() { // filter empty set - assertEquals(Set.of(), new MinCostFilterChain(List.of(it -> it.x)).filter(Set.of())); + assertEquals(Set.of(), new MinCostPathTailFilter(List.of(it -> it.x)).filter(Set.of())); } @Test @@ -51,7 +51,7 @@ void filterTwoEqualVectors() { } private Set filter(A... as) { - return new MinCostFilterChain(List.of(it -> it.x, it -> it.y)).filter(setOf(as)); + return new MinCostPathTailFilter(List.of(it -> it.x, it -> it.y)).filter(setOf(as)); } static class A { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index fee23864fcf..86644b79194 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -20,7 +20,7 @@ import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostFilterChain; +import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; public class TransitPathLegSelectorTest implements RaptorTestConstants { @@ -39,7 +39,7 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { null ); - public static final MinCostFilterChain> FILTER_CHAIN = new MinCostFilterChain<>( + public static final MinCostPathTailFilter> FILTER_CHAIN = new MinCostPathTailFilter<>( List.of(OptimizedPathTail::generalizedCost) ); From cf592d7aee1fa0d017fdfae4c3f5d415095178f2 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 16:56:49 +0200 Subject: [PATCH 069/120] refactor: Rename FilterFactory to PathTailFilterFactory --- .../configure/TransferOptimizationServiceConfigurator.java | 6 +++--- .../{FilterFactory.java => PathTailFilterFactory.java} | 2 +- .../services/OptimizePathDomainService.java | 6 +++--- .../services/OptimizeTransfersCostAndC2FilterFactory.java | 4 ++-- .../services/TransferOptimizedFilterFactory.java | 4 ++-- ...ilterFactoryTest.java => PathTailFilterFactoryTest.java} | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{FilterFactory.java => PathTailFilterFactory.java} (79%) rename src/test/java/org/opentripplanner/model/modes/{FilterFactoryTest.java => PathTailFilterFactoryTest.java} (99%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 484a9991316..2afe5da5de9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -9,8 +9,8 @@ import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService; import org.opentripplanner.routing.algorithm.transferoptimization.api.TransferOptimizationParameters; -import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizeTransfersCostAndC2FilterFactory; @@ -107,7 +107,7 @@ private OptimizeTransferService createOptimizeTransferService() { } } - private FilterFactory createFilterFactory() { + private PathTailFilterFactory createFilterFactory() { if (multiCriteriaRequest.hasPassThroughPoints()) { return new OptimizeTransfersCostAndC2FilterFactory<>( config.optimizeTransferPriority(), @@ -124,7 +124,7 @@ private FilterFactory createFilterFactory() { private OptimizePathDomainService createOptimizePathService( TransferGenerator transferGenerator, - FilterFactory filterFactory, + PathTailFilterFactory filterFactory, TransferWaitTimeCostCalculator transferWaitTimeCostCalculator, RaptorCostCalculator costCalculator ) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java similarity index 79% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java index d145dbc0544..ff7d58496d1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -public interface FilterFactory { +public interface PathTailFilterFactory { PathTailFilter> createFilter( List>> possibleTransfers ); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 0678403a73b..c0c1e6b35dc 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -16,9 +16,9 @@ import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; -import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; @@ -72,7 +72,7 @@ public class OptimizePathDomainService { private final TransferGenerator transferGenerator; private final RaptorCostCalculator costCalculator; private final RaptorSlackProvider slackProvider; - private final FilterFactory filterFactory; + private final PathTailFilterFactory filterFactory; private final RaptorStopNameResolver stopNameTranslator; @Nullable @@ -90,7 +90,7 @@ public OptimizePathDomainService( @Nullable TransferWaitTimeCostCalculator waitTimeCostCalculator, int[] stopBoardAlightCosts, double extraStopBoardAlightCostsFactor, - FilterFactory filterFactory, + PathTailFilterFactory filterFactory, RaptorStopNameResolver stopNameTranslator ) { this.transferGenerator = transferGenerator; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java index e8368725351..8ceac092f32 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java @@ -5,15 +5,15 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.path.PathBuilderLeg; -import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PassThroughPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public class OptimizeTransfersCostAndC2FilterFactory - implements FilterFactory { + implements PathTailFilterFactory { private final boolean transferPriority; private final boolean optimizeWaitTime; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java index 34a5b4b0855..fcfd2fa55bf 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java @@ -4,14 +4,14 @@ import java.util.List; import java.util.function.ToIntFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.routing.algorithm.transferoptimization.model.FilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public class TransferOptimizedFilterFactory - implements FilterFactory { + implements PathTailFilterFactory { private final boolean transferPriority; private final boolean optimizeWaitTime; diff --git a/src/test/java/org/opentripplanner/model/modes/FilterFactoryTest.java b/src/test/java/org/opentripplanner/model/modes/PathTailFilterFactoryTest.java similarity index 99% rename from src/test/java/org/opentripplanner/model/modes/FilterFactoryTest.java rename to src/test/java/org/opentripplanner/model/modes/PathTailFilterFactoryTest.java index 72df8256d9f..1431fd8e20a 100644 --- a/src/test/java/org/opentripplanner/model/modes/FilterFactoryTest.java +++ b/src/test/java/org/opentripplanner/model/modes/PathTailFilterFactoryTest.java @@ -13,7 +13,7 @@ import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; -class FilterFactoryTest { +class PathTailFilterFactoryTest { private static final SubMode LOCAL_BUS = SubMode.getOrBuildAndCacheForever("localBus"); private static final SubMode EXPRESS_BUS = SubMode.getOrBuildAndCacheForever("expressBus"); From 8b8566d866de0d65204587180f4036e4d215f70f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:07:43 +0200 Subject: [PATCH 070/120] refactor: Rename OptimizeTransfersCostAndC2FilterFactory to PassThroughFilterFactory --- .../configure/TransferOptimizationServiceConfigurator.java | 4 ++-- ...tAndC2FilterFactory.java => PassThroughFilterFactory.java} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/{OptimizeTransfersCostAndC2FilterFactory.java => PassThroughFilterFactory.java} (98%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 2afe5da5de9..fcec16e87f5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -13,7 +13,7 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; -import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizeTransfersCostAndC2FilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.services.PassThroughFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferServiceAdaptor; @@ -109,7 +109,7 @@ private OptimizeTransferService createOptimizeTransferService() { private PathTailFilterFactory createFilterFactory() { if (multiCriteriaRequest.hasPassThroughPoints()) { - return new OptimizeTransfersCostAndC2FilterFactory<>( + return new PassThroughFilterFactory<>( config.optimizeTransferPriority(), config.optimizeTransferWaitTime(), multiCriteriaRequest.passThroughPoints() diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java similarity index 98% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java index 8ceac092f32..7c75601f43b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizeTransfersCostAndC2FilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java @@ -12,14 +12,14 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -public class OptimizeTransfersCostAndC2FilterFactory +public class PassThroughFilterFactory implements PathTailFilterFactory { private final boolean transferPriority; private final boolean optimizeWaitTime; private final List passThroughPoints; - public OptimizeTransfersCostAndC2FilterFactory( + public PassThroughFilterFactory( boolean transferPriority, boolean optimizeWaitTime, List passThroughPoints From 025e7831e785fe6057cad139d762c7dc49cbb1f7 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:08:53 +0200 Subject: [PATCH 071/120] refactor: Move pass-though specific code to its own package --- .../configure/TransferOptimizationServiceConfigurator.java | 2 +- .../passthrough}/PassThroughFilterFactory.java | 4 ++-- .../model/{ => passthrough}/PassThroughPathTailFilter.java | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/{services => model/passthrough}/PassThroughFilterFactory.java (99%) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{ => passthrough}/PassThroughPathTailFilter.java (84%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index fcec16e87f5..002b3788f43 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -12,8 +12,8 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.PassThroughFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; -import org.opentripplanner.routing.algorithm.transferoptimization.services.PassThroughFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferServiceAdaptor; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java similarity index 99% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index 7c75601f43b..5f20a893562 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.services; +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; import java.util.ArrayList; import java.util.List; @@ -7,10 +7,10 @@ import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PassThroughPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; +import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; public class PassThroughFilterFactory implements PathTailFilterFactory { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java similarity index 84% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index 13e79470f8d..c3ba575f40f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.model; +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; import static java.util.stream.Collectors.toSet; @@ -7,6 +7,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; public class PassThroughPathTailFilter implements PathTailFilter { From 85f1d014cf8f706530db021344f8ede95ffe82ee Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:16:42 +0200 Subject: [PATCH 072/120] doc: Fix reference in doc --- .../services/TransitPathLegSelector.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index 10187f0f19f..b927ee96c6d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -9,8 +9,9 @@ /** * This class takes a list of transit legs and returns the best leg based on the {@link - * TransferOptimizedFilterFactory} and the earliest-boarding-time. The filter is used to pick the - * best leg from the legs which can be boarded after the earliest-boarding-time. + * org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter} and + * the earliest-boarding-time. The filter is used to pick the best leg from the legs which can be + * boarded after the earliest-boarding-time. *

* HOW IT WORKS *

From 1e7ad2920ee97aead89527b28a37dc265b777d40 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:17:16 +0200 Subject: [PATCH 073/120] refactor: PassThroughPathTailFilter does not need to be public --- .../model/passthrough/PassThroughPathTailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index c3ba575f40f..42494d5fe33 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -10,7 +10,7 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -public class PassThroughPathTailFilter implements PathTailFilter { +class PassThroughPathTailFilter implements PathTailFilter { private final MinCostPathTailFilter filterChain; private final Function getC2; From df177cc2eebe629136e6304d6181f64404e9ef5d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:20:00 +0200 Subject: [PATCH 074/120] refactor: MinCostPathTailFilter down to costfilter package --- .../model/{ => costfilter}/MinCostPathTailFilter.java | 3 ++- .../model/passthrough/PassThroughFilterFactory.java | 2 +- .../model/passthrough/PassThroughPathTailFilter.java | 2 +- .../services/TransferOptimizedFilterFactory.java | 2 +- .../transferoptimization/model/MinCostPathTailFilterTest.java | 1 + .../services/TransitPathLegSelectorTest.java | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{ => costfilter}/MinCostPathTailFilter.java (93%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java similarity index 93% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java index 68971227c13..3dea560563f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java @@ -1,9 +1,10 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.model; +package org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.ToIntFunction; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; /** * This class takes a list of "cost functions" and creates a filter chain for them. The precedence diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index 5f20a893562..57ca6ebc282 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -5,11 +5,11 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.path.PathBuilderLeg; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; public class PassThroughFilterFactory diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index 42494d5fe33..728d3003123 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -7,8 +7,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; class PassThroughPathTailFilter implements PathTailFilter { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java index fcfd2fa55bf..e6d5117c2ed 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.function.ToIntFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; public class TransferOptimizedFilterFactory implements PathTailFilterFactory { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java index 6ec6f3c296f..55f4fdbb55c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java @@ -7,6 +7,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; class MinCostPathTailFilterTest { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index 86644b79194..f92cf69839d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -20,8 +20,8 @@ import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.MinCostPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; public class TransitPathLegSelectorTest implements RaptorTestConstants { From 7e883ca0dbb25abd6550ad91e8b1c79d684a9642 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:23:08 +0200 Subject: [PATCH 075/120] refactor: Move TransferOptimizedFilterFactory to model.costfilter package --- .../configure/TransferOptimizationServiceConfigurator.java | 2 +- .../costfilter}/TransferOptimizedFilterFactory.java | 3 +-- .../model/passthrough/PassThroughFilterFactory.java | 2 +- .../model/{ => costfilter}/MinCostPathTailFilterTest.java | 3 +-- .../services/OptimizePathDomainServiceTest.java | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/{services => model/costfilter}/TransferOptimizedFilterFactory.java (92%) rename src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/{ => costfilter}/MinCostPathTailFilterTest.java (95%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 002b3788f43..14789e1327b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -12,10 +12,10 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.PassThroughFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; -import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferServiceAdaptor; import org.opentripplanner.transit.model.site.StopLocation; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java similarity index 92% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java index e6d5117c2ed..3c5b81f946c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.services; +package org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter; import java.util.ArrayList; import java.util.List; @@ -8,7 +8,6 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; public class TransferOptimizedFilterFactory implements PathTailFilterFactory { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index 57ca6ebc282..e0d62c16953 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -10,7 +10,7 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferOptimizedFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; public class PassThroughFilterFactory implements PathTailFilterFactory { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java similarity index 95% rename from src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java index 55f4fdbb55c..1e6dd833768 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.model; +package org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,7 +7,6 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; class MinCostPathTailFilterTest { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index 1aee0cdeb5e..a2919be3bee 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -8,7 +8,6 @@ import java.util.Collection; import java.util.List; -import java.util.Optional; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; import org.opentripplanner.raptor._data.RaptorTestConstants; @@ -20,6 +19,7 @@ import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; public class OptimizePathDomainServiceTest implements RaptorTestConstants { From e89156cb256f1a29e2b6d86fd8f71bdbc1125bcf Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:51:41 +0200 Subject: [PATCH 076/120] refactor: Protect MinCostPathTailFilter by making it package local --- ...ansferOptimizationServiceConfigurator.java | 16 +++++++------- .../costfilter/MinCostPathTailFilter.java | 4 ++-- .../TransferOptimizedFilterFactory.java | 10 +++++++++ .../passthrough/PassThroughFilterFactory.java | 21 ++++++------------- .../PassThroughPathTailFilter.java | 8 ++----- .../services/TransitPathLegSelectorTest.java | 8 +++---- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 14789e1327b..a7d92e0c96e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -108,17 +108,15 @@ private OptimizeTransferService createOptimizeTransferService() { } private PathTailFilterFactory createFilterFactory() { + PathTailFilterFactory costFilter = new TransferOptimizedFilterFactory<>( + config.optimizeTransferPriority(), + config.optimizeTransferWaitTime() + ); + if (multiCriteriaRequest.hasPassThroughPoints()) { - return new PassThroughFilterFactory<>( - config.optimizeTransferPriority(), - config.optimizeTransferWaitTime(), - multiCriteriaRequest.passThroughPoints() - ); + return new PassThroughFilterFactory<>(multiCriteriaRequest.passThroughPoints(), costFilter); } else { - return new TransferOptimizedFilterFactory<>( - config.optimizeTransferPriority(), - config.optimizeTransferWaitTime() - ); + return costFilter; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java index 3dea560563f..2757f68b795 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java @@ -18,11 +18,11 @@ * * @param The element type of the cost-functions and the filtered list */ -public class MinCostPathTailFilter implements PathTailFilter { +class MinCostPathTailFilter implements PathTailFilter { private final List> costFunctions; - public MinCostPathTailFilter(List> costFunctions) { + MinCostPathTailFilter(List> costFunctions) { this.costFunctions = costFunctions; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java index 3c5b81f946c..91e32df4d2f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java @@ -40,4 +40,14 @@ public PathTailFilter> createFilter( return new MinCostPathTailFilter<>(filters); } + + /** + * This factory method is used for unit testing. It allows you to pass in a simple cost function + * instead of the more complicated functions used in the main version of this. + */ + public static PathTailFilter> ofCostFunction( + ToIntFunction> costFunction + ) { + return new MinCostPathTailFilter<>(List.of(costFunction)); + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index e0d62c16953..c2d799b9b43 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -9,24 +9,19 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; public class PassThroughFilterFactory implements PathTailFilterFactory { - private final boolean transferPriority; - private final boolean optimizeWaitTime; private final List passThroughPoints; + private final PathTailFilterFactory delegate; public PassThroughFilterFactory( - boolean transferPriority, - boolean optimizeWaitTime, - List passThroughPoints + List passThroughPoints, + PathTailFilterFactory delegate ) { - this.transferPriority = transferPriority; - this.optimizeWaitTime = optimizeWaitTime; this.passThroughPoints = passThroughPoints; + this.delegate = delegate; } /** @@ -60,12 +55,8 @@ public PassThroughFilterFactory( public PathTailFilter> createFilter( List>> possibleTransfers ) { - return new PassThroughPathTailFilter<>( - (MinCostPathTailFilter>) new TransferOptimizedFilterFactory( - transferPriority, - optimizeWaitTime - ) - .createFilter(possibleTransfers), + return new PassThroughPathTailFilter>( + delegate.createFilter(possibleTransfers), tail -> this.calculateC2(tail, possibleTransfers, passThroughPoints) ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index 728d3003123..4990db2513d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -8,17 +8,13 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; class PassThroughPathTailFilter implements PathTailFilter { - private final MinCostPathTailFilter filterChain; + private final PathTailFilter filterChain; private final Function getC2; - public PassThroughPathTailFilter( - MinCostPathTailFilter filterChain, - Function getC2 - ) { + public PassThroughPathTailFilter(PathTailFilter filterChain, Function getC2) { this.filterChain = filterChain; this.getC2 = getC2; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index f92cf69839d..e1bda7fc0d5 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; -import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; @@ -21,7 +20,8 @@ import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; public class TransitPathLegSelectorTest implements RaptorTestConstants { @@ -39,8 +39,8 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { null ); - public static final MinCostPathTailFilter> FILTER_CHAIN = new MinCostPathTailFilter<>( - List.of(OptimizedPathTail::generalizedCost) + public static final PathTailFilter> FILTER_CHAIN = TransferOptimizedFilterFactory.ofCostFunction( + OptimizedPathTail::generalizedCost ); private final int T10_00 = TimeUtils.time("10:00"); From 6b881e3069b66b60e41a637d2d28220fda5d4e49 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 17:52:55 +0200 Subject: [PATCH 077/120] refactor: Rename TransferOptimizedFilterFactory to MinCostPathTailFilterFactory --- .../configure/TransferOptimizationServiceConfigurator.java | 4 ++-- ...edFilterFactory.java => MinCostPathTailFilterFactory.java} | 4 ++-- .../services/OptimizePathDomainServiceTest.java | 4 ++-- .../services/TransitPathLegSelectorTest.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/{TransferOptimizedFilterFactory.java => MinCostPathTailFilterFactory.java} (91%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index a7d92e0c96e..1afd291cfcd 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -12,7 +12,7 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.PassThroughFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; @@ -108,7 +108,7 @@ private OptimizeTransferService createOptimizeTransferService() { } private PathTailFilterFactory createFilterFactory() { - PathTailFilterFactory costFilter = new TransferOptimizedFilterFactory<>( + PathTailFilterFactory costFilter = new MinCostPathTailFilterFactory<>( config.optimizeTransferPriority(), config.optimizeTransferWaitTime() ); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java similarity index 91% rename from src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java rename to src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java index 91e32df4d2f..254a1aa1585 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/TransferOptimizedFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java @@ -9,13 +9,13 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -public class TransferOptimizedFilterFactory +public class MinCostPathTailFilterFactory implements PathTailFilterFactory { private final boolean transferPriority; private final boolean optimizeWaitTime; - public TransferOptimizedFilterFactory(boolean transferPriority, boolean optimizeWaitTime) { + public MinCostPathTailFilterFactory(boolean transferPriority, boolean optimizeWaitTime) { this.transferPriority = transferPriority; this.optimizeWaitTime = optimizeWaitTime; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index a2919be3bee..f6be59ab59e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -19,7 +19,7 @@ import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilterFactory; public class OptimizePathDomainServiceTest implements RaptorTestConstants { @@ -295,7 +295,7 @@ static OptimizePathDomainService subject( waitTimeCalculator, null, 0.0, - new TransferOptimizedFilterFactory<>(true, waitTimeCalculator != null), + new MinCostPathTailFilterFactory<>(true, waitTimeCalculator != null), (new RaptorTestConstants() {})::stopIndexToName ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index e1bda7fc0d5..9e5d49dae2f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -21,7 +21,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.TransferOptimizedFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilterFactory; public class TransitPathLegSelectorTest implements RaptorTestConstants { @@ -39,7 +39,7 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { null ); - public static final PathTailFilter> FILTER_CHAIN = TransferOptimizedFilterFactory.ofCostFunction( + public static final PathTailFilter> FILTER_CHAIN = MinCostPathTailFilterFactory.ofCostFunction( OptimizedPathTail::generalizedCost ); From 6c088196a7d51e6085099316a2f1bb4293bf0eb9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 18:31:45 +0200 Subject: [PATCH 078/120] refactor: Extract c2 calculation logic from PassThroughFilterFactory and cleanup JavaDoc --- .../passthrough/PassThroughFilterFactory.java | 156 +++++------------- .../passthrough/PathTailC2Calculator.java | 107 ++++++++++++ 2 files changed, 145 insertions(+), 118 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index c2d799b9b43..103becc5c43 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -1,15 +1,48 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; -import java.util.ArrayList; import java.util.List; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; +/** + * Create a filer chain function and find the best transfers combination for journey that also + * includes all pass-through points. + *

+ * The algorithm starts with the last trip in the journey, then goes backwards looping through all + * possible transfers for each transit leg. For each possible transfer stop position C2 value is + * calculated. The filter chain function is going to use c2 value and cost function to determine + * whether the tail should be included or excluded from result. + *

+ *Example: + *

+ * Let's say we have a trip with 2 transit legs and 3 possible transfer points: AD, BE and CF. + *

+ * There are 3 possible transfer combination with the first and second transit: + *

+ *    Iteration 1 (initial c2 value is 1 since we have one pass-through point):
+ *
+ *      ? ~ transit 2 ~ egress | c2 = 1
+ *
+ *    Iteration 2 (create all possible journey combinations with transfers and calculate c2):
+ *
+ *      // C2 is 0 since we will pass through E if we board Transit 2 at D
+ *      ? ~ transit 1 ~ AD ~ Transit 2 ~ egress | c2 = 0
+ *
+ *      ? ~ transit 1 ~ BE ~ Transit 2 ~ egress | c2 = 0
+ *
+ *      // C2 is 1 since we will not pass through E if we board at F
+ *      ? ~ transit 1 ~ CF ~ Transit 2 ~ egress | c2 = 1
+ *
+ *    Iteration 3 (insert access and filter out all combinations where c2 != 0)
+ *      access ~ transit 1 ~ AD ~ transit 2 ~ egress | C2 = 0
+ *      access ~ transit 1 ~ BE ~ transit 2 ~ egress | C2 = 0
+ * 
+ * Then we're going to fall back the delegate filter to choose between the two options. + */ public class PassThroughFilterFactory implements PathTailFilterFactory { @@ -24,127 +57,14 @@ public PassThroughFilterFactory( this.delegate = delegate; } - /** - * Create a filer chain function and find the best transfers combination for journey that also - * includes all pass-through points - * Algorithm starts with the last trip in the journey, then goes backwards looping through all - * possible transfers for each transit leg. - * For each possible transfer stop position C2 value is calculated. Filter chain function is going - * to use c2 value and cost function to determine whether tail should be included or excluded from - * the collection. - * - * Example: - * Let's say we have a trip with 2 transit legs and the pass-through point is (B) - * There are 3 possible transfer combination with first and second transit - * - * Iteration 1 (initial c2 value is 1 since we have one pass-through point): - * (?) Transit 2 -> (Egress) | C2 = 1 - * - * Iteration 2 (create all possible journey combinations with transfers and calculate c2): - * (?) Transit 1 -> (A) Transit 2 -> (Egress) | C2 = 0 <- C2 is 0 since we will pass through (B) if we board here - * (?) Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 - * (?) Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 1 <- C2 is 1 since we will not pass through (B) if we board here - * - * Iteration 3 (insert access and filter out all combinations where c2 != 0) - * (Access) -> Transit 1 -> (B) Transit 2 -> (Egress) | C2 = 0 - * (Access) -> Transit 1 -> (C) Transit 2 -> (Egress) | C2 = 0 - * - * Then we're going to use minCostFilterChain to determine which of those 2 possibilities is better - */ @Override public PathTailFilter> createFilter( List>> possibleTransfers ) { - return new PassThroughPathTailFilter>( + var calculator = new PathTailC2Calculator<>(possibleTransfers, passThroughPoints); + return new PassThroughPathTailFilter<>( delegate.createFilter(possibleTransfers), - tail -> this.calculateC2(tail, possibleTransfers, passThroughPoints) + calculator::calculateC2 ); } - - /** - * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 - * value given position. - * - * @param tail Current OptimizedPathTail - * @param possibleTransfers List all possible transfers for the whole journey - * @param passThroughPoints Pass-through points for the search - * @return c2 value for the earliest possible boarding position. - */ - private int calculateC2( - OptimizedPathTail tail, - List>> possibleTransfers, - List passThroughPoints - ) { - var legs = new ArrayList<>(tail.legsAsStream().toList()); - - var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); - - int c2; - if (transits.size() == 1) { - // That's the last transit in the journey we are checking. There can't be any via stops yet. - // This should be via.size - // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can - // set initial C2 value. That's why I added size() method to PassThroughPoints - c2 = passThroughPoints.size(); - } else { - // Check c2 value on board position from previous transit leg. - var previousTransit = transits.get(1); - c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); - } - - var transitLeg = transits.get(0); - int fromStopPosition; - if (possibleTransfers.size() + 1 == transits.size()) { - // Here we reached the first transit in the journey. - // We do not have to check possible transfers. - fromStopPosition = transitLeg.fromStopPos(); - } else { - // This should be the earliest possible board stop position for a leg. - // We can verify that with transfers. - fromStopPosition = - possibleTransfers - .get(possibleTransfers.size() - transits.size()) - .stream() - .map(t -> t.to().stopPosition()) - .reduce((first, second) -> first < second ? first : second) - .get(); - } - - // TODO: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. - // The value is gonna persist into the next loop so that we know what c2 value we had - // on previous (next) leg - // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting - // the whole filter logic. - // We already visited all via stops. - // Don't have to check anything. Just set on all stop positions. - if (c2 == 0) { - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - transitLeg.setC2OnStopPosition(pos, 0); - } - } else { - // Loop through all possible boarding position and calculate potential c2 value - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - var stopIndex = transitLeg.trip().pattern().stopIndex(pos); - // TODO: 2023-09-01 We need to check here whether point is a pass through for a given - // c2 value. That's why I need to include this method in PassThroughPoint class. - // This might not be the best solution. What are the other options we have? - if (isPassThroughPoint(passThroughPoints, c2, stopIndex)) { - c2--; - } - - transitLeg.setC2OnStopPosition(pos, c2); - } - } - - return transitLeg.c2ForStopPosition(fromStopPosition); - } - - private boolean isPassThroughPoint( - List passThroughPoints, - int c2, - int stopIndex - ) { - var point = passThroughPoints.get(c2 - 1); - return point != null && point.asBitSet().get(stopIndex); - } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java new file mode 100644 index 00000000000..6dfc536997f --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java @@ -0,0 +1,107 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import java.util.ArrayList; +import java.util.List; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.path.PathBuilderLeg; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; + +class PathTailC2Calculator { + + private final List>> possibleTransfers; + private final List passThroughPoints; + + /** + * @param possibleTransfers List all possible transfers for the whole journey + * @param passThroughPoints Pass-through points for the search + */ + public PathTailC2Calculator( + List>> possibleTransfers, + List passThroughPoints + ) { + this.possibleTransfers = possibleTransfers; + this.passThroughPoints = passThroughPoints; + } + + /** + * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 + * value given position. + * + * @param tail Current OptimizedPathTail + * @return c2 value for the earliest possible boarding position. + */ + int calculateC2(OptimizedPathTail tail) { + var legs = new ArrayList<>(tail.legsAsStream().toList()); + + var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); + + int c2; + if (transits.size() == 1) { + // That's the last transit in the journey we are checking. There can't be any via stops yet. + // This should be via.size + // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can + // set initial C2 value. That's why I added size() method to PassThroughPoints + c2 = passThroughPoints.size(); + } else { + // Check c2 value on board position from previous transit leg. + var previousTransit = transits.get(1); + c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); + } + + var transitLeg = transits.get(0); + int fromStopPosition; + if (possibleTransfers.size() + 1 == transits.size()) { + // Here we reached the first transit in the journey. + // We do not have to check possible transfers. + fromStopPosition = transitLeg.fromStopPos(); + } else { + // This should be the earliest possible board stop position for a leg. + // We can verify that with transfers. + fromStopPosition = + possibleTransfers + .get(possibleTransfers.size() - transits.size()) + .stream() + .map(t -> t.to().stopPosition()) + .reduce((first, second) -> first < second ? first : second) + .get(); + } + + // TODO: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. + // The value is gonna persist into the next loop so that we know what c2 value we had + // on previous (next) leg + // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting + // the whole filter logic. + // We already visited all via stops. + // Don't have to check anything. Just set on all stop positions. + if (c2 == 0) { + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + transitLeg.setC2OnStopPosition(pos, 0); + } + } else { + // Loop through all possible boarding position and calculate potential c2 value + for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { + var stopIndex = transitLeg.trip().pattern().stopIndex(pos); + // TODO: 2023-09-01 We need to check here whether point is a pass through for a given + // c2 value. That's why I need to include this method in PassThroughPoint class. + // This might not be the best solution. What are the other options we have? + if (isPassThroughPoint(passThroughPoints, c2, stopIndex)) { + c2--; + } + + transitLeg.setC2OnStopPosition(pos, c2); + } + } + return transitLeg.c2ForStopPosition(fromStopPosition); + } + + private boolean isPassThroughPoint( + List passThroughPoints, + int c2, + int stopIndex + ) { + var point = passThroughPoints.get(c2 - 1); + return point != null && point.asBitSet().get(stopIndex); + } +} From 6b3ab2893ceb8628cd3cee5472bcaa026ffa7147 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 6 Oct 2023 18:41:27 +0200 Subject: [PATCH 079/120] refactor: Rename filter methods to reflect their place in the algorithm --- .../transferoptimization/model/PathTailFilter.java | 4 ++-- .../model/costfilter/MinCostPathTailFilter.java | 4 ++-- .../model/passthrough/PassThroughPathTailFilter.java | 6 +++--- .../services/OptimizePathDomainService.java | 2 +- .../services/TransitPathLegSelector.java | 2 +- .../model/costfilter/MinCostPathTailFilterTest.java | 8 ++++++-- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 1fee07d1de9..4f86ce50c1d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -9,9 +9,9 @@ public interface PathTailFilter { /** * TODO PT - This need JavaDoc */ - Set filter(Set elements); + Set filterIntermediateResult(Set elements); /** * TODO PT - This need JavaDoc */ - Set finalizeFilter(Set elements); + Set filterFinalResult(Set elements); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java index 2757f68b795..33576433b3a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java @@ -27,7 +27,7 @@ class MinCostPathTailFilter implements PathTailFilter { } @Override - public Set filter(Set elements) { + public Set filterIntermediateResult(Set elements) { for (var costFunction : costFunctions) { elements = filter(elements, costFunction); } @@ -53,7 +53,7 @@ private Set filter(Set elements, ToIntFunction costFunction) { } @Override - public Set finalizeFilter(Set elements) { + public Set filterFinalResult(Set elements) { return elements; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index 4990db2513d..d1e7684e586 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -20,19 +20,19 @@ public PassThroughPathTailFilter(PathTailFilter filterChain, Function filter(Set elements) { + public Set filterIntermediateResult(Set elements) { Map> elementsByC2Value = elements .stream() .collect(Collectors.groupingBy(getC2, toSet())); Set result = new HashSet<>(); for (Integer c2 : elementsByC2Value.keySet()) { - result.addAll(filterChain.filter(elementsByC2Value.get(c2))); + result.addAll(filterChain.filterIntermediateResult(elementsByC2Value.get(c2))); } return result; } @Override - public Set finalizeFilter(Set elements) { + public Set filterFinalResult(Set elements) { return elements.stream().filter(tail -> getC2.apply(tail) == 0).collect(toSet()); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index c0c1e6b35dc..c68f8d19e8d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -111,7 +111,7 @@ public Set> findBestTransitPath(RaptorPath originalPath) { // Combine transit legs and transfers var tails = findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter); - var filteredTails = filter.finalizeFilter(tails); + var filteredTails = filter.filterFinalResult(tails); return filteredTails.stream().map(OptimizedPathTail::build).collect(toSet()); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index b927ee96c6d..4a5c3fe3099 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -75,7 +75,7 @@ Set> next(final int earliestBoardingTime) { // Set state remindingLegs = rest; - selectedLegs = filter.filter(candidates); + selectedLegs = filter.filterIntermediateResult(candidates); return selectedLegs; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java index 1e6dd833768..1beec0b89c3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java @@ -22,7 +22,10 @@ static Set setOf(A... as) { @Test void filterEmptySet() { // filter empty set - assertEquals(Set.of(), new MinCostPathTailFilter(List.of(it -> it.x)).filter(Set.of())); + assertEquals( + Set.of(), + new MinCostPathTailFilter(List.of(it -> it.x)).filterIntermediateResult(Set.of()) + ); } @Test @@ -51,7 +54,8 @@ void filterTwoEqualVectors() { } private Set filter(A... as) { - return new MinCostPathTailFilter(List.of(it -> it.x, it -> it.y)).filter(setOf(as)); + return new MinCostPathTailFilter(List.of(it -> it.x, it -> it.y)) + .filterIntermediateResult(setOf(as)); } static class A { From 853893c7c4fb99a660cab5ceb183d9027bea8c48 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 10 Oct 2023 15:12:44 +0200 Subject: [PATCH 080/120] refactor: TestTransferBuilder so it becomes easier to reuse in pass-through tests later --- .../raptor/_data/RaptorTestConstants.java | 3 + .../model/OptimizedPathTailTest.java | 22 ++- ...imizePathDomainServiceConstrainedTest.java | 20 +-- .../OptimizePathDomainServiceTest.java | 19 +- .../services/TestTransferBuilder.java | 168 ++++++++++++++---- .../services/TransferGeneratorDummy.java | 83 --------- 6 files changed, 169 insertions(+), 146 deletions(-) diff --git a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java index e94cca13d62..ffc7ce17059 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java +++ b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java @@ -52,6 +52,9 @@ public interface RaptorTestConstants { int STOP_G = 7; int STOP_H = 8; int STOP_I = 9; + int STOP_J = 10; + int STOP_K = 11; + int STOP_L = 12; // Stop position in pattern int STOP_POS_0 = 0; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java index af643d9810c..bd4e5e12c38 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTailTest.java @@ -1,7 +1,6 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder.txConstrained; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,7 +9,7 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.api.path.TransitPathLeg; -import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy; +import org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder; class OptimizedPathTailTest implements RaptorTestConstants { @@ -27,17 +26,16 @@ class OptimizedPathTailTest implements RaptorTestConstants { private final TransitPathLeg t3 = t2.nextTransitLeg(); @SuppressWarnings("ConstantConditions") - private final TripToTripTransfer tx23 = TransferGeneratorDummy.tx( - txConstrained(t2.trip(), STOP_D, t3.trip(), STOP_D).staySeated() - ); + private final TripToTripTransfer tx23 = TestTransferBuilder + .tx(t2.trip(), STOP_D, t3.trip(), STOP_D) + .staySeated() + .build(); + + private final TripToTripTransfer tx12 = TestTransferBuilder + .tx(t1.trip(), STOP_B, t2.trip(), STOP_C) + .walk(D2m) + .build(); - private final TripToTripTransfer tx12 = TransferGeneratorDummy.tx( - t1.trip(), - STOP_B, - D2m, - STOP_C, - t2.trip() - ); private final TransferWaitTimeCostCalculator waitTimeCalc = new TransferWaitTimeCostCalculator( 1.0, 5.0 diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java index a85cf2e699d..d6725f4a425 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceConstrainedTest.java @@ -7,9 +7,7 @@ import static org.opentripplanner.model.transfer.TransferPriority.NOT_ALLOWED; import static org.opentripplanner.model.transfer.TransferPriority.PREFERRED; import static org.opentripplanner.model.transfer.TransferPriority.RECOMMENDED; -import static org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder.txConstrained; import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.dummyTransferGenerator; -import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.tx; import java.util.List; import org.junit.jupiter.api.Test; @@ -65,15 +63,15 @@ public class OptimizePathDomainServiceConstrainedTest implements RaptorTestConst TransferGenerator transfers = dummyTransferGenerator( List.of( - tx(txConstrained(trip1, STOP_B, trip2, STOP_C).priority(ALLOWED), D1m), - tx(txConstrained(trip1, STOP_C, trip2, STOP_D).priority(RECOMMENDED), D2m), - tx(txConstrained(trip1, STOP_D, trip2, STOP_E).priority(PREFERRED), D3m), - tx(txConstrained(trip1, STOP_E, trip2, STOP_F).guaranteed(), D4m), - tx(txConstrained(trip1, STOP_F, trip2, STOP_G).staySeated(), D5m), - tx(txConstrained(trip1, STOP_C, trip2, STOP_C).priority(NOT_ALLOWED)), - tx(txConstrained(trip1, STOP_D, trip2, STOP_D).priority(NOT_ALLOWED)), - tx(txConstrained(trip1, STOP_E, trip2, STOP_E).priority(NOT_ALLOWED)), - tx(txConstrained(trip1, STOP_F, trip2, STOP_F).priority(NOT_ALLOWED)) + TestTransferBuilder.tx(trip1, STOP_B, trip2, STOP_C).priority(ALLOWED).walk(D1m).build(), + TestTransferBuilder.tx(trip1, STOP_C, trip2, STOP_D).priority(RECOMMENDED).walk(D2m).build(), + TestTransferBuilder.tx(trip1, STOP_D, trip2, STOP_E).priority(PREFERRED).walk(D3m).build(), + TestTransferBuilder.tx(trip1, STOP_E, trip2, STOP_F).guaranteed().walk(D4m).build(), + TestTransferBuilder.tx(trip1, STOP_F, trip2, STOP_G).staySeated().walk(D5m).build(), + TestTransferBuilder.tx(trip1, STOP_C, trip2, STOP_C).priority(NOT_ALLOWED).build(), + TestTransferBuilder.tx(trip1, STOP_D, trip2, STOP_D).priority(NOT_ALLOWED).build(), + TestTransferBuilder.tx(trip1, STOP_E, trip2, STOP_E).priority(NOT_ALLOWED).build(), + TestTransferBuilder.tx(trip1, STOP_F, trip2, STOP_F).priority(NOT_ALLOWED).build() ) ); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index f6be59ab59e..86f92fc4be5 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -2,9 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; -import static org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder.txConstrained; +import static org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder.tx; import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.dummyTransferGenerator; -import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.tx; import java.util.Collection; import java.util.List; @@ -113,7 +112,9 @@ public void testTripWithOneTransfer() { .times("10:12 10:22 10:50") .build(); - var transfers = dummyTransferGenerator(List.of(tx(trip1, STOP_C, D30s, STOP_F, trip2))); + var transfers = dummyTransferGenerator( + List.of(tx(trip1, STOP_C, trip2, STOP_F).walk(D30s).build()) + ); // Path: Access ~ B ~ T1 ~ C ~ Walk 30s ~ D ~ T2 ~ E ~ Egress var original = pathBuilder() @@ -169,11 +170,11 @@ public void testPathWithThreeTripsAndMultiplePlacesToTransfer() { var transfers = dummyTransferGenerator( List.of( - tx(trip1, STOP_B, trip2), - tx(trip1, STOP_B, D30s, STOP_C, trip2), - tx(trip1, STOP_D, trip2) + tx(trip1, STOP_B, trip2).build(), + tx(trip1, STOP_B, trip2, STOP_C).walk(D30s).build(), + tx(trip1, STOP_D, trip2).build() ), - List.of(tx(trip2, STOP_D, D30s, STOP_E, trip3), tx(trip2, STOP_F, trip3)) + List.of(tx(trip2, STOP_D, trip3, STOP_E).walk(D30s).build(), tx(trip2, STOP_F, trip3).build()) ); var original = pathBuilder() @@ -244,8 +245,8 @@ public void testConstrainedTransferIsPreferred() { var transfers = dummyTransferGenerator( List.of( - tx(trip1, STOP_B, trip2), - tx(txConstrained(trip1, STOP_C, trip2, STOP_C).guaranteed()) + tx(trip1, STOP_B, trip2).build(), + tx(trip1, STOP_C, trip2, STOP_C).guaranteed().build() ) ); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java index 382d81df4e3..ec29eb04cb0 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java @@ -1,11 +1,16 @@ package org.opentripplanner.routing.algorithm.transferoptimization.services; +import java.util.Objects; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.transfer.ConstrainedTransfer; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.model.transfer.TransferPriority; import org.opentripplanner.model.transfer.TripTransferPoint; +import org.opentripplanner.raptor._data.transit.TestTransfer; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripStopTime; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.timetable.Trip; @@ -16,17 +21,37 @@ @SuppressWarnings("UnusedReturnValue") public class TestTransferBuilder { - private final T fromTrip; - private final int fromStopIndex; - private final T toTrip; - private final int toStopIndex; - private final TransferConstraint.Builder constraint = TransferConstraint.of(); + private T fromTrip; + private int fromStopIndex = RaptorConstants.NOT_SET; + private T toTrip; + private int toStopIndex = RaptorConstants.NOT_SET; - private TestTransferBuilder(T fromTrip, int fromStopIndex, T toTrip, int toStopIndex) { - this.fromTrip = fromTrip; - this.fromStopIndex = fromStopIndex; - this.toTrip = toTrip; - this.toStopIndex = toStopIndex; + // We set the default walk time to zero - it is not relevant for many tests and zero is easy + private int walkDurationSec = 0; + private TransferConstraint.Builder constraint = null; + + public static TestTransferBuilder tx(T fromTrip, T toTrip) { + return new TestTransferBuilder().fromTrip(fromTrip).toTrip(toTrip); + } + + /** + * Set all required parameter for a transfer. The walk duration is set to zero. + */ + public static TestTransferBuilder tx( + T fromTrip, + int fromStopIndex, + T toTrip, + int toStopIndex + ) { + return tx(fromTrip, toTrip).fromStopIndex(fromStopIndex).toStopIndex(toStopIndex); + } + + public static TestTransferBuilder tx( + T fromTrip, + int sameStopIndex, + T toTrip + ) { + return tx(fromTrip, sameStopIndex, toTrip, sameStopIndex); } public static TestTransferBuilder txConstrained( @@ -35,53 +60,109 @@ public static TestTransferBuilder txConstraine T toTrip, int toStopIndex ) { - return new TestTransferBuilder<>(fromTrip, fromStopIndex, toTrip, toStopIndex); + var builder = tx(fromTrip, fromStopIndex, toTrip, toStopIndex); + // Make sure the constraint is initialized; hence an object generated in the build step. + // If none of the constraints are set this still generates a constraint instance, witch + // should behave like a regular transfer, but is not the same structure. + builder.constraint(); + return builder; } - public T getFromTrip() { + public T fromTrip() { return fromTrip; } - public int getFromStopIndex() { + public TestTransferBuilder fromTrip(T fromTrip) { + this.fromTrip = fromTrip; + return this; + } + + public int fromStopIndex() { return fromStopIndex; } - public T getToTrip() { + public TestTransferBuilder fromStopIndex(int fromStopIndex) { + this.fromStopIndex = fromStopIndex; + return this; + } + + public T toTrip() { return toTrip; } - public int getToStopIndex() { + public TestTransferBuilder toTrip(T toTrip) { + this.toTrip = toTrip; + return this; + } + + public int toStopIndex() { return toStopIndex; } + public TestTransferBuilder toStopIndex(int toStopIndex) { + this.toStopIndex = toStopIndex; + return this; + } + + /** + * Walk duration in seconds + */ + public int walk() { + return walkDurationSec; + } + + public TestTransferBuilder walk(int walkDurationSec) { + this.walkDurationSec = walkDurationSec; + return this; + } + public TestTransferBuilder staySeated() { - this.constraint.staySeated(); + this.constraint().staySeated(); return this; } public TestTransferBuilder guaranteed() { - this.constraint.guaranteed(); + this.constraint().guaranteed(); return this; } public TestTransferBuilder maxWaitTime(int maxWaitTime) { - this.constraint.maxWaitTime(maxWaitTime); + this.constraint().maxWaitTime(maxWaitTime); return this; } public TestTransferBuilder priority(TransferPriority priority) { - this.constraint.priority(priority); + this.constraint().priority(priority); return this; } - public ConstrainedTransfer build() { - if (fromTrip == null) { - throw new NullPointerException(); - } - if (toTrip == null) { - throw new NullPointerException(); - } + public TripToTripTransfer build() { + validateFromTo(); + validateWalkDurationSec(); + + var pathTransfer = fromStopIndex == toStopIndex + ? null + : TestTransfer.transfer(toStopIndex, walkDurationSec); + return new TripToTripTransfer<>( + departure(fromTrip, fromStopIndex), + arrival(toTrip, toStopIndex), + pathTransfer, + buildConstrainedTransfer() + ); + } + + private static Trip createDummyTrip(T trip) { + // Set an uniq id: pattern + the first stop departure time + return TransitModelForTest + .trip(trip.pattern().debugInfo() + ":" + TimeUtils.timeToStrCompact(trip.departure(0))) + .build(); + } + + private ConstrainedTransfer buildConstrainedTransfer() { + if (constraint == null) { + return null; + } int fromStopPos = fromTrip.pattern().findStopPositionAfter(0, fromStopIndex); int toStopPos = toTrip.pattern().findStopPositionAfter(0, toStopIndex); @@ -93,10 +174,35 @@ public ConstrainedTransfer build() { ); } - private static Trip createDummyTrip(T trip) { - // Set a uniq id: pattern + the first stop departure time - return TransitModelForTest - .trip(trip.pattern().debugInfo() + ":" + TimeUtils.timeToStrCompact(trip.departure(0))) - .build(); + private static TripStopTime departure(T trip, int stopIndex) { + return TripStopTime.departure(trip, trip.pattern().findStopPositionAfter(0, stopIndex)); + } + + private static TripStopTime arrival(T trip, int stopIndex) { + return TripStopTime.arrival(trip, trip.pattern().findStopPositionAfter(0, stopIndex)); + } + + private void validateFromTo() { + Objects.requireNonNull(fromTrip); + Objects.requireNonNull(toTrip); + if (fromStopIndex < 0) { + throw new IllegalStateException(); + } + if (toStopIndex < 0) { + throw new IllegalStateException(); + } + } + + private void validateWalkDurationSec() { + if (walkDurationSec < 0) { + throw new IllegalStateException(); + } + } + + private TransferConstraint.Builder constraint() { + if (constraint == null) { + constraint = TransferConstraint.of(); + } + return constraint; } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java index af29caafb2b..e994fcd469e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java @@ -2,12 +2,9 @@ import java.util.Arrays; import java.util.List; -import org.opentripplanner.raptor._data.transit.TestTransfer; import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.TransitPathLeg; -import org.opentripplanner.routing.algorithm.transferoptimization.model.TripStopTime; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; /** @@ -15,43 +12,6 @@ */ public class TransferGeneratorDummy { - private static final int D0s = 0; - - /** Transfer from trip & stop, walk, to stop & trip */ - public static TripToTripTransfer tx( - TestTripSchedule fromTrip, - int fromStop, - int walkDuration, - int toStop, - TestTripSchedule toTrip - ) { - return createTripToTripTransfer(fromTrip, fromStop, walkDuration, toStop, toTrip); - } - - /** Transfer from trip via same stop to trip */ - public static TripToTripTransfer tx( - TestTripSchedule fromTrip, - int sameStop, - TestTripSchedule toTrip - ) { - return createTripToTripTransfer(fromTrip, sameStop, D0s, sameStop, toTrip); - } - - /** Transfer from transfer constraints - same stop */ - public static TripToTripTransfer tx( - TestTransferBuilder builder - ) { - return createTripToTripTransfer(builder, D0s); - } - - /** Transfer from transfer constraints - with walking */ - public static TripToTripTransfer tx( - TestTransferBuilder builder, - int walkDuration - ) { - return createTripToTripTransfer(builder, walkDuration); - } - @SafeVarargs static TransferGenerator dummyTransferGenerator( final List>... transfers @@ -65,47 +25,4 @@ public List>> findAllPossibleTransfers } }; } - - /* private methods */ - - private static TripToTripTransfer createTripToTripTransfer( - TestTripSchedule fromTrip, - int fromStop, - int walkDuration, - int toStop, - TestTripSchedule toTrip - ) { - var pathTransfer = fromStop == toStop ? null : TestTransfer.transfer(toStop, walkDuration); - - return new TripToTripTransfer<>( - departure(fromTrip, fromStop), - arrival(toTrip, toStop), - pathTransfer, - null - ); - } - - private static TripToTripTransfer createTripToTripTransfer( - TestTransferBuilder builder, - int walkDuration - ) { - int fromStop = builder.getFromStopIndex(); - int toStop = builder.getToStopIndex(); - var pathTransfer = fromStop == toStop ? null : TestTransfer.transfer(toStop, walkDuration); - - return new TripToTripTransfer<>( - departure(builder.getFromTrip(), builder.getFromStopIndex()), - arrival(builder.getToTrip(), builder.getToStopIndex()), - pathTransfer, - builder.build() - ); - } - - private static TripStopTime departure(T trip, int stopIndex) { - return TripStopTime.departure(trip, trip.pattern().findStopPositionAfter(0, stopIndex)); - } - - private static TripStopTime arrival(T trip, int stopIndex) { - return TripStopTime.arrival(trip, trip.pattern().findStopPositionAfter(0, stopIndex)); - } } From 7cb70c7849b08ae1c8c6a0d5262b3ad8d527a1c1 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 10 Oct 2023 15:47:33 +0200 Subject: [PATCH 081/120] refactor: Deprecated c2PerStopPosition in PathBuilderLeg --- .../opentripplanner/raptor/path/PathBuilder.java | 15 ++++++++------- .../raptor/path/PathBuilderLeg.java | 13 +++++++------ .../services/OptimizePathDomainService.java | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java index 7184d70a9c2..3f6bed60a70 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorConstrainedTransfer; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -18,19 +19,20 @@ /** * The path builder is a utility to build paths. The path builder is responsible for reconstructing - * information that was used in decision making inside Raptor, but not kept due to performance + * information that was used in decision-making inside Raptor, but not kept due to performance * reasons. For example information about the transfer like transfer constraints. *

* The path builder enforces the same logic as Raptor and generates information like the * generalized-cost instead of getting it from the stop-arrivals. This is convenient if a path is - * created OUTSIDE Raptor, which is the case in the {@link org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService}. + * created OUTSIDE Raptor, which is the case in the {@link + * org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService}. *

* The path builder comes in two versions. One which adds new legs to the tail of the path, allowing * us to add legs starting with the access leg and ending with the egress leg. The other adds legs - * in the opposite order, from egress to access. Hence the forward and reverse mappers are + * in the opposite order, from egress to access. Hence, the forward and reverse mappers are * simplified using the head and tail builder respectively. See {@link #headPathBuilder( - * RaptorSlackProvider, RaptorCostCalculator, RaptorStopNameResolver, - * RaptorPathConstrainedTransferSearch)} and {@link #tailPathBuilder(RaptorSlackProvider, + * RaptorSlackProvider, int, RaptorCostCalculator, RaptorStopNameResolver, + * RaptorPathConstrainedTransferSearch)} and {@link #tailPathBuilder(RaptorSlackProvider, int, * RaptorCostCalculator, RaptorStopNameResolver, RaptorPathConstrainedTransferSearch)}. *

* The builder is also used for creating test data in unit test. @@ -53,8 +55,7 @@ public abstract class PathBuilder { @Nullable private final RaptorPathConstrainedTransferSearch transferConstraintsSearch; - @Nullable - private int c2; + private int c2 = RaptorConstants.NOT_SET; // Path leg elements as a double linked list. This makes it easy to look at // legs before and after in the logic and easy to fork, building alternative diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index 6756863f795..7187d7fbb72 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -23,8 +23,8 @@ import org.opentripplanner.raptor.spi.RaptorSlackProvider; /** - * This is the leg implementation for the {@link PathBuilder}. It is a private inner class which - * helps to cache and calculate values before constructing a path. + * This is the leg implementation for the {@link PathBuilder}. It helps to cache and calculate + * values before constructing a path. */ public class PathBuilderLeg { @@ -45,9 +45,9 @@ public class PathBuilderLeg { private PathBuilderLeg prev = null; private PathBuilderLeg next = null; - // TODO: 2023-09-01 We need some storage space to keep track of c2 value per stop - // when we are filtering out possible transfers. - // I choose to include it here because it is practical but maybe we should have it somewhere else? + // TODO PT: 2023-09-01 We need some storage space to keep track of c2 value per stop + // when we are filtering out possible transfers. + // I choose to include it here because it is practical but maybe we should have it somewhere else? private final int[] c2PerStopPosition; /** @@ -121,18 +121,19 @@ public int durationInSec() { * Get c2 value associate with given stop position in a pattern. * This works only for transit legs and if c2 is already set */ + @Deprecated public int c2ForStopPosition(int pos) { var c2 = c2PerStopPosition[pos]; if (c2 == NOT_SET) { throw new IllegalArgumentException("C2 for stop position " + pos + " not set"); } - return c2; } /** * Set c2 value on a given stop position in a transit leg */ + @Deprecated public void setC2OnStopPosition(int pos, int c2) { c2PerStopPosition[pos] = c2; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index c68f8d19e8d..aba81a80c3b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -34,7 +34,7 @@ * M : number of possible transfer location for a given pair of trips * * Without any pruning the permutations have an order of O(N^M), but by filtering during the path - * construction we get close to O(N*M) - which is acceptable. + * construction we get close to o(N*M) - which is acceptable. * * Example with 3 lines(trips), where the `+` indicate stop places: * From 2aaadbf00b198abbe62ab2778848016f27ac46be Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 10 Oct 2023 17:29:44 +0200 Subject: [PATCH 082/120] doc: Fix JavaDoc PassThroughFilterFactorygs --- .../model/passthrough/PassThroughFilterFactory.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index 103becc5c43..bb097f88b1f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -9,13 +9,13 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; /** - * Create a filer chain function and find the best transfers combination for journey that also - * includes all pass-through points. + * Create a filter chain function and find the best combination of transfers for the journey + * that also includes all pass-through points. *

* The algorithm starts with the last trip in the journey, then goes backwards looping through all - * possible transfers for each transit leg. For each possible transfer stop position C2 value is - * calculated. The filter chain function is going to use c2 value and cost function to determine - * whether the tail should be included or excluded from result. + * possible transfers for each transit leg. For each possible transfer stop position the C2-value + * is calculated. The filter chain function is going to use the c2-value and the cost function to + * determine whether the tail should be included or excluded from result. *

*Example: *

From 147ea8fd718c7d17d6b97abdf9f45afea7b2df02 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 11 Oct 2023 17:13:14 +0200 Subject: [PATCH 083/120] test: Add module test on pass-through-point in optimized-transfers Note! There are test failing! These are bugs. NN --- .../raptor/api/request/PassThroughPoint.java | 28 +- .../raptor/_data/RaptorTestConstants.java | 1 + .../PassThroughNoTransfersTest.java | 97 +++++++ .../PassThroughOneTransferTest.java | 189 ++++++++++++++ .../PassThroughTwoTransfersTest.java | 240 ++++++++++++++++++ .../model/passthrough/StopPair.java | 3 + .../model/passthrough/TestCase.java | 70 +++++ .../model/passthrough/TestCaseBuilder.java | 42 +++ .../model/passthrough/TestUtils.java | 86 +++++++ .../WalkDurationForStopCombinations.java | 60 +++++ .../services/TransferGeneratorDummy.java | 2 +- 11 files changed, 813 insertions(+), 5 deletions(-) create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/StopPair.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCaseBuilder.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java diff --git a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java index 724cc649243..b17fab84347 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java @@ -3,9 +3,9 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Objects; +import java.util.function.IntFunction; import java.util.stream.IntStream; import javax.annotation.Nullable; -import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.framework.lang.StringUtils; /** @@ -55,8 +55,28 @@ public int hashCode() { */ @Override public String toString() { - return ( - "(" + (name == null ? "" : name + ", ") + "stops: " + IntUtils.intArrayToString(stops) + ")" - ); + return toString(Integer::toString); + } + + public String toString(IntFunction nameResolver) { + StringBuilder buf = new StringBuilder("("); + if (name != null) { + buf.append(name).append(", "); + } + buf.append("stops: "); + appendStops(buf, ", ", nameResolver); + return buf.append(")").toString(); + } + + public void appendStops(StringBuilder buf, String sep, IntFunction nameResolver) { + boolean skipFirst = true; + for (int stop : stops) { + if (skipFirst) { + skipFirst = false; + } else { + buf.append(sep); + } + buf.append(nameResolver.apply(stop)); + } } } diff --git a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java index ffc7ce17059..d293f7326e6 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java +++ b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java @@ -55,6 +55,7 @@ public interface RaptorTestConstants { int STOP_J = 10; int STOP_K = 11; int STOP_L = 12; + int STOP_M = 13; // Stop position in pattern int STOP_POS_0 = 0; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java new file mode 100644 index 00000000000..9f0db5fe324 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java @@ -0,0 +1,97 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.first; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; + +/** + * This test focus on the PASS-THROUGH functionality with a very simple scenario - one transit leg + * and no transfers. + *

+ * FEATURE UNDER TESTED + *

+ * We want the path with the lowest generalized-cost that visit the pass-through points in the + * correct order. + *

+ * TEST SETUP + *

+ * We will use one trip with 5 stops. There is only one possible path - the original. + */ +@SuppressWarnings("SameParameterValue") +public class PassThroughNoTransfersTest implements RaptorTestConstants { + + private static final int ITERATION_START_TIME = time("10:00"); + /** Any stop not part of trip. */ + private static final int ANY_STOP = STOP_I; + + private final TestTripSchedule trip1 = TestTripSchedule + .schedule() + .pattern("Line 1", STOP_A, STOP_B, STOP_C, STOP_D, STOP_E) + .times("10:05 10:10 10:15 10:20 10:25") + .build(); + + static List tripWithoutTransfersTestCases() { + return List.of( + testCase("").build(), + testCase("at board stop B").points(STOP_B).build(), + testCase("at intermediate stop C").points(STOP_C).build(), + testCase("at alight stop D").points(STOP_D).build(), + testCase("at either B or C").points(STOP_B, STOP_C).build(), + testCase("at either C or D").points(STOP_C, STOP_D).build(), + testCase("at C, w/unreachable A").points(STOP_A, STOP_C).build(), + testCase("at C, w/unreachable E").points(STOP_C, STOP_E).build(), + testCase("at C, w/unreachable stop not part of trip").points(STOP_C, ANY_STOP).build(), + testCase("at board stop B & intermediate stop C").points(STOP_B).points(STOP_C).build(), + testCase("at intermediate stop C & alight stop D").points(STOP_C).points(STOP_D).build(), + testCase("at board stop C & the alight stop D").points(STOP_C).points(STOP_D).build(), + testCase("at B, C, and D") + .points(STOP_A, STOP_B) + .points(STOP_C, STOP_I) + .points(STOP_D) + .build() + ); + } + + /** + * The pass-though point used can be the board-, intermediate-, and/or alight-stop. We will also + * test all combinations of these to make sure a pass-though point is only accounted for once. + *

+ * The trip1 used: + *

+   * Origin ~ walk ~ B ~ Trip 1 ~ D ~ walk ~ Destination
+   * 
+ * Note! Stop A and E is not visited. Stop I is part of one transfer-point, but not part of the + * trip. + */ + @ParameterizedTest + @MethodSource("tripWithoutTransfersTestCases") + public void tripWithoutTransfers(TestCase tc) { + var originalPath = pathBuilder() + .access(ITERATION_START_TIME, STOP_B, D1s) + .bus(trip1, STOP_D) + .egress(D1s); + + var subject = subject(tc.points()); + + // When + var result = subject.findBestTransitPath(originalPath); + + assertEquals(1, result.size()); + + // Then expect a set containing the original path + System.out.println(first(result).toString(this::stopIndexToName)); + assertEquals( + originalPath.toString(this::stopIndexToName), + first(result).toString(this::stopIndexToName) + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java new file mode 100644 index 00000000000..8210f7cbe48 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java @@ -0,0 +1,189 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathFocus; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.tx; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; + +/** + * This test focus on the PASS-THROUGH functionality with two transit legs and one transfer in the + * path. But between trip 1 and trip 2, there will be may transfer options to choose from. + * + *

+ * FEATURE UNDER TESTED + *

+ * We want the path with the lowest generalized-cost that visit the pass-through points in the + * correct order. + *

+ * TEST SETUP + *

+ * We will use 2 trips with a fixed set of transfers for each test. Each trip has 5 stops and + * plenty of slack to do the transfers for all possible stops combinations. We will set the + * transfer durations to get different generalized-costs for each possible path. We will set the + * cost so the transfers witch do not contain any transfer-points have the lowest cost - is optimal + * on generalized-cost. We do this to make sure the subject-under-test is using the pass-through- + * points, and not the generalized cost to choose the correct path. + */ +@SuppressWarnings("SameParameterValue") +public class PassThroughOneTransferTest implements RaptorTestConstants { + + private static final int ITERATION_START_TIME = time("10:00"); + + /** + * We use arrays to store stuff per stop, so this is the max value of all stop indexes used, + * plus one. Gaps are Ok, if they exist. + */ + private static final int N_STOPS = STOP_M + 1; + + private final TestTripSchedule trip1 = TestTripSchedule + .schedule() + .pattern("Line 1", STOP_A, STOP_B, STOP_C, STOP_D, STOP_E) + .times("10:05 10:10 10:15 10:20 10:25") + .build(); + + private final TestTripSchedule trip2 = TestTripSchedule + .schedule() + .pattern("Line 2", STOP_F, STOP_G, STOP_H, STOP_I, STOP_J) + .times("10:30 10:35 10:40 10:45 10:50") + .build(); + + static List tripWithOneTransferTestCases() { + return List.of( + testCase().expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_B).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_C).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_D).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_G).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_H).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_I).expectTransfer(STOP_C, STOP_H), + // Two stops in one pass-through point + testCase().points(STOP_B, STOP_C).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_B, STOP_D).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_B, STOP_G).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_B, STOP_H).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_B, STOP_I).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_C, STOP_D).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_C, STOP_G).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_C, STOP_H).expectTransfer(STOP_D, STOP_G), + testCase().points(STOP_C, STOP_I).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_D, STOP_G).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_D, STOP_H).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_D, STOP_I).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_G, STOP_H).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_G, STOP_I).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_H, STOP_I).expectTransfer(STOP_C, STOP_G), + // Two stops in two pass-through points + testCase().points(STOP_B).points(STOP_C).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_B).points(STOP_D).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_B).points(STOP_G).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_B).points(STOP_H).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_B).points(STOP_I).expectTransfer(STOP_C, STOP_H), + testCase().points(STOP_C).points(STOP_D).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_C).points(STOP_G).expectTransfer(STOP_D, STOP_G), + testCase().points(STOP_C).points(STOP_H).expectTransfer(STOP_D, STOP_G), + testCase().points(STOP_C).points(STOP_I).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_D).points(STOP_G).expectTransfer(STOP_D, STOP_G), + testCase().points(STOP_D).points(STOP_H).expectTransfer(STOP_D, STOP_G), + testCase().points(STOP_D).points(STOP_I).expectTransfer(STOP_D, STOP_H), + testCase().points(STOP_G).points(STOP_H).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_G).points(STOP_I).expectTransfer(STOP_C, STOP_G), + testCase().points(STOP_H).points(STOP_I).expectTransfer(STOP_C, STOP_G) + ); + } + + /** + * In this test we will use trip 1 and 2. We will have one test for each possible pass-though- + * point. We will add 4 transfers between the trips, [from]-[to]: {@code C-G, C-H, D-G, D-H}. We + * will also add transfers between B-F and E-I, these transfers can not be used with the access + * and egress, because we are not allowed to have two walking legs in a row. We include this + * transfers to make sure the implementation ignores them. + *

+ * + *

+   *           Origin
+   *                \
+   *  Trip 1   A --- B --- C --- D --- E
+   *                 |     | \ / |     |
+   *                 |     | / \ |     |
+   *  Trip 2         F --- G --- H --- I --- J
+   *                                    \
+   *                                     Destination
+   * 
+ * With this setup we will try all possible combinations of pass-through points and make sure + * the correct path is chosen. + *

+ * We will adjust the transfer walk duration so that paths containing the transfer-point get a + * high cost, and paths without it get a lower cost. + */ + @ParameterizedTest + @MethodSource("tripWithOneTransferTestCases") + public void tripWithOneTransfer(TestCase tc) { + var txCost = new WalkDurationForStopCombinations(N_STOPS) + .withPassThroughPoints(tc.points(), 10) + // This transfer do not visit D + .addTxCost(STOP_C, STOP_G, 2) + // This transfer do not visit D and G; hence given the lowest cost + .addTxCost(STOP_C, STOP_H, 1) + // This transfer visit all stops; Hence given the highest cost + .addTxCost(STOP_D, STOP_G, 3) + // This transfer do not visit G + .addTxCost(STOP_D, STOP_H, 2); + + // We need *a* path - the transfer her can be any + var originalPath = pathBuilder() + .access(ITERATION_START_TIME, STOP_B, D1s) + .bus(trip1, STOP_D) + .walk(txCost.walkDuration(STOP_D, STOP_F), STOP_F) + .bus(trip2, STOP_I) + .egress(D1s); + + var expectedPath = pathBuilder() + .access(ITERATION_START_TIME, STOP_B, D1s) + .bus(trip1, tc.stopIndexA()) + .walk(txCost.walkDuration(tc.stopIndexA(), tc.stopIndexB()), tc.stopIndexB()) + .bus(trip2, STOP_I) + .egress(D1s); + + // These are illegal transfer for the given path, we add them here to make sure they + // do not interfere with the result. For simpler debugging problems try comment out these + // lines, just do not forget to comment them back in when the problem is fixed. + var subject = subject( + tc.points(), + List.of( + tx(trip1, STOP_C, trip2, STOP_G, txCost), + tx(trip1, STOP_C, trip2, STOP_H, txCost), + tx(trip1, STOP_D, trip2, STOP_G, txCost), + tx(trip1, STOP_D, trip2, STOP_H, txCost), + // These are illegal transfer for the given path, we add them here to make sure they + // do not interfere with the result. For simpler debugging problems try comment out these + // lines, just do not forget to comment them back in when the problem is fixed. + tx(trip1, STOP_B, trip2, STOP_F, txCost), + tx(trip1, STOP_E, trip2, STOP_I, txCost) + ) + ); + + // When + var result = subject.findBestTransitPath(originalPath); + + // Then expect a set containing the expected path only + var resultAsString = result + .stream() + .map(it -> it.toString(this::stopIndexToName)) + .collect(Collectors.joining(", ")); + assertEquals( + pathFocus(expectedPath.toString(this::stopIndexToName)), + pathFocus(resultAsString), + resultAsString + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java new file mode 100644 index 00000000000..ed6ea660575 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java @@ -0,0 +1,240 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestCase.testCase; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathBuilder; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.pathFocus; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.subject; +import static org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.TestUtils.tx; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.path.RaptorPath; + +/** + * This test focus on the PASS-THROUGH functionality with three transit legs and two transfer in + * the path. + *

+ * FEATURE UNDER TESTED + *

+ * We want the path with the lowest generalized-cost that visit the pass-through points in the + * correct order. + *

+ * TEST SETUP + *

+ * We will use 3 trips with a fixed set of transfers for each test. Each trip has 5 stops and + * plenty of slack to do the transfers for all possible stops combinations. There is two + * transfers to choose from between trip 1 and trip 2, and between trip 2 and trip 3. We will set + * the transfer durations to get different generalized-costs for each possible path. We will set + * the cost so the transfers witch do not contain any transfer-points have the lowest cost - is + * optimal on generalized-cost. We do this to make sure the subject-under-test is using the + * pass-through-points, and not the generalized cost to choose the correct path. + */ +@SuppressWarnings("SameParameterValue") +public class PassThroughTwoTransfersTest implements RaptorTestConstants { + + private static final int ITERATION_START_TIME = time("10:00"); + + private final TestTripSchedule trip1 = TestTripSchedule + .schedule() + .pattern("Line 1", STOP_A, STOP_B, STOP_C, STOP_D, STOP_E) + .times("10:05 10:10 10:15 10:20 10:25") + .build(); + + private final TestTripSchedule trip2 = TestTripSchedule + .schedule() + .pattern("Line 2", STOP_F, STOP_G, STOP_H, STOP_I, STOP_J) + .times("10:30 10:35 10:40 10:45 10:50") + .build(); + + private final TestTripSchedule trip3 = TestTripSchedule + .schedule() + .pattern("Line 3", STOP_K, STOP_L, STOP_M) + .times("10:55 11:00 11:05") + .build(); + + static List tripWithTwoTransferTestCases() { + return List.of( + // None pass-through-points + testCase("").expectTransfersFrom(STOP_C, STOP_H), + // One pass-through-points + testCase().points(STOP_B).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_C).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_D).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_K, STOP_D).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_K, STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_K, STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_K, STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_K, STOP_L).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_K, STOP_M).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_G, STOP_B).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_C).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_D).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_I).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_J).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_L).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G, STOP_M).expectTransfersFrom(STOP_E, STOP_J), + testCase("at D w/unreachable A").points(STOP_D, STOP_A).expectTransfersFrom(STOP_E, STOP_J), + testCase("at E w/unreachable A").points(STOP_E, STOP_A).expectTransfersFrom(STOP_E, STOP_J), + testCase("at I w/unreachable A").points(STOP_I, STOP_A).expectTransfersFrom(STOP_C, STOP_J), + testCase("at J w/unreachable A").points(STOP_J, STOP_A).expectTransfersFrom(STOP_C, STOP_J), + testCase("at L w/unreachable A").points(STOP_L, STOP_A).expectTransfersFrom(STOP_C, STOP_H), + testCase("at D w/unreachable F").points(STOP_D, STOP_F).expectTransfersFrom(STOP_E, STOP_J), + testCase("at E w/unreachable F").points(STOP_E, STOP_F).expectTransfersFrom(STOP_E, STOP_J), + testCase("at I w/unreachable F").points(STOP_I, STOP_F).expectTransfersFrom(STOP_C, STOP_J), + testCase("at J w/unreachable F").points(STOP_J, STOP_F).expectTransfersFrom(STOP_C, STOP_J), + testCase("at L w/unreachable F").points(STOP_L, STOP_F).expectTransfersFrom(STOP_C, STOP_H), + // Two pass-through-points - a few samples + testCase().points(STOP_B).points(STOP_C).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_B).points(STOP_D).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_B).points(STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_B).points(STOP_G).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_B).points(STOP_H).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_B).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_B).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_B).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_B).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_C).points(STOP_D).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_C).points(STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_C).points(STOP_G).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_C).points(STOP_H).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_C).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_C).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_C).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_C).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_D).points(STOP_E).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_G).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_H).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_I).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_J).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_K).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_L).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_G).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_H).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_I).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_J).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_K).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_L).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_G).points(STOP_H).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_G).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_G).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_G).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_G).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_H).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_H).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_H).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_H).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_I).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_I).points(STOP_L).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_I).points(STOP_M).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_J).points(STOP_L).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_J).points(STOP_M).expectTransfersFrom(STOP_C, STOP_J), + testCase().points(STOP_K).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_K).points(STOP_M).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_L).points(STOP_M).expectTransfersFrom(STOP_C, STOP_H) + ); + } + + /** + * In this test we will test a path with *three* transit legs and 2 transfers. For each transfer + * there is two options, an in total three possible paths: + *

    + *
  1. Origin ~ B ~ C ~ G ~ H ~ K ~ M ~ Destination
  2. + *
  3. Origin ~ B ~ C ~ G ~ J ~ L ~ M ~ Destination
  4. + *
  5. Origin ~ B ~ E ~ I ~ J ~ L ~ M ~ Destination
  6. + *
+ * This is how the network look like: + *
+   *          Origin
+   *                \
+   *  Trip 1   A --- B --- C --- D --- E
+   *                        \ 5s[20s]   \ 10s
+   *  Trip 2           F --- G --- H --- I --- J
+   *                               \ 5s[20s]    \ 10s
+   *  Trip 3                        K ---------- L --- M
+   *                                                    \
+   *                                                     Destination
+   * 
+ * The point in this test is to give the path with transfer C-G & H-K an advantage + * (generalized-cost), but at the same time miss out on possible transfer-points (D,E,I,J). + *

+ * If stop G or K is part of a pass-through-point, then we would like to make an exception to the + * generalized-cost by increasing the cost for transfer K -> H-K and G -> C-G to 20s - making + * these transfers less favorable on generalized-cost. + *

+ * We will variate this test with zero, one and two pass-through point and by making unreachable + * stops(A,F) part of the pass-through-points. + */ + @ParameterizedTest + @MethodSource("tripWithTwoTransferTestCases") + public void tripWithTwoTransfer(TestCase tc) { + // We set up the cost so that the maximum number of stops are skipped if we route on + // generalized-cost only. + final int costCG = tc.contains(STOP_G) ? 20 : 5; + final int costEI = 10; + final int costHK = tc.contains(STOP_K) ? 20 : 5; + final int costJL = 10; + + // We need *a* path - the transfer her can be any + var originalPath = pathBuilder() + .access(ITERATION_START_TIME, STOP_B, D1s) + .bus(trip1, STOP_C) + .walk(costCG, STOP_G) + .bus(trip2, STOP_H) + .walk(costHK, STOP_K) + .bus(trip3, STOP_M) + .egress(D1s); + + RaptorPath expectedPath; + { + var b = pathBuilder().access(ITERATION_START_TIME, STOP_B, D1s); + + if (tc.stopIndexA() == STOP_C) { + b.bus(trip1, STOP_C).walk(costCG, STOP_G); + } else { + b.bus(trip1, STOP_E).walk(costEI, STOP_I); + } + if (tc.stopIndexB() == STOP_H) { + b.bus(trip2, STOP_H).walk(costHK, STOP_K); + } else { + b.bus(trip2, STOP_J).walk(costJL, STOP_L); + } + expectedPath = b.bus(trip3, STOP_M).egress(D1s); + } + + var firstTransfers = List.of( + tx(trip1, STOP_C, trip2, STOP_G, costCG), + tx(trip1, STOP_E, trip2, STOP_I, costEI) + ); + var secondTransfers = List.of( + tx(trip2, STOP_H, trip3, STOP_K, costHK), + tx(trip2, STOP_J, trip3, STOP_L, costJL) + ); + + var subject = subject(tc.points(), firstTransfers, secondTransfers); + + // When + var result = subject.findBestTransitPath(originalPath); + + // Then expect a set containing the expected path only + var resultAsString = result + .stream() + .map(it -> it.toString(this::stopIndexToName)) + .collect(Collectors.joining(", ")); + assertEquals( + pathFocus(expectedPath.toString(this::stopIndexToName)), + pathFocus(resultAsString), + resultAsString + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/StopPair.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/StopPair.java new file mode 100644 index 00000000000..0291c3529eb --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/StopPair.java @@ -0,0 +1,3 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +record StopPair(int from, int to) {} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java new file mode 100644 index 00000000000..255b081d529 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java @@ -0,0 +1,70 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import java.util.List; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor.api.request.PassThroughPoint; + +record TestCase( + String description, + int stopIndexA, + int stopIndexB, + boolean fromAToB, + List points +) + implements RaptorTestConstants { + static TestCaseBuilder testCase(String description) { + return new TestCaseBuilder(description); + } + + static TestCaseBuilder testCase() { + return new TestCaseBuilder(null); + } + + @Override + public String toString() { + var buf = new StringBuilder(); + buf.append( + switch (points.size()) { + case 0 -> "No pass-through-points"; + case 1 -> "One pass-through-point "; + case 2 -> "Two pass-through-points "; + default -> points.size() + " pass-through-points "; + } + ); + if (description != null) { + buf.append(description); + } else { + if (points.size() == 1) { + buf.append("at "); + appendPoint(buf, 0); + } + if (points.size() > 1) { + buf.append("at ("); + appendPoint(buf, 0); + for (int i = 1; i < points.size(); ++i) { + buf.append(") or ("); + appendPoint(buf, i); + } + buf.append(")"); + } + } + + if (stopIndexA > 0 || stopIndexB > 0) { + buf + .append(". Expects transfer" + (fromAToB ? "" : "s") + " from ") + .append(stopIndexToName(stopIndexA)) + .append(fromAToB ? " to " : " and ") + .append(stopIndexToName(stopIndexB)); + } + buf.append(". ").append(points.stream().map(p -> p.toString(this::stopIndexToName)).toList()); + return buf.toString(); + } + + private void appendPoint(StringBuilder buf, int passThroughPointIndex) { + points.get(passThroughPointIndex).appendStops(buf, " and ", this::stopIndexToName); + } + + boolean contains(int stopIndex) { + return points.stream().anyMatch(it -> it.asBitSet().get(stopIndex)); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCaseBuilder.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCaseBuilder.java new file mode 100644 index 00000000000..aed3170df3f --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCaseBuilder.java @@ -0,0 +1,42 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import java.util.ArrayList; +import java.util.List; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.request.PassThroughPoint; + +class TestCaseBuilder { + + final String description; + int stopIndexA = RaptorConstants.NOT_SET; + int stopIndexB = RaptorConstants.NOT_SET; + boolean txFromAToB = false; + final List points = new ArrayList<>(); + + TestCaseBuilder(String description) { + this.description = description; + } + + TestCaseBuilder points(int... stops) { + points.add(new PassThroughPoint("PT" + (points.size() + 1), stops)); + return this; + } + + TestCase expectTransfer(int fromStopIndex, int toStopIndex) { + this.stopIndexA = fromStopIndex; + this.stopIndexB = toStopIndex; + this.txFromAToB = true; + return build(); + } + + TestCase expectTransfersFrom(int fromStopIndexA, int fromStopIndexB) { + this.stopIndexA = fromStopIndexA; + this.stopIndexB = fromStopIndexB; + this.txFromAToB = false; + return build(); + } + + TestCase build() { + return new TestCase(description, stopIndexA, stopIndexB, txFromAToB, points); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java new file mode 100644 index 00000000000..104e5fc1327 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java @@ -0,0 +1,86 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import static org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGeneratorDummy.dummyTransferGenerator; + +import java.util.Collection; +import java.util.List; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.api.TestPathBuilder; +import org.opentripplanner.raptor._data.transit.TestTransitData; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.spi.RaptorCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; +import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; +import org.opentripplanner.routing.algorithm.transferoptimization.services.TestTransferBuilder; + +class TestUtils implements RaptorTestConstants { + + private static final int BOARD_COST_SEC = 0; + private static final int TRANSFER_COST_SEC = 0; + private static final double WAIT_RELUCTANCE = 1.0; + + private static final RaptorCostCalculator COST_CALCULATOR = new DefaultCostCalculator<>( + BOARD_COST_SEC, + TRANSFER_COST_SEC, + WAIT_RELUCTANCE, + null, + null + ); + + static TestPathBuilder pathBuilder() { + return new TestPathBuilder(TestTransitData.SLACK_PROVIDER, COST_CALCULATOR); + } + + static TripToTripTransfer tx( + TestTripSchedule fromTrip, + int fromStop, + TestTripSchedule toTrip, + int toStop, + WalkDurationForStopCombinations txCost + ) { + return tx(fromTrip, fromStop, toTrip, toStop, txCost.walkDuration(fromStop, toStop)); + } + + static TripToTripTransfer tx( + TestTripSchedule fromTrip, + int fromStop, + TestTripSchedule toTrip, + int toStop, + int txCost + ) { + return TestTransferBuilder.tx(fromTrip, fromStop, toTrip, toStop).walk(txCost).build(); + } + + static OptimizePathDomainService subject( + List passThroughPoints, + final List>... transfers + ) { + var filterFactory = new MinCostPathTailFilterFactory(false, false); + var generator = dummyTransferGenerator(transfers); + + return new OptimizePathDomainService<>( + generator, + COST_CALCULATOR, + TestTransitData.SLACK_PROVIDER, + null, + null, + 0.0, + new PassThroughFilterFactory<>(passThroughPoints, filterFactory), + (new RaptorTestConstants() {})::stopIndexToName + ); + } + + static T first(Collection c) { + return c.stream().findFirst().orElseThrow(); + } + + /** + * Remove stuff we do not care about, like the priority cost and times. + */ + static String pathFocus(String resultString) { + return resultString.replaceAll(" \\$\\d+pri]", "]").replaceAll(" \\d{2}:\\d{2}", ""); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java new file mode 100644 index 00000000000..ecb44226ee1 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java @@ -0,0 +1,60 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import java.util.BitSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.raptor.api.request.PassThroughPoint; + +/** + * This class is used to adjust the walk time - giving each path a unique generalized-cost. We want + * the paths witch do NOT visit the pass-through points to have a lower generalized-cost than the + * "correct" paths. We do this by adding a small increasing cost to each paths ordered by the number + * of stops visited. Then we add a bigger cost for each stop in the transfer witch is a + * pass-through-point. + */ +class WalkDurationForStopCombinations { + + private final int[] stopCost; + private final Map transferCost = new HashMap<>(); + + public WalkDurationForStopCombinations(int nStops) { + stopCost = IntUtils.intArray(nStops, 0); + } + + WalkDurationForStopCombinations withPassThroughPoints( + Collection points, + int passThroughPointExtraCost + ) { + var passThroughStops = new BitSet(); + points.stream().map(PassThroughPoint::asBitSet).forEach(passThroughStops::or); + + for (int i = 0; i < stopCost.length; i++) { + if (passThroughStops.get(i)) { + stopCost[i] += passThroughPointExtraCost; + } + } + return this; + } + + /** + * Use this method to add a small cost({@code costInWalkSec}) to a transfer. Give the path that + * visit the least number of stops the lowest value. In addition, the + * {@link #withPassThroughPoints(Collection, int)} method can be used to add a extra cost to all + * transfers containing a pass-through-point. + */ + WalkDurationForStopCombinations addTxCost(int fromStop, int toStop, int costInWalkSec) { + StopPair tx = new StopPair(fromStop, toStop); + transferCost.put(tx, costTxOnly(tx) + costInWalkSec); + return this; + } + + int walkDuration(int fromStop, int toStop) { + return costTxOnly(new StopPair(fromStop, toStop)) + stopCost[fromStop] + stopCost[toStop]; + } + + int costTxOnly(StopPair tx) { + return transferCost.getOrDefault(tx, 0); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java index e994fcd469e..4392e89a83d 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorDummy.java @@ -13,7 +13,7 @@ public class TransferGeneratorDummy { @SafeVarargs - static TransferGenerator dummyTransferGenerator( + public static TransferGenerator dummyTransferGenerator( final List>... transfers ) { return new TransferGenerator<>(null, new TestTransitData()) { From efb0c4ba08df52b45a48e919d071611ebdeafa17 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 11 Oct 2023 17:16:32 +0200 Subject: [PATCH 084/120] fix: Fix index out of bounds for c2 in pass-through calculator --- .../model/passthrough/PathTailC2Calculator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java index 6dfc536997f..3ead0e21099 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java @@ -101,6 +101,9 @@ private boolean isPassThroughPoint( int c2, int stopIndex ) { + if (c2 == 0) { + return false; + } var point = passThroughPoints.get(c2 - 1); return point != null && point.asBitSet().get(stopIndex); } From db54a4832fc620d64831cfd9b330e13e6fd0efa2 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 15 Oct 2023 20:45:11 +0200 Subject: [PATCH 085/120] refactor: Make the PathTailFilter operate on OptimizedPathTail --- .../model/PathTailFilter.java | 8 ++- .../model/PathTailFilterFactory.java | 4 +- .../costfilter/MinCostPathTailFilter.java | 19 ++++-- .../MinCostPathTailFilterFactory.java | 6 +- .../passthrough/PassThroughFilterFactory.java | 5 +- .../PassThroughPathTailFilter.java | 19 ++++-- .../services/OptimizePathDomainService.java | 2 +- .../services/TransitPathLegSelector.java | 7 +- .../costfilter/MinCostPathTailFilterTest.java | 68 ++++++++++--------- .../services/TransitPathLegSelectorTest.java | 2 +- 10 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 4f86ce50c1d..10e0d5be1b5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -1,17 +1,19 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model; import java.util.Set; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; /** * TODO PT - This need JavaDoc */ -public interface PathTailFilter { +public interface PathTailFilter { /** * TODO PT - This need JavaDoc */ - Set filterIntermediateResult(Set elements); + Set> filterIntermediateResult(Set> elements); + /** * TODO PT - This need JavaDoc */ - Set filterFinalResult(Set elements); + Set> filterFinalResult(Set> elements); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java index ff7d58496d1..25fe83661a5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java @@ -4,7 +4,5 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; public interface PathTailFilterFactory { - PathTailFilter> createFilter( - List>> possibleTransfers - ); + PathTailFilter createFilter(List>> possibleTransfers); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java index 33576433b3a..374298299ad 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.Set; import java.util.function.ToIntFunction; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; /** @@ -18,24 +20,27 @@ * * @param The element type of the cost-functions and the filtered list */ -class MinCostPathTailFilter implements PathTailFilter { +class MinCostPathTailFilter implements PathTailFilter { - private final List> costFunctions; + private final List>> costFunctions; - MinCostPathTailFilter(List> costFunctions) { + MinCostPathTailFilter(List>> costFunctions) { this.costFunctions = costFunctions; } @Override - public Set filterIntermediateResult(Set elements) { + public Set> filterIntermediateResult(Set> elements) { for (var costFunction : costFunctions) { elements = filter(elements, costFunction); } return elements; } - private Set filter(Set elements, ToIntFunction costFunction) { - var result = new HashSet(); + private Set> filter( + Set> elements, + ToIntFunction> costFunction + ) { + var result = new HashSet>(); int minCost = Integer.MAX_VALUE; for (var it : elements) { @@ -53,7 +58,7 @@ private Set filter(Set elements, ToIntFunction costFunction) { } @Override - public Set filterFinalResult(Set elements) { + public Set> filterFinalResult(Set> elements) { return elements; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java index 254a1aa1585..79834e6cb30 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java @@ -21,9 +21,7 @@ public MinCostPathTailFilterFactory(boolean transferPriority, boolean optimizeWa } @Override - public PathTailFilter> createFilter( - List>> possibleTransfers - ) { + public PathTailFilter createFilter(List>> possibleTransfers) { List>> filters = new ArrayList<>(3); if (transferPriority) { @@ -45,7 +43,7 @@ public PathTailFilter> createFilter( * This factory method is used for unit testing. It allows you to pass in a simple cost function * instead of the more complicated functions used in the main version of this. */ - public static PathTailFilter> ofCostFunction( + public static PathTailFilter ofCostFunction( ToIntFunction> costFunction ) { return new MinCostPathTailFilter<>(List.of(costFunction)); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index bb097f88b1f..55df9a19be2 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -3,7 +3,6 @@ import java.util.List; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; @@ -58,9 +57,7 @@ public PassThroughFilterFactory( } @Override - public PathTailFilter> createFilter( - List>> possibleTransfers - ) { + public PathTailFilter createFilter(List>> possibleTransfers) { var calculator = new PathTailC2Calculator<>(possibleTransfers, passThroughPoints); return new PassThroughPathTailFilter<>( delegate.createFilter(possibleTransfers), diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index d1e7684e586..c037fcb8be5 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -7,24 +7,29 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -class PassThroughPathTailFilter implements PathTailFilter { +class PassThroughPathTailFilter implements PathTailFilter { private final PathTailFilter filterChain; - private final Function getC2; + private final Function, Integer> getC2; - public PassThroughPathTailFilter(PathTailFilter filterChain, Function getC2) { + public PassThroughPathTailFilter( + PathTailFilter filterChain, + Function, Integer> getC2 + ) { this.filterChain = filterChain; this.getC2 = getC2; } @Override - public Set filterIntermediateResult(Set elements) { - Map> elementsByC2Value = elements + public Set> filterIntermediateResult(Set> elements) { + Map>> elementsByC2Value = elements .stream() .collect(Collectors.groupingBy(getC2, toSet())); - Set result = new HashSet<>(); + Set> result = new HashSet<>(); for (Integer c2 : elementsByC2Value.keySet()) { result.addAll(filterChain.filterIntermediateResult(elementsByC2Value.get(c2))); } @@ -32,7 +37,7 @@ public Set filterIntermediateResult(Set elements) { } @Override - public Set filterFinalResult(Set elements) { + public Set> filterFinalResult(Set> elements) { return elements.stream().filter(tail -> getC2.apply(tail) == 0).collect(toSet()); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index aba81a80c3b..caa20d68568 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -135,7 +135,7 @@ private Set> findBestTransferOption( RaptorPath originalPath, List> originalTransitLegs, List>> possibleTransfers, - PathTailFilter> filter + PathTailFilter filter ) { final int iterationDepartureTime = originalPath.rangeRaptorIterationDepartureTime(); // Create a set of tails with the last transit leg in it (one element) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index 4a5c3fe3099..f77fbddedfa 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -29,16 +29,13 @@ */ class TransitPathLegSelector { - private final PathTailFilter> filter; + private final PathTailFilter filter; private Set> remindingLegs; private Set> selectedLegs; private int lastLimit = Integer.MAX_VALUE; - TransitPathLegSelector( - final PathTailFilter> filter, - final Set> legs - ) { + TransitPathLegSelector(final PathTailFilter filter, final Set> legs) { this.filter = filter; this.remindingLegs = Set.copyOf(legs); this.selectedLegs = new HashSet<>(); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java index 1beec0b89c3..11d388d81ab 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java @@ -1,64 +1,74 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.raptor._data.stoparrival.BasicPathTestCase.COST_CALCULATOR; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; +import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; -class MinCostPathTailFilterTest { +class MinCostPathTailFilterTest implements RaptorTestConstants { - private final A v01 = new A("A", 0, 1); - private final A v10 = new A("B", 1, 0); - private final A v02 = new A("C", 0, 2); - private final A w01 = new A("A'", 0, 1); + private static final TransferWaitTimeCostCalculator WAIT_TIME_CALC = new TransferWaitTimeCostCalculator( + 1.0, + 5.0 + ); - static Set setOf(A... as) { - return Set.of(as); - } + private final A v01 = new A("A", 0, 11); + private final A v10 = new A("B", 1, 10); + private final A v02 = new A("C", 0, 12); + private final A w01 = new A("A'", 0, 11); @Test void filterEmptySet() { // filter empty set assertEquals( Set.of(), - new MinCostPathTailFilter(List.of(it -> it.x)).filterIntermediateResult(Set.of()) + new MinCostPathTailFilter(List.of(it -> ((A) it).x)) + .filterIntermediateResult(Set.of()) ); } @Test void filterOneElement() { - assertEquals(setOf(v01), filter(v01)); + assertEquals(Set.of(v01), filter(v01)); } @Test void filterTwoDistinctEntries() { - assertEquals(setOf(v01), filter(v01, v10)); + assertEquals(Set.of(v01), filter(v01, v10)); // swap order, should not matter - assertEquals(setOf(v01), filter(v10, v01)); + assertEquals(Set.of(v01), filter(v10, v01)); } @Test void filterTwoDistinctEntriesWithTheSameFirstValueX() { // Keep best y (x is same) - assertEquals(setOf(v01), filter(v01, v02)); - assertEquals(setOf(v01), filter(v02, v01)); + assertEquals(Set.of(v01), filter(v01, v02)); + assertEquals(Set.of(v01), filter(v02, v01)); } @Test void filterTwoEqualVectors() { - assertEquals(setOf(v01, w01), filter(v01, w01)); - assertEquals(setOf(v01, w01), filter(w01, v01)); + assertEquals(Set.of(v01, w01), filter(v01, w01)); + assertEquals(Set.of(v01, w01), filter(w01, v01)); } private Set filter(A... as) { - return new MinCostPathTailFilter(List.of(it -> it.x, it -> it.y)) - .filterIntermediateResult(setOf(as)); + return new MinCostPathTailFilter(List.of(it -> ((A) it).x, it -> ((A) it).y)) + .filterIntermediateResult(Set.of(as)) + .stream() + .map(it -> (A) it) + .collect(Collectors.toSet()); } - static class A { + static class A extends OptimizedPathTail { /** Name is included in eq/hc to be able to add the "same" [x,y] vector to a set. */ public final String name; @@ -66,6 +76,7 @@ static class A { public final int y; private A(String name, int x, int y) { + super(SLACK_PROVIDER, COST_CALCULATOR, T00_00, WAIT_TIME_CALC, null, 0.0, null); this.name = name; this.x = x; this.y = y; @@ -81,24 +92,15 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof A)) { - return false; + if (o instanceof A a) { + return name.equals(a.name) && x == a.x && y == a.y; } - final A a = (A) o; - return name.equals(a.name) && x == a.x && y == a.y; + return false; } @Override public String toString() { - return ValueObjectToStringBuilder - .of() - .addText(name) - .addText("(") - .addNum(x) - .addText(", ") - .addNum(y) - .addText(")") - .toString(); + return name + "(" + x + ", " + y + ")"; } } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index 9e5d49dae2f..6491423a1f2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -39,7 +39,7 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { null ); - public static final PathTailFilter> FILTER_CHAIN = MinCostPathTailFilterFactory.ofCostFunction( + public static final PathTailFilter FILTER_CHAIN = MinCostPathTailFilterFactory.ofCostFunction( OptimizedPathTail::generalizedCost ); From 2192806912ef705341939a6d35875e853caa23ca Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 15 Oct 2023 19:31:29 +0200 Subject: [PATCH 086/120] fix: Fix optimized-transfer pass-though-point This fix remove duplicate functionality and fixes the problem where the filter does not respect differences in alight-/board-positions. --- .../raptor/path/PathBuilder.java | 6 +- .../raptor/path/PathBuilderLeg.java | 50 ++----- .../model/PathTailFilter.java | 20 ++- .../model/PathTailFilterFactory.java | 3 +- .../costfilter/MinCostPathTailFilter.java | 15 +- .../MinCostPathTailFilterFactory.java | 3 +- .../passthrough/PassThroughFilterFactory.java | 9 +- .../PassThroughPathTailFilter.java | 35 +++-- .../PassThroughPointsIterator.java | 57 ++++++++ .../passthrough/PathTailC2Calculator.java | 131 +++++++----------- .../services/OptimizePathDomainService.java | 29 ++-- .../services/TransitPathLegSelector.java | 21 ++- .../costfilter/MinCostPathTailFilterTest.java | 10 +- .../PassThroughTwoTransfersTest.java | 18 +-- .../model/passthrough/TestCase.java | 4 +- .../services/TransitPathLegSelectorTest.java | 43 +++--- 16 files changed, 238 insertions(+), 216 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java index 3f6bed60a70..6dae7ee2c5a 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java @@ -178,10 +178,14 @@ public void c2(int c2) { this.c2 = c2; } + public int c2() { + return tail.isC2Set() ? tail.c2() : c2; + } + public RaptorPath build() { updateAggregatedFields(); var pathLegs = createPathLegs(costCalculator, slackProvider); - return new Path<>(iterationDepartureTime, pathLegs, pathLegs.generalizedCostTotal(), c2); + return new Path<>(iterationDepartureTime, pathLegs, pathLegs.generalizedCostTotal(), c2()); } @Override diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index 7187d7fbb72..eebdf69bfd9 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -1,6 +1,5 @@ package org.opentripplanner.raptor.path; -import java.util.Arrays; import java.util.function.Predicate; import javax.annotation.Nullable; import org.opentripplanner.framework.time.TimeUtils; @@ -41,15 +40,11 @@ public class PathBuilderLeg { private int fromTime = NOT_SET; private int toTime = NOT_SET; + private int c2 = RaptorConstants.NOT_SET; private PathBuilderLeg prev = null; private PathBuilderLeg next = null; - // TODO PT: 2023-09-01 We need some storage space to keep track of c2 value per stop - // when we are filtering out possible transfers. - // I choose to include it here because it is practical but maybe we should have it somewhere else? - private final int[] c2PerStopPosition; - /** * Copy-constructor - do a deep copy with the exception of immutable types. Always start with the * desired head. The constructor will recursively copy the entire path (all legs until {@code @@ -60,9 +55,7 @@ private PathBuilderLeg(PathBuilderLeg other) { this.fromTime = other.fromTime; this.toTime = other.toTime; this.leg = other.leg; - // TODO: 2023-09-01 We have to copy over values here so that they are present - // in the next filter loop cycle - this.c2PerStopPosition = other.c2PerStopPosition.clone(); + this.c2 = other.c2; // Mutable fields if (other.next != null) { @@ -78,14 +71,10 @@ private PathBuilderLeg(MyLeg leg) { var transit = (MyTransitLeg) leg; this.fromTime = transit.fromTime(); this.toTime = transit.toTime(); - c2PerStopPosition = new int[transit.trip.pattern().numberOfStopsInPattern()]; - Arrays.fill(c2PerStopPosition, NOT_SET); - } else { - c2PerStopPosition = new int[0]; } } - /* factory methods */ + /* accessors */ public int fromTime() { return fromTime; @@ -107,8 +96,6 @@ public int toStop() { return leg.toStop(); } - /* accessors */ - public int toStopPos() { return asTransitLeg().toStopPos(); } @@ -117,25 +104,16 @@ public int durationInSec() { return toTime - fromTime; } - /** - * Get c2 value associate with given stop position in a pattern. - * This works only for transit legs and if c2 is already set - */ - @Deprecated - public int c2ForStopPosition(int pos) { - var c2 = c2PerStopPosition[pos]; - if (c2 == NOT_SET) { - throw new IllegalArgumentException("C2 for stop position " + pos + " not set"); - } + public int c2() { return c2; } - /** - * Set c2 value on a given stop position in a transit leg - */ - @Deprecated - public void setC2OnStopPosition(int pos, int c2) { - c2PerStopPosition[pos] = c2; + public void c2(int c2) { + this.c2 = c2; + } + + public boolean isC2Set() { + return c2 != RaptorConstants.NOT_SET; } @Nullable @@ -180,6 +158,10 @@ public T trip() { return asTransitLeg().trip; } + public PathBuilderLeg prev() { + return prev; + } + public PathBuilderLeg next() { return next; } @@ -328,10 +310,6 @@ static PathBuilderLeg egress(RaptorAccessEgres return new PathBuilderLeg<>(new MyEgressLeg(egress)); } - PathBuilderLeg prev() { - return prev; - } - void setPrev(PathBuilderLeg prev) { this.prev = prev; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 10e0d5be1b5..4895f21441a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -4,16 +4,28 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; /** - * TODO PT - This need JavaDoc + * Filter path tails for a given stopPosition during the optimization process. The algorithm only + * feed in paths witch can be compared. If the head is alighted at a position witch gives it an + * advantage over another path (accept boarding at more stops), then the paths are split into + * different sets and the filter is called for each set. */ public interface PathTailFilter { /** - * TODO PT - This need JavaDoc + * Filter path while building the paths. The {@code head} of each path is guranteed to be a + * transit leg, and the {@code boardStopPosition} is guranteed to be the last position in the + * head leg witch will be used for boarding. The {@code boardStopPosition} should be used when + * calculating the property witch is used for comparason. If the comparison can be done without + * looking at a stop-position, then this can be ignored. */ - Set> filterIntermediateResult(Set> elements); + Set> filterIntermediateResult( + Set> elements, + int boardStopPosition + ); /** - * TODO PT - This need JavaDoc + * Filter the paths one last time. The {@code head} is no guaranteed to be the access-leg. This + * can be used to insert values into the path or checking if all requirements are meet. + * */ Set> filterFinalResult(Set> elements); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java index 25fe83661a5..516ad536e20 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java @@ -1,8 +1,7 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model; -import java.util.List; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; public interface PathTailFilterFactory { - PathTailFilter createFilter(List>> possibleTransfers); + PathTailFilter createFilter(); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java index 374298299ad..3feb4cb5a1c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilter.java @@ -29,13 +29,21 @@ class MinCostPathTailFilter implements PathTailFil } @Override - public Set> filterIntermediateResult(Set> elements) { + public Set> filterIntermediateResult( + Set> elements, + int boardStopPosition + ) { for (var costFunction : costFunctions) { elements = filter(elements, costFunction); } return elements; } + @Override + public Set> filterFinalResult(Set> elements) { + return filterIntermediateResult(elements, 0); + } + private Set> filter( Set> elements, ToIntFunction> costFunction @@ -56,9 +64,4 @@ private Set> filter( } return result; } - - @Override - public Set> filterFinalResult(Set> elements) { - return elements; - } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java index 79834e6cb30..b36de46a43e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java @@ -7,7 +7,6 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; public class MinCostPathTailFilterFactory implements PathTailFilterFactory { @@ -21,7 +20,7 @@ public MinCostPathTailFilterFactory(boolean transferPriority, boolean optimizeWa } @Override - public PathTailFilter createFilter(List>> possibleTransfers) { + public PathTailFilter createFilter() { List>> filters = new ArrayList<>(3); if (transferPriority) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java index 55df9a19be2..717564cad11 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java @@ -5,7 +5,6 @@ import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; /** * Create a filter chain function and find the best combination of transfers for the journey @@ -57,11 +56,7 @@ public PassThroughFilterFactory( } @Override - public PathTailFilter createFilter(List>> possibleTransfers) { - var calculator = new PathTailC2Calculator<>(possibleTransfers, passThroughPoints); - return new PassThroughPathTailFilter<>( - delegate.createFilter(possibleTransfers), - calculator::calculateC2 - ); + public PathTailFilter createFilter() { + return new PassThroughPathTailFilter<>(delegate.createFilter(), passThroughPoints); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index c037fcb8be5..99838d2c085 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -3,41 +3,56 @@ import static java.util.stream.Collectors.toSet; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; class PassThroughPathTailFilter implements PathTailFilter { private final PathTailFilter filterChain; - private final Function, Integer> getC2; + private final PathTailC2Calculator c2Calculator; public PassThroughPathTailFilter( PathTailFilter filterChain, - Function, Integer> getC2 + List passThroughPoints ) { this.filterChain = filterChain; - this.getC2 = getC2; + this.c2Calculator = new PathTailC2Calculator(passThroughPoints); } @Override - public Set> filterIntermediateResult(Set> elements) { + public Set> filterIntermediateResult( + Set> elements, + int boardStopPosition + ) { Map>> elementsByC2Value = elements .stream() - .collect(Collectors.groupingBy(getC2, toSet())); - Set> result = new HashSet<>(); - for (Integer c2 : elementsByC2Value.keySet()) { - result.addAll(filterChain.filterIntermediateResult(elementsByC2Value.get(c2))); + .collect( + Collectors.groupingBy( + it -> c2Calculator.calculateC2AtStopPos(it, boardStopPosition), + toSet() + ) + ); + var result = new HashSet>(); + for (var set : elementsByC2Value.values()) { + result.addAll(filterChain.filterIntermediateResult(set, boardStopPosition)); } return result; } @Override public Set> filterFinalResult(Set> elements) { - return elements.stream().filter(tail -> getC2.apply(tail) == 0).collect(toSet()); + Set> result = elements + .stream() + .peek(c2Calculator::calculateC2) + .filter(it -> it.head().c2() == 0) + .collect(toSet()); + + return filterChain.filterFinalResult(result); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java new file mode 100644 index 00000000000..017d360637f --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java @@ -0,0 +1,57 @@ +package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; + +import java.util.BitSet; +import java.util.List; +import org.opentripplanner.raptor.api.request.PassThroughPoint; + +/** + * Iterate over the pass-though-points. Note! this class iterate backwards starting at the last + * pass-through-point and forward. + */ +class PassThroughPointsIterator { + + private final List passThroughPoints; + private int index; + private BitSet current; + + private PassThroughPointsIterator(List passThroughPoints, int c2) { + this.passThroughPoints = passThroughPoints; + this.index = c2; + next(); + } + + /** + * Iterate from the given {@code c2} value (pass-through-pint number minus one) towards the + * beginning. + */ + static PassThroughPointsIterator tailIterator(List passThroughPoints, int c2) { + return new PassThroughPointsIterator(passThroughPoints, c2); + } + + /** + * Iterate over the pass-though-points starting at the end/destination and towards the beginning + * or the points, until the origin is reached. + */ + static PassThroughPointsIterator tailIterator(List passThroughPoints) { + return new PassThroughPointsIterator(passThroughPoints, passThroughPoints.size()); + } + + /** + * The current c2 value reached by the iterator. + */ + int currC2() { + return index + 1; + } + + /** + * Go to the next element (move to the previous pass-though-point) + */ + void next() { + --index; + this.current = index < 0 ? null : passThroughPoints.get(index).asBitSet(); + } + + boolean isPassThroughPoint(int stopIndex) { + return current != null && current.get(stopIndex); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java index 3ead0e21099..45605d45062 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PathTailC2Calculator.java @@ -1,110 +1,79 @@ package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; -import java.util.ArrayList; import java.util.List; -import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.path.PathBuilderLeg; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; -import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; -class PathTailC2Calculator { +class PathTailC2Calculator { - private final List>> possibleTransfers; private final List passThroughPoints; - /** - * @param possibleTransfers List all possible transfers for the whole journey - * @param passThroughPoints Pass-through points for the search - */ - public PathTailC2Calculator( - List>> possibleTransfers, - List passThroughPoints - ) { - this.possibleTransfers = possibleTransfers; + PathTailC2Calculator(List passThroughPoints) { this.passThroughPoints = passThroughPoints; } - /** - * Loop through all possible boarding positions in OptimizedPathTail and calculate potential c2 - * value given position. - * - * @param tail Current OptimizedPathTail - * @return c2 value for the earliest possible boarding position. - */ - int calculateC2(OptimizedPathTail tail) { - var legs = new ArrayList<>(tail.legsAsStream().toList()); + int calculateC2(OptimizedPathTail tail) { + return calculateAndSetC2ForLeg(tail.head()).currC2(); + } - var transits = legs.stream().filter(PathBuilderLeg::isTransit).toList(); + int calculateC2AtStopPos(OptimizedPathTail tail, int fromStopPos) { + return calculateC2ForLeg(tail.head(), fromStopPos); + } - int c2; - if (transits.size() == 1) { - // That's the last transit in the journey we are checking. There can't be any via stops yet. - // This should be via.size - // TODO: 2023-09-01 Here we need to know how many pass through points we have so that we can - // set initial C2 value. That's why I added size() method to PassThroughPoints - c2 = passThroughPoints.size(); - } else { - // Check c2 value on board position from previous transit leg. - var previousTransit = transits.get(1); - c2 = previousTransit.c2ForStopPosition(previousTransit.fromStopPos()); - } + private int calculateC2ForLeg(PathBuilderLeg curr, int fromStopPos) { + var ptpIter = calculateAndSetC2ForLeg(curr); + calculateC2AtStopPos(curr, fromStopPos, ptpIter); + return ptpIter.currC2(); + } + + private PassThroughPointsIterator calculateAndSetC2ForLeg(PathBuilderLeg tail) { + PassThroughPointsIterator ptpIter; + + var curr = findFirstLegWithC2Set(tail); - var transitLeg = transits.get(0); - int fromStopPosition; - if (possibleTransfers.size() + 1 == transits.size()) { - // Here we reached the first transit in the journey. - // We do not have to check possible transfers. - fromStopPosition = transitLeg.fromStopPos(); + if (curr.isEgress()) { + ptpIter = PassThroughPointsIterator.tailIterator(passThroughPoints); + curr.c2(ptpIter.currC2()); } else { - // This should be the earliest possible board stop position for a leg. - // We can verify that with transfers. - fromStopPosition = - possibleTransfers - .get(possibleTransfers.size() - transits.size()) - .stream() - .map(t -> t.to().stopPosition()) - .reduce((first, second) -> first < second ? first : second) - .get(); + ptpIter = PassThroughPointsIterator.tailIterator(passThroughPoints, curr.c2()); } - // TODO: 2023-09-01 here we are modifying existing transit legs and setting new c2 values on them. - // The value is gonna persist into the next loop so that we know what c2 value we had - // on previous (next) leg - // This is kinda anti-pattern. But I do not know how to solve it in other way without rewriting - // the whole filter logic. - // We already visited all via stops. - // Don't have to check anything. Just set on all stop positions. - if (c2 == 0) { - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - transitLeg.setC2OnStopPosition(pos, 0); + while (curr != tail) { + if (curr.isTransit()) { + calculateC2AtStopPos(curr, curr.fromStopPos(), ptpIter); } - } else { - // Loop through all possible boarding position and calculate potential c2 value - for (int pos = transitLeg.toStopPos(); pos >= fromStopPosition; pos--) { - var stopIndex = transitLeg.trip().pattern().stopIndex(pos); - // TODO: 2023-09-01 We need to check here whether point is a pass through for a given - // c2 value. That's why I need to include this method in PassThroughPoint class. - // This might not be the best solution. What are the other options we have? - if (isPassThroughPoint(passThroughPoints, c2, stopIndex)) { - c2--; - } + curr = curr.prev(); + curr.c2(ptpIter.currC2()); + } + curr.c2(ptpIter.currC2()); + return ptpIter; + } - transitLeg.setC2OnStopPosition(pos, c2); + /** + * Find the first leg that has the c2 value set - starting with the given leg and ending with + * the egress leg. If no c2 value is set in the tail, then the egress leg is returned. + */ + private PathBuilderLeg findFirstLegWithC2Set(PathBuilderLeg tail) { + while (!tail.isEgress()) { + if (tail.isC2Set()) { + return tail; } + tail = tail.next(); } - return transitLeg.c2ForStopPosition(fromStopPosition); + return tail; } - private boolean isPassThroughPoint( - List passThroughPoints, - int c2, - int stopIndex + private void calculateC2AtStopPos( + PathBuilderLeg leg, + int stopPos, + PassThroughPointsIterator ptpIter ) { - if (c2 == 0) { - return false; + var pattern = leg.trip().pattern(); + for (int pos = leg.toStopPos(); pos >= stopPos; --pos) { + if (ptpIter.isPassThroughPoint(pattern.stopIndex(pos))) { + ptpIter.next(); + } } - var point = passThroughPoints.get(c2 - 1); - return point != null && point.asBitSet().get(stopIndex); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index caa20d68568..7bf245f5b14 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -104,9 +104,15 @@ public OptimizePathDomainService( } public Set> findBestTransitPath(RaptorPath originalPath) { - var possibleTransfers = findPossibleTransfers(originalPath); List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - var filter = filterFactory.createFilter(possibleTransfers); + + // Find all possible transfers between each pair of transit legs, and sort on arrival time + var possibleTransfers = sortTransfersOnArrivalTimeInDecOrder( + transferGenerator.findAllPossibleTransfers(transitLegs) + ); + + // TODO : Is the factory needed? + var filter = filterFactory.createFilter(); // Combine transit legs and transfers var tails = findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter); @@ -116,17 +122,6 @@ public Set> findBestTransitPath(RaptorPath originalPath) { return filteredTails.stream().map(OptimizedPathTail::build).collect(toSet()); } - /** - * Find all possible transfers between each pair of transit legs, and sort on arrival time. - */ - private List>> findPossibleTransfers(RaptorPath originalPath) { - List> transitLegs = originalPath.transitLegs().collect(Collectors.toList()); - - return sortTransfersOnArrivalTimeInDecOrder( - transferGenerator.findAllPossibleTransfers(transitLegs) - ); - } - private static T last(List list) { return list.get(list.size() - 1); } @@ -178,13 +173,13 @@ private Set> findBestTransferOption( tails = new HashSet<>(); for (TripToTripTransfer tx : transfers) { - // Skip transfers happening before earliest possible board time + // Skip transfers happening before the earliest possible board time if (tx.from().time() <= earliestDepartureTimeFromLeg) { continue; } // Find the best tails that are safe to board with respect to the arrival - var candidateTails = tailSelector.next(tx.to().time()); + var candidateTails = tailSelector.next(tx.to().stopPosition()); for (OptimizedPathTail tail : candidateTails) { // Tail can be used with current transfer @@ -196,7 +191,9 @@ private Set> findBestTransferOption( } // Filter tails one final time - tails = new TransitPathLegSelector<>(filter, tails).next(originalPath.accessLeg().toTime()); + tails = + new TransitPathLegSelector<>(filter, tails) + .next(originalPath.accessLeg().nextTransitLeg().getFromStopPosition()); // Insert the access leg and the following transfer insertAccess(originalPath, tails); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java index f77fbddedfa..9c1af6fc1a4 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelector.java @@ -2,7 +2,6 @@ import java.util.HashSet; import java.util.Set; -import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; @@ -33,7 +32,7 @@ class TransitPathLegSelector { private Set> remindingLegs; private Set> selectedLegs; - private int lastLimit = Integer.MAX_VALUE; + private int prevStopPosition = Integer.MAX_VALUE; TransitPathLegSelector(final PathTailFilter filter, final Set> legs) { this.filter = filter; @@ -41,23 +40,23 @@ class TransitPathLegSelector { this.selectedLegs = new HashSet<>(); } - Set> next(final int earliestBoardingTime) { - if (earliestBoardingTime > lastLimit) { + Set> next(final int fromStopPosition) { + if (fromStopPosition > prevStopPosition) { throw new IllegalStateException( "The next method must be called with decreasing time limits. " + - "minTimeLimit=" + - TimeUtils.timeToStrLong(earliestBoardingTime) + - ", lastLimit=" + - TimeUtils.timeToStrLong(lastLimit) + "fromStopPosition=" + + fromStopPosition + + ", previousStopPosition=" + + prevStopPosition ); } - lastLimit = earliestBoardingTime; + prevStopPosition = fromStopPosition; Set> candidates = new HashSet<>(); Set> rest = new HashSet<>(); for (OptimizedPathTail it : remindingLegs) { - if (earliestBoardingTime < it.latestPossibleBoardingTime()) { + if (fromStopPosition < it.head().toStopPos()) { candidates.add(it); } else { rest.add(it); @@ -72,7 +71,7 @@ Set> next(final int earliestBoardingTime) { // Set state remindingLegs = rest; - selectedLegs = filter.filterIntermediateResult(candidates); + selectedLegs = filter.filterIntermediateResult(candidates, fromStopPosition); return selectedLegs; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java index 11d388d81ab..910789edbd5 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterTest.java @@ -30,8 +30,8 @@ void filterEmptySet() { // filter empty set assertEquals( Set.of(), - new MinCostPathTailFilter(List.of(it -> ((A) it).x)) - .filterIntermediateResult(Set.of()) + new MinCostPathTailFilter(List.of(OptimizedPathTail::generalizedCost)) + .filterIntermediateResult(Set.of(), 0) ); } @@ -62,12 +62,16 @@ void filterTwoEqualVectors() { private Set filter(A... as) { return new MinCostPathTailFilter(List.of(it -> ((A) it).x, it -> ((A) it).y)) - .filterIntermediateResult(Set.of(as)) + .filterIntermediateResult(Set.of(as), 0) .stream() .map(it -> (A) it) .collect(Collectors.toSet()); } + A toA(OptimizedPathTail e) { + return (A) e; + } + static class A extends OptimizedPathTail { /** Name is included in eq/hc to be able to add the "same" [x,y] vector to a set. */ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java index ed6ea660575..09a3b035d65 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java @@ -112,28 +112,26 @@ static List tripWithTwoTransferTestCases() { testCase().points(STOP_C).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_C).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_C).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), - testCase().points(STOP_D).points(STOP_E).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_D).points(STOP_G).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_D).points(STOP_H).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_D).points(STOP_I).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_D).points(STOP_J).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_D).points(STOP_K).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_E, STOP_K).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_I, STOP_H).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_J, STOP_K).expectTransfersFrom(STOP_E, STOP_J), testCase().points(STOP_D).points(STOP_L).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_E).points(STOP_G).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_E).points(STOP_H).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_D).points(STOP_M).expectTransfersFrom(STOP_E, STOP_J), testCase().points(STOP_E).points(STOP_I).expectTransfersFrom(STOP_E, STOP_J), testCase().points(STOP_E).points(STOP_J).expectTransfersFrom(STOP_E, STOP_J), - testCase().points(STOP_E).points(STOP_K).expectTransfersFrom(STOP_E, STOP_J), testCase().points(STOP_E).points(STOP_L).expectTransfersFrom(STOP_E, STOP_J), + testCase().points(STOP_E).points(STOP_M).expectTransfersFrom(STOP_E, STOP_J), testCase().points(STOP_G).points(STOP_H).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_G).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_G).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_G).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_G).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_G).points(STOP_M).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_H).points(STOP_I).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_H).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_H).points(STOP_K).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_H).points(STOP_L).expectTransfersFrom(STOP_C, STOP_H), + testCase().points(STOP_H).points(STOP_M).expectTransfersFrom(STOP_C, STOP_H), testCase().points(STOP_I).points(STOP_J).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_I).points(STOP_L).expectTransfersFrom(STOP_C, STOP_J), testCase().points(STOP_I).points(STOP_M).expectTransfersFrom(STOP_C, STOP_J), @@ -224,6 +222,8 @@ public void tripWithTwoTransfer(TestCase tc) { var subject = subject(tc.points(), firstTransfers, secondTransfers); // When + System.out.println(pathFocus(expectedPath.toString(this::stopIndexToName))); + var result = subject.findBestTransitPath(originalPath); // Then expect a set containing the expected path only diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java index 255b081d529..3b2059cf4d9 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestCase.java @@ -42,7 +42,7 @@ public String toString() { buf.append("at ("); appendPoint(buf, 0); for (int i = 1; i < points.size(); ++i) { - buf.append(") or ("); + buf.append(") and ("); appendPoint(buf, i); } buf.append(")"); @@ -61,7 +61,7 @@ public String toString() { } private void appendPoint(StringBuilder buf, int passThroughPointIndex) { - points.get(passThroughPointIndex).appendStops(buf, " and ", this::stopIndexToName); + points.get(passThroughPointIndex).appendStops(buf, " or ", this::stopIndexToName); } boolean contains(int stopIndex) { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java index 6491423a1f2..5e92adc4d9e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransitPathLegSelectorTest.java @@ -14,7 +14,6 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.path.EgressPathLeg; import org.opentripplanner.raptor.api.path.TransitPathLeg; -import org.opentripplanner.raptor.spi.BoardAndAlightTime; import org.opentripplanner.raptor.spi.DefaultSlackProvider; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; @@ -43,9 +42,13 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { OptimizedPathTail::generalizedCost ); - private final int T10_00 = TimeUtils.time("10:00"); - private final int T10_20 = TimeUtils.time("10:20"); - private final int T10_40 = TimeUtils.time("10:40"); + private final int STOP_TIME_ONE = TimeUtils.time("10:00"); + private final int STOP_TIME_TWO = TimeUtils.time("10:20"); + private final int STOP_TIME_THREE = TimeUtils.time("10:40"); + + private final int STOP_POS_ONE = 0; + private final int STOP_POS_TWO = 1; + private final int STOP_POS_THREE = 2; private final OptimizedPathTail pathTail = new OptimizedPathTail<>( SLACK_PROVIDER, @@ -60,10 +63,10 @@ public class TransitPathLegSelectorTest implements RaptorTestConstants { private final TestTripSchedule TRIP = TestTripSchedule .schedule() .pattern("L1", STOP_A, STOP_C, STOP_E) - .times(T10_00, T10_20, T10_40) + .times(STOP_TIME_ONE, STOP_TIME_TWO, STOP_TIME_THREE) .build(); - private final int EGRESS_START = T10_40 + D1m; + private final int EGRESS_START = STOP_TIME_THREE + D1m; private final int EGRESS_END = EGRESS_START + D5m; @Test @@ -78,10 +81,10 @@ public void testOneElementIsReturnedIfTimeLimitThresholdIsPassed() { var subject = new TransitPathLegSelector<>(FILTER_CHAIN, Set.of(leg)); - var result = subject.next(T10_40); + var result = subject.next(STOP_POS_THREE); assertTrue(result.isEmpty(), result.toString()); - result = subject.next(T10_40 - 1); + result = subject.next(STOP_POS_TWO); assertFalse(result.isEmpty(), result.toString()); } @@ -92,25 +95,14 @@ public void testTwoPathLegs() { var subject = new TransitPathLegSelector<>(FILTER_CHAIN, Set.of(leg1, leg2)); - var result = subject.next(T10_40); + var result = subject.next(STOP_POS_THREE); assertTrue(result.isEmpty(), result.toString()); - result = subject.next(T10_40 - 1); - assertEquals("BUS L1 10:00 10:40", firstRide(result)); - assertEquals(result.size(), 1); - - // No change yet - result = subject.next(T10_20); + result = subject.next(STOP_POS_TWO); assertEquals("BUS L1 10:00 10:40", firstRide(result)); assertEquals(result.size(), 1); - // Get next - result = subject.next(T10_20 - 1); - assertEquals("BUS L1 10:00 10:20", firstRide(result)); - assertEquals(result.size(), 1); - - // Same as previous - result = subject.next(0); + result = subject.next(STOP_POS_ONE); assertEquals("BUS L1 10:00 10:20", firstRide(result)); assertEquals(result.size(), 1); } @@ -132,13 +124,12 @@ private TransitPathLeg transitLeg(int egressStop) { walk.generalizedCost() ); int toTime = TRIP.arrival(TRIP.findArrivalStopPosition(Integer.MAX_VALUE, egressStop)); - var times = BoardAndAlightTime.create(TRIP, STOP_A, T10_00, egressStop, toTime); - int cost = 100 * (T10_40 - T10_00); + int cost = 100 * (STOP_TIME_THREE - STOP_TIME_ONE); return new TransitPathLeg<>( TRIP, - T10_00, + STOP_TIME_ONE, toTime, - TRIP.findDepartureStopPosition(T10_00, STOP_A), + TRIP.findDepartureStopPosition(STOP_TIME_ONE, STOP_A), TRIP.findArrivalStopPosition(toTime, egressStop), null, cost, From 2b537fea66a2ce6ed2a1ad64ceccd8eba884acae Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 15 Oct 2023 22:14:04 +0200 Subject: [PATCH 087/120] refactor: Remove the PathTailFilterFactory The factory does not play a role in the optimized transfer service --- ...ansferOptimizationServiceConfigurator.java | 37 +++++------ .../model/PathTailFilterFactory.java | 7 --- .../MinCostPathTailFilterFactory.java | 5 +- .../passthrough/PassThroughFilterFactory.java | 62 ------------------- .../PassThroughPathTailFilter.java | 37 ++++++++++- .../services/OptimizePathDomainService.java | 10 +-- .../model/passthrough/TestUtils.java | 5 +- .../OptimizePathDomainServiceTest.java | 7 ++- 8 files changed, 65 insertions(+), 105 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java index 1afd291cfcd..a733927f068 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/configure/TransferOptimizationServiceConfigurator.java @@ -10,10 +10,10 @@ import org.opentripplanner.routing.algorithm.transferoptimization.OptimizeTransferService; import org.opentripplanner.routing.algorithm.transferoptimization.api.TransferOptimizationParameters; import org.opentripplanner.routing.algorithm.transferoptimization.model.MinSafeTransferTimeCalculator; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.costfilter.MinCostPathTailFilterFactory; -import org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.PassThroughFilterFactory; +import org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough.PassThroughPathTailFilter; import org.opentripplanner.routing.algorithm.transferoptimization.services.OptimizePathDomainService; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferGenerator; import org.opentripplanner.routing.algorithm.transferoptimization.services.TransferServiceAdaptor; @@ -79,14 +79,11 @@ > OptimizeTransferService createOptimizeTransferService( private OptimizeTransferService createOptimizeTransferService() { var pathTransferGenerator = createTransferGenerator(config.optimizeTransferPriority()); - var filterFactory = createFilterFactory(); - if (config.optimizeTransferWaitTime()) { var transferWaitTimeCalculator = createTransferWaitTimeCalculator(); var transfersPermutationService = createOptimizePathService( pathTransferGenerator, - filterFactory, transferWaitTimeCalculator, transitDataProvider.multiCriteriaCostCalculator() ); @@ -99,7 +96,6 @@ private OptimizeTransferService createOptimizeTransferService() { } else { var transfersPermutationService = createOptimizePathService( pathTransferGenerator, - filterFactory, null, transitDataProvider.multiCriteriaCostCalculator() ); @@ -107,22 +103,8 @@ private OptimizeTransferService createOptimizeTransferService() { } } - private PathTailFilterFactory createFilterFactory() { - PathTailFilterFactory costFilter = new MinCostPathTailFilterFactory<>( - config.optimizeTransferPriority(), - config.optimizeTransferWaitTime() - ); - - if (multiCriteriaRequest.hasPassThroughPoints()) { - return new PassThroughFilterFactory<>(multiCriteriaRequest.passThroughPoints(), costFilter); - } else { - return costFilter; - } - } - private OptimizePathDomainService createOptimizePathService( TransferGenerator transferGenerator, - PathTailFilterFactory filterFactory, TransferWaitTimeCostCalculator transferWaitTimeCostCalculator, RaptorCostCalculator costCalculator ) { @@ -133,7 +115,7 @@ private OptimizePathDomainService createOptimizePathService( transferWaitTimeCostCalculator, stopBoardAlightCosts, config.extraStopBoardAlightCostsFactor(), - filterFactory, + createFilter(), stopNameResolver ); } @@ -156,4 +138,17 @@ private TransferWaitTimeCostCalculator createTransferWaitTimeCalculator() { config.minSafeWaitTimeFactor() ); } + + private PathTailFilter createFilter() { + var filter = new MinCostPathTailFilterFactory( + config.optimizeTransferPriority(), + config.optimizeTransferWaitTime() + ) + .createFilter(); + + if (multiCriteriaRequest.hasPassThroughPoints()) { + filter = new PassThroughPathTailFilter<>(filter, multiCriteriaRequest.passThroughPoints()); + } + return filter; + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java deleted file mode 100644 index 516ad536e20..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilterFactory.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.model; - -import org.opentripplanner.raptor.api.model.RaptorTripSchedule; - -public interface PathTailFilterFactory { - PathTailFilter createFilter(); -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java index b36de46a43e..5bc3440c269 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/costfilter/MinCostPathTailFilterFactory.java @@ -6,10 +6,8 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; -public class MinCostPathTailFilterFactory - implements PathTailFilterFactory { +public class MinCostPathTailFilterFactory { private final boolean transferPriority; private final boolean optimizeWaitTime; @@ -19,7 +17,6 @@ public MinCostPathTailFilterFactory(boolean transferPriority, boolean optimizeWa this.optimizeWaitTime = optimizeWaitTime; } - @Override public PathTailFilter createFilter() { List>> filters = new ArrayList<>(3); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java deleted file mode 100644 index 717564cad11..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughFilterFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.opentripplanner.routing.algorithm.transferoptimization.model.passthrough; - -import java.util.List; -import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.request.PassThroughPoint; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; - -/** - * Create a filter chain function and find the best combination of transfers for the journey - * that also includes all pass-through points. - *

- * The algorithm starts with the last trip in the journey, then goes backwards looping through all - * possible transfers for each transit leg. For each possible transfer stop position the C2-value - * is calculated. The filter chain function is going to use the c2-value and the cost function to - * determine whether the tail should be included or excluded from result. - *

- *Example: - *

- * Let's say we have a trip with 2 transit legs and 3 possible transfer points: AD, BE and CF. - *

- * There are 3 possible transfer combination with the first and second transit: - *

- *    Iteration 1 (initial c2 value is 1 since we have one pass-through point):
- *
- *      ? ~ transit 2 ~ egress | c2 = 1
- *
- *    Iteration 2 (create all possible journey combinations with transfers and calculate c2):
- *
- *      // C2 is 0 since we will pass through E if we board Transit 2 at D
- *      ? ~ transit 1 ~ AD ~ Transit 2 ~ egress | c2 = 0
- *
- *      ? ~ transit 1 ~ BE ~ Transit 2 ~ egress | c2 = 0
- *
- *      // C2 is 1 since we will not pass through E if we board at F
- *      ? ~ transit 1 ~ CF ~ Transit 2 ~ egress | c2 = 1
- *
- *    Iteration 3 (insert access and filter out all combinations where c2 != 0)
- *      access ~ transit 1 ~ AD ~ transit 2 ~ egress | C2 = 0
- *      access ~ transit 1 ~ BE ~ transit 2 ~ egress | C2 = 0
- * 
- * Then we're going to fall back the delegate filter to choose between the two options. - */ -public class PassThroughFilterFactory - implements PathTailFilterFactory { - - private final List passThroughPoints; - private final PathTailFilterFactory delegate; - - public PassThroughFilterFactory( - List passThroughPoints, - PathTailFilterFactory delegate - ) { - this.passThroughPoints = passThroughPoints; - this.delegate = delegate; - } - - @Override - public PathTailFilter createFilter() { - return new PassThroughPathTailFilter<>(delegate.createFilter(), passThroughPoints); - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java index 99838d2c085..f0e6ec8d24b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPathTailFilter.java @@ -12,7 +12,42 @@ import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -class PassThroughPathTailFilter implements PathTailFilter { +/** + * Create a filter chain function and find the best combination of transfers for the journey + * that also includes all pass-through points. + *

+ * The algorithm starts with the last trip in the journey, then goes backwards looping through all + * possible transfers for each transit leg. For each possible transfer stop position the C2-value + * is calculated. The filter chain function is going to use the c2-value and the cost function to + * determine whether the tail should be included or excluded from result. + *

+ *Example: + *

+ * Let's say we have a trip with 2 transit legs and 3 possible transfer points: AD, BE and CF. + *

+ * There are 3 possible transfer combination with the first and second transit: + *

+ *    Iteration 1 (initial c2 value is 1 since we have one pass-through point):
+ *
+ *      ? ~ transit 2 ~ egress | c2 = 1
+ *
+ *    Iteration 2 (create all possible journey combinations with transfers and calculate c2):
+ *
+ *      // C2 is 0 since we will pass through E if we board Transit 2 at D
+ *      ? ~ transit 1 ~ AD ~ Transit 2 ~ egress | c2 = 0
+ *
+ *      ? ~ transit 1 ~ BE ~ Transit 2 ~ egress | c2 = 0
+ *
+ *      // C2 is 1 since we will not pass through E if we board at F
+ *      ? ~ transit 1 ~ CF ~ Transit 2 ~ egress | c2 = 1
+ *
+ *    Iteration 3 (insert access and filter out all combinations where c2 != 0)
+ *      access ~ transit 1 ~ AD ~ transit 2 ~ egress | C2 = 0
+ *      access ~ transit 1 ~ BE ~ transit 2 ~ egress | C2 = 0
+ * 
+ * Then we're going to fall back the delegate filter to choose between the two options. + */ +public class PassThroughPathTailFilter implements PathTailFilter { private final PathTailFilter filterChain; private final PathTailC2Calculator c2Calculator; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java index 7bf245f5b14..e587bff8e70 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainService.java @@ -18,7 +18,6 @@ import org.opentripplanner.routing.algorithm.transferoptimization.api.OptimizedPath; import org.opentripplanner.routing.algorithm.transferoptimization.model.OptimizedPathTail; import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilter; -import org.opentripplanner.routing.algorithm.transferoptimization.model.PathTailFilterFactory; import org.opentripplanner.routing.algorithm.transferoptimization.model.TransferWaitTimeCostCalculator; import org.opentripplanner.routing.algorithm.transferoptimization.model.TripToTripTransfer; @@ -72,7 +71,7 @@ public class OptimizePathDomainService { private final TransferGenerator transferGenerator; private final RaptorCostCalculator costCalculator; private final RaptorSlackProvider slackProvider; - private final PathTailFilterFactory filterFactory; + private final PathTailFilter filter; private final RaptorStopNameResolver stopNameTranslator; @Nullable @@ -90,7 +89,7 @@ public OptimizePathDomainService( @Nullable TransferWaitTimeCostCalculator waitTimeCostCalculator, int[] stopBoardAlightCosts, double extraStopBoardAlightCostsFactor, - PathTailFilterFactory filterFactory, + PathTailFilter filter, RaptorStopNameResolver stopNameTranslator ) { this.transferGenerator = transferGenerator; @@ -99,7 +98,7 @@ public OptimizePathDomainService( this.waitTimeCostCalculator = waitTimeCostCalculator; this.stopBoardAlightCosts = stopBoardAlightCosts; this.extraStopBoardAlightCostsFactor = extraStopBoardAlightCostsFactor; - this.filterFactory = filterFactory; + this.filter = filter; this.stopNameTranslator = stopNameTranslator; } @@ -111,9 +110,6 @@ public Set> findBestTransitPath(RaptorPath originalPath) { transferGenerator.findAllPossibleTransfers(transitLegs) ); - // TODO : Is the factory needed? - var filter = filterFactory.createFilter(); - // Combine transit legs and transfers var tails = findBestTransferOption(originalPath, transitLegs, possibleTransfers, filter); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java index 104e5fc1327..57695d2ac5c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/TestUtils.java @@ -58,7 +58,8 @@ static OptimizePathDomainService subject( List passThroughPoints, final List>... transfers ) { - var filterFactory = new MinCostPathTailFilterFactory(false, false); + var filter = new MinCostPathTailFilterFactory(false, false).createFilter(); + filter = new PassThroughPathTailFilter<>(filter, passThroughPoints); var generator = dummyTransferGenerator(transfers); return new OptimizePathDomainService<>( @@ -68,7 +69,7 @@ static OptimizePathDomainService subject( null, null, 0.0, - new PassThroughFilterFactory<>(passThroughPoints, filterFactory), + filter, (new RaptorTestConstants() {})::stopIndexToName ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index 86f92fc4be5..8b7d589852e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -289,6 +289,11 @@ static OptimizePathDomainService subject( TransferGenerator generator, @Nullable TransferWaitTimeCostCalculator waitTimeCalculator ) { + var filter = new MinCostPathTailFilterFactory( + true, + waitTimeCalculator != null + ) + .createFilter(); return new OptimizePathDomainService<>( generator, COST_CALCULATOR, @@ -296,7 +301,7 @@ static OptimizePathDomainService subject( waitTimeCalculator, null, 0.0, - new MinCostPathTailFilterFactory<>(true, waitTimeCalculator != null), + filter, (new RaptorTestConstants() {})::stopIndexToName ); } From b9c41dcec9fc5483800a343e533e2a335f16c35b Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 16 Oct 2023 08:54:45 +0000 Subject: [PATCH 088/120] Add changelog entry for #5403 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 113120b2857..baa34fc1a89 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -19,6 +19,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix fare calculation for combined interlined legs [#5408](https://github.com/opentripplanner/OpenTripPlanner/pull/5408) - Fix board slack list mapping in Transmodel API [#5420](https://github.com/opentripplanner/OpenTripPlanner/pull/5420) - Fix flexible quay querying in Transmodel API [#5417](https://github.com/opentripplanner/OpenTripPlanner/pull/5417) +- Add validation for missing calls in SIRI update [#5403](https://github.com/opentripplanner/OpenTripPlanner/pull/5403) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 3aa96931f58efb0103bb9d30185eff9382e2e1ac Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 12:35:39 +0300 Subject: [PATCH 089/120] Fix schema comment formatting --- src/ext/graphql/transmodelapi/schema.graphql | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 5f17dd5c16d..b2bf6fb52c6 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1528,8 +1528,8 @@ enum MultiModalMode { enum OccupancyStatus { """ The vehicle or carriage can currently accommodate only standing passengers and has limited - space for them. There isn't a big difference between this and `full` so it's possible to handle - them as the same value, if one wants to limit the number of different values. + space for them. There isn't a big difference between this and `full` so it's possible to + handle them as the same value, if one wants to limit the number of different values. SIRI nordic profile: merge into `standingRoomOnly`. """ crushedStandingRoomOnly @@ -1546,7 +1546,10 @@ enum OccupancyStatus { SIRI nordic profile: less than ~50% of seats available. """ fewSeatsAvailable - "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board." + """ + The vehicle or carriage is considered full by most measures, but may still be allowing + passengers to board. + """ full """ The vehicle or carriage has a large number of seats available. @@ -1557,8 +1560,8 @@ enum OccupancyStatus { noData """ The vehicle or carriage has no seats or standing room available. - SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed - to alight due to e.g. crowding. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only + allowed to alight due to e.g. crowding. """ notAcceptingPassengers """ From 7793f6914eebe7f34a23ea45ceda8c1bd08db86a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 12:35:58 +0300 Subject: [PATCH 090/120] Return immutable sets and fix default value --- .../standalone/config/framework/json/ParameterBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index cd5e9a202b2..0302fce4d3a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -234,7 +234,8 @@ public > Set asEnumSet(Class enumClass) { it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); + // Set is immutable + return result.isEmpty() ? Set.of() : Set.copyOf(result); } public > Set asEnumSet(Class enumClass, Collection defaultValues) { @@ -247,7 +248,8 @@ public > Set asEnumSet(Class enumClass, Collection de it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); + // Set is immutable + return result.isEmpty() ? Set.copyOf(dft) : Set.copyOf(result); } /** From efaabe3b77589bf3a7953b8af66f1dc5b7a6794e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:16:57 +0000 Subject: [PATCH 091/120] fix(deps): update geotools.version to v30 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c26e6980914..da225be8c89 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 121 - 29.2 + 30.0 2.48.1 2.15.3 3.1.3 From 748c461c56c7d5457905166c3e039e1a9582c524 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 13 Oct 2023 22:45:21 +0200 Subject: [PATCH 092/120] Run ant script to migrate to new APIs --- pom.xml | 2 +- .../ext/traveltime/IsochroneRenderer.java | 2 +- .../ext/traveltime/RasterRenderer.java | 8 +-- .../framework/geometry/GeometryUtils.java | 2 +- .../issue/report/GeoJsonWriter.java | 2 +- .../module/ned/ElevationModule.java | 10 ++-- .../ned/GeotiffGridCoverageFactoryImpl.java | 2 +- .../ned/NEDGridCoverageFactoryImpl.java | 2 +- .../module/ned/NoDataGridCoverage.java | 49 +++++++++---------- .../module/ned/UnifiedGridCoverage.java | 16 +++--- .../ned/ElevationGridCoverageFactory.java | 2 +- .../model/elevation/ElevationUtils.java | 12 ++--- .../OtpArchitectureModules.java | 2 +- 13 files changed, 55 insertions(+), 56 deletions(-) diff --git a/pom.xml b/pom.xml index da225be8c89..59a2bfc272b 100644 --- a/pom.xml +++ b/pom.xml @@ -641,7 +641,7 @@ org.geotools - gt-opengis + gt-api ${geotools.version} diff --git a/src/ext/java/org/opentripplanner/ext/traveltime/IsochroneRenderer.java b/src/ext/java/org/opentripplanner/ext/traveltime/IsochroneRenderer.java index e4c055daf24..2c4f2ed1ac1 100644 --- a/src/ext/java/org/opentripplanner/ext/traveltime/IsochroneRenderer.java +++ b/src/ext/java/org/opentripplanner/ext/traveltime/IsochroneRenderer.java @@ -4,13 +4,13 @@ import java.util.ArrayList; import java.util.List; import org.geojson.MultiPolygon; +import org.geotools.api.feature.simple.SimpleFeatureType; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.locationtech.jts.geom.Geometry; -import org.opengis.feature.simple.SimpleFeatureType; import org.opentripplanner.ext.traveltime.geometry.DelaunayIsolineBuilder; import org.opentripplanner.ext.traveltime.geometry.ZMetric; import org.opentripplanner.ext.traveltime.geometry.ZSampleGrid; diff --git a/src/ext/java/org/opentripplanner/ext/traveltime/RasterRenderer.java b/src/ext/java/org/opentripplanner/ext/traveltime/RasterRenderer.java index 8168b59efda..6343c6a1f68 100644 --- a/src/ext/java/org/opentripplanner/ext/traveltime/RasterRenderer.java +++ b/src/ext/java/org/opentripplanner/ext/traveltime/RasterRenderer.java @@ -5,6 +5,8 @@ import jakarta.ws.rs.core.StreamingOutput; import java.awt.image.DataBuffer; import javax.media.jai.RasterFactory; +import org.geotools.api.parameter.GeneralParameterValue; +import org.geotools.api.parameter.ParameterValueGroup; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridEnvelope2D; @@ -13,12 +15,10 @@ import org.geotools.gce.geotiff.GeoTiffFormat; import org.geotools.gce.geotiff.GeoTiffWriteParams; import org.geotools.gce.geotiff.GeoTiffWriter; -import org.geotools.geometry.Envelope2D; +import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.locationtech.jts.geom.Coordinate; -import org.opengis.parameter.GeneralParameterValue; -import org.opengis.parameter.ParameterValueGroup; import org.opentripplanner.ext.traveltime.geometry.ZSampleGrid; public class RasterRenderer { @@ -49,7 +49,7 @@ static StreamingOutput createGeoTiffRaster(ZSampleGrid sampleGrid) { raster.setSample(s.getX() - minX, maxY - s.getY(), 0, z.wTime / z.w); } - Envelope2D geom = new GridGeometry2D( + ReferencedEnvelope geom = new GridGeometry2D( new GridEnvelope2D(0, 0, width, height), new AffineTransform2D(resX, 0, 0, resY, center.x + resX * minX, center.y + resY * minY), DefaultGeographicCRS.WGS84 diff --git a/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java b/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java index 27725f5ddf4..7e9335b1ad8 100644 --- a/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java +++ b/src/main/java/org/opentripplanner/framework/geometry/GeometryUtils.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import org.geojson.GeoJsonObject; import org.geojson.LngLatAlt; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.referencing.CRS; import org.locationtech.jts.algorithm.ConvexHull; import org.locationtech.jts.geom.Coordinate; @@ -27,7 +28,6 @@ import org.locationtech.jts.linearref.LengthLocationMap; import org.locationtech.jts.linearref.LinearLocation; import org.locationtech.jts.linearref.LocationIndexedLine; -import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/opentripplanner/graph_builder/issue/report/GeoJsonWriter.java b/src/main/java/org/opentripplanner/graph_builder/issue/report/GeoJsonWriter.java index a0123b41631..656daff1bbd 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issue/report/GeoJsonWriter.java +++ b/src/main/java/org/opentripplanner/graph_builder/issue/report/GeoJsonWriter.java @@ -3,13 +3,13 @@ import java.io.IOException; import java.util.Collection; import org.geojson.MultiPolygon; +import org.geotools.api.feature.simple.SimpleFeatureType; import org.geotools.data.geojson.GeoJSONWriter; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.crs.DefaultGeographicCRS; -import org.opengis.feature.simple.SimpleFeatureType; import org.opentripplanner.datastore.api.CompositeDataSource; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java index fa5432bd321..592d34fb543 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java @@ -18,13 +18,13 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import org.geotools.geometry.DirectPosition2D; +import org.geotools.api.coverage.Coverage; +import org.geotools.api.coverage.PointOutsideCoverageException; +import org.geotools.api.referencing.operation.TransformException; +import org.geotools.geometry.Position2D; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.impl.PackedCoordinateSequence; -import org.opengis.coverage.Coverage; -import org.opengis.coverage.PointOutsideCoverageException; -import org.opengis.referencing.operation.TransformException; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; @@ -564,7 +564,7 @@ private double getElevation(Coverage coverage, double x, double y) // GeoTIFFs in various projections. Note that GeoTools defaults to strict EPSG axis ordering of (lat, long) // for DefaultGeographicCRS.WGS84, but OTP is using (long, lat) throughout and assumes unprojected DEM // rasters to also use (long, lat). - coverage.evaluate(new DirectPosition2D(GeometryUtils.WGS84_XY, x, y), values); + coverage.evaluate(new Position2D(GeometryUtils.WGS84_XY, x, y), values); } catch (PointOutsideCoverageException e) { nPointsOutsideDEM.incrementAndGet(); throw e; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/GeotiffGridCoverageFactoryImpl.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/GeotiffGridCoverageFactoryImpl.java index 1f00afac322..1155e9dbb66 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/ned/GeotiffGridCoverageFactoryImpl.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/GeotiffGridCoverageFactoryImpl.java @@ -3,12 +3,12 @@ import java.io.File; import java.io.IOException; import javax.media.jai.InterpolationBilinear; +import org.geotools.api.coverage.grid.GridCoverage; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.Interpolator2D; import org.geotools.gce.geotiff.GeoTiffFormat; import org.geotools.gce.geotiff.GeoTiffReader; import org.geotools.util.factory.Hints; -import org.opengis.coverage.grid.GridCoverage; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDGridCoverageFactoryImpl.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDGridCoverageFactoryImpl.java index 04e80909263..dbbea1ef0f5 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDGridCoverageFactoryImpl.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDGridCoverageFactoryImpl.java @@ -11,8 +11,8 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import org.geotools.api.coverage.Coverage; import org.geotools.coverage.grid.GridCoverage2D; -import org.opengis.coverage.Coverage; import org.opentripplanner.graph_builder.services.ned.ElevationGridCoverageFactory; import org.opentripplanner.graph_builder.services.ned.NEDTileSource; import org.opentripplanner.routing.graph.Graph; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/NoDataGridCoverage.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/NoDataGridCoverage.java index 7f415e6389d..9da8c45c348 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/ned/NoDataGridCoverage.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/NoDataGridCoverage.java @@ -6,18 +6,18 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import org.geotools.api.coverage.CannotEvaluateException; +import org.geotools.api.coverage.PointOutsideCoverageException; +import org.geotools.api.coverage.SampleDimension; +import org.geotools.api.coverage.grid.GridCoverage; +import org.geotools.api.coverage.grid.GridGeometry; +import org.geotools.api.geometry.Bounds; +import org.geotools.api.geometry.Position; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; +import org.geotools.api.util.Record; +import org.geotools.api.util.RecordType; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.util.CoverageUtilities; -import org.opengis.coverage.CannotEvaluateException; -import org.opengis.coverage.PointOutsideCoverageException; -import org.opengis.coverage.SampleDimension; -import org.opengis.coverage.grid.GridCoverage; -import org.opengis.coverage.grid.GridGeometry; -import org.opengis.geometry.DirectPosition; -import org.opengis.geometry.Envelope; -import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.util.Record; -import org.opengis.util.RecordType; public class NoDataGridCoverage implements GridCoverage { @@ -75,7 +75,7 @@ public CoordinateReferenceSystem getCoordinateReferenceSystem() { } @Override - public Envelope getEnvelope() { + public Bounds getEnvelope() { return gridCoverage.getEnvelope(); } @@ -86,44 +86,43 @@ public RecordType getRangeType() { // Override the evaluate methods, so that a PointOutsideCoverageException is thrown for NO_DATE values @Override - public Set evaluate(DirectPosition directPosition, Collection collection) - throws PointOutsideCoverageException, CannotEvaluateException { + public Set evaluate(Position directPosition, Collection collection) + throws CannotEvaluateException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public Object evaluate(DirectPosition directPosition) - throws PointOutsideCoverageException, CannotEvaluateException { + public Object evaluate(Position directPosition) throws CannotEvaluateException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public boolean[] evaluate(DirectPosition directPosition, boolean[] booleans) - throws PointOutsideCoverageException, CannotEvaluateException, ArrayIndexOutOfBoundsException { + public boolean[] evaluate(Position directPosition, boolean[] booleans) + throws CannotEvaluateException, ArrayIndexOutOfBoundsException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public byte[] evaluate(DirectPosition directPosition, byte[] bytes) - throws PointOutsideCoverageException, CannotEvaluateException, ArrayIndexOutOfBoundsException { + public byte[] evaluate(Position directPosition, byte[] bytes) + throws CannotEvaluateException, ArrayIndexOutOfBoundsException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public int[] evaluate(DirectPosition directPosition, int[] ints) - throws PointOutsideCoverageException, CannotEvaluateException, ArrayIndexOutOfBoundsException { + public int[] evaluate(Position directPosition, int[] ints) + throws CannotEvaluateException, ArrayIndexOutOfBoundsException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public float[] evaluate(DirectPosition directPosition, float[] floats) - throws PointOutsideCoverageException, CannotEvaluateException, ArrayIndexOutOfBoundsException { + public float[] evaluate(Position directPosition, float[] floats) + throws CannotEvaluateException, ArrayIndexOutOfBoundsException { throw new UnsupportedOperationException("This methods is unsupported"); } @Override - public double[] evaluate(DirectPosition directPosition, double[] dest) - throws PointOutsideCoverageException, CannotEvaluateException, ArrayIndexOutOfBoundsException { + public double[] evaluate(Position directPosition, double[] dest) + throws CannotEvaluateException, ArrayIndexOutOfBoundsException { gridCoverage.evaluate(directPosition, dest); if (this.noData != null && this.noData.getAsSingleValue() == dest[0]) { throw new PointOutsideCoverageException("Value is NO_DATA."); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java index 28a31e7b1a8..943f1ff6031 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java @@ -3,6 +3,11 @@ import java.util.ArrayList; import java.util.List; import javax.media.jai.InterpolationBilinear; +import org.geotools.api.coverage.CannotEvaluateException; +import org.geotools.api.coverage.Coverage; +import org.geotools.api.coverage.PointOutsideCoverageException; +import org.geotools.api.coverage.SampleDimension; +import org.geotools.api.geometry.Position; import org.geotools.coverage.AbstractCoverage; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.Interpolator2D; @@ -11,11 +16,6 @@ import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.index.SpatialIndex; import org.locationtech.jts.index.strtree.STRtree; -import org.opengis.coverage.CannotEvaluateException; -import org.opengis.coverage.Coverage; -import org.opengis.coverage.PointOutsideCoverageException; -import org.opengis.coverage.SampleDimension; -import org.opengis.geometry.DirectPosition; /** * Stitches together multiple elevation maps into a single elevation map, hackily. This is @@ -75,8 +75,8 @@ protected UnifiedGridCoverage(List regionCoverages, List Date: Mon, 16 Oct 2023 12:23:27 +0200 Subject: [PATCH 093/120] Update docs Co-authored-by: Zsombor Welker --- .../org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index af2156fd40e..ffe46604744 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -13,7 +13,7 @@ /** * A custom analyzer for stop names. It removes english stop words (at,the...) and splits - * the input into ing NGrams (https://en.wikipedia.org/wiki/N-gram) so that the middle + * the input into NGrams (https://en.wikipedia.org/wiki/N-gram) so that the middle * of a stop name can be matched efficiently. *

* For example the query of "exanderpl" will match the stop name "Alexanderplatz". From 8da27c493838f4100aef182f2f3ed1353d996784 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 16 Oct 2023 12:21:21 +0000 Subject: [PATCH 094/120] Add changelog entry for #5372 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index baa34fc1a89..0e78be11ece 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -20,6 +20,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix board slack list mapping in Transmodel API [#5420](https://github.com/opentripplanner/OpenTripPlanner/pull/5420) - Fix flexible quay querying in Transmodel API [#5417](https://github.com/opentripplanner/OpenTripPlanner/pull/5417) - Add validation for missing calls in SIRI update [#5403](https://github.com/opentripplanner/OpenTripPlanner/pull/5403) +- Import Occupancy Status from GTFS-RT Vehicle Positions [#5372](https://github.com/opentripplanner/OpenTripPlanner/pull/5372) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From d3b28019cc1ac0eae7c09ce01b1105e482ab9a86 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 16 Oct 2023 15:40:04 +0200 Subject: [PATCH 095/120] Apply suggestions from code review --- .../transferoptimization/model/PathTailFilter.java | 6 +++--- .../model/passthrough/PassThroughPointsIterator.java | 4 ++-- .../model/passthrough/PassThroughNoTransfersTest.java | 2 +- .../model/passthrough/PassThroughOneTransferTest.java | 6 +++--- .../model/passthrough/PassThroughTwoTransfersTest.java | 3 +-- .../model/passthrough/WalkDurationForStopCombinations.java | 6 +++--- .../transferoptimization/services/TestTransferBuilder.java | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 4895f21441a..9e4edb42fef 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -5,7 +5,7 @@ /** * Filter path tails for a given stopPosition during the optimization process. The algorithm only - * feed in paths witch can be compared. If the head is alighted at a position witch gives it an + * feed in paths witch can be compared. If the head is alighted at a position which gives it an * advantage over another path (accept boarding at more stops), then the paths are split into * different sets and the filter is called for each set. */ @@ -14,7 +14,7 @@ public interface PathTailFilter { * Filter path while building the paths. The {@code head} of each path is guranteed to be a * transit leg, and the {@code boardStopPosition} is guranteed to be the last position in the * head leg witch will be used for boarding. The {@code boardStopPosition} should be used when - * calculating the property witch is used for comparason. If the comparison can be done without + * calculating the property witch is used for comparison. If the comparison can be done without * looking at a stop-position, then this can be ignored. */ Set> filterIntermediateResult( @@ -23,7 +23,7 @@ Set> filterIntermediateResult( ); /** - * Filter the paths one last time. The {@code head} is no guaranteed to be the access-leg. This + * Filter the paths one last time. The {@code head} is not guaranteed to be the access-leg. This * can be used to insert values into the path or checking if all requirements are meet. * */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java index 017d360637f..d910e4076db 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java @@ -21,7 +21,7 @@ private PassThroughPointsIterator(List passThroughPoints, int } /** - * Iterate from the given {@code c2} value (pass-through-pint number minus one) towards the + * Iterate from the given {@code c2} value (pass-through-point number minus one) towards the * beginning. */ static PassThroughPointsIterator tailIterator(List passThroughPoints, int c2) { @@ -30,7 +30,7 @@ static PassThroughPointsIterator tailIterator(List passThrough /** * Iterate over the pass-though-points starting at the end/destination and towards the beginning - * or the points, until the origin is reached. + * of the points, until the origin is reached. */ static PassThroughPointsIterator tailIterator(List passThroughPoints) { return new PassThroughPointsIterator(passThroughPoints, passThroughPoints.size()); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java index 9f0db5fe324..a914f6c8766 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java @@ -63,7 +63,7 @@ static List tripWithoutTransfersTestCases() { /** * The pass-though point used can be the board-, intermediate-, and/or alight-stop. We will also - * test all combinations of these to make sure a pass-though point is only accounted for once. + * test all combinations of these to make sure a pass-through point is only accounted for once. *

* The trip1 used: *

diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java
index 8210f7cbe48..fdbc65a1adc 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java
@@ -17,7 +17,7 @@
 
 /**
  * This test focus on the PASS-THROUGH functionality with two transit legs and one transfer in the
- * path. But between trip 1 and trip 2, there will be may transfer options to choose from.
+ * path. But between trip 1 and trip 2, there may be many transfer options to choose from.
  *
  * 

* FEATURE UNDER TESTED @@ -102,7 +102,7 @@ static List tripWithOneTransferTestCases() { } /** - * In this test we will use trip 1 and 2. We will have one test for each possible pass-though- + * In this test we will use trip 1 and 2. We will have one test for each possible pass-through- * point. We will add 4 transfers between the trips, [from]-[to]: {@code C-G, C-H, D-G, D-H}. We * will also add transfers between B-F and E-I, these transfers can not be used with the access * and egress, because we are not allowed to have two walking legs in a row. We include this @@ -155,7 +155,7 @@ public void tripWithOneTransfer(TestCase tc) { .egress(D1s); // These are illegal transfer for the given path, we add them here to make sure they - // do not interfere with the result. For simpler debugging problems try comment out these + // do not interfere with the result. For simpler debugging problems try commenting out these // lines, just do not forget to comment them back in when the problem is fixed. var subject = subject( tc.points(), diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java index 09a3b035d65..d6dfc9264c2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java @@ -145,7 +145,7 @@ static List tripWithTwoTransferTestCases() { /** * In this test we will test a path with *three* transit legs and 2 transfers. For each transfer - * there is two options, an in total three possible paths: + * there is two options, and in total three possible paths: *

    *
  1. Origin ~ B ~ C ~ G ~ H ~ K ~ M ~ Destination
  2. *
  3. Origin ~ B ~ C ~ G ~ J ~ L ~ M ~ Destination
  4. @@ -222,7 +222,6 @@ public void tripWithTwoTransfer(TestCase tc) { var subject = subject(tc.points(), firstTransfers, secondTransfers); // When - System.out.println(pathFocus(expectedPath.toString(this::stopIndexToName))); var result = subject.findBestTransitPath(originalPath); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java index ecb44226ee1..cfd8f6c4e53 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/WalkDurationForStopCombinations.java @@ -8,10 +8,10 @@ import org.opentripplanner.raptor.api.request.PassThroughPoint; /** - * This class is used to adjust the walk time - giving each path a unique generalized-cost. We want - * the paths witch do NOT visit the pass-through points to have a lower generalized-cost than the + * This class is used to adjust the walk time - giving each path an unique generalized-cost. We want + * the paths which do NOT visit the pass-through points to have a lower generalized-cost than the * "correct" paths. We do this by adding a small increasing cost to each paths ordered by the number - * of stops visited. Then we add a bigger cost for each stop in the transfer witch is a + * of stops visited. Then we add a bigger cost for each stop in the transfer which is a * pass-through-point. */ class WalkDurationForStopCombinations { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java index ec29eb04cb0..3d25c19b527 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java @@ -62,7 +62,7 @@ public static TestTransferBuilder txConstraine ) { var builder = tx(fromTrip, fromStopIndex, toTrip, toStopIndex); // Make sure the constraint is initialized; hence an object generated in the build step. - // If none of the constraints are set this still generates a constraint instance, witch + // If none of the constraints are set this still generates a constraint instance, which // should behave like a regular transfer, but is not the same structure. builder.constraint(); return builder; From 2661965d9794077328dff524b45e0d5049818e5c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 16 Oct 2023 15:41:32 +0200 Subject: [PATCH 096/120] Update src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java --- .../model/passthrough/PassThroughNoTransfersTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java index a914f6c8766..db16f30d504 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java @@ -62,7 +62,7 @@ static List tripWithoutTransfersTestCases() { } /** - * The pass-though point used can be the board-, intermediate-, and/or alight-stop. We will also + * The pass-through point used can be the board-, intermediate-, and/or alight-stop. We will also * test all combinations of these to make sure a pass-through point is only accounted for once. *

    * The trip1 used: From 460b326c19ddac36b1635502714df9c82ffe04ba Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 16 Oct 2023 15:53:12 +0200 Subject: [PATCH 097/120] Fix parameter type --- .../opentripplanner/ext/transmodelapi/model/stop/QuayType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java index d2fb70fe151..caa7aca050e 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/stop/QuayType.java @@ -322,7 +322,7 @@ public static GraphQLObjectType create( JourneyWhiteListed whiteListed = new JourneyWhiteListed(environment); Collection transitModes = environment.getArgument("whiteListedModes"); - Integer startTimeInput = environment.getArgument("startTime"); + Long startTimeInput = environment.getArgument("startTime"); Instant startTime = startTimeInput != null ? Instant.ofEpochMilli(startTimeInput) : Instant.now(); From dc46eac5aa2e3e6663e7676363df49b87df1d6e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:07:48 +0000 Subject: [PATCH 098/120] chore(deps): update dependency org.jacoco:jacoco-maven-plugin to v0.8.11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c26e6980914..40a66588f0c 100644 --- a/pom.xml +++ b/pom.xml @@ -313,7 +313,7 @@ org.jacoco jacoco-maven-plugin - 0.8.10 + 0.8.11 +- Why: +- When: -**Why is this a problem?** -Describe the context, the goal or use-case this is part of. +### Linked issue(s) + -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +### OTP PO Discussion meeting details: +- Date: +- Link(s): -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +### Extra Comments (Optional) -**Additional context** -Add any other context or screenshots about the feature request here. From 005778be3879b737133a24c6ec26b9b524adcc0c Mon Sep 17 00:00:00 2001 From: eibakke Date: Wed, 18 Oct 2023 16:09:28 +0200 Subject: [PATCH 105/120] Adds start and end time AsInstant methods to Itinerary and ItinerarySortKey for sorting purposes. This simplifies reading and writing start and end times from serialized page cursors. The timezone is not needed for sorting. --- .../org/opentripplanner/model/plan/Itinerary.java | 15 +++++++++++++++ .../model/plan/ItinerarySortKey.java | 12 ++++++------ .../comparator/SortOrderComparator.java | 7 +++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 330044effbe..09a9ce7d126 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -1,6 +1,7 @@ package org.opentripplanner.model.plan; import java.time.Duration; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; @@ -82,6 +83,13 @@ public ZonedDateTime startTime() { return firstLeg().getStartTime(); } + /** + * Time that the trip departs as a Java Instant type. + */ + public Instant startTimeAsInstant() { + return firstLeg().getStartTime().toInstant(); + } + /** * Time that the trip arrives. */ @@ -89,6 +97,13 @@ public ZonedDateTime endTime() { return lastLeg().getEndTime(); } + /** + * Time that the trip arrives as a Java Instant type. + */ + public Instant endTimeAsInstant() { + return lastLeg().getEndTime().toInstant(); + } + /** * Reflects the departureDelay on the first Leg Unit: seconds. */ diff --git a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java index 345732d278e..0918f398325 100644 --- a/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java +++ b/src/main/java/org/opentripplanner/model/plan/ItinerarySortKey.java @@ -1,6 +1,6 @@ package org.opentripplanner.model.plan; -import java.time.ZonedDateTime; +import java.time.Instant; import org.opentripplanner.routing.algorithm.filterchain.comparator.SortOrderComparator; import org.opentripplanner.routing.algorithm.filterchain.filter.SortingFilter; @@ -8,13 +8,13 @@ * This interface is used to sort itineraries and other instances that we might want to sort among * itineraries. It is used in the {@link SortingFilter} as defined by the * {@link SortOrderComparator}. -

    - The methods in this interface are NOT documented here, but in the Itinerary class. To keep it simple, this - interface should be kept in sync with method names in the itinerary. + *

    + * The methods in this interface are NOT documented here, but in the Itinerary class. To keep it simple, this + * interface should be kept in sync with method names in the itinerary. */ public interface ItinerarySortKey { - ZonedDateTime startTime(); - ZonedDateTime endTime(); + Instant startTimeAsInstant(); + Instant endTimeAsInstant(); int getGeneralizedCost(); int getNumberOfTransfers(); boolean isOnStreetAllTheWay(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java index fb5dee0fc20..02afdfa8ad9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java @@ -27,13 +27,12 @@ public class SortOrderComparator extends CompositeComparator { /** Sort latest arrival-time first */ static final Comparator ARRIVAL_TIME_COMP = comparing( - ItinerarySortKey::endTime + ItinerarySortKey::endTimeAsInstant ); static final Comparator DEPARTURE_TIME_COMP = comparing( - ItinerarySortKey::startTime - ) - .reversed(); + ItinerarySortKey::startTimeAsInstant + ).reversed(); static final Comparator GENERALIZED_COST_COMP = comparingInt( ItinerarySortKey::getGeneralizedCost From 2ee2bc3e538ed6749c7057d906e548d5e55b1af3 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 17:17:55 +0200 Subject: [PATCH 106/120] Apply suggestions from code review Co-authored-by: Johan Torin --- .../transferoptimization/model/PathTailFilter.java | 10 +++++----- .../model/passthrough/PassThroughPointsIterator.java | 4 ++-- .../model/passthrough/PassThroughNoTransfersTest.java | 5 ++--- .../model/passthrough/PassThroughOneTransferTest.java | 10 +++++----- .../model/passthrough/PassThroughTwoTransfersTest.java | 4 ++-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 9e4edb42fef..65dd84f5239 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -5,16 +5,16 @@ /** * Filter path tails for a given stopPosition during the optimization process. The algorithm only - * feed in paths witch can be compared. If the head is alighted at a position which gives it an + * feed in paths which can be compared. If the head is alighted at a position which gives it an * advantage over another path (accept boarding at more stops), then the paths are split into * different sets and the filter is called for each set. */ public interface PathTailFilter { /** - * Filter path while building the paths. The {@code head} of each path is guranteed to be a - * transit leg, and the {@code boardStopPosition} is guranteed to be the last position in the - * head leg witch will be used for boarding. The {@code boardStopPosition} should be used when - * calculating the property witch is used for comparison. If the comparison can be done without + * Filter path while building the paths. The {@code head} of each path is guaranteed to be a + * transit leg, and the {@code boardStopPosition} is guaranteed to be the last position in the + * head leg which will be used for boarding. The {@code boardStopPosition} should be used when + * calculating the property which is used for comparison. If the comparison can be done without * looking at a stop-position, then this can be ignored. */ Set> filterIntermediateResult( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java index d910e4076db..01ea482d450 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughPointsIterator.java @@ -5,8 +5,8 @@ import org.opentripplanner.raptor.api.request.PassThroughPoint; /** - * Iterate over the pass-though-points. Note! this class iterate backwards starting at the last - * pass-through-point and forward. + * Iterate over the pass-through points. Note! This implementation iterates backwards starting at the last + * pass-through point. */ class PassThroughPointsIterator { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java index db16f30d504..cc071d91799 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughNoTransfersTest.java @@ -17,9 +17,9 @@ * This test focus on the PASS-THROUGH functionality with a very simple scenario - one transit leg * and no transfers. *

    - * FEATURE UNDER TESTED + * FEATURE UNDER TEST *

    - * We want the path with the lowest generalized-cost that visit the pass-through points in the + * We want the path with the lowest generalized-cost that visits the pass-through points in the * correct order. *

    * TEST SETUP @@ -88,7 +88,6 @@ public void tripWithoutTransfers(TestCase tc) { assertEquals(1, result.size()); // Then expect a set containing the original path - System.out.println(first(result).toString(this::stopIndexToName)); assertEquals( originalPath.toString(this::stopIndexToName), first(result).toString(this::stopIndexToName) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java index fdbc65a1adc..054915e84f4 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughOneTransferTest.java @@ -20,7 +20,7 @@ * path. But between trip 1 and trip 2, there may be many transfer options to choose from. * *

    - * FEATURE UNDER TESTED + * FEATURE UNDER TEST *

    * We want the path with the lowest generalized-cost that visit the pass-through points in the * correct order. @@ -30,7 +30,7 @@ * We will use 2 trips with a fixed set of transfers for each test. Each trip has 5 stops and * plenty of slack to do the transfers for all possible stops combinations. We will set the * transfer durations to get different generalized-costs for each possible path. We will set the - * cost so the transfers witch do not contain any transfer-points have the lowest cost - is optimal + * cost so the transfers which do not contain any transfer-points have the lowest cost - is optimal * on generalized-cost. We do this to make sure the subject-under-test is using the pass-through- * points, and not the generalized cost to choose the correct path. */ @@ -139,7 +139,7 @@ public void tripWithOneTransfer(TestCase tc) { // This transfer do not visit G .addTxCost(STOP_D, STOP_H, 2); - // We need *a* path - the transfer her can be any + // We need *a* path - the transfer here can be any. var originalPath = pathBuilder() .access(ITERATION_START_TIME, STOP_B, D1s) .bus(trip1, STOP_D) @@ -154,7 +154,7 @@ public void tripWithOneTransfer(TestCase tc) { .bus(trip2, STOP_I) .egress(D1s); - // These are illegal transfer for the given path, we add them here to make sure they + // These are illegal transfers for the given path, we add them here to make sure they // do not interfere with the result. For simpler debugging problems try commenting out these // lines, just do not forget to comment them back in when the problem is fixed. var subject = subject( @@ -164,7 +164,7 @@ public void tripWithOneTransfer(TestCase tc) { tx(trip1, STOP_C, trip2, STOP_H, txCost), tx(trip1, STOP_D, trip2, STOP_G, txCost), tx(trip1, STOP_D, trip2, STOP_H, txCost), - // These are illegal transfer for the given path, we add them here to make sure they + // These are illegal transfers for the given path, we add them here to make sure they // do not interfere with the result. For simpler debugging problems try comment out these // lines, just do not forget to comment them back in when the problem is fixed. tx(trip1, STOP_B, trip2, STOP_F, txCost), diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java index d6dfc9264c2..fd2fd827d7c 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/model/passthrough/PassThroughTwoTransfersTest.java @@ -20,7 +20,7 @@ * This test focus on the PASS-THROUGH functionality with three transit legs and two transfer in * the path. *

    - * FEATURE UNDER TESTED + * FEATURE UNDER TEST *

    * We want the path with the lowest generalized-cost that visit the pass-through points in the * correct order. @@ -31,7 +31,7 @@ * plenty of slack to do the transfers for all possible stops combinations. There is two * transfers to choose from between trip 1 and trip 2, and between trip 2 and trip 3. We will set * the transfer durations to get different generalized-costs for each possible path. We will set - * the cost so the transfers witch do not contain any transfer-points have the lowest cost - is + * the cost so the transfers which do not contain any transfer-points have the lowest cost - is * optimal on generalized-cost. We do this to make sure the subject-under-test is using the * pass-through-points, and not the generalized cost to choose the correct path. */ From 7836c4c21e9f8d83010311d56a0707dcb9bf4d8d Mon Sep 17 00:00:00 2001 From: Brede Date: Wed, 18 Oct 2023 16:54:38 -0400 Subject: [PATCH 107/120] Rename feature_request.md to roadmap_epic.md --- .github/ISSUE_TEMPLATE/{feature_request.md => roadmap_epic.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{feature_request.md => roadmap_epic.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/roadmap_epic.md similarity index 100% rename from .github/ISSUE_TEMPLATE/feature_request.md rename to .github/ISSUE_TEMPLATE/roadmap_epic.md From a55f8277a22d36e330a1382e2b7b9ad6dd39d3ea Mon Sep 17 00:00:00 2001 From: Brede Date: Wed, 18 Oct 2023 16:56:17 -0400 Subject: [PATCH 108/120] Update roadmap_epic.md --- .github/ISSUE_TEMPLATE/roadmap_epic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/roadmap_epic.md b/.github/ISSUE_TEMPLATE/roadmap_epic.md index 46779962661..2ca78bbaac2 100644 --- a/.github/ISSUE_TEMPLATE/roadmap_epic.md +++ b/.github/ISSUE_TEMPLATE/roadmap_epic.md @@ -13,7 +13,7 @@ assignees: '' - When: ### Linked issue(s) - + ### OTP PO Discussion meeting details: - Date: From df6a439f0d2bed5b5af40fd9a9b7f65e7fa99d66 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 18 Oct 2023 21:00:59 +0000 Subject: [PATCH 109/120] Add changelog entry for #5413 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 0e78be11ece..1804c680890 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -21,6 +21,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix flexible quay querying in Transmodel API [#5417](https://github.com/opentripplanner/OpenTripPlanner/pull/5417) - Add validation for missing calls in SIRI update [#5403](https://github.com/opentripplanner/OpenTripPlanner/pull/5403) - Import Occupancy Status from GTFS-RT Vehicle Positions [#5372](https://github.com/opentripplanner/OpenTripPlanner/pull/5372) +- Add Roadmap epic template [#5413](https://github.com/opentripplanner/OpenTripPlanner/pull/5413) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 9f6be8268bbdf0eb2eef983dbd36c8fb25b5dbff Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 23:17:04 +0200 Subject: [PATCH 110/120] move ISSUE_TEMPLATE --- ISSUE_TEMPLATE => .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ISSUE_TEMPLATE => .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE (100%) diff --git a/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE similarity index 100% rename from ISSUE_TEMPLATE rename to .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE From e6459b079990e12052b5fb6be595d45eab8e291d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 23:21:11 +0200 Subject: [PATCH 111/120] Change issue to bug report --- .github/ISSUE_TEMPLATE/{ISSUE_TEMPLATE => bug_report.md} | 9 +++++++++ 1 file changed, 9 insertions(+) rename .github/ISSUE_TEMPLATE/{ISSUE_TEMPLATE => bug_report.md} (89%) diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 89% rename from .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE rename to .github/ISSUE_TEMPLATE/bug_report.md index 87616c9e767..698dd059acd 100644 --- a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,12 @@ +--- +name: Bug report +about: '' +title: '' +labels: Bug +assignees: '' + +--- + **NOTE:** this issue system is intended for reporting bugs and tracking progress in software development. For all other usage and software development questions or discussion, please write to the user mailing list(https://groups.google.com/forum/#!forum/opentripplanner-users) or post a From e916eede0d823d8be9b4b6291074c7dd8f1aca3d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 23:21:11 +0200 Subject: [PATCH 112/120] Change issue to bug report --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 698dd059acd..0d68b024510 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: '' +about: 'Report a bug or issue' title: '' labels: Bug assignees: '' From 47826676e080aac59dc7991bddfd050fb66802df Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 23:37:37 +0200 Subject: [PATCH 113/120] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ++--- .github/ISSUE_TEMPLATE/feature_request.md | 24 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/roadmap_epic.md | 3 +-- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0d68b024510..1212e44d368 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,8 @@ --- name: Bug report -about: 'Report a bug or issue' +about: Report a bug or issue title: '' -labels: Bug +labels: '' assignees: '' --- @@ -26,4 +26,3 @@ question in the developer chat: https://gitter.im/opentripplanner/OpenTripPlanne ## Router config and graph build config JSON ## Steps to reproduce the problem - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..58d049941a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: new feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + + +**Goal / high level use-case** + + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/roadmap_epic.md b/.github/ISSUE_TEMPLATE/roadmap_epic.md index 2ca78bbaac2..0bc8f9c6485 100644 --- a/.github/ISSUE_TEMPLATE/roadmap_epic.md +++ b/.github/ISSUE_TEMPLATE/roadmap_epic.md @@ -19,5 +19,4 @@ assignees: '' - Date: - Link(s): -### Extra Comments (Optional) - +### Extra Comments (Optional) From fc29339c23acdc8e52b3953398dd97a2387c0efe Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 18 Oct 2023 23:50:38 +0200 Subject: [PATCH 114/120] doc: Fix feature request about doc --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 58d049941a7..3d316646331 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,6 @@ --- name: Feature request -about: Suggest an idea for this project +about: Suggest a feature or improvement for OTP title: '' labels: new feature assignees: '' From f3c0ab396f591a09081b5a0d9a9845cfee7783fa Mon Sep 17 00:00:00 2001 From: eibakke Date: Wed, 18 Oct 2023 16:09:28 +0200 Subject: [PATCH 115/120] Adds start and end time AsInstant methods to Itinerary and ItinerarySortKey for sorting purposes. This simplifies reading and writing start and end times from serialized page cursors. The timezone is not needed for sorting. --- .../algorithm/filterchain/comparator/SortOrderComparator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java index 02afdfa8ad9..c48b6f0bd5d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/comparator/SortOrderComparator.java @@ -32,7 +32,8 @@ public class SortOrderComparator extends CompositeComparator { static final Comparator DEPARTURE_TIME_COMP = comparing( ItinerarySortKey::startTimeAsInstant - ).reversed(); + ) + .reversed(); static final Comparator GENERALIZED_COST_COMP = comparingInt( ItinerarySortKey::getGeneralizedCost From ee668011232c5a0ba2bd8063bcb606528d3783c3 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 19 Oct 2023 12:49:36 +0200 Subject: [PATCH 116/120] Update src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java Co-authored-by: Leonard Ehrenfried --- .../transferoptimization/services/TestTransferBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java index 3d25c19b527..4155d46fe63 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TestTransferBuilder.java @@ -35,7 +35,7 @@ public static TestTransferBuilder tx(T fromTri } /** - * Set all required parameter for a transfer. The walk duration is set to zero. + * Set all required parameters for a transfer. The walk duration is set to zero. */ public static TestTransferBuilder tx( T fromTrip, From d61d10fa0c70cabeda4c7ffebce0b312d6824682 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 19 Oct 2023 12:50:41 +0200 Subject: [PATCH 117/120] Apply suggestions from code review Co-authored-by: Leonard Ehrenfried --- .../algorithm/transferoptimization/model/PathTailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java index 65dd84f5239..cabc9ac33aa 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/PathTailFilter.java @@ -5,7 +5,7 @@ /** * Filter path tails for a given stopPosition during the optimization process. The algorithm only - * feed in paths which can be compared. If the head is alighted at a position which gives it an + * feeds in paths which can be compared. If the head is alighted at a position which gives it an * advantage over another path (accept boarding at more stops), then the paths are split into * different sets and the filter is called for each set. */ From 003d535904be6c006c387bb9f7e77111dec7db3a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 20 Oct 2023 10:22:04 +0200 Subject: [PATCH 118/120] Point users to Gitter in bug template [ci skip] --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1212e44d368..15e9e772595 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,9 +8,8 @@ assignees: '' --- **NOTE:** this issue system is intended for reporting bugs and tracking progress in software -development. For all other usage and software development questions or discussion, please write to -the user mailing list(https://groups.google.com/forum/#!forum/opentripplanner-users) or post a -question in the developer chat: https://gitter.im/opentripplanner/OpenTripPlanner. +development. For all other usage and software development questions or discussion, please post a +question in our chat room: https://gitter.im/opentripplanner/OpenTripPlanner. ## Expected behavior From 3eb990913badb35308d2d772de5f2ca13b65f51b Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Fri, 20 Oct 2023 10:21:41 +0000 Subject: [PATCH 119/120] Bump serialization version id for #5430 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5ea0e24107..eaf09e96082 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 121 + 122 30.0 2.48.1 From ec0c0fca2dc42a5eae18d2d45ba98e18ffe4472c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Fri, 20 Oct 2023 13:09:36 +0000 Subject: [PATCH 120/120] Add changelog entry for #5376 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1804c680890..c0938adfcc2 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -22,6 +22,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add validation for missing calls in SIRI update [#5403](https://github.com/opentripplanner/OpenTripPlanner/pull/5403) - Import Occupancy Status from GTFS-RT Vehicle Positions [#5372](https://github.com/opentripplanner/OpenTripPlanner/pull/5372) - Add Roadmap epic template [#5413](https://github.com/opentripplanner/OpenTripPlanner/pull/5413) +- Allow multiple zones in an unscheduled flex trip [#5376](https://github.com/opentripplanner/OpenTripPlanner/pull/5376) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13)