From 6abd2939a19a81d392d6cfe9f082c720c8677c14 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 22 Jul 2024 16:49:35 +0200 Subject: [PATCH 01/36] refactor: Remove RaptorWorker usage --- .../opentripplanner/raptor/configure/RaptorConfig.java | 9 ++++----- .../raptor/rangeraptor/DefaultRangeRaptorWorker.java | 5 +---- .../rangeraptor/internalapi/RaptorWorkerResult.java | 2 +- .../multicriteria/configure/McRangeRaptorConfig.java | 6 +++--- .../raptor/service/HeuristicSearchTask.java | 3 ++- .../raptor/service/RangeRaptorDynamicSearch.java | 4 ++-- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index f1477ecc9f3..f97bdb0b665 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -12,7 +12,6 @@ import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -52,7 +51,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return new SearchContext<>(request, tuningParameters, transit, acceptC2AtDestination()); } - public RaptorWorker createStdWorker( + public DefaultRangeRaptorWorker createStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -61,7 +60,7 @@ public RaptorWorker createStdWorker( return createWorker(context, stdConfig.state(), stdConfig.strategy()); } - public RaptorWorker createMcWorker( + public DefaultRangeRaptorWorker createMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -74,7 +73,7 @@ public RaptorWorker createMcWorker( ); } - public RaptorWorker createHeuristicSearch( + public DefaultRangeRaptorWorker createHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -116,7 +115,7 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private RaptorWorker createWorker( + private DefaultRangeRaptorWorker createWorker( SearchContext ctx, RaptorWorkerState workerState, RoutingStrategy routingStrategy diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 0f2ceb9d6e4..730cee934cc 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -6,7 +6,6 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; @@ -49,8 +48,7 @@ * @param The TripSchedule type defined by the user of the raptor API. */ @SuppressWarnings("Duplicates") -public final class DefaultRangeRaptorWorker - implements RaptorWorker { +public final class DefaultRangeRaptorWorker { private final RoutingStrategy transitWorker; @@ -123,7 +121,6 @@ public DefaultRangeRaptorWorker( *

* Run the scheduled search, round 0 is the street search. */ - @Override public RaptorWorkerResult route() { timers.route(() -> { lifeCycle.notifyRouteSearchStart(calculator.searchForward()); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java index 59aeb48e6ab..ce6f7deb673 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java @@ -5,7 +5,7 @@ import org.opentripplanner.raptor.api.path.RaptorPath; /** - * This is the result of the {@link RaptorWorker#route()} call. + * This is the result of a RangeRaptor route call. */ public interface RaptorWorkerResult { /** 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 3673e78ee47..dc1ffb850db 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 @@ -7,11 +7,11 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; +import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.multicriteria.McRangeRaptorWorkerState; @@ -70,9 +70,9 @@ public static PassThroughPointsService passThroughPointsService( /** * Create new multi-criteria worker with optional heuristics. */ - public RaptorWorker createWorker( + public DefaultRangeRaptorWorker createWorker( Heuristics heuristics, - BiFunction, RoutingStrategy, RaptorWorker> createWorker + BiFunction, RoutingStrategy, DefaultRangeRaptorWorker> createWorker ) { McRangeRaptorWorkerState state = createState(heuristics); return createWorker.apply(state, createTransitWorkerStrategy(state)); diff --git a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index 6a8bc003dd5..af28bc4675a 100644 --- a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -8,6 +8,7 @@ import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; @@ -33,7 +34,7 @@ public class HeuristicSearchTask { private final RaptorTransitDataProvider transitData; private boolean run = false; - private RaptorWorker search = null; + private DefaultRangeRaptorWorker search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; private RaptorWorkerResult result = null; diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fb96b7a8724..8361c1d05d6 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -19,8 +19,8 @@ import org.opentripplanner.raptor.api.request.SearchParamsBuilder; import org.opentripplanner.raptor.api.response.RaptorResponse; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; @@ -130,7 +130,7 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RaptorWorker raptorWorker; + DefaultRangeRaptorWorker raptorWorker; // Create worker if (request.profile().is(MULTI_CRITERIA)) { From 1420ab366353db054abd98a7a083851b81e618bb Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 22 Jul 2024 16:52:52 +0200 Subject: [PATCH 02/36] refactor: Rename DefaultRangeRaptorWorker to RangeRaptor --- .../raptor/configure/RaptorConfig.java | 12 ++++++------ ...efaultRangeRaptorWorker.java => RangeRaptor.java} | 4 ++-- .../rangeraptor/internalapi/RaptorWorkerState.java | 4 ++-- .../rangeraptor/internalapi/RoutingStrategy.java | 4 ++-- .../multicriteria/configure/McRangeRaptorConfig.java | 6 +++--- .../raptor/service/HeuristicSearchTask.java | 4 ++-- .../raptor/service/RangeRaptorDynamicSearch.java | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) rename src/main/java/org/opentripplanner/raptor/rangeraptor/{DefaultRangeRaptorWorker.java => RangeRaptor.java} (99%) diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index f97bdb0b665..be53f27075d 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -8,7 +8,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; @@ -51,7 +51,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return new SearchContext<>(request, tuningParameters, transit, acceptC2AtDestination()); } - public DefaultRangeRaptorWorker createStdWorker( + public RangeRaptor createStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -60,7 +60,7 @@ public DefaultRangeRaptorWorker createStdWorker( return createWorker(context, stdConfig.state(), stdConfig.strategy()); } - public DefaultRangeRaptorWorker createMcWorker( + public RangeRaptor createMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -73,7 +73,7 @@ public DefaultRangeRaptorWorker createMcWorker( ); } - public DefaultRangeRaptorWorker createHeuristicSearch( + public RangeRaptor createHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -115,12 +115,12 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private DefaultRangeRaptorWorker createWorker( + private RangeRaptor createWorker( SearchContext ctx, RaptorWorkerState workerState, RoutingStrategy routingStrategy ) { - return new DefaultRangeRaptorWorker<>( + return new RangeRaptor<>( workerState, routingStrategy, ctx.transit(), diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java similarity index 99% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 730cee934cc..5eca3ff4a34 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -48,7 +48,7 @@ * @param The TripSchedule type defined by the user of the raptor API. */ @SuppressWarnings("Duplicates") -public final class DefaultRangeRaptorWorker { +public final class RangeRaptor { private final RoutingStrategy transitWorker; @@ -87,7 +87,7 @@ public final class DefaultRangeRaptorWorker { private int iterationDepartureTime; - public DefaultRangeRaptorWorker( + public RangeRaptor( RaptorWorkerState state, RoutingStrategy transitWorker, RaptorTransitDataProvider transitData, diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java index a71b8765adb..cb73d668450 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java @@ -3,11 +3,11 @@ import java.util.Iterator; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.spi.IntIterator; /** - * The contract the state must implement for the {@link DefaultRangeRaptorWorker} to do its job. This + * The contract the state must implement for the {@link RangeRaptor} to do its job. This * allows us to mix workers and states to implement different versions of the algorithm like * Standard, Standard-reversed and multi-criteria and use this with different states keeping only * the information needed by the use-case. Some example use-cases are calculating heuristics, diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoutingStrategy.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoutingStrategy.java index 63be40e9e8a..df9577aaeff 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoutingStrategy.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoutingStrategy.java @@ -2,13 +2,13 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.spi.RaptorConstrainedBoardingSearch; import org.opentripplanner.raptor.spi.RaptorRoute; import org.opentripplanner.raptor.spi.RaptorTimeTable; /** - * Provides alternative implementations of some logic within the {@link DefaultRangeRaptorWorker}. + * Provides alternative implementations of some logic within the {@link RangeRaptor}. * * @param The TripSchedule type defined by the user of the raptor API. */ 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 dc1ffb850db..1f143f85d0b 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 @@ -7,7 +7,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; @@ -70,9 +70,9 @@ public static PassThroughPointsService passThroughPointsService( /** * Create new multi-criteria worker with optional heuristics. */ - public DefaultRangeRaptorWorker createWorker( + public RangeRaptor createWorker( Heuristics heuristics, - BiFunction, RoutingStrategy, DefaultRangeRaptorWorker> createWorker + BiFunction, RoutingStrategy, RangeRaptor> createWorker ) { McRangeRaptorWorkerState state = createState(heuristics); return createWorker.apply(state, createTransitWorkerStrategy(state)); diff --git a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index af28bc4675a..a0d0a943ae0 100644 --- a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -8,7 +8,7 @@ import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; @@ -34,7 +34,7 @@ public class HeuristicSearchTask { private final RaptorTransitDataProvider transitData; private boolean run = false; - private DefaultRangeRaptorWorker search = null; + private RangeRaptor search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; private RaptorWorkerResult result = null; diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 8361c1d05d6..d93d9e377b4 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -19,7 +19,7 @@ import org.opentripplanner.raptor.api.request.SearchParamsBuilder; import org.opentripplanner.raptor.api.response.RaptorResponse; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; @@ -130,7 +130,7 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - DefaultRangeRaptorWorker raptorWorker; + RangeRaptor raptorWorker; // Create worker if (request.profile().is(MULTI_CRITERIA)) { From 0ada612944288f3a5ec98a74ee7ce1a269f65597 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 22 Jul 2024 16:57:33 +0200 Subject: [PATCH 03/36] refactor: Rename RaptorWorker interface - unused p.t. --- .../{RaptorWorker.java => RangeRaptorWorker.java} | 2 +- .../raptor/service/HeuristicSearchTask.java | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) rename src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/{RaptorWorker.java => RangeRaptorWorker.java} (85%) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java similarity index 85% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorker.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java index fc5786b2407..4ba64c35f4e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java @@ -8,7 +8,7 @@ * * @param The TripSchedule type defined by the user of the raptor API. */ -public interface RaptorWorker { +public interface RangeRaptorWorker { /** * Perform the routing request. */ diff --git a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index a0d0a943ae0..cabc03fcf3a 100644 --- a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -10,16 +10,15 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Thin wrapper around a {@link RaptorWorker} to allow for some small additional features. This - * is mostly to extracted some "glue" out of the {@link RangeRaptorDynamicSearch} to make that - * simpler and let it focus on the main bossiness logic. + * This is a thin wrapper around the {@link RangeRaptor} to allow for some small additional + * features. This is mostly to extract some "glue" out of the {@link RangeRaptorDynamicSearch} to + * make that simpler and let it focus on the main bossiness logic. *

* This class is not meant for reuse, create one task for each potential heuristic search. The task * must be {@link #enable()}d before it is {@link #run()}. From 97a2621eb87bdd8ac40abe8849c97ce2df33dfb4 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 22 Jul 2024 20:09:57 +0200 Subject: [PATCH 04/36] refactor: Split RangeRaptor in a pure driver and an algorithm implementation --- .../raptor/configure/RaptorConfig.java | 15 +- .../rangeraptor/DefaultRangeRaptorWorker.java | 233 ++++++++++++++++++ .../raptor/rangeraptor/RangeRaptor.java | 171 ++----------- .../raptor/RaptorArchitectureTest.java | 3 +- 4 files changed, 271 insertions(+), 151 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index be53f27075d..de566bd1364 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -8,6 +8,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; @@ -120,7 +121,7 @@ private RangeRaptor createWorker( RaptorWorkerState workerState, RoutingStrategy routingStrategy ) { - return new RangeRaptor<>( + var worker = new DefaultRangeRaptorWorker<>( workerState, routingStrategy, ctx.transit(), @@ -128,10 +129,20 @@ private RangeRaptor createWorker( ctx.accessPaths(), ctx.roundProvider(), ctx.calculator(), - ctx.createLifeCyclePublisher(), + ctx.lifeCycle(), ctx.performanceTimers(), ctx.useConstrainedTransfers() ); + + return new RangeRaptor<>( + worker, + ctx.transit(), + ctx.accessPaths(), + ctx.roundProvider(), + ctx.calculator(), + ctx.createLifeCyclePublisher(), + ctx.performanceTimers() + ); } private IntPredicate acceptC2AtDestination() { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java new file mode 100644 index 00000000000..68c38c862bd --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -0,0 +1,233 @@ +package org.opentripplanner.raptor.rangeraptor; + +import java.util.Collection; +import org.opentripplanner.raptor.api.debug.RaptorTimers; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; +import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; +import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; +import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; +import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; +import org.opentripplanner.raptor.rangeraptor.transit.RaptorTransitCalculator; +import org.opentripplanner.raptor.rangeraptor.transit.RoundTracker; +import org.opentripplanner.raptor.spi.IntIterator; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; + +/** + * The algorithm used herein is described in + *

+ * Conway, Matthew Wigginton, Andrew Byrd, and Marco van der Linden. “Evidence-Based Transit and + * Land Use Sketch Planning Using Interactive Accessibility Methods on Combined Schedule and + * Headway-Based Networks.” Transportation Research Record 2653 (2017). doi:10.3141/2653-06. + *

+ * + * Delling, Daniel, Thomas Pajor, and Renato Werneck. “Round-Based Public Transit Routing”, + * January 1, 2012. + * . + *

+ * This version supports the following features: + *

    + *
  • Raptor (R) + *
  • Range Raptor (RR) + *
  • Multi-criteria pareto optimal Range Raptor (McRR) + *
  • Reverse search in combination with R and RR + *
+ * This version does NOT support the following features: + *
    + *
  • Frequency routes, supported by the original code using Monte Carlo methods + * (generating randomized schedules) + *
+ *

+ * This class originated as a rewrite of Conveyals RAPTOR code: https://github.com/conveyal/r5. + * + * @param The TripSchedule type defined by the user of the raptor API. + */ +@SuppressWarnings("Duplicates") +public final class DefaultRangeRaptorWorker { + + private final RoutingStrategy transitWorker; + + /** + * The RangeRaptor state - we delegate keeping track of state to the state object, this allows + * the worker implementation to focus on the algorithm, while the state keep track of the result. + *

+ * This also allows us to try out different strategies for storing the result in memory. For a + * long time, we had a state which stored all data as int arrays in addition to the current + * object-oriented approach. There were no performance differences(=> GC is not the bottleneck), + * so we dropped the integer array implementation. + */ + private final RaptorWorkerState state; + + /** + * The round tracker keep track for the current Raptor round, and abort the search if the round + * max limit is reached. + */ + private final RoundTracker roundTracker; + + private final RaptorTransitDataProvider transitData; + + private final SlackProvider slackProvider; + + private final RaptorTransitCalculator calculator; + + private final RaptorTimers timers; + + private final AccessPaths accessPaths; + + private final int minNumberOfRounds; + + private final boolean enableTransferConstraints; + + private int iterationDepartureTime; + + public DefaultRangeRaptorWorker( + RaptorWorkerState state, + RoutingStrategy transitWorker, + RaptorTransitDataProvider transitData, + SlackProvider slackProvider, + AccessPaths accessPaths, + RoundProvider roundProvider, + RaptorTransitCalculator calculator, + WorkerLifeCycle lifeCycle, + RaptorTimers timers, + boolean enableTransferConstraints + ) { + this.transitWorker = transitWorker; + this.state = state; + this.transitData = transitData; + this.slackProvider = slackProvider; + this.calculator = calculator; + this.timers = timers; + this.accessPaths = accessPaths; + this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); + this.enableTransferConstraints = enableTransferConstraints; + + // We do a cast here to avoid exposing the round tracker and the life cycle publisher to + // "everyone" by providing access to it in the context. + this.roundTracker = (RoundTracker) roundProvider; + lifeCycle.onSetupIteration(time -> this.iterationDepartureTime = time); + } + + public RaptorWorkerResult results() { + return state.results(); + } + + /** + * Check if the RangeRaptor should continue with a new round. + */ + boolean hasMoreRounds() { + if (round() < minNumberOfRounds) { + return true; + } + return state.isNewRoundAvailable() && roundTracker.hasMoreRounds(); + } + + /** + * Perform a scheduled search + */ + void findTransitForRound() { + timers.findTransitForRound(() -> { + IntIterator stops = state.stopsTouchedPreviousRound(); + IntIterator routeIndexIterator = transitData.routeIndexIterator(stops); + + while (routeIndexIterator.hasNext()) { + var routeIndex = routeIndexIterator.next(); + var route = transitData.getRouteForIndex(routeIndex); + var pattern = route.pattern(); + var txSearch = enableTransferConstraints + ? calculator.transferConstraintsSearch(transitData, routeIndex) + : null; + + int alightSlack = slackProvider.alightSlack(pattern.slackIndex()); + int boardSlack = slackProvider.boardSlack(pattern.slackIndex()); + + transitWorker.prepareForTransitWith(route); + + IntIterator stop = calculator.patternStopIterator(pattern.numberOfStopsInPattern()); + + while (stop.hasNext()) { + int stopPos = stop.next(); + int stopIndex = pattern.stopIndex(stopPos); + + transitWorker.prepareForNextStop(stopIndex, stopPos); + + // attempt to alight if we're on board, this is done above the board search + // so that we don't alight on first stop boarded + if (calculator.alightingPossibleAt(pattern, stopPos)) { + if (enableTransferConstraints && txSearch.transferExistSourceStop(stopPos)) { + transitWorker.alightConstrainedTransferExist(stopIndex, stopPos, alightSlack); + } else { + transitWorker.alightOnlyRegularTransferExist(stopIndex, stopPos, alightSlack); + } + } + + if (calculator.boardingPossibleAt(pattern, stopPos)) { + // Don't attempt to board if this stop was not reached in the last round. + // Allow to reboard the same pattern - a pattern may loop and visit the same stop twice + if (state.isStopReachedInPreviousRound(stopIndex)) { + // has constrained transfers + if (enableTransferConstraints && txSearch.transferExistTargetStop(stopPos)) { + transitWorker.boardWithConstrainedTransfer( + stopIndex, + stopPos, + boardSlack, + txSearch + ); + } else { + transitWorker.boardWithRegularTransfer(stopIndex, stopPos, boardSlack); + } + } + } + } + } + }); + } + + void findTransfersForRound() { + timers.findTransfersForRound(() -> { + IntIterator it = state.stopsTouchedByTransitCurrentRound(); + + while (it.hasNext()) { + final int fromStop = it.next(); + // no need to consider loop transfers, since we don't mark patterns here any more + // loop transfers are already included by virtue of those stops having been reached + state.transferToStops(fromStop, calculator.getTransfers(transitData, fromStop)); + } + }); + } + + boolean isDestinationReachedInCurrentRound() { + return state.isDestinationReachedInCurrentRound(); + } + + private int round() { + return roundTracker.round(); + } + + void findAccessOnStreetForRound() { + addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round())); + } + + void findAccessOnBoardForRound() { + addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round())); + } + + /** + * Set the departure time in the scheduled search to the given departure time, and prepare for the + * scheduled search at the next-earlier minute. + */ + private void addAccessPaths(Collection accessPaths) { + for (RaptorAccessEgress it : accessPaths) { + int departureTime = calculator.departureTime(it, iterationDepartureTime); + + // Access must be available after the iteration departure time + if (departureTime != RaptorConstants.TIME_NOT_SET) { + transitWorker.setAccessToStop(it, departureTime); + } + } + } +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 5eca3ff4a34..44c6a12bc5e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -1,16 +1,11 @@ package org.opentripplanner.raptor.rangeraptor; -import java.util.Collection; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.api.debug.RaptorTimers; -import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; -import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleEventPublisher; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.RaptorTransitCalculator; @@ -50,18 +45,7 @@ @SuppressWarnings("Duplicates") public final class RangeRaptor { - private final RoutingStrategy transitWorker; - - /** - * The RangeRaptor state - we delegate keeping track of state to the state object, this allows - * the worker implementation to focus on the algorithm, while the state keep track of the result. - *

- * This also allows us to try out different strategies for storing the result in memory. For a - * long time, we had a state which stored all data as int arrays in addition to the current - * object-oriented approach. There were no performance differences(=> GC is not the bottleneck), - * so we dropped the integer array implementation. - */ - private final RaptorWorkerState state; + private final DefaultRangeRaptorWorker worker; /** * The round tracker keep track for the current Raptor round, and abort the search if the round @@ -71,8 +55,6 @@ public final class RangeRaptor { private final RaptorTransitDataProvider transitData; - private final SlackProvider slackProvider; - private final RaptorTransitCalculator calculator; private final RaptorTimers timers; @@ -83,31 +65,21 @@ public final class RangeRaptor { private final int minNumberOfRounds; - private final boolean enableTransferConstraints; - - private int iterationDepartureTime; - public RangeRaptor( - RaptorWorkerState state, - RoutingStrategy transitWorker, + DefaultRangeRaptorWorker worker, RaptorTransitDataProvider transitData, - SlackProvider slackProvider, AccessPaths accessPaths, RoundProvider roundProvider, RaptorTransitCalculator calculator, LifeCycleEventPublisher lifeCyclePublisher, - RaptorTimers timers, - boolean enableTransferConstraints + RaptorTimers timers ) { - this.transitWorker = transitWorker; - this.state = state; + this.worker = worker; this.transitData = transitData; - this.slackProvider = slackProvider; this.calculator = calculator; this.timers = timers; this.accessPaths = accessPaths; this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); - this.enableTransferConstraints = enableTransferConstraints; // We do a cast here to avoid exposing the round tracker and the life cycle publisher to // "everyone" by providing access to it in the context. @@ -123,6 +95,7 @@ public RangeRaptor( */ public RaptorWorkerResult route() { timers.route(() -> { + int iterationDepartureTime = RaptorConstants.TIME_NOT_SET; lifeCycle.notifyRouteSearchStart(calculator.searchForward()); transitData.setup(); @@ -132,8 +105,8 @@ public RaptorWorkerResult route() { // the arrival time given departure at minute t + 1. final IntIterator it = calculator.rangeRaptorMinutes(); while (it.hasNext()) { - setupIteration(it.next()); - runRaptorForMinute(); + iterationDepartureTime = it.next(); + runRaptorForMinute(iterationDepartureTime); } // Iterate over virtual departure times - this is needed to allow access with a time-penalty @@ -141,34 +114,37 @@ public RaptorWorkerResult route() { if (!calculator.oneIterationOnly()) { final IntIterator as = accessPaths.iterateOverPathsWithPenalty(iterationDepartureTime); while (as.hasNext()) { - setupIteration(as.next()); - runRaptorForMinute(); + iterationDepartureTime = as.next(); + runRaptorForMinute(iterationDepartureTime); } } }); - return state.results(); + return worker.results(); } /** * Perform one minute of a RAPTOR search. */ - private void runRaptorForMinute() { - findAccessOnStreetForRound(); + private void runRaptorForMinute(int iterationDepartureTime) { + setupIteration(iterationDepartureTime); + worker.findAccessOnStreetForRound(); - while (hasMoreRounds()) { + while (worker.hasMoreRounds()) { lifeCycle.prepareForNextRound(roundTracker.nextRound()); // NB since we have transfer limiting not bothering to cut off search when there are no // more transfers as that will be rare and complicates the code - findTransitForRound(); + worker.findTransitForRound(); + lifeCycle.transitsForRoundComplete(); - findAccessOnBoardForRound(); + worker.findAccessOnBoardForRound(); - findTransfersForRound(); + worker.findTransfersForRound(); + lifeCycle.transfersForRoundComplete(); - lifeCycle.roundComplete(state.isDestinationReachedInCurrentRound()); + lifeCycle.roundComplete(worker.isDestinationReachedInCurrentRound()); - findAccessOnStreetForRound(); + worker.findAccessOnStreetForRound(); } // This state is repeatedly modified as the outer loop progresses over departure minutes. @@ -184,119 +160,18 @@ private boolean hasMoreRounds() { if (round() < minNumberOfRounds) { return true; } - return state.isNewRoundAvailable() && roundTracker.hasMoreRounds(); - } - - /** - * Perform a scheduled search - */ - private void findTransitForRound() { - timers.findTransitForRound(() -> { - IntIterator stops = state.stopsTouchedPreviousRound(); - IntIterator routeIndexIterator = transitData.routeIndexIterator(stops); - - while (routeIndexIterator.hasNext()) { - var routeIndex = routeIndexIterator.next(); - var route = transitData.getRouteForIndex(routeIndex); - var pattern = route.pattern(); - var txSearch = enableTransferConstraints - ? calculator.transferConstraintsSearch(transitData, routeIndex) - : null; - - int alightSlack = slackProvider.alightSlack(pattern.slackIndex()); - int boardSlack = slackProvider.boardSlack(pattern.slackIndex()); - - transitWorker.prepareForTransitWith(route); - - IntIterator stop = calculator.patternStopIterator(pattern.numberOfStopsInPattern()); - - while (stop.hasNext()) { - int stopPos = stop.next(); - int stopIndex = pattern.stopIndex(stopPos); - - transitWorker.prepareForNextStop(stopIndex, stopPos); - - // attempt to alight if we're on board, this is done above the board search - // so that we don't alight on first stop boarded - if (calculator.alightingPossibleAt(pattern, stopPos)) { - if (enableTransferConstraints && txSearch.transferExistSourceStop(stopPos)) { - transitWorker.alightConstrainedTransferExist(stopIndex, stopPos, alightSlack); - } else { - transitWorker.alightOnlyRegularTransferExist(stopIndex, stopPos, alightSlack); - } - } - - if (calculator.boardingPossibleAt(pattern, stopPos)) { - // Don't attempt to board if this stop was not reached in the last round. - // Allow to reboard the same pattern - a pattern may loop and visit the same stop twice - if (state.isStopReachedInPreviousRound(stopIndex)) { - // has constrained transfers - if (enableTransferConstraints && txSearch.transferExistTargetStop(stopPos)) { - transitWorker.boardWithConstrainedTransfer( - stopIndex, - stopPos, - boardSlack, - txSearch - ); - } else { - transitWorker.boardWithRegularTransfer(stopIndex, stopPos, boardSlack); - } - } - } - } - } - lifeCycle.transitsForRoundComplete(); - }); - } - - private void findTransfersForRound() { - timers.findTransfersForRound(() -> { - IntIterator it = state.stopsTouchedByTransitCurrentRound(); - - while (it.hasNext()) { - final int fromStop = it.next(); - // no need to consider loop transfers, since we don't mark patterns here any more - // loop transfers are already included by virtue of those stops having been reached - state.transferToStops(fromStop, calculator.getTransfers(transitData, fromStop)); - } - - lifeCycle.transfersForRoundComplete(); - }); + return worker.hasMoreRounds() && roundTracker.hasMoreRounds(); } private int round() { return roundTracker.round(); } - private void findAccessOnStreetForRound() { - addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round())); - } - - private void findAccessOnBoardForRound() { - addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round())); - } - /** * Run the raptor search for this particular iteration departure time */ private void setupIteration(int iterationDepartureTime) { OTPRequestTimeoutException.checkForTimeout(); - this.iterationDepartureTime = iterationDepartureTime; - lifeCycle.setupIteration(this.iterationDepartureTime); - } - - /** - * Set the departure time in the scheduled search to the given departure time, and prepare for the - * scheduled search at the next-earlier minute. - */ - private void addAccessPaths(Collection accessPaths) { - for (RaptorAccessEgress it : accessPaths) { - int departureTime = calculator.departureTime(it, iterationDepartureTime); - - // Access must be available after the iteration departure time - if (departureTime != RaptorConstants.TIME_NOT_SET) { - transitWorker.setAccessToStop(it, departureTime); - } - } + lifeCycle.setupIteration(iterationDepartureTime); } } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..05711b856b6 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -200,7 +200,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + RANGE_RAPTOR ) .verify(); } From b952ad89f5effa7d347d78adcfb888a418a41a1c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 26 Jul 2024 11:20:40 +0200 Subject: [PATCH 05/36] refactor: Use WorkerLifeCycle to inject round counter and remove RoundProvider --- .../raptor/configure/RaptorConfig.java | 3 +- .../rangeraptor/DefaultRangeRaptorWorker.java | 28 ++++-------------- .../raptor/rangeraptor/RangeRaptor.java | 12 ++++---- .../rangeraptor/context/SearchContext.java | 4 +-- .../internalapi/RoundProvider.java | 25 ---------------- .../configure/McRangeRaptorConfig.java | 2 +- .../heuristic/HeuristicsProvider.java | 27 ++++++++++------- .../SimpleBestNumberOfTransfers.java | 11 +++---- .../configure/StdRangeRaptorConfig.java | 6 ++-- .../debug/DebugStopArrivalsState.java | 6 ++-- .../standard/debug/StateDebugger.java | 29 +++++++------------ .../stoparrivals/StdStopArrivals.java | 24 +++++++-------- .../support/TimeBasedBoardingSupport.java | 9 +++--- .../rangeraptor/transit/RoundTracker.java | 14 +++++---- 14 files changed, 75 insertions(+), 125 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoundProvider.java diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index de566bd1364..832753a8ec9 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -127,7 +127,6 @@ private RangeRaptor createWorker( ctx.transit(), ctx.slackProvider(), ctx.accessPaths(), - ctx.roundProvider(), ctx.calculator(), ctx.lifeCycle(), ctx.performanceTimers(), @@ -138,7 +137,7 @@ private RangeRaptor createWorker( worker, ctx.transit(), ctx.accessPaths(), - ctx.roundProvider(), + ctx.roundTracker(), ctx.calculator(), ctx.createLifeCyclePublisher(), ctx.performanceTimers() diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 68c38c862bd..84e3fb6120e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -7,13 +7,11 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.RaptorTransitCalculator; -import org.opentripplanner.raptor.rangeraptor.transit.RoundTracker; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; @@ -62,12 +60,6 @@ public final class DefaultRangeRaptorWorker { */ private final RaptorWorkerState state; - /** - * The round tracker keep track for the current Raptor round, and abort the search if the round - * max limit is reached. - */ - private final RoundTracker roundTracker; - private final RaptorTransitDataProvider transitData; private final SlackProvider slackProvider; @@ -84,13 +76,14 @@ public final class DefaultRangeRaptorWorker { private int iterationDepartureTime; + private int round; + public DefaultRangeRaptorWorker( RaptorWorkerState state, RoutingStrategy transitWorker, RaptorTransitDataProvider transitData, SlackProvider slackProvider, AccessPaths accessPaths, - RoundProvider roundProvider, RaptorTransitCalculator calculator, WorkerLifeCycle lifeCycle, RaptorTimers timers, @@ -106,10 +99,8 @@ public DefaultRangeRaptorWorker( this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); this.enableTransferConstraints = enableTransferConstraints; - // We do a cast here to avoid exposing the round tracker and the life cycle publisher to - // "everyone" by providing access to it in the context. - this.roundTracker = (RoundTracker) roundProvider; lifeCycle.onSetupIteration(time -> this.iterationDepartureTime = time); + lifeCycle.onPrepareForNextRound(round -> this.round = round); } public RaptorWorkerResult results() { @@ -120,10 +111,7 @@ public RaptorWorkerResult results() { * Check if the RangeRaptor should continue with a new round. */ boolean hasMoreRounds() { - if (round() < minNumberOfRounds) { - return true; - } - return state.isNewRoundAvailable() && roundTracker.hasMoreRounds(); + return state.isNewRoundAvailable(); } /** @@ -204,16 +192,12 @@ boolean isDestinationReachedInCurrentRound() { return state.isDestinationReachedInCurrentRound(); } - private int round() { - return roundTracker.round(); - } - void findAccessOnStreetForRound() { - addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round())); + addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round)); } void findAccessOnBoardForRound() { - addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round())); + addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round)); } /** diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 44c6a12bc5e..1e7f22c45f0 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -5,7 +5,6 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleEventPublisher; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.RaptorTransitCalculator; @@ -69,7 +68,7 @@ public RangeRaptor( DefaultRangeRaptorWorker worker, RaptorTransitDataProvider transitData, AccessPaths accessPaths, - RoundProvider roundProvider, + RoundTracker roundTracker, RaptorTransitCalculator calculator, LifeCycleEventPublisher lifeCyclePublisher, RaptorTimers timers @@ -80,10 +79,7 @@ public RangeRaptor( this.timers = timers; this.accessPaths = accessPaths; this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); - - // We do a cast here to avoid exposing the round tracker and the life cycle publisher to - // "everyone" by providing access to it in the context. - this.roundTracker = (RoundTracker) roundProvider; + this.roundTracker = roundTracker; this.lifeCycle = lifeCyclePublisher; } @@ -129,7 +125,7 @@ private void runRaptorForMinute(int iterationDepartureTime) { setupIteration(iterationDepartureTime); worker.findAccessOnStreetForRound(); - while (worker.hasMoreRounds()) { + while (hasMoreRounds()) { lifeCycle.prepareForNextRound(roundTracker.nextRound()); // NB since we have transfer limiting not bothering to cut off search when there are no @@ -172,6 +168,8 @@ private int round() { */ private void setupIteration(int iterationDepartureTime) { OTPRequestTimeoutException.checkForTimeout(); + roundTracker.setupIteration(); + lifeCycle.prepareForNextRound(round()); lifeCycle.setupIteration(iterationDepartureTime); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index 518d02ae3ad..bdeaee45631 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -25,7 +25,6 @@ import org.opentripplanner.raptor.api.request.SearchParams; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleEventPublisher; @@ -193,7 +192,7 @@ public int nRounds() { return tuningParameters.maxNumberOfTransfers() + 1; } - public RoundProvider roundProvider() { + public RoundTracker roundTracker() { return roundTracker; } @@ -227,7 +226,6 @@ public TimeBasedBoardingSupport createTimeBasedBoardingSupport() { accessPaths().hasTimeDependentAccess(), slackProvider(), calculator(), - roundProvider(), lifeCycle() ); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoundProvider.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoundProvider.java deleted file mode 100644 index 21bd86f8133..00000000000 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RoundProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.opentripplanner.raptor.rangeraptor.internalapi; - -import org.opentripplanner.raptor.rangeraptor.transit.RoundTracker; - -/** - * Keep track of current Raptor round. The provider is injected where needed instead of passing the - * current round down the call stack. This is faster than passing the round on the stack because the - * round is access so frequently thet in most cases it is cached in the CPU registry - at least - * tests indicate this. - *

- * - * @see RoundTracker - */ -public interface RoundProvider { - /** - * The current Raptor round. - */ - int round(); - - /** - * Return true if this round is the first round, calculating the first transit path. Access is - * calculated in round zero (0). - */ - boolean isFirstRound(); -} 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 1f143f85d0b..5d579cff7bf 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 @@ -143,8 +143,8 @@ private HeuristicsProvider createHeuristicsProvider(Heuristics heuristics) { } else { return new HeuristicsProvider<>( heuristics, - context.roundProvider(), createDestinationArrivalPaths(), + context.lifeCycle(), context.debugFactory() ); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/heuristic/HeuristicsProvider.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/heuristic/HeuristicsProvider.java index c7b1c43c991..fc4d11c33a4 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/heuristic/HeuristicsProvider.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/heuristic/HeuristicsProvider.java @@ -1,11 +1,12 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria.heuristic; +import java.util.Objects; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.Optimization; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.internalapi.HeuristicAtStop; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths; @@ -18,26 +19,32 @@ public final class HeuristicsProvider { private final Heuristics heuristics; - private final RoundProvider roundProvider; private final DestinationArrivalPaths paths; private final HeuristicAtStop[] stops; private final DebugHandlerFactory debugHandlerFactory; + private int round; + public HeuristicsProvider() { - this(null, null, null, null); + this.heuristics = null; + this.paths = null; + this.stops = null; + this.debugHandlerFactory = null; } public HeuristicsProvider( Heuristics heuristics, - RoundProvider roundProvider, DestinationArrivalPaths paths, + WorkerLifeCycle lifeCycle, DebugHandlerFactory debugHandlerFactory ) { - this.heuristics = heuristics; - this.roundProvider = roundProvider; - this.paths = paths; - this.stops = heuristics == null ? null : new HeuristicAtStop[heuristics.size()]; - this.debugHandlerFactory = debugHandlerFactory; + this.heuristics = Objects.requireNonNull(heuristics); + this.paths = Objects.requireNonNull(paths); + this.stops = new HeuristicAtStop[heuristics.size()]; + this.debugHandlerFactory = Objects.requireNonNull(debugHandlerFactory); + + // Use life-cycle events to inject the range-raptor round + lifeCycle.onPrepareForNextRound(r -> this.round = r); } /** @@ -89,7 +96,7 @@ private boolean qualify(int stop, int arrivalTime, int travelDuration, int cost) return false; } int minArrivalTime = arrivalTime + h.minTravelDuration(); - int minNumberOfTransfers = roundProvider.round() - 1 + h.minNumTransfers(); + int minNumberOfTransfers = round - 1 + h.minNumTransfers(); int minTravelDuration = travelDuration + h.minTravelDuration(); int minCost = cost + h.minCost(); int departureTime = minArrivalTime - minTravelDuration; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/SimpleBestNumberOfTransfers.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/SimpleBestNumberOfTransfers.java index 1fb703565f6..22b0f44a579 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/SimpleBestNumberOfTransfers.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/SimpleBestNumberOfTransfers.java @@ -1,8 +1,8 @@ package org.opentripplanner.raptor.rangeraptor.standard.besttimes; import org.opentripplanner.framework.lang.IntUtils; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.BestNumberOfTransfers; import org.opentripplanner.raptor.rangeraptor.support.IntArraySingleCriteriaArrivals; @@ -13,11 +13,12 @@ public class SimpleBestNumberOfTransfers implements BestNumberOfTransfers { private final int[] bestNumOfTransfers; - private final RoundProvider roundProvider; + private int round; - public SimpleBestNumberOfTransfers(int nStops, RoundProvider roundProvider) { + public SimpleBestNumberOfTransfers(int nStops, WorkerLifeCycle lifeCycle) { this.bestNumOfTransfers = IntUtils.intArray(nStops, unreachedMinNumberOfTransfers()); - this.roundProvider = roundProvider; + + lifeCycle.onPrepareForNextRound(r -> this.round = r); } @Override @@ -29,7 +30,7 @@ public int calculateMinNumberOfTransfers(int stop) { * Call this method to notify that the given stop is reached in the current round of Raptor. */ void arriveAtStop(int stop) { - final int numOfTransfers = roundProvider.round() - 1; + final int numOfTransfers = round - 1; if (numOfTransfers < bestNumOfTransfers[stop]) { bestNumOfTransfers[stop] = numOfTransfers; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java index 6e0c3ee5afd..74e0f286992 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java @@ -163,7 +163,7 @@ private StopArrivalsState stdStopArrivalsState() { private StopArrivalsState wrapStopArrivalsStateWithDebugger(StopArrivalsState state) { if (ctx.debugFactory().isDebugStopArrival()) { return new DebugStopArrivalsState<>( - ctx.roundProvider(), + ctx.lifeCycle(), ctx.debugFactory(), stopsCursor(), state @@ -219,7 +219,7 @@ private StdStopArrivals resolveStopArrivals() { this.stopArrivals = withBestNumberOfTransfers( oneOf( - new StdStopArrivals(ctx.nRounds(), ctx.nStops(), ctx.roundProvider()), + new StdStopArrivals(ctx.nRounds(), ctx.nStops(), ctx.lifeCycle()), StdStopArrivals.class ) ); @@ -232,7 +232,7 @@ private StdStopArrivals resolveStopArrivals() { */ private SimpleBestNumberOfTransfers createSimpleBestNumberOfTransfers() { return withBestNumberOfTransfers( - new SimpleBestNumberOfTransfers(ctx.nStops(), ctx.roundProvider()) + new SimpleBestNumberOfTransfers(ctx.nStops(), ctx.lifeCycle()) ); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/DebugStopArrivalsState.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/DebugStopArrivalsState.java index 6249764340f..2e0f3277a2a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/DebugStopArrivalsState.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/DebugStopArrivalsState.java @@ -7,7 +7,7 @@ import org.opentripplanner.raptor.api.model.TransitArrival; import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.StopArrivalsState; import org.opentripplanner.raptor.rangeraptor.standard.stoparrivals.view.StopsCursor; @@ -28,12 +28,12 @@ public final class DebugStopArrivalsState * Create a Standard range raptor state for the given context */ public DebugStopArrivalsState( - RoundProvider roundProvider, + WorkerLifeCycle lifeCycle, DebugHandlerFactory dFactory, StopsCursor stopsCursor, StopArrivalsState delegate ) { - this.debug = new StateDebugger<>(stopsCursor, roundProvider, dFactory); + this.debug = new StateDebugger<>(stopsCursor, lifeCycle, dFactory); this.delegate = delegate; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/StateDebugger.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/StateDebugger.java index 374b4aaf17e..1aee775b920 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/StateDebugger.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/debug/StateDebugger.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.internalapi.DebugHandler; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.standard.stoparrivals.view.StopsCursor; /** @@ -17,28 +17,25 @@ class StateDebugger { private final StopsCursor cursor; - private final RoundProvider roundProvider; private final DebugHandler> debugHandlerStopArrivals; + private int round; - StateDebugger( - StopsCursor cursor, - RoundProvider roundProvider, - DebugHandlerFactory dFactory - ) { + StateDebugger(StopsCursor cursor, WorkerLifeCycle lifeCycle, DebugHandlerFactory dFactory) { this.cursor = cursor; - this.roundProvider = roundProvider; this.debugHandlerStopArrivals = dFactory.debugStopArrival(); + + lifeCycle.onPrepareForNextRound(r -> this.round = r); } void acceptAccessPath(int stop, RaptorAccessEgress access) { if (isDebug(stop)) { - debugHandlerStopArrivals.accept(cursor.access(round(), stop, access)); + debugHandlerStopArrivals.accept(cursor.access(round, stop, access)); } } void rejectAccessPath(RaptorAccessEgress accessPath, int arrivalTime) { if (isDebug(accessPath.stop())) { - reject(cursor.fictiveAccess(round(), accessPath, arrivalTime)); + reject(cursor.fictiveAccess(round, accessPath, arrivalTime)); } } @@ -64,13 +61,13 @@ void dropOldStateAndAcceptNewOnStreetArrival(int stop, Runnable body) { void rejectTransit(int alightStop, int alightTime, T trip, int boardStop, int boardTime) { if (isDebug(alightStop)) { - reject(cursor.fictiveTransit(round(), alightStop, alightTime, trip, boardStop, boardTime)); + reject(cursor.fictiveTransit(round, alightStop, alightTime, trip, boardStop, boardTime)); } } void rejectTransfer(int fromStop, RaptorTransfer transfer, int toStop, int arrivalTime) { if (isDebug(transfer.stop())) { - reject(cursor.fictiveTransfer(round(), fromStop, transfer, toStop, arrivalTime)); + reject(cursor.fictiveTransfer(round, fromStop, transfer, toStop, arrivalTime)); } } @@ -81,7 +78,7 @@ private boolean isDebug(int stop) { } private void accept(int stop, boolean stopReachedOnBoard) { - debugHandlerStopArrivals.accept(cursor.stop(round(), stop, stopReachedOnBoard)); + debugHandlerStopArrivals.accept(cursor.stop(round, stop, stopReachedOnBoard)); } /** @@ -94,8 +91,6 @@ private void accept(int stop, boolean stopReachedOnBoard) { * handler about arrivals that are about to be dropped. */ private void drop(int stop, boolean onBoard, boolean newBestOverall) { - final int round = round(); - // if new arrival arrived on-board, if (onBoard) { // and an existing on-board arrival exist @@ -122,8 +117,4 @@ private void reject(ArrivalView arrival) { private void dropExistingArrival(int round, int stop, boolean onBoard) { debugHandlerStopArrivals.drop(cursor.stop(round, stop, onBoard), null, null); } - - private int round() { - return roundProvider.round(); - } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/StdStopArrivals.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/StdStopArrivals.java index b3b32890ec7..a6dee126211 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/StdStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/stoparrivals/StdStopArrivals.java @@ -4,8 +4,8 @@ import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.TransitArrival; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.BestNumberOfTransfers; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.DestinationArrivalListener; import org.opentripplanner.raptor.rangeraptor.support.IntArraySingleCriteriaArrivals; @@ -18,12 +18,12 @@ public final class StdStopArrivals implements Best /** Arrivals by round and stop - [round][stop] */ private final StopArrivalState[][] arrivals; - private final RoundProvider roundProvider; + private int round; - public StdStopArrivals(int nRounds, int nStops, RoundProvider roundProvider) { - this.roundProvider = roundProvider; + public StdStopArrivals(int nRounds, int nStops, WorkerLifeCycle lifeCycle) { //noinspection unchecked this.arrivals = (StopArrivalState[][]) new StopArrivalState[nRounds][nStops]; + lifeCycle.onPrepareForNextRound(r -> this.round = r); } /** @@ -71,12 +71,12 @@ public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { void setAccessTime(int time, RaptorAccessEgress access, boolean bestTime) { final int stop = access.stop(); - var existingArrival = getOrCreateStopIndex(round(), stop); + var existingArrival = getOrCreateStopIndex(round, stop); if (existingArrival instanceof AccessStopArrivalState) { ((AccessStopArrivalState) existingArrival).setAccessTime(time, access, bestTime); } else { - arrivals[round()][stop] = + arrivals[round][stop] = new AccessStopArrivalState<>( time, access, @@ -92,13 +92,13 @@ void setAccessTime(int time, RaptorAccessEgress access, boolean bestTime) { */ void transferToStop(int fromStop, RaptorTransfer transfer, int arrivalTime) { int stop = transfer.stop(); - var state = getOrCreateStopIndex(round(), stop); + var state = getOrCreateStopIndex(round, stop); state.transferToStop(fromStop, arrivalTime, transfer); } void transitToStop(int stop, int time, int boardStop, int boardTime, T trip, boolean bestTime) { - var state = getOrCreateStopIndex(round(), stop); + var state = getOrCreateStopIndex(round, stop); state.arriveByTransit(time, boardStop, boardTime, trip); @@ -108,13 +108,13 @@ void transitToStop(int stop, int time, int boardStop, int boardTime, T trip, boo } int bestTimePreviousRound(int stop) { - return get(round() - 1, stop).time(); + return get(round - 1, stop).time(); } /* private methods */ TransitArrival previousTransit(int boardStopIndex) { - final int prevRound = round() - 1; + final int prevRound = round - 1; int stopIndex = boardStopIndex; StopArrivalState state = get(prevRound, boardStopIndex); @@ -129,10 +129,6 @@ TransitArrival previousTransit(int boardStopIndex) { : null; } - private int round() { - return roundProvider.round(); - } - private StopArrivalState getOrCreateStopIndex(final int round, final int stop) { if (arrivals[round][stop] == null) { arrivals[round][stop] = StopArrivalState.create(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/support/TimeBasedBoardingSupport.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/support/TimeBasedBoardingSupport.java index 40df7000461..9ecd2bbf100 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/support/TimeBasedBoardingSupport.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/support/TimeBasedBoardingSupport.java @@ -1,10 +1,10 @@ package org.opentripplanner.raptor.rangeraptor.support; +import static org.opentripplanner.raptor.rangeraptor.transit.RoundTracker.isFirstRound; import static org.opentripplanner.raptor.spi.RaptorTripScheduleSearch.UNBOUNDED_TRIP_INDEX; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.TransitArrival; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; @@ -23,25 +23,24 @@ public final class TimeBasedBoardingSupport { private final SlackProvider slackProvider; private final RaptorTransitCalculator calculator; - private final RoundProvider roundProvider; private final boolean hasTimeDependentAccess; private boolean inFirstIteration = true; private RaptorTimeTable timeTable; private RaptorTripScheduleSearch tripSearch; + private int round; public TimeBasedBoardingSupport( boolean hasTimeDependentAccess, SlackProvider slackProvider, RaptorTransitCalculator calculator, - RoundProvider roundProvider, WorkerLifeCycle subscriptions ) { this.hasTimeDependentAccess = hasTimeDependentAccess; this.slackProvider = slackProvider; this.calculator = calculator; - this.roundProvider = roundProvider; subscriptions.onIterationComplete(() -> inFirstIteration = false); + subscriptions.onPrepareForNextRound(r -> this.round = r); } public void prepareForTransitWith(RaptorTimeTable timeTable) { @@ -124,7 +123,7 @@ private int earliestBoardTime(int prevArrivalTime, int boardSlack) { * Create a trip search using {@link TripScheduleBoardSearch}. */ private RaptorTripScheduleSearch createTripSearch(RaptorTimeTable timeTable) { - if (!inFirstIteration && roundProvider.isFirstRound() && !hasTimeDependentAccess) { + if (!inFirstIteration && isFirstRound(round) && !hasTimeDependentAccess) { // For the first round of every iteration(except the first) we restrict the first // departure to happen within the time-window of the iteration. Another way to put this, // is to say that we allow for the access path to be time-shifted to a later departure, diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java index 05159a19a1e..f355bc72ebb 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java @@ -1,6 +1,5 @@ package org.opentripplanner.raptor.rangeraptor.transit; -import org.opentripplanner.raptor.rangeraptor.internalapi.RoundProvider; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; /** @@ -9,7 +8,10 @@ * In round 0 the access paths with one leg are added. In round 1 the first transit and transfers is * added, ... */ -public class RoundTracker implements RoundProvider { +public class RoundTracker { + + private static final int ROUND_ZERO = 0; + private static final int FIRST_ROUND = 1; /** * The extra number of rounds/transfers we accept compared to the trip with the fewest number of @@ -20,7 +22,7 @@ public class RoundTracker implements RoundProvider { /** * The current round in progress (round index). */ - private int round = 0; + private int round = ROUND_ZERO; /** * The round upper limit for when to abort the search. @@ -61,14 +63,14 @@ public int round() { * Return true if this round is the fist round, calculating the first transit path. Access is * calculated in round zero (0). */ - public boolean isFirstRound() { - return round == 1; + public static boolean isFirstRound(int round) { + return round == FIRST_ROUND; } /** * Before each iteration, initialize the round to 0. */ - private void setupIteration() { + public void setupIteration() { round = 0; } From edda31832820c327486c6bd1a863702ae6f0a8b8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 26 Jul 2024 11:42:40 +0200 Subject: [PATCH 06/36] refactor: Reintroduce the RangeRaptorWorker interface - one level down. --- .../rangeraptor/DefaultRangeRaptorWorker.java | 23 ++++++++---- .../raptor/rangeraptor/RangeRaptor.java | 5 +-- .../internalapi/RangeRaptorWorker.java | 35 +++++++++++++++++-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 84e3fb6120e..27e267f071f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -5,6 +5,7 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -45,7 +46,8 @@ * @param The TripSchedule type defined by the user of the raptor API. */ @SuppressWarnings("Duplicates") -public final class DefaultRangeRaptorWorker { +public final class DefaultRangeRaptorWorker + implements RangeRaptorWorker { private final RoutingStrategy transitWorker; @@ -103,6 +105,7 @@ public DefaultRangeRaptorWorker( lifeCycle.onPrepareForNextRound(round -> this.round = round); } + @Override public RaptorWorkerResult results() { return state.results(); } @@ -110,14 +113,16 @@ public RaptorWorkerResult results() { /** * Check if the RangeRaptor should continue with a new round. */ - boolean hasMoreRounds() { + @Override + public boolean hasMoreRounds() { return state.isNewRoundAvailable(); } /** * Perform a scheduled search */ - void findTransitForRound() { + @Override + public void findTransitForRound() { timers.findTransitForRound(() -> { IntIterator stops = state.stopsTouchedPreviousRound(); IntIterator routeIndexIterator = transitData.routeIndexIterator(stops); @@ -175,7 +180,8 @@ void findTransitForRound() { }); } - void findTransfersForRound() { + @Override + public void findTransfersForRound() { timers.findTransfersForRound(() -> { IntIterator it = state.stopsTouchedByTransitCurrentRound(); @@ -188,15 +194,18 @@ void findTransfersForRound() { }); } - boolean isDestinationReachedInCurrentRound() { + @Override + public boolean isDestinationReachedInCurrentRound() { return state.isDestinationReachedInCurrentRound(); } - void findAccessOnStreetForRound() { + @Override + public void findAccessOnStreetForRound() { addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round)); } - void findAccessOnBoardForRound() { + @Override + public void findAccessOnBoardForRound() { addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round)); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 1e7f22c45f0..53514f04b24 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -4,6 +4,7 @@ import org.opentripplanner.raptor.api.debug.RaptorTimers; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleEventPublisher; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; @@ -44,7 +45,7 @@ @SuppressWarnings("Duplicates") public final class RangeRaptor { - private final DefaultRangeRaptorWorker worker; + private final RangeRaptorWorker worker; /** * The round tracker keep track for the current Raptor round, and abort the search if the round @@ -65,7 +66,7 @@ public final class RangeRaptor { private final int minNumberOfRounds; public RangeRaptor( - DefaultRangeRaptorWorker worker, + RangeRaptorWorker worker, RaptorTransitDataProvider transitData, AccessPaths accessPaths, RoundTracker roundTracker, diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java index 4ba64c35f4e..3a911125630 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java @@ -10,7 +10,38 @@ */ public interface RangeRaptorWorker { /** - * Perform the routing request. + * Fetch the result after the search is performed. */ - RaptorWorkerResult route(); + RaptorWorkerResult results(); + + /** + * Check if the RangeRaptor should continue with a new round. + */ + boolean hasMoreRounds(); + + /** + * Perform a transit search for the current round. + */ + void findTransitForRound(); + + /** + * Apply transfers for the current round. + */ + void findTransfersForRound(); + + /** + * Return {@code true} if the destination is reached in the current round. + */ + boolean isDestinationReachedInCurrentRound(); + + /** + * Apply access for the current round, including round zero - before the first transit. + * This is applied in each round because the access may include transit (FLEX). + */ + void findAccessOnStreetForRound(); + + /** + * Apply access for the current round, when the access arrives to the stop on-board (FLEX). + */ + void findAccessOnBoardForRound(); } From 07b7c22d23f1bc485f103791fcfa7bb4b0994c52 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sat, 27 Jul 2024 14:34:56 +0200 Subject: [PATCH 07/36] refactor: Move RaptorStopNameResolver to package o.o.raptor.api.model --- .../raptor/api/{path => model}/RaptorStopNameResolver.java | 2 +- .../org/opentripplanner/raptor/api/path/PathStringBuilder.java | 1 + .../java/org/opentripplanner/raptor/api/path/RaptorPath.java | 1 + src/main/java/org/opentripplanner/raptor/path/Path.java | 2 +- src/main/java/org/opentripplanner/raptor/path/PathBuilder.java | 2 +- .../raptor/rangeraptor/context/SearchContext.java | 2 +- .../raptor/rangeraptor/path/DestinationArrivalPaths.java | 2 +- .../raptor/rangeraptor/path/ForwardPathMapper.java | 2 +- .../raptor/rangeraptor/path/ReversePathMapper.java | 2 +- .../raptor/rangeraptor/path/configure/PathConfig.java | 2 +- .../opentripplanner/raptor/spi/RaptorTransitDataProvider.java | 2 +- src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java | 2 +- .../transit/request/RaptorRoutingRequestTransitData.java | 2 +- .../algorithm/transferoptimization/api/OptimizedPath.java | 2 +- .../configure/TransferOptimizationServiceConfigurator.java | 2 +- .../algorithm/transferoptimization/model/OptimizedPathTail.java | 2 +- .../services/OptimizePathDomainService.java | 2 +- .../org/opentripplanner/raptor/_data/api/TestPathBuilder.java | 2 +- .../org/opentripplanner/raptor/_data/api/TestRaptorPath.java | 2 +- .../opentripplanner/raptor/_data/transit/TestTransitData.java | 2 +- .../org/opentripplanner/raptor/util/PathStringBuilderTest.java | 2 +- 21 files changed, 21 insertions(+), 19 deletions(-) rename src/main/java/org/opentripplanner/raptor/api/{path => model}/RaptorStopNameResolver.java (94%) diff --git a/src/main/java/org/opentripplanner/raptor/api/path/RaptorStopNameResolver.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorStopNameResolver.java similarity index 94% rename from src/main/java/org/opentripplanner/raptor/api/path/RaptorStopNameResolver.java rename to src/main/java/org/opentripplanner/raptor/api/model/RaptorStopNameResolver.java index 1825c7965fe..a973e1732be 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/RaptorStopNameResolver.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorStopNameResolver.java @@ -1,4 +1,4 @@ -package org.opentripplanner.raptor.api.path; +package org.opentripplanner.raptor.api.model; import javax.annotation.Nullable; diff --git a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java index b96d1a96f14..da56474ae02 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/PathStringBuilder.java @@ -7,6 +7,7 @@ import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorValueFormatter; import org.opentripplanner.raptor.spi.RaptorCostCalculator; diff --git a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java index b92d30643ec..78d90f1d9f4 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; diff --git a/src/main/java/org/opentripplanner/raptor/path/Path.java b/src/main/java/org/opentripplanner/raptor/path/Path.java index ebade8b2690..99f226df0ef 100644 --- a/src/main/java/org/opentripplanner/raptor/path/Path.java +++ b/src/main/java/org/opentripplanner/raptor/path/Path.java @@ -8,6 +8,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; @@ -15,7 +16,6 @@ import org.opentripplanner.raptor.api.path.PathLeg; import org.opentripplanner.raptor.api.path.PathStringBuilder; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransitPathLeg; /** diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java index 7612cc0b3ba..3d0d5e706f6 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilder.java @@ -6,12 +6,12 @@ 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.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.PathStringBuilder; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.spi.BoardAndAlightTime; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorPathConstrainedTransferSearch; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index bdeaee45631..f3fd3943f27 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -13,10 +13,10 @@ import javax.annotation.Nullable; import org.opentripplanner.raptor.api.debug.RaptorTimers; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripPattern; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.SearchDirection; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.request.DebugRequest; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.api.request.RaptorProfile; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index f5e67d593ca..78dc7da4967 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -9,9 +9,9 @@ import org.opentripplanner.framework.logging.Throttle; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.path.Path; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java index 8e9b77f9cb8..6f48aacf652 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ForwardPathMapper.java @@ -1,8 +1,8 @@ package org.opentripplanner.raptor.rangeraptor.path; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.path.PathBuilder; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java index fde483b4cd8..94b68e10985 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/ReversePathMapper.java @@ -1,8 +1,8 @@ package org.opentripplanner.raptor.rangeraptor.path; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.path.PathBuilder; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.transit.TripTimesSearch; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java index 673c83e6b78..43d504ced7d 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java @@ -4,11 +4,11 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; diff --git a/src/main/java/org/opentripplanner/raptor/spi/RaptorTransitDataProvider.java b/src/main/java/org/opentripplanner/raptor/spi/RaptorTransitDataProvider.java index b4accdbcc2b..f0f103070a7 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/RaptorTransitDataProvider.java +++ b/src/main/java/org/opentripplanner/raptor/spi/RaptorTransitDataProvider.java @@ -2,11 +2,11 @@ import java.util.Iterator; import javax.annotation.Nonnull; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; import org.opentripplanner.raptor.api.model.RaptorTripPattern; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.request.RaptorRequest; /** diff --git a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java index 24eae14f997..91de7373650 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java +++ b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java @@ -3,13 +3,13 @@ import java.util.List; import java.util.stream.Stream; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.EgressPathLeg; import org.opentripplanner.raptor.api.path.PathLeg; import org.opentripplanner.raptor.api.path.PathStringBuilder; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransitPathLeg; /** diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index 5b9d81fa6c3..c58e18385bf 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -10,8 +10,8 @@ import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.transfer.TransferService; import org.opentripplanner.raptor.api.model.RaptorConstrainedTransfer; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.spi.RaptorConstrainedBoardingSearch; import org.opentripplanner.raptor.spi.RaptorCostCalculator; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java index ad4df23a42c..f2a36e1678c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/api/OptimizedPath.java @@ -3,12 +3,12 @@ import java.util.function.Supplier; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorConstrainedTransfer; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.PathLeg; import org.opentripplanner.raptor.api.path.PathStringBuilder; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.path.Path; /** 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 cb5f3d7fdab..214e79216e5 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 @@ -3,8 +3,8 @@ import java.util.function.IntFunction; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferService; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.request.MultiCriteriaRequest; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java index 8d18c461101..b36a7d6ebfd 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/OptimizedPathTail.java @@ -4,10 +4,10 @@ import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RaptorValueFormatter; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransitPathLeg; import org.opentripplanner.raptor.path.PathBuilder; import org.opentripplanner.raptor.path.PathBuilderLeg; 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 ca8f953ced5..ebfbf6d76b9 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 @@ -8,9 +8,9 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -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.spi.RaptorCostCalculator; diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java index 007018ca6c4..3c59c8543dd 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestPathBuilder.java @@ -9,8 +9,8 @@ import org.opentripplanner.raptor._data.transit.TestTripPattern; import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.path.PathBuilder; import org.opentripplanner.raptor.spi.DefaultSlackProvider; import org.opentripplanner.raptor.spi.RaptorCostCalculator; diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java index dab47a6d18b..b1cd2c4d411 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java @@ -4,12 +4,12 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.EgressPathLeg; import org.opentripplanner.raptor.api.path.PathLeg; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.TransitPathLeg; /** diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransitData.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransitData.java index 97ad09e3bfd..5f1617075c4 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransitData.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestTransitData.java @@ -16,9 +16,9 @@ import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor._data.RaptorTestConstants; import org.opentripplanner.raptor.api.model.RaptorConstrainedTransfer; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripPattern; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.raptor.spi.DefaultSlackProvider; diff --git a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java index 771efe6ab03..1179712e2fd 100644 --- a/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java +++ b/src/test/java/org/opentripplanner/raptor/util/PathStringBuilderTest.java @@ -5,8 +5,8 @@ import static org.opentripplanner.raptor._data.transit.TestAccessEgress.free; import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.path.PathStringBuilder; -import org.opentripplanner.raptor.api.path.RaptorStopNameResolver; public class PathStringBuilderTest { From 7dd0252c6c3a6d03ef50d7082af2c5675a693899 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 30 Jul 2024 13:37:29 +0200 Subject: [PATCH 08/36] feature: Add raptor Via search API --- .../raptor/api/model/RaptorTransfer.java | 3 +- .../raptor/api/request/SearchParams.java | 32 +++- .../api/request/SearchParamsBuilder.java | 24 ++- .../raptor/api/request/ViaConnection.java | 181 ++++++++++++++++++ .../raptor/api/request/ViaLocation.java | 46 +++++ .../raptor/api/request/ViaConnectionTest.java | 129 +++++++++++++ .../raptor/api/request/ViaLocationTest.java | 53 +++++ 7 files changed, 461 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java create mode 100644 src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java create mode 100644 src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java index bab4b9ab166..856d6828b30 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorTransfer.java @@ -14,8 +14,7 @@ public interface RaptorTransfer { int stop(); /** - * The generalized cost of this transfer in centi-seconds. The value is used to compare with - * riding transit, and will be one component of a full itinerary. + * The generalized cost of this transfer in centi-seconds. *

* This method is called many times, so care needs to be taken that the value is stored, not * calculated for each invocation. diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index 9bad7cf7222..811f9c39465 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -16,6 +16,12 @@ */ public class SearchParams { + /** + * The maximum number of via-locations is used as a check to avoid exploiting the + * search performance. Consider restricting this further in the upstream services. + */ + private static final int MAX_VIA_POINTS = 10; + private final int earliestDepartureTime; private final int latestArrivalTime; private final int searchWindowInSeconds; @@ -26,6 +32,7 @@ public class SearchParams { private final boolean constrainedTransfers; private final Collection accessPaths; private final Collection egressPaths; + private final List viaLocations; /** * Default values are defined in the default constructor. @@ -41,6 +48,7 @@ private SearchParams() { constrainedTransfers = false; accessPaths = List.of(); egressPaths = List.of(); + viaLocations = List.of(); } SearchParams(SearchParamsBuilder builder) { @@ -54,6 +62,7 @@ private SearchParams() { this.constrainedTransfers = builder.constrainedTransfers(); this.accessPaths = List.copyOf(builder.accessPaths()); this.egressPaths = List.copyOf(builder.egressPaths()); + this.viaLocations = List.copyOf(builder.viaLocations()); } /** @@ -195,6 +204,18 @@ public Collection egressPaths() { return egressPaths; } + /** + * List of all possible via locations. All + * *

+ * * NOTE! The {@link RaptorTransfer#stop()} is the stop where the egress path start, NOT the + * * destination - think of it as a reversed path. + * *

+ * * Required, at least one egress path must exist. + */ + public List viaLocations() { + return viaLocations; + } + /** * Get the maximum duration of any access or egress path in seconds. */ @@ -214,7 +235,8 @@ public int hashCode() { preferLateArrival, numberOfAdditionalTransfers, accessPaths, - egressPaths + egressPaths, + viaLocations ); } @@ -234,7 +256,8 @@ public boolean equals(Object o) { preferLateArrival == that.preferLateArrival && numberOfAdditionalTransfers == that.numberOfAdditionalTransfers && accessPaths.equals(that.accessPaths) && - egressPaths.equals(that.egressPaths) + egressPaths.equals(that.egressPaths) && + viaLocations.equals(viaLocations) ); } @@ -254,6 +277,7 @@ public String toString() { ) .addCollection("accessPaths", accessPaths, 5, RaptorAccessEgress::defaultToString) .addCollection("egressPaths", egressPaths, 5, RaptorAccessEgress::defaultToString) + .addCollection("via", viaLocations, 5) .toString(); } @@ -278,5 +302,9 @@ void verify() { !(preferLateArrival && timetable), "The 'departAsLateAsPossible' is not allowed together with 'timetableEnabled'." ); + assertProperty( + viaLocations.size() <= MAX_VIA_POINTS, + "The 'viaLocations' exceeds the maximum number of via-locations (" + MAX_VIA_POINTS + ")." + ); } } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java index 5774a92d6e1..814734a8685 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -18,9 +19,7 @@ public class SearchParamsBuilder { private final RaptorRequestBuilder parent; - private final Collection accessPaths = new ArrayList<>(); - private final Collection egressPaths = new ArrayList<>(); - // Search + private int earliestDepartureTime; private int latestArrivalTime; private int searchWindowInSeconds; @@ -29,6 +28,9 @@ public class SearchParamsBuilder { private int maxNumberOfTransfers; private boolean timetable; private boolean constrainedTransfers; + private final Collection accessPaths = new ArrayList<>(); + private final Collection egressPaths = new ArrayList<>(); + private final List viaLocations = new ArrayList<>(); public SearchParamsBuilder(RaptorRequestBuilder parent, SearchParams defaults) { this.parent = parent; @@ -42,6 +44,7 @@ public SearchParamsBuilder(RaptorRequestBuilder parent, SearchParams defaults this.constrainedTransfers = defaults.constrainedTransfers(); this.accessPaths.addAll(defaults.accessPaths()); this.egressPaths.addAll(defaults.egressPaths()); + this.viaLocations.addAll(defaults.viaLocations()); } public int earliestDepartureTime() { @@ -160,6 +163,20 @@ public SearchParamsBuilder addEgressPaths(RaptorAccessEgress... egressPaths) return addEgressPaths(Arrays.asList(egressPaths)); } + public List viaLocations() { + return viaLocations; + } + + public SearchParamsBuilder addViaLocation(ViaLocation location) { + viaLocations.add(location); + return this; + } + + public SearchParamsBuilder addViaLocations(Collection locations) { + viaLocations.addAll(locations); + return this; + } + public RaptorRequest build() { return parent.build(); } @@ -180,6 +197,7 @@ public String toString() { .addNum("numberOfAdditionalTransfers", numberOfAdditionalTransfers) .addCollection("accessPaths", accessPaths, 5) .addCollection("egressPaths", egressPaths, 5) + .addCollection("via", viaLocations, 10) .toString(); } } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java b/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java new file mode 100644 index 00000000000..b2d001c1f0f --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java @@ -0,0 +1,181 @@ +package org.opentripplanner.raptor.api.request; + +import java.time.Duration; +import java.util.Objects; +import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; + +/** + * A via-connection is used to connect two stops during the raptor routing. The Raptor + * implementation uses these connections to force the path through at least one connection per + * via-location. This is not an alternative to transfers. Raptor supports several use-cases through + * via-connections: + + *

Route via a single stop with a minimum-wait-time

+ + * Raptor will allow a path to go through a single stop, if the from- and to-stop is the same and + * the {@code durationInSeconds} is at least one(1) second. + + *

Route via a pass-through-stop

+ + * Raptor will allow a path to go through a pass-through-stop, if the {@code durationInSeconds} is + * zero and the from and to stop is the same. + + *

Route via a coordinate

+ + * To route through a coordinate you need to find all nearby stops, then calculate the "walk" + * durations and produce the set of connection. If you want to spend a min-wait-time at the + * coordinate, this time must be added to the {@code durationInSeconds}. The calculation of + * {@code c1} need to include the walk time, but not the wait time (assuming all connections + * have the same minimum wait time). + */ +public final class ViaConnection { + + private static final int MAX_WAIT_TIME_LIMIT = (int) Duration.ofHours(24).toSeconds(); + + private final int fromStop; + private final int toStop; + private final int c1; + private final int durationInSeconds; + + private ViaConnection(int fromStop, int toStop, int durationInSeconds, int c1) { + this.fromStop = fromStop; + this.toStop = toStop; + // To transfer from one stop to another must take at least one second + int minDuration = isSameStop() ? RaptorConstants.ZERO : 1; + this.durationInSeconds = + IntUtils.requireInRange( + durationInSeconds, + minDuration, + MAX_WAIT_TIME_LIMIT, + "durationInSeconds" + ); + this.c1 = IntUtils.requireNotNegative(c1, "c1"); + } + + /** + * Force the path through a stop, either on-board or as an alight or board stop. + */ + public static ViaConnection passThroughStop(int stop) { + return new ViaConnection(stop, stop, RaptorConstants.ZERO, RaptorConstants.ZERO); + } + + /** + * Force the path through a stop and wait at least the givan {@code duration} before continuing. + * To visit the stop, the path must board or alight transit at the stop. + */ + public static ViaConnection stop(int stop, Duration duration) { + return new ViaConnection(stop, stop, (int) duration.getSeconds(), RaptorConstants.ZERO); + } + + /** + * Force a path through a user provided transfer. This is meant for supporting a coordinate as a + * via point. The path will alight from transit at the {@code fromStop} and board transit at the + * {@code toStop}. + */ + public static ViaConnection stop(int fromStop, int toStop, Duration duration, int c1) { + return new ViaConnection(fromStop, toStop, (int) duration.getSeconds(), c1); + } + + /** + * Stop index where the connection starts. + */ + public int fromStop() { + return fromStop; + } + + /** + * Stop index where the connection ends. This can be the same as the {@code fromStop}. + */ + public int toStop() { + return toStop; + } + + /** + * The time duration to walk or travel from the {@code fromStop} to the {@code toStop}. + */ + public int durationInSeconds() { + return durationInSeconds; + } + + /** + * The generalized cost of this via-connection in centi-seconds. + *

+ * This method is called many times, so care needs to be taken that the value is stored, not + * calculated for each invocation. + */ + public int c1() { + return c1; + } + + /** + * The path must visit + */ + public boolean allowPassThrough() { + return durationInSeconds == RaptorConstants.ZERO; + } + + public boolean isSameStop() { + return fromStop == toStop; + } + + /** + * This method is used to chack that all connections are unique/provide an optimal path. + * If this connection is better than the other connection, the other connection can be dropped. + *

+ * This is the same as being pareto-optimal. + */ + boolean isBetterThan(ViaConnection other) { + if (fromStop != other.fromStop || toStop != other.toStop) { + return false; + } + return durationInSeconds <= other.durationInSeconds && c1 <= other.c1; + } + + /** + * Only from and to stop is part of the equals/hashCode, duplicate connection between to stops + * are not allowed. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViaConnection that = (ViaConnection) o; + return ( + fromStop == that.fromStop && + toStop == that.toStop && + durationInSeconds == that.durationInSeconds && + c1 == that.c1 + ); + } + + @Override + public int hashCode() { + return Objects.hash(fromStop, toStop, durationInSeconds, c1); + } + + @Override + public String toString() { + return toString(Integer::toString); + } + + public String toString(RaptorStopNameResolver stopNameResolver) { + if (allowPassThrough()) { + return "PassThrough(" + stopNameResolver.apply(fromStop) + ")"; + } + var buf = new StringBuilder("Via("); + + if (durationInSeconds > RaptorConstants.ZERO) { + buf.append(DurationUtils.durationToStr(durationInSeconds())).append(" "); + } + + buf.append(stopNameResolver.apply(fromStop)); + + if (toStop != fromStop) { + buf.append("~").append(stopNameResolver.apply(toStop)); + } + return buf.append(")").toString(); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java new file mode 100644 index 00000000000..0a7a8a78c3b --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java @@ -0,0 +1,46 @@ +package org.opentripplanner.raptor.api.request; + +import java.util.Collection; +import java.util.List; + +public final class ViaLocation { + + private final String label; + private final Collection connections; + + public ViaLocation(String label, Collection connections) { + this.label = label; + this.connections = validateConnections(connections); + } + + public String label() { + return label; + } + + public Collection connections() { + return connections; + } + + @Override + public String toString() { + return "ViaLocation{label: " + label + ", connections: " + connections + "}"; + } + + private Collection validateConnections(Collection connections) { + var list = List.copyOf(connections); + + // Compare all pairs to check for duplicates and none optimal connections + for (int i = 0; i < list.size(); ++i) { + var a = list.get(i); + for (int j = i + 1; j < list.size(); ++j) { + var b = list.get(j); + if (a.equals(b) || a.isBetterThan(b) || b.isBetterThan(a)) { + throw new IllegalArgumentException( + "All connection need to be pareto-optimal. " + "a: " + a + ", b: " + b + ); + } + } + } + return list; + } +} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java new file mode 100644 index 00000000000..998c71e3da4 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java @@ -0,0 +1,129 @@ +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.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Assertions; +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.raptor.api.model.RaptorConstants; + +class ViaConnectionTest { + + private static final int STOP_A = 12; + private static final int STOP_B = 13; + private static final int STOP_C = 14; + private static final Duration MIN_DURATION = Duration.ofMinutes(3); + private static final int MIN_DURATION_SEC = (int) MIN_DURATION.toSeconds(); + private static final int C1 = 200; + + @Test + void passThroughStop() { + var subject = ViaConnection.passThroughStop(STOP_C); + assertEquals(STOP_C, subject.fromStop()); + assertEquals(STOP_C, subject.toStop()); + assertTrue(subject.allowPassThrough()); + assertTrue(subject.isSameStop()); + Assertions.assertEquals(RaptorConstants.ZERO, subject.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, subject.c1()); + } + + @Test + void viaSingleStop() { + var subject = ViaConnection.stop(STOP_C, MIN_DURATION); + assertEquals(STOP_C, subject.fromStop()); + assertEquals(STOP_C, subject.toStop()); + assertFalse(subject.allowPassThrough()); + assertTrue(subject.isSameStop()); + assertEquals(MIN_DURATION_SEC, subject.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, subject.c1()); + } + + @Test + void viaCoordinateOrTransfer() { + var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); + assertEquals(STOP_A, subject.fromStop()); + assertEquals(STOP_B, subject.toStop()); + assertFalse(subject.allowPassThrough()); + assertFalse(subject.isSameStop()); + assertEquals(MIN_DURATION_SEC, subject.durationInSeconds()); + assertEquals(C1, subject.c1()); + } + + static List isBetterThanTestCases() { + // Subject is: STOP_A, STOP_B, MIN_DURATION, C1 + return List.of( + Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1, true, "Same"), + Arguments.of(STOP_C, STOP_B, MIN_DURATION_SEC, C1, false, "toStop differ"), + Arguments.of(STOP_A, STOP_C, MIN_DURATION_SEC, C1, false, "fromStop differ"), + Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC + 1, C1, true, "Wait time is better"), + Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC - 1, C1, false, "Wait time is worse"), + Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1 + 1, true, "C1 is better"), + Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1 - 1, false, "C1 is worse") + ); + } + + @ParameterizedTest + @MethodSource("isBetterThanTestCases") + void isBetterThan( + int fromStop, + int toStop, + int minWaitTime, + int c1, + boolean expected, + String description + ) { + var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); + var candidate = ViaConnection.stop(fromStop, toStop, Duration.ofSeconds(minWaitTime), c1); + assertEquals(subject.isBetterThan(candidate), expected, description); + } + + @Test + void testEqualsAndHashCode() { + var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); + var same = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); + // Slightly less wait-time and slightly larger cost(c1) + var other = ViaConnection.stop( + STOP_A, + STOP_B, + MIN_DURATION.minus(Duration.ofSeconds(1)), + C1 + 1 + ); + + assertEquals(subject, same); + assertNotEquals(subject, other); + assertNotEquals(subject, "Does not match another type"); + + assertEquals(subject.hashCode(), same.hashCode()); + assertNotEquals(subject.hashCode(), other.hashCode()); + } + + @Test + void testToString() { + var viaStopAB = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); + var viaStopB = ViaConnection.stop(STOP_B, MIN_DURATION); + var passThroughC = ViaConnection.passThroughStop(STOP_C); + + assertEquals("Via(3m 12~13)", viaStopAB.toString()); + assertEquals("Via(3m A~B)", viaStopAB.toString(ViaConnectionTest::stopName)); + assertEquals("Via(3m 13)", viaStopB.toString()); + assertEquals("Via(3m B)", viaStopB.toString(ViaConnectionTest::stopName)); + assertEquals("PassThrough(14)", passThroughC.toString()); + assertEquals("PassThrough(C)", passThroughC.toString(ViaConnectionTest::stopName)); + } + + private static String stopName(int i) { + return switch (i) { + case 12 -> "A"; + case 13 -> "B"; + case 14 -> "C"; + default -> throw new IllegalArgumentException("Unknown stop: " + i); + }; + } +} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java new file mode 100644 index 00000000000..b313152f628 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java @@ -0,0 +1,53 @@ +package org.opentripplanner.raptor.api.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ViaLocationTest { + + private static final int STOP_A = 12; + private static final int STOP_B = 13; + private static final Duration DURATION = Duration.ofMinutes(3); + private static final int C1 = 200; + + @Test + void testAssessors() { + var connections = List.of(ViaConnection.stop(STOP_A, STOP_B, DURATION, C1)); + var subject = new ViaLocation("Nnn", connections); + + assertEquals("Nnn", subject.label()); + assertEquals(connections, subject.connections()); + } + + @Test + void twoNoneParetoOptimalConnectionsAreNotAllowed() { + var e = assertThrows( + IllegalArgumentException.class, + () -> + new ViaLocation( + "Via", + List.of(ViaConnection.passThroughStop(STOP_A), ViaConnection.stop(STOP_A, DURATION)) + ) + ); + assertEquals( + "All connection need to be pareto-optimal. a: PassThrough(12), b: Via(3m 12)", + e.getMessage() + ); + } + + @Test + void testToString() { + assertEquals( + "ViaLocation{label: Nnn, connections: [PassThrough(12), PassThrough(13)]}", + new ViaLocation( + "Nnn", + List.of(ViaConnection.passThroughStop(STOP_A), ViaConnection.passThroughStop(STOP_B)) + ) + .toString() + ); + } +} From 1d6a7f1e1a75060bc2aa82953d390dfb6085fc5d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 30 Jul 2024 14:06:34 +0200 Subject: [PATCH 09/36] refactor: Improve local field name: dynamicSearchWindowCalculator (from dynamicSearchParamsCalculator) --- .../raptor/service/RangeRaptorDynamicSearch.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index d93d9e377b4..2e0b88be715 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -43,7 +43,7 @@ public class RangeRaptorDynamicSearch { private final RaptorConfig config; private final RaptorTransitDataProvider transitData; private final RaptorRequest originalRequest; - private final RaptorSearchWindowCalculator dynamicSearchParamsCalculator; + private final RaptorSearchWindowCalculator dynamicSearchWindowCalculator; private final HeuristicSearchTask fwdHeuristics; private final HeuristicSearchTask revHeuristics; @@ -56,7 +56,7 @@ public RangeRaptorDynamicSearch( this.config = config; this.transitData = transitData; this.originalRequest = originalRequest; - this.dynamicSearchParamsCalculator = + this.dynamicSearchWindowCalculator = config.searchWindowCalculator().withSearchParams(originalRequest.searchParams()); this.fwdHeuristics = new HeuristicSearchTask<>(FORWARD, "Forward", config, transitData); @@ -274,10 +274,10 @@ private RaptorRequest requestWithDynamicSearchParams(RaptorRequest request SearchParamsBuilder builder = request.mutate().searchParams(); if (!request.searchParams().isEarliestDepartureTimeSet()) { - builder.earliestDepartureTime(dynamicSearchParamsCalculator.getEarliestDepartureTime()); + builder.earliestDepartureTime(dynamicSearchWindowCalculator.getEarliestDepartureTime()); } if (!request.searchParams().isSearchWindowSet()) { - builder.searchWindowInSeconds(dynamicSearchParamsCalculator.getSearchWindowSeconds()); + builder.searchWindowInSeconds(dynamicSearchWindowCalculator.getSearchWindowSeconds()); } // We do not set the latest-arrival-time, because we do not want to limit the forward // multi-criteria search, it does not have much effect on the performance - we only risk @@ -287,7 +287,7 @@ private RaptorRequest requestWithDynamicSearchParams(RaptorRequest request private void calculateDynamicSearchParametersFromHeuristics(@Nullable Heuristics heuristics) { if (heuristics != null) { - dynamicSearchParamsCalculator + dynamicSearchWindowCalculator .withHeuristics( heuristics.bestOverallJourneyTravelDuration(), heuristics.minWaitTimeForJourneysReachingDestination() From 5a7f9c8f64fdfd3558ada3b677914eeb3945ead3 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 30 Jul 2024 17:45:52 +0200 Subject: [PATCH 10/36] refactor: Minor cleanups in Raptor - no logic changed. --- .../api/request/SearchParamsBuilder.java | 6 +- .../raptor/configure/RaptorConfig.java | 32 +++++---- .../multicriteria/McStopArrivals.java | 55 +++++++-------- .../multicriteria/StopArrivalParetoSet.java | 68 ++++++++++++------- .../configure/McRangeRaptorConfig.java | 48 ++++++++----- .../service/RangeRaptorDynamicSearch.java | 7 +- .../ParetoSetEventListenerComposite.java | 20 +++++- .../routing/api/request/RouteRequest.java | 6 +- .../StopArrivalStateParetoSetTest.java | 35 ++++------ 9 files changed, 158 insertions(+), 119 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java index 814734a8685..32edd1233ff 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java @@ -75,9 +75,9 @@ public SearchParamsBuilder searchWindowInSeconds(int searchWindowInSeconds) { } public SearchParamsBuilder searchWindow(Duration searchWindow) { - this.searchWindowInSeconds = - searchWindow == null ? RaptorConstants.NOT_SET : (int) searchWindow.toSeconds(); - return this; + return searchWindowInSeconds( + searchWindow == null ? RaptorConstants.NOT_SET : (int) searchWindow.toSeconds() + ); } /** diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index 832753a8ec9..18253fddd95 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -13,6 +13,7 @@ import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; +import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; @@ -58,7 +59,7 @@ public RangeRaptor createStdWorker( ) { var context = context(transitData, request); var stdConfig = new StdRangeRaptorConfig<>(context); - return createWorker(context, stdConfig.state(), stdConfig.strategy()); + return createRangeRaptor(context, stdConfig.state(), stdConfig.strategy()); } public RangeRaptor createMcWorker( @@ -66,21 +67,18 @@ public RangeRaptor createMcWorker( RaptorRequest request, Heuristics heuristics ) { - final SearchContext context = context(transitData, request); - return new McRangeRaptorConfig<>(context, passThroughPointsService) - .createWorker( - heuristics, - (state, routingStrategy) -> createWorker(context, state, routingStrategy) - ); + var context = context(transitData, request); + var mcConfig = new McRangeRaptorConfig<>(context, passThroughPointsService) + .withHeuristics(heuristics); + + return createRangeRaptor(context, mcConfig.state(), mcConfig.strategy()); } public RangeRaptor createHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { - var context = context(transitData, request); - var stdConfig = new StdRangeRaptorConfig<>(context); - return createWorker(context, stdConfig.state(), stdConfig.strategy()); + return createStdWorker(transitData, request); } public Heuristics createHeuristic( @@ -116,12 +114,12 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq return McRangeRaptorConfig.passThroughPointsService(request.multiCriteria()); } - private RangeRaptor createWorker( + private RangeRaptorWorker createWorker( SearchContext ctx, RaptorWorkerState workerState, RoutingStrategy routingStrategy ) { - var worker = new DefaultRangeRaptorWorker<>( + return new DefaultRangeRaptorWorker<>( workerState, routingStrategy, ctx.transit(), @@ -132,7 +130,9 @@ private RangeRaptor createWorker( ctx.performanceTimers(), ctx.useConstrainedTransfers() ); + } + private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker worker) { return new RangeRaptor<>( worker, ctx.transit(), @@ -144,6 +144,14 @@ private RangeRaptor createWorker( ); } + private RangeRaptor createRangeRaptor( + SearchContext ctx, + RaptorWorkerState workerState, + RoutingStrategy routingStrategy + ) { + return createRangeRaptor(ctx, createWorker(ctx, workerState, routingStrategy)); + } + private IntPredicate acceptC2AtDestination() { return passThroughPointsService.isNoop() ? null diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java index 4090890cc82..dc0fbb9baee 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java @@ -55,16 +55,16 @@ public McStopArrivals( glueTogetherEgressStopWithDestinationArrivals(egressPaths, paths); } - public boolean reached(int stopIndex) { + boolean reached(int stopIndex) { return arrivals[stopIndex] != null && !arrivals[stopIndex].isEmpty(); } /** Slow! do not use during routing! */ - public int bestArrivalTime(int stopIndex) { + int bestArrivalTime(int stopIndex) { return minInt(arrivals[stopIndex].stream(), McStopArrival::arrivalTime); } - public boolean reachedByTransit(int stopIndex) { + boolean reachedByTransit(int stopIndex) { return ( arrivals[stopIndex] != null && arrivals[stopIndex].stream().anyMatch(a -> a.arrivedBy(TRANSIT)) @@ -72,12 +72,12 @@ public boolean reachedByTransit(int stopIndex) { } /** Slow! do not use during routing! */ - public int bestTransitArrivalTime(int stopIndex) { + int bestTransitArrivalTime(int stopIndex) { return transitStopArrivalsMinInt(stopIndex, McStopArrival::arrivalTime); } /** Slow! do not use during routing! */ - public int smallestNumberOfTransfers(int stopIndex) { + int smallestNumberOfTransfers(int stopIndex) { return transitStopArrivalsMinInt(stopIndex, McStopArrival::numberOfTransfers); } @@ -100,22 +100,16 @@ void debugStateInfo() { debugStats.debugStatInfo(arrivals); } - public boolean hasArrivalsAfterMarker(int stop) { - StopArrivalParetoSet it = arrivals[stop]; - if (it == null) { - return false; - } - return it.hasElementsAfterMarker(); + boolean hasArrivalsAfterMarker(int stop) { + var it = arrivals[stop]; + return it != null && it.hasElementsAfterMarker(); } /** List all transits arrived this round. */ Iterable> listArrivalsAfterMarker(final int stop) { - StopArrivalParetoSet it = arrivals[stop]; - if (it == null) { - // Avoid creating new objects in a tight loop - return Collections::emptyIterator; - } - return it.elementsAfterMarker(); + var it = arrivals[stop]; + // Avoid creating new objects in a tight loop + return it == null ? Collections::emptyIterator : it.elementsAfterMarker(); } void clearTouchedStopsAndSetStopMarkers() { @@ -131,10 +125,10 @@ void clearTouchedStopsAndSetStopMarkers() { private StopArrivalParetoSet findOrCreateSet(final int stop) { if (arrivals[stop] == null) { arrivals[stop] = - StopArrivalParetoSet.createStopArrivalSet( - comparatorFactory.compareArrivalTimeRoundAndCost(), - debugHandlerFactory.paretoSetStopArrivalListener(stop) - ); + StopArrivalParetoSet + .of(comparatorFactory.compareArrivalTimeRoundAndCost()) + .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) + .build(); } return arrivals[stop]; } @@ -145,10 +139,10 @@ private void initAccessArrivals(AccessPaths accessPaths) { for (var access : accessPaths.arrivedOnBoardByNumOfRides(nRides)) { int stop = access.stop(); arrivals[stop] = - StopArrivalParetoSet.createStopArrivalSet( - comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival(), - debugHandlerFactory.paretoSetStopArrivalListener(stop) - ); + StopArrivalParetoSet + .of(comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival()) + .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) + .build(); } } } @@ -166,12 +160,11 @@ private void glueTogetherEgressStopWithDestinationArrivals( .forEachEntry((stop, list) -> { // The factory is creating the actual "glue" this.arrivals[stop] = - StopArrivalParetoSet.createEgressStopArrivalSet( - comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival(), - list, - paths, - debugHandlerFactory.paretoSetStopArrivalListener(stop) - ); + StopArrivalParetoSet + .of(comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival()) + .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) + .withEgressListener(list, paths) + .build(); return true; }); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java index ca653be6fc9..3d75a0d3438 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java @@ -1,7 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria; import java.util.List; -import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.view.ArrivalView; @@ -30,36 +29,55 @@ private StopArrivalParetoSet( super(comparator, listener); } - /** - * Create a stop arrivals pareto set and attach an optional {@code paretoSetEventListener} - * (debug handler). - */ - static StopArrivalParetoSet createStopArrivalSet( - ParetoComparator> comparator, - @Nullable ParetoSetEventListener> paretoSetEventListener + public static Builder of( + ParetoComparator> comparator ) { - return new StopArrivalParetoSet<>(comparator, paretoSetEventListener); + return new Builder<>(comparator); } - /** - * Create a new StopArrivalParetoSet and attach a debugger if it exist. Also attach a {@link - * CalculateTransferToDestination} listener which will create new destination arrivals for each - * accepted egress stop arrival. - */ - static StopArrivalParetoSet createEgressStopArrivalSet( - ParetoComparator> comparator, - List egressPaths, - DestinationArrivalPaths destinationArrivals, - @Nullable ParetoSetEventListener> paretoSetEventListener - ) { - ParetoSetEventListener> listener; + static class Builder { - listener = new CalculateTransferToDestination<>(egressPaths, destinationArrivals); + private ParetoSetEventListener> debugListener = null; + private ParetoSetEventListener> egressListener = null; + private ParetoSetEventListener> nextSearchListener = null; + private final ParetoComparator> comparator; - if (paretoSetEventListener != null) { - listener = new ParetoSetEventListenerComposite<>(paretoSetEventListener, listener); + public Builder(ParetoComparator> comparator) { + this.comparator = comparator; } - return new StopArrivalParetoSet<>(comparator, listener); + /** + * Attach an optional {@code paretoSetEventListener} debug handler. + */ + Builder withDebugListener(ParetoSetEventListener> debugListener) { + this.debugListener = debugListener; + return this; + } + + /** + * Attach a {@link CalculateTransferToDestination} listener which will create new destination + * arrivals for each accepted egress stop arrival. + */ + Builder withEgressListener( + List egressPaths, + DestinationArrivalPaths destinationArrivals + ) { + this.egressListener = new CalculateTransferToDestination<>(egressPaths, destinationArrivals); + return this; + } + + Builder withNextSearchListener(ParetoSetEventListener> nextSearchListener) { + this.nextSearchListener = nextSearchListener; + return this; + } + + StopArrivalParetoSet build() { + // The order of the listeners is important, we want the debug event for reaching a + // stop to appear before the path is logged (in case both debuggers are enabled). + return new StopArrivalParetoSet<>( + comparator, + ParetoSetEventListenerComposite.of(debugListener, egressListener, nextSearchListener) + ); + } } } 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 5d579cff7bf..5a55eeb424a 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 @@ -1,7 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria.configure; import java.util.Objects; -import java.util.function.BiFunction; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -44,8 +43,11 @@ public class McRangeRaptorConfig { private final SearchContext context; private final PathConfig pathConfig; + private final PassThroughPointsService passThroughPointsService; private DestinationArrivalPaths paths; - private PassThroughPointsService passThroughPointsService; + private McRangeRaptorWorkerState state; + private RoutingStrategy strategy; + private Heuristics heuristics; public McRangeRaptorConfig( SearchContext context, @@ -70,12 +72,20 @@ public static PassThroughPointsService passThroughPointsService( /** * Create new multi-criteria worker with optional heuristics. */ - public RangeRaptor createWorker( - Heuristics heuristics, - BiFunction, RoutingStrategy, RangeRaptor> createWorker - ) { - McRangeRaptorWorkerState state = createState(heuristics); - return createWorker.apply(state, createTransitWorkerStrategy(state)); + public McRangeRaptorConfig withHeuristics(Heuristics heuristics) { + this.heuristics = heuristics; + return this; + } + + /** + * Create new multi-criteria worker with optional heuristics. + */ + public RoutingStrategy strategy() { + return createTransitWorkerStrategy(createState(heuristics)); + } + + public RaptorWorkerState state() { + return createState(heuristics); } /* private factory methods */ @@ -111,15 +121,19 @@ private > RoutingStrategy createTransitWorkerStrateg } private McRangeRaptorWorkerState createState(Heuristics heuristics) { - return new McRangeRaptorWorkerState<>( - createStopArrivals(), - createDestinationArrivalPaths(), - createHeuristicsProvider(heuristics), - createStopArrivalFactory(), - context.costCalculator(), - context.calculator(), - context.lifeCycle() - ); + if (state == null) { + state = + new McRangeRaptorWorkerState<>( + createStopArrivals(), + createDestinationArrivalPaths(), + createHeuristicsProvider(heuristics), + createStopArrivalFactory(), + context.costCalculator(), + context.calculator(), + context.lifeCycle() + ); + } + return state; } private McStopArrivalFactory createStopArrivalFactory() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 2e0b88be715..922f89db792 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -67,19 +67,18 @@ public RaptorResponse route() { try { enableHeuristicSearchBasedOnOptimizationsAndSearchParameters(); - // Run heuristics, if no destination is reached + // Run the heuristics if no destination is reached runHeuristics(); // Set search-window and other dynamic calculated parameters - RaptorRequest dynamicRequest = originalRequest; - dynamicRequest = requestWithDynamicSearchParams(dynamicRequest); + var dynamicRequest = requestWithDynamicSearchParams(originalRequest); return createAndRunDynamicRRWorker(dynamicRequest); } catch (DestinationNotReachedException e) { return new RaptorResponse<>( Collections.emptyList(), null, - // If a trip exist(forward heuristics succeed), but is outside the calculated + // If a trip exists(forward heuristics succeed), but is outside the calculated // search-window, then set the search-window params as if the request was // performed. This enables the client to page to the next window requestWithDynamicSearchParams(originalRequest), diff --git a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java index 01d68af4392..73c37744ba6 100644 --- a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java +++ b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java @@ -4,6 +4,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; /** * The {@link ParetoSet} do only support ONE listener, this class uses the composite pattern to @@ -16,9 +18,23 @@ public class ParetoSetEventListenerComposite implements ParetoSetEventListene private final List> listeners = new ArrayList<>(); + /** + * Take a list of listeners and return a composite listener. Input listeners witch is {@code null} + * is skipped. If no listeners are provided, all listeners are {@code null}, then + * {@code null} is returned. If just one listener is passed in the listener it-self is returned + * (without any wrapper). + */ + @Nullable @SafeVarargs - public ParetoSetEventListenerComposite(ParetoSetEventListener... listeners) { - this(Arrays.asList(listeners)); + public static ParetoSetEventListener of(ParetoSetEventListener... listeners) { + var list = Arrays.stream(listeners).filter(Objects::nonNull).toList(); + if (list.isEmpty()) { + return null; + } + if (list.size() == 1) { + return list.get(0); + } + return new ParetoSetEventListenerComposite<>(list); } private ParetoSetEventListenerComposite( diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 76e5dcc558a..c0873e407ae 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -291,15 +291,15 @@ public void setPassThroughPoints(final List passThroughPoints) * latest-departure-time(LDT). In case of a reverse search it will be the time from earliest to * latest arrival time (LAT - EAT). *

- * All optimal travels that depart within the search window is guaranteed to be found. + * All optimal travels that depart within the search window are guaranteed to be found. *

* This is sometimes referred to as the Range Raptor Search Window - but could be used in a none * Transit search as well; Hence this is named search-window and not raptor-search-window. Do not * confuse this with the travel-window, which is the time between EDT to LAT. *

* Use {@code null} to unset, and {@link Duration#ZERO} to do one Raptor iteration. The value is - * dynamically assigned a suitable value, if not set. In a small to medium size operation you may - * use a fixed value, like 60 minutes. If you have a mixture of high frequency cities routes and + * dynamically assigned a suitable value, if not set. In a small-to-medium size operation, you may + * use a fixed value, like 60 minutes. If you have a mixture of high-frequency city routes and * infrequent long distant journeys, the best option is normally to use the dynamic auto * assignment. *

diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java index e9c39ba6bbf..60fa14e9385 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalStateParetoSetTest.java @@ -1,8 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.raptor.rangeraptor.multicriteria.StopArrivalParetoSet.createEgressStopArrivalSet; -import static org.opentripplanner.raptor.rangeraptor.multicriteria.StopArrivalParetoSet.createStopArrivalSet; import java.util.Arrays; import java.util.List; @@ -20,6 +18,7 @@ import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c1.StopArrivalFactoryC1; import org.opentripplanner.raptor.rangeraptor.multicriteria.ride.c1.PatternRideC1; +import org.opentripplanner.raptor.util.paretoset.ParetoComparator; public class StopArrivalStateParetoSetTest { @@ -33,9 +32,9 @@ public class StopArrivalStateParetoSetTest { .schedule("10:00 10:30") .build(); - // In this test each stop is used to identify the pareto vector - it is just one - // ParetoSet "subject" with multiple "stops" in it. The stop have no effect on - // the Pareto functionality. + // In this test, each stop is used to identify the pareto vector - it is just one + // ParetoSet "subject" with multiple "stops" in it. The stop has no effect on + // the Pareto functionality - the stop is not a criteria in the pareto-function. private static final int STOP_1 = 1; private static final int STOP_2 = 2; private static final int STOP_3 = 3; @@ -72,19 +71,12 @@ public class StopArrivalStateParetoSetTest { ); private static Stream testCases() { + ParetoComparator> comparator = COMPARATOR_FACTORY.compareArrivalTimeRoundAndCost(); return Stream.of( - Arguments.of( - "Stop Arrival - regular", - createStopArrivalSet(COMPARATOR_FACTORY.compareArrivalTimeRoundAndCost(), null) - ), + Arguments.of("Stop Arrival - regular", StopArrivalParetoSet.of(comparator).build()), Arguments.of( "Stop Arrival - w/egress", - createEgressStopArrivalSet( - COMPARATOR_FACTORY.compareArrivalTimeRoundCostAndOnBoardArrival(), - List.of(), - null, - null - ) + StopArrivalParetoSet.of(comparator).withEgressListener(List.of(), null).build() ) ); } @@ -169,7 +161,8 @@ public void testRoundAndTimeDominance( */ @Test public void testTransitAndTransferDoesNotAffectDominance() { - var subject = createStopArrivalSet(COMPARATOR_FACTORY.compareArrivalTimeRoundAndCost(), null); + ParetoComparator> comparator = COMPARATOR_FACTORY.compareArrivalTimeRoundAndCost(); + var subject = StopArrivalParetoSet.of(comparator).build(); subject.add(newAccessStopState(STOP_1, 20, ANY)); subject.add(newTransitStopState(ROUND_1, STOP_2, 10, ANY)); subject.add(newTransferStopState(ROUND_1, STOP_4, 8, ANY)); @@ -184,12 +177,10 @@ public void testTransitAndTransferDoesNotAffectDominance() { */ @Test public void testTransitAndTransferDoesAffectDominanceForStopArrivalsWithEgress() { - var subject = createEgressStopArrivalSet( - COMPARATOR_FACTORY.compareArrivalTimeRoundCostAndOnBoardArrival(), - List.of(), - null, - null - ); + var subject = StopArrivalParetoSet + .of(COMPARATOR_FACTORY.compareArrivalTimeRoundCostAndOnBoardArrival()) + .withEgressListener(List.of(), null) + .build(); subject.add(newAccessStopState(STOP_1, 20, ANY)); subject.add(newTransitStopState(ROUND_1, STOP_2, 10, ANY)); subject.add(newTransferStopState(ROUND_1, STOP_4, 8, ANY)); From c1bcc7ed253cf6bb19b36268e75f05405034e7b8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 12:16:50 +0200 Subject: [PATCH 11/36] refactor: Add CompositeUtil and more unit tests to the ParetoSet This extract logic to merge a hierarchy of objects containing composites, flatten the structure into one list of elements. There is a small performance optimization, but the important thing is that we will use this later in the design later. --- .../raptor/util/composite/CompositeUtil.java | 54 +++++ .../raptor/util/paretoset/ParetoSet.java | 8 + .../ParetoSetEventListenerComposite.java | 29 +-- .../raptor/RaptorArchitectureTest.java | 10 +- .../util/composite/CompositeUtilTest.java | 76 +++++++ .../ParetoSetEventListenerCompositeTest.java | 63 ++++++ .../paretoset/ParetoSetEventListenerTest.java | 58 ++---- .../raptor/util/paretoset/ParetoSetTest.java | 194 +++++++++--------- .../paretoset/TestParetoSetEventListener.java | 58 ++++++ .../{Vector.java => TestVector.java} | 18 +- 10 files changed, 412 insertions(+), 156 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/util/composite/CompositeUtil.java create mode 100644 src/test/java/org/opentripplanner/raptor/util/composite/CompositeUtilTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerCompositeTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/util/paretoset/TestParetoSetEventListener.java rename src/test/java/org/opentripplanner/raptor/util/paretoset/{Vector.java => TestVector.java} (71%) diff --git a/src/main/java/org/opentripplanner/raptor/util/composite/CompositeUtil.java b/src/main/java/org/opentripplanner/raptor/util/composite/CompositeUtil.java new file mode 100644 index 00000000000..5e0851c7f6b --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/util/composite/CompositeUtil.java @@ -0,0 +1,54 @@ +package org.opentripplanner.raptor.util.composite; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +public class CompositeUtil { + + /** + * Take a list of children and return a composite instance. Input children witch is {@code null} + * is skipped. If no none {@code null} children are provided, {@code null} is retuned + * thrown. If just one listener is passed in the listener it-self is returned (without any wrapper). + * + * @param The base type which the composite inherit from. + * @param makeComposite Factory method to create a new composite. + * @param isComposite see the {@code listChildren} parameter. + * @param listChildren is a function used together with the {@code isComposite} test to extract + * all children out of a composite. This is used to produce one flat list of + * concrete children, without any composite instances in it. The order is + * kept; the composite children are inserted in the new list of children in + * the same place as the composite instance appeared. + * @return {@code null} if all children are {@code null}, the child it-self if only one child + * exist, and a new composite instance if more than one child exist. + */ + @Nullable + @SafeVarargs + public static T of( + Function, T> makeComposite, + Predicate isComposite, + Function> listChildren, + T... children + ) { + Objects.requireNonNull(children); + + var list = Arrays + .stream(children) + .filter(Objects::nonNull) + .flatMap(it -> isComposite.test(it) ? listChildren.apply(it).stream() : Stream.of(it)) + .toList(); + + if (list.isEmpty()) { + return null; + } + if (list.size() == 1) { + return list.getFirst(); + } + return makeComposite.apply(list); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index 39656d433e9..536378e3722 100644 --- a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -211,6 +211,14 @@ protected void notifyElementMoved(int fromIndex, int toIndex) { // Noop } + protected ParetoComparator getComparator() { + return comparator; + } + + protected ParetoSetEventListener getEventListener() { + return eventListener; + } + /** * Return an iterable instance. This is made to be as FAST AS POSSIBLE, sacrificing thread-safety * and modifiable protection. diff --git a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java index 73c37744ba6..46dd929869c 100644 --- a/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java +++ b/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerComposite.java @@ -1,11 +1,9 @@ package org.opentripplanner.raptor.util.paretoset; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.Nullable; +import org.opentripplanner.raptor.util.composite.CompositeUtil; /** * The {@link ParetoSet} do only support ONE listener, this class uses the composite pattern to @@ -16,31 +14,29 @@ */ public class ParetoSetEventListenerComposite implements ParetoSetEventListener { - private final List> listeners = new ArrayList<>(); + private final List> listeners; /** * Take a list of listeners and return a composite listener. Input listeners witch is {@code null} * is skipped. If no listeners are provided, all listeners are {@code null}, then * {@code null} is returned. If just one listener is passed in the listener it-self is returned - * (without any wrapper). + * (without any wrapper). If more than one listener exists, a composite instance is returned. */ @Nullable @SafeVarargs public static ParetoSetEventListener of(ParetoSetEventListener... listeners) { - var list = Arrays.stream(listeners).filter(Objects::nonNull).toList(); - if (list.isEmpty()) { - return null; - } - if (list.size() == 1) { - return list.get(0); - } - return new ParetoSetEventListenerComposite<>(list); + return CompositeUtil.of( + ParetoSetEventListenerComposite::new, + it -> it instanceof ParetoSetEventListenerComposite, + it -> ((ParetoSetEventListenerComposite) it).listeners, + listeners + ); } private ParetoSetEventListenerComposite( Collection> listeners ) { - this.listeners.addAll(listeners); + this.listeners = List.copyOf(listeners); } @Override @@ -63,4 +59,9 @@ public void notifyElementRejected(T element, T rejectedByElement) { it.notifyElementRejected(element, rejectedByElement); } } + + @Override + public String toString() { + return "ParetoSetEventListenerComposite{" + "listeners=" + listeners + '}'; + } } diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 05711b856b6..657e060c30b 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -21,7 +21,12 @@ public class RaptorArchitectureTest { private static final Package API_PATH = API.subPackage("path"); private static final Package RAPTOR_UTIL = RAPTOR.subPackage("util"); private static final Package RAPTOR_UTIL_PARETO_SET = RAPTOR_UTIL.subPackage("paretoset"); - private static final Module RAPTOR_UTILS = Module.of(RAPTOR_UTIL, RAPTOR_UTIL_PARETO_SET); + private static final Package RAPTOR_UTIL_COMPOSITE = RAPTOR_UTIL.subPackage("composite"); + private static final Module RAPTOR_UTILS = Module.of( + RAPTOR_UTIL, + RAPTOR_UTIL_PARETO_SET, + RAPTOR_UTIL_COMPOSITE + ); private static final Package RAPTOR_SPI = RAPTOR.subPackage("spi"); private static final Package RAPTOR_PATH = RAPTOR.subPackage("path"); private static final Package CONFIGURE = RAPTOR.subPackage("configure"); @@ -78,7 +83,8 @@ void enforcePackageDependenciesRaptorSPI() { @Test void enforcePackageDependenciesUtil() { RAPTOR_UTIL.dependsOn(FRAMEWORK_UTILS, RAPTOR_SPI).verify(); - RAPTOR_UTIL_PARETO_SET.verify(); + RAPTOR_UTIL_PARETO_SET.dependsOn(RAPTOR_UTIL_COMPOSITE).verify(); + RAPTOR_UTIL_COMPOSITE.verify(); } @Test diff --git a/src/test/java/org/opentripplanner/raptor/util/composite/CompositeUtilTest.java b/src/test/java/org/opentripplanner/raptor/util/composite/CompositeUtilTest.java new file mode 100644 index 00000000000..6de1f8b842d --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/util/composite/CompositeUtilTest.java @@ -0,0 +1,76 @@ +package org.opentripplanner.raptor.util.composite; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +class CompositeUtilTest { + + @Test + void testOf() { + TNamed EMPTY = null; + assertNull(composite(EMPTY)); + assertNull(composite(EMPTY, EMPTY)); + + assertEquals("A", composite(tnamed("A")).name()); + assertEquals("A", composite(EMPTY, tnamed("A"), EMPTY).name()); + assertEquals("(A:B)", composite(tnamed("A"), tnamed("B")).name()); + // Nested composites are flattened into one composite + assertEquals( + "(A:B:C)", + composite(composite(tnamed("A")), composite(tnamed("B")), composite(tnamed("C"))).name() + ); + } + + TNamed composite(TNamed... children) { + return TNamedComposite.of(children); + } + + TNamed tnamed(String name) { + return new DefaultTNamed(name); + } + + interface TNamed { + String name(); + } + + static final class DefaultTNamed implements TNamed { + + private final String name; + + public DefaultTNamed(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + } + + static final class TNamedComposite implements TNamed { + + private final List children; + + private TNamedComposite(List children) { + this.children = children; + } + + static TNamed of(TNamed... children) { + return CompositeUtil.of( + TNamedComposite::new, + TNamedComposite.class::isInstance, + it -> ((TNamedComposite) it).children, + children + ); + } + + @Override + public String name() { + return "(" + children.stream().map(TNamed::name).collect(Collectors.joining(":")) + ")"; + } + } +} diff --git a/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerCompositeTest.java b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerCompositeTest.java new file mode 100644 index 00000000000..3fe05f80afd --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerCompositeTest.java @@ -0,0 +1,63 @@ +package org.opentripplanner.raptor.util.paretoset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +class ParetoSetEventListenerCompositeTest { + + public static final String EMPTY = ""; + private final TestParetoSetEventListener l1 = new TestParetoSetEventListener<>(); + private final TestParetoSetEventListener l2 = new TestParetoSetEventListener<>(); + private final ParetoSetEventListener subject = ParetoSetEventListenerComposite.of(l1, l2); + + @Test + void notifyElementAccepted() { + assertNotNull(subject); + subject.notifyElementAccepted("A"); + assertState("A", EMPTY, EMPTY); + subject.notifyElementAccepted("B"); + assertState("A B", EMPTY, EMPTY); + } + + @Test + void notifyElementDropped() { + assertNotNull(subject); + subject.notifyElementDropped("A", "x"); + assertState(EMPTY, "A", EMPTY); + subject.notifyElementDropped("C", "y"); + assertState(EMPTY, "A C", EMPTY); + } + + @Test + void notifyElementRejected() { + assertNotNull(subject); + subject.notifyElementRejected("A", "x"); + assertState(EMPTY, EMPTY, "A"); + subject.notifyElementRejected("C", "y"); + assertState(EMPTY, EMPTY, "A C"); + } + + @Test + void verifyTheListenerStructureIsFlattenOut() { + assertNotNull(subject); + assertEquals( + subject.toString(), + "ParetoSetEventListenerComposite{listeners=[" + + "TestParetoSetEventListener, TestParetoSetEventListener" + + "]}" + ); + } + + private void assertState(String accepted, String dropped, String rejected) { + assertEquals(l1.acceptedAsString(), accepted); + assertEquals(l2.acceptedAsString(), accepted); + + assertEquals(l1.droppedAsString(), dropped); + assertEquals(l2.droppedAsString(), dropped); + + assertEquals(l1.rejectedAsString(), rejected); + assertEquals(l2.rejectedAsString(), rejected); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerTest.java b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerTest.java index 1843d2c18ba..0f23144efc6 100644 --- a/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerTest.java +++ b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetEventListenerTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; @@ -10,22 +9,20 @@ public class ParetoSetEventListenerTest { - private final List accepted = new ArrayList<>(); - private final List rejected = new ArrayList<>(); - private final List dropped = new ArrayList<>(); - // Given a set and function + private final TestParetoSetEventListener listener = new TestParetoSetEventListener(); + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private final ParetoSet subject = new ParetoSet<>( + private final ParetoSet subject = new ParetoSet<>( (l, r) -> l.v1 < r.v1 || l.v2 < r.v2, - eventListener() + listener ); @BeforeEach public void setup() { subject.clear(); - clearResult(); + listener.clear(); } @Test @@ -43,7 +40,7 @@ public void testAccept() { public void testReject() { // Add a initial value subject.add(vector(5, 1)); - clearResult(); + listener.clear(); // Add another value -> expect rejected subject.add(vector(6, 2)); @@ -56,7 +53,7 @@ public void testDropped() { subject.add(vector(2, 5)); subject.add(vector(4, 4)); subject.add(vector(5, 3)); - clearResult(); + listener.clear(); // Add another value -> expect rejected subject.add(vector(1, 5)); @@ -66,14 +63,8 @@ public void testDropped() { assertAcceptedRejectedAndDropped("[1, 0]", "", "[4, 4] [5, 3] [1, 5]"); } - private Vector vector(int u, int v) { - return new Vector("", u, v); - } - - private void clearResult() { - accepted.clear(); - rejected.clear(); - dropped.clear(); + private TestVector vector(int u, int v) { + return new TestVector("", u, v); } private void assertAcceptedRejectedAndDropped( @@ -81,32 +72,17 @@ private void assertAcceptedRejectedAndDropped( String expRejected, String expDropped ) { - assertEquals(expAccepted, toString(accepted)); - assertEquals(expRejected, toString(rejected)); - assertEquals(expDropped, toString(dropped)); - clearResult(); + assertEquals(expAccepted, listener.acceptedAsString()); + assertEquals(expRejected, listener.rejectedAsString()); + assertEquals(expDropped, listener.droppedAsString()); + listener.clear(); } - private String toString(List list) { - return list.stream().map(Vector::toString).collect(Collectors.joining(" ")); + private String toString(List list) { + return list.stream().map(TestVector::toString).collect(Collectors.joining(" ")); } - private ParetoSetEventListener eventListener() { - return new ParetoSetEventListener<>() { - @Override - public void notifyElementAccepted(Vector newElement) { - accepted.add(newElement); - } - - @Override - public void notifyElementDropped(Vector element, Vector droppedByElement) { - dropped.add(element); - } - - @Override - public void notifyElementRejected(Vector element, Vector rejectedByElement) { - rejected.add(element); - } - }; + private TestParetoSetEventListener eventListener() { + return new TestParetoSetEventListener<>(); } } diff --git a/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetTest.java b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetTest.java index 21f998b4757..1c410c13e1c 100644 --- a/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetTest.java +++ b/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoSetTest.java @@ -16,29 +16,29 @@ public class ParetoSetTest { - private static final ParetoComparator DIFFERENT = (l, r) -> l.v1 != r.v1; - private static final ParetoComparator LESS_THEN = (l, r) -> l.v1 < r.v1; - private static final ParetoComparator LESS_LESS_THEN = (l, r) -> + private static final ParetoComparator DIFFERENT = (l, r) -> l.v1 != r.v1; + private static final ParetoComparator LESS_THEN = (l, r) -> l.v1 < r.v1; + private static final ParetoComparator LESS_LESS_THEN = (l, r) -> l.v1 < r.v1 || l.v2 < r.v2; - private static final ParetoComparator LESS_DIFFERENT_THEN = (l, r) -> + private static final ParetoComparator LESS_DIFFERENT_THEN = (l, r) -> l.v1 < r.v1 || l.v2 != r.v2; // Used to stored dropped vectors (callback from set) - private final List dropped = new ArrayList<>(); + private final List dropped = new ArrayList<>(); - private final ParetoSetEventListener listener = new ParetoSetEventListener<>() { + private final ParetoSetEventListener listener = new ParetoSetEventListener<>() { @Override - public void notifyElementAccepted(Vector newElement) { + public void notifyElementAccepted(TestVector newElement) { /* NOOP */ } @Override - public void notifyElementDropped(Vector element, Vector droppedByElement) { + public void notifyElementDropped(TestVector element, TestVector droppedByElement) { dropped.add(element); } @Override - public void notifyElementRejected(Vector element, Vector rejectedByElement) { + public void notifyElementRejected(TestVector element, TestVector rejectedByElement) { /* NOOP */ } }; @@ -46,7 +46,7 @@ public void notifyElementRejected(Vector element, Vector rejectedByElement) { @Test public void initiallyEmpty() { // Given a empty set - ParetoSet set = new ParetoSet<>(LESS_THEN); + ParetoSet set = new ParetoSet<>(LESS_THEN); assertEquals("{}", set.toString(), "The initial set should be empty."); assertTrue(set.isEmpty(), "The initial set should be empty."); @@ -54,14 +54,14 @@ public void initiallyEmpty() { @Test public void addVector() { - ParetoSet set = new ParetoSet<>((l, r) -> + ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || // less than l.v2 != r.v2 || // different dominates l.v3 + 2 < r.v3 // at least 2 less than ); // When one element is added - addOk(set, new Vector("V0", 5, 5, 5)); + addOk(set, new TestVector("V0", 5, 5, 5)); // Then the element should be the only element in the set assertEquals("{V0[5, 5, 5]}", set.toString()); @@ -73,8 +73,8 @@ public void addVector() { @Test public void removeAVectorIsNotAllowed() { // Given a set with a vector - ParetoSet set = new ParetoSet<>(LESS_THEN); - Vector vector = new Vector("V0", 5); + ParetoSet set = new ParetoSet<>(LESS_THEN); + TestVector vector = new TestVector("V0", 5); addOk(set, vector); // When vector is removed, expect an exception @@ -84,23 +84,23 @@ public void removeAVectorIsNotAllowed() { @Test public void testLessThen() { // Given a set with one element: [5] - ParetoSet set = new ParetoSet<>(LESS_THEN); - set.add(new Vector("V0", 5)); + ParetoSet set = new ParetoSet<>(LESS_THEN); + set.add(new TestVector("V0", 5)); // When adding the same value - addRejected(set, new Vector("Not", 5)); + addRejected(set, new TestVector("Not", 5)); // Then expect no change in the set assertEquals("{V0[5]}", set.toString()); // When adding a greater value - addRejected(set, new Vector("Not", 6)); + addRejected(set, new TestVector("Not", 6)); // Then expect no change in the set assertEquals("{V0[5]}", set.toString()); // When adding the a lesser value - addOk(set, new Vector("V1", 4)); + addOk(set, new TestVector("V1", 4)); // Then the lesser value should replace the bigger one assertEquals("{V1[4]}", set.toString()); @@ -109,23 +109,23 @@ public void testLessThen() { @Test public void testDifferent() { // Given a set with one element: [5] - ParetoSet set = new ParetoSet<>(DIFFERENT); - set.add(new Vector("V0", 5)); + ParetoSet set = new ParetoSet<>(DIFFERENT); + set.add(new TestVector("V0", 5)); // When adding the same value - addRejected(set, new Vector("NOT ADDED", 5)); + addRejected(set, new TestVector("NOT ADDED", 5)); // Then expect no change in the set assertEquals("{V0[5]}", set.toString()); // When adding the a different value - addOk(set, new Vector("D1", 6)); + addOk(set, new TestVector("D1", 6)); // Then both values should be included assertEquals("{V0[5], D1[6]}", set.toString()); // When adding the several more different values - addOk(set, new Vector("D2", 3)); - addOk(set, new Vector("D3", 4)); - addOk(set, new Vector("D4", 8)); + addOk(set, new TestVector("D2", 3)); + addOk(set, new TestVector("D3", 4)); + addOk(set, new TestVector("D4", 8)); // Then all values should be included assertEquals("{V0[5], D1[6], D2[3], D3[4], D4[8]}", set.toString()); } @@ -134,8 +134,8 @@ public void testDifferent() { public void testTwoCriteriaWithLessThen() { // Given a set with one element with 2 criteria: [5, 5] // and a function where at least one value is less then to make it into the set - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); - Vector v0 = new Vector("V0", 5, 5); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); + TestVector v0 = new TestVector("V0", 5, 5); // Cases that does NOT make it into the set testNotAdded(set, v0, vector(6, 5), "Add a new vector where 1st value disqualifies it"); @@ -154,8 +154,8 @@ public void testTwoCriteriaWithLessThen() { @Test public void testTwoCriteria_lessThen_and_different() { // Given a set with one element with 2 criteria: [5, 5] - ParetoSet set = new ParetoSet<>(LESS_DIFFERENT_THEN); - Vector v0 = new Vector("V0", 5, 5); + ParetoSet set = new ParetoSet<>(LESS_DIFFERENT_THEN); + TestVector v0 = new TestVector("V0", 5, 5); // Cases that does NOT make it into the set testNotAdded(set, v0, vector(6, 5), "1st value disqualifies it"); @@ -173,8 +173,8 @@ public void testTwoCriteria_lessThen_and_different() { @Test public void testTwoCriteria_lessThen_and_lessThenValue() { // Given a set with one element with 2 criteria: [5, 5] - ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 < r.v2 + 1); - Vector v0 = new Vector("V0", 5, 5); + ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 < r.v2 + 1); + TestVector v0 = new TestVector("V0", 5, 5); // Cases that does NOT make it into the set testNotAdded(set, v0, vector(6, 6), "1st value is to big"); @@ -194,25 +194,25 @@ public void testTwoCriteria_lessThen_and_lessThenValue() { @Test public void testOneVectorDominatesMany() { // Given a set and function - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); // Add some values - all pareto optimal - set.add(new Vector("V0", 5, 1)); - set.add(new Vector("V1", 3, 3)); - set.add(new Vector("V2", 0, 7)); - set.add(new Vector("V3", 1, 5)); + set.add(new TestVector("V0", 5, 1)); + set.add(new TestVector("V1", 3, 3)); + set.add(new TestVector("V2", 0, 7)); + set.add(new TestVector("V3", 1, 5)); // Assert all vectors is there assertEquals("{V0[5, 1], V1[3, 3], V2[0, 7], V3[1, 5]}", set.toString()); // Add a vector which dominates all vectors in set, except [0, 7] - set.add(new Vector("V", 1, 1)); + set.add(new TestVector("V", 1, 1)); // Expect just 2 vectors assertEquals("{V2[0, 7], V[1, 1]}", set.toString()); // Add a vector which dominates all vectors in set - set.add(new Vector("X", 0, 1)); + set.add(new TestVector("X", 0, 1)); // Expect just 1 vector - the last assertEquals("{X[0, 1]}", set.toString()); @@ -221,19 +221,19 @@ public void testOneVectorDominatesMany() { @Test public void testRelaxedCriteriaAcceptingTheTwoSmallestValues() { // Given a set and function - ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 < r.v2 + 2); + ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 < r.v2 + 2); // Add some values - set.add(new Vector("V0", 5, 5)); - set.add(new Vector("V1", 4, 4)); - set.add(new Vector("V2", 5, 4)); - set.add(new Vector("V3", 5, 3)); - set.add(new Vector("V4", 5, 2)); - set.add(new Vector("V5", 5, 1)); - set.add(new Vector("V6", 5, 2)); - set.add(new Vector("V7", 5, 3)); - set.add(new Vector("V8", 5, 4)); - set.add(new Vector("V9", 5, 5)); + set.add(new TestVector("V0", 5, 5)); + set.add(new TestVector("V1", 4, 4)); + set.add(new TestVector("V2", 5, 4)); + set.add(new TestVector("V3", 5, 3)); + set.add(new TestVector("V4", 5, 2)); + set.add(new TestVector("V5", 5, 1)); + set.add(new TestVector("V6", 5, 2)); + set.add(new TestVector("V7", 5, 3)); + set.add(new TestVector("V8", 5, 4)); + set.add(new TestVector("V9", 5, 5)); // Expect all vectors with v1=4 or v2 in [1,2] assertEquals("{V1[4, 4], V4[5, 2], V5[5, 1], V6[5, 2]}", set.toString()); @@ -242,16 +242,16 @@ public void testRelaxedCriteriaAcceptingTheTwoSmallestValues() { @Test public void testRelaxedCriteriaAcceptingTenPercentExtra() { // Given a set and function - ParetoSet set = new ParetoSet<>((l, r) -> + ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 <= IntUtils.round(r.v2 * 1.1) ); // Add some values - set.add(new Vector("a", 1, 110)); - set.add(new Vector("a", 1, 111)); - set.add(new Vector("d", 1, 100)); - set.add(new Vector("g", 1, 111)); - set.add(new Vector("g", 1, 110)); + set.add(new TestVector("a", 1, 110)); + set.add(new TestVector("a", 1, 111)); + set.add(new TestVector("d", 1, 100)); + set.add(new TestVector("g", 1, 111)); + set.add(new TestVector("g", 1, 110)); assertEquals("{a[1, 110], d[1, 100], g[1, 110]}", set.toString()); } @@ -260,10 +260,10 @@ public void testRelaxedCriteriaAcceptingTenPercentExtra() { public void testFourCriteria() { // Given a set with one element with 2 criteria: [5, 5] // and the pareto function is: <, !=, >, <+2 - ParetoSet set = new ParetoSet<>((l, r) -> + ParetoSet set = new ParetoSet<>((l, r) -> l.v1 < r.v1 || l.v2 != r.v2 || l.v3 > r.v3 || l.v4 < r.v4 + 2 ); - Vector v0 = new Vector("V0", 5, 5, 5, 5); + TestVector v0 = new TestVector("V0", 5, 5, 5, 5); // Cases that does NOT make it into the set testNotAdded(set, v0, vector(5, 5, 5, 7), "same as v0"); @@ -299,7 +299,7 @@ public void testFourCriteria() { @Test public void testAutoScalingOfParetoSet() { // Given a set with 2 criteria - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); // The initial size is set to 16. // Add 100 mutually dominant values @@ -319,13 +319,13 @@ public void testAutoScalingOfParetoSet() { @Test public void testAddingMultipleElements() { // Given a set with 2 criteria: LT and LT - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); - Vector v55 = new Vector("v55", 5, 5); - Vector v53 = new Vector("v53", 5, 3); - Vector v44 = new Vector("v44", 4, 4); - Vector v35 = new Vector("v35", 3, 5); - Vector v25 = new Vector("v25", 2, 5); - Vector v22 = new Vector("v22", 2, 2); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN); + TestVector v55 = new TestVector("v55", 5, 5); + TestVector v53 = new TestVector("v53", 5, 3); + TestVector v44 = new TestVector("v44", 4, 4); + TestVector v35 = new TestVector("v35", 3, 5); + TestVector v25 = new TestVector("v25", 2, 5); + TestVector v22 = new TestVector("v22", 2, 2); // A dominant vector should replace more than one other vector test(set, "v25", v25, v35); @@ -355,7 +355,7 @@ public void testAddingMultipleElements() { @Test public void elementsAreNotDroppedWhenParetoOptimalElementsAreAdded() { // Given a set with 2 criteria: LT and LT - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); // Before any elements are added the list of dropped elements should be empty assertTrue(dropped.isEmpty()); @@ -372,7 +372,7 @@ public void elementsAreNotDroppedWhenParetoOptimalElementsAreAdded() { @Test public void firstElementIsDroppedWhenANewDominatingElementIsAdded() { // Given a set with 2 criteria: LT and LT and a vector [7, 3] - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); set.add(vector(7, 3)); assertTrue(dropped.isEmpty()); @@ -397,7 +397,7 @@ public void firstElementIsDroppedWhenANewDominatingElementIsAdded() { @Test public void lastElementIsDroppedWhenANewDominatingElementIsAdded() { // Given a set with 2 criteria: LT and LT and a vector [7, 3] - ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); + ParetoSet set = new ParetoSet<>(LESS_LESS_THEN, listener); set.add(vector(5, 5)); set.add(vector(7, 3)); assertTrue(dropped.isEmpty()); @@ -411,7 +411,7 @@ public void lastElementIsDroppedWhenANewDominatingElementIsAdded() { * Test that both #add and #qualify return the same value - true. The set should contain the * vector, but that is left to the caller to verify. */ - private static void addOk(ParetoSet set, Vector v) { + private static void addOk(ParetoSet set, TestVector v) { assertTrue(set.qualify(v)); assertTrue(set.add(v)); } @@ -420,58 +420,68 @@ private static void addOk(ParetoSet set, Vector v) { * Test that both #add and #qualify return the same value - false. The set should not contain the * vector, but that is left to the caller to verify. */ - private static void addRejected(ParetoSet set, Vector v) { + private static void addRejected(ParetoSet set, TestVector v) { assertFalse(set.qualify(v)); assertFalse(set.add(v)); } - private static String names(Iterable set) { + private static String names(Iterable set) { return StreamSupport .stream(set.spliterator(), false) .map(it -> it == null ? "null" : it.name) .collect(Collectors.joining(" ")); } - private static Vector vector(int a, int b) { - return new Vector("Test", a, b); + private static TestVector vector(int a, int b) { + return new TestVector("Test", a, b); } - private static Vector vector(int a, int b, int c, int d) { - return new Vector("Test", a, b, c, d); + private static TestVector vector(int a, int b, int c, int d) { + return new TestVector("Test", a, b, c, d); } private static void testNotAdded( - ParetoSet set, - Vector v0, - Vector v1, + ParetoSet set, + TestVector v0, + TestVector v1, String description ) { test(set, v0, v1, description, v0); } - private static void testReplace(ParetoSet set, Vector v0, Vector v1, String description) { + private static void testReplace( + ParetoSet set, + TestVector v0, + TestVector v1, + String description + ) { test(set, v0, v1, description, v1); } - private static void keepBoth(ParetoSet set, Vector v0, Vector v1, String description) { + private static void keepBoth( + ParetoSet set, + TestVector v0, + TestVector v1, + String description + ) { test(set, v0, v1, description, v0, v1); } private static void test( - ParetoSet set, - Vector v0, - Vector v1, + ParetoSet set, + TestVector v0, + TestVector v1, String description, - Vector... expected + TestVector... expected ) { new TestCase(v0, v1, description, expected).run(set); } - private void test(ParetoSet set, String expected, Vector... vectorsToAdd) { + private void test(ParetoSet set, String expected, TestVector... vectorsToAdd) { set.clear(); - for (Vector v : vectorsToAdd) { + for (TestVector v : vectorsToAdd) { // Copy vector to avoid any identity pitfalls - Vector vector = new Vector(v); + TestVector vector = new TestVector(v); boolean qualify = set.qualify(vector); assertEquals(qualify, set.add(vector), "Qualify and add should return the same value."); } @@ -480,12 +490,12 @@ private void test(ParetoSet set, String expected, Vector... vectorsToAdd static class TestCase { - final Vector v0; - final Vector v1; + final TestVector v0; + final TestVector v1; final String expected; final String description; - TestCase(Vector v0, Vector v1, String description, Vector... expected) { + TestCase(TestVector v0, TestVector v1, String description, TestVector... expected) { this.v0 = v0; this.v1 = v1; this.expected = @@ -495,7 +505,7 @@ static class TestCase { this.description = description; } - void run(ParetoSet set) { + void run(ParetoSet set) { set.clear(); set.add(v0); diff --git a/src/test/java/org/opentripplanner/raptor/util/paretoset/TestParetoSetEventListener.java b/src/test/java/org/opentripplanner/raptor/util/paretoset/TestParetoSetEventListener.java new file mode 100644 index 00000000000..7e4bb539dc5 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/util/paretoset/TestParetoSetEventListener.java @@ -0,0 +1,58 @@ +package org.opentripplanner.raptor.util.paretoset; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * An event listener witch keeps the result for each event type. Used in tests in this package + * only! + */ +class TestParetoSetEventListener implements ParetoSetEventListener { + + private final List accepted = new ArrayList<>(); + private final List rejected = new ArrayList<>(); + private final List dropped = new ArrayList<>(); + + @Override + public void notifyElementAccepted(T newElement) { + accepted.add(newElement); + } + + @Override + public void notifyElementDropped(T element, T droppedByElement) { + dropped.add(element); + } + + @Override + public void notifyElementRejected(T element, T rejectedByElement) { + rejected.add(element); + } + + @Override + public String toString() { + return "TestParetoSetEventListener"; + } + + public String acceptedAsString() { + return toString(accepted); + } + + public String rejectedAsString() { + return toString(rejected); + } + + public String droppedAsString() { + return toString(dropped); + } + + void clear() { + accepted.clear(); + rejected.clear(); + dropped.clear(); + } + + private String toString(List list) { + return list.stream().map(Object::toString).collect(Collectors.joining(" ")); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/util/paretoset/Vector.java b/src/test/java/org/opentripplanner/raptor/util/paretoset/TestVector.java similarity index 71% rename from src/test/java/org/opentripplanner/raptor/util/paretoset/Vector.java rename to src/test/java/org/opentripplanner/raptor/util/paretoset/TestVector.java index fa696b9af17..7f9998f5485 100644 --- a/src/test/java/org/opentripplanner/raptor/util/paretoset/Vector.java +++ b/src/test/java/org/opentripplanner/raptor/util/paretoset/TestVector.java @@ -2,29 +2,33 @@ import java.util.Objects; -class Vector { +/** + * Create a value object type to test the ParetoSet "generic" type criteria. We create a new type + * to isolate the tests in this package from any other randomly chosen type. + */ +class TestVector { private static final int NOT_SET = -999; final String name; final int v1, v2, v3, v4; - Vector(Vector o) { + TestVector(TestVector o) { this(o.name, o.v1, o.v2, o.v3, o.v4); } - Vector(String name, int v1) { + TestVector(String name, int v1) { this(name, v1, NOT_SET, NOT_SET, NOT_SET); } - Vector(String name, int v1, int v2) { + TestVector(String name, int v1, int v2) { this(name, v1, v2, NOT_SET, NOT_SET); } - Vector(String name, int v1, int v2, int v3) { + TestVector(String name, int v1, int v2, int v3) { this(name, v1, v2, v3, NOT_SET); } - Vector(String name, int v1, int v2, int v3, int v4) { + TestVector(String name, int v1, int v2, int v3, int v4) { this.name = name; this.v1 = v1; this.v2 = v2; @@ -45,7 +49,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Vector v = (Vector) o; + TestVector v = (TestVector) o; return v1 == v.v1 && v2 == v.v2 && v3 == v.v3 && v4 == v.v4 && name.equals(v.name); } From 6e6f69d5ba49ef6f11e51eab570f23f921017a29 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 13 Aug 2024 14:38:43 +0200 Subject: [PATCH 12/36] refactor: Arrange methods in AccessPaths --- .../rangeraptor/transit/AccessPaths.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 66b7227584d..3e2a5d4ecb8 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -141,6 +141,16 @@ public int next() { }; } + /** Raptor uses this information to optimize boarding of the first trip */ + public boolean hasTimeDependentAccess() { + return ( + hasTimeDependentAccess(arrivedOnBoardByNumOfRides) || + hasTimeDependentAccess(arrivedOnStreetByNumOfRides) + ); + } + + /* private methods */ + private int maxTimePenalty(TIntObjectMap> col) { return col .valueCollection() @@ -161,14 +171,6 @@ private static List decorateWithTimePenaltyLogic( return paths.stream().map(it -> it.hasTimePenalty() ? new AccessWithPenalty(it) : it).toList(); } - /** Raptor uses this information to optimize boarding of the first trip */ - public boolean hasTimeDependentAccess() { - return ( - hasTimeDependentAccess(arrivedOnBoardByNumOfRides) || - hasTimeDependentAccess(arrivedOnStreetByNumOfRides) - ); - } - private boolean hasTimePenalty() { return maxTimePenalty != RaptorConstants.TIME_NOT_SET; } From 1fa7ad8830b5c89d6b0aa0450daa88f2286d4271 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 4 Aug 2024 12:30:52 +0200 Subject: [PATCH 13/36] refactor: Extract builder out of SearchContext (o.o.r.rr.context) --- .../raptor/configure/RaptorConfig.java | 2 +- .../rangeraptor/context/SearchContext.java | 38 ++++++------- .../context/SearchContextBuilder.java | 57 +++++++++++++++++++ 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index 18253fddd95..6d65b54bce4 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -50,7 +50,7 @@ public static RaptorConfig defaultConfigForTes public SearchContext context(RaptorTransitDataProvider transit, RaptorRequest request) { // The passThroughPointsService is needed to create the context, so we initialize it here. this.passThroughPointsService = createPassThroughPointsService(request); - return new SearchContext<>(request, tuningParameters, transit, acceptC2AtDestination()); + return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination()); } public RangeRaptor createStdWorker( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index f3fd3943f27..bcf4a0c021e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -73,21 +73,19 @@ public class SearchContext { /** Lazy initialized */ private RaptorCostCalculator costCalculator = null; - /** - * @param acceptC2AtDestination Currently only the pass-through has a constraint on the c2 value - * for accepting it at the destination, if not this is {@code null}. - */ public SearchContext( RaptorRequest request, RaptorTuningParameters tuningParameters, RaptorTransitDataProvider transit, + AccessPaths accessPaths, + EgressPaths egressPaths, @Nullable IntPredicate acceptC2AtDestination ) { this.request = request; this.tuningParameters = tuningParameters; this.transit = transit; - this.accessPaths = accessPaths(tuningParameters.iterationDepartureStepInSeconds(), request); - this.egressPaths = egressPaths(request); + this.accessPaths = accessPaths; + this.egressPaths = egressPaths; this.calculator = createCalculator(request, tuningParameters); this.roundTracker = new RoundTracker( @@ -99,6 +97,20 @@ public SearchContext( this.acceptC2AtDestination = acceptC2AtDestination; } + /** + * @param acceptC2AtDestination Currently only the pass-through has a constraint on the c2 value + * for accepting it at the destination, if not this is {@code null}. + */ + public static SearchContext of( + RaptorRequest request, + RaptorTuningParameters tuningParameters, + RaptorTransitDataProvider transit, + @Nullable IntPredicate acceptC2AtDestination + ) { + return new SearchContextBuilder<>(request, tuningParameters, transit, acceptC2AtDestination) + .build(); + } + public AccessPaths accessPaths() { return accessPaths; } @@ -312,20 +324,6 @@ private static ToIntFunction createBoardSlackProvider( : p -> slackProvider.alightSlack(p.slackIndex()); } - private static AccessPaths accessPaths(int iterationStep, RaptorRequest request) { - boolean forward = request.searchDirection().isForward(); - var params = request.searchParams(); - var paths = forward ? params.accessPaths() : params.egressPaths(); - return AccessPaths.create(iterationStep, paths, request.profile(), request.searchDirection()); - } - - private static EgressPaths egressPaths(RaptorRequest request) { - boolean forward = request.searchDirection().isForward(); - var params = request.searchParams(); - var paths = forward ? params.egressPaths() : params.accessPaths(); - return EgressPaths.create(paths, request.profile()); - } - static ParetoSetTime paretoSetTimeConfig( SearchParams searchParams, SearchDirection searchDirection diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java new file mode 100644 index 00000000000..a894d43f50c --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java @@ -0,0 +1,57 @@ +package org.opentripplanner.raptor.rangeraptor.context; + +import java.util.function.IntPredicate; +import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; +import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; + +public class SearchContextBuilder { + + private final RaptorRequest request; + private final RaptorTuningParameters tuningParameters; + private final RaptorTransitDataProvider transit; + + @Nullable + private final IntPredicate acceptC2AtDestination; + + public SearchContextBuilder( + RaptorRequest request, + RaptorTuningParameters tuningParameters, + RaptorTransitDataProvider transit, + @Nullable IntPredicate acceptC2AtDestination + ) { + this.request = request; + this.tuningParameters = tuningParameters; + this.transit = transit; + this.acceptC2AtDestination = acceptC2AtDestination; + } + + public SearchContext build() { + return new SearchContext<>( + request, + tuningParameters, + transit, + accessPaths(tuningParameters.iterationDepartureStepInSeconds(), request), + egressPaths(request), + acceptC2AtDestination + ); + } + + private static AccessPaths accessPaths(int iterationStep, RaptorRequest request) { + boolean forward = request.searchDirection().isForward(); + var params = request.searchParams(); + var paths = forward ? params.accessPaths() : params.egressPaths(); + return AccessPaths.create(iterationStep, paths, request.profile(), request.searchDirection()); + } + + private static EgressPaths egressPaths(RaptorRequest request) { + boolean forward = request.searchDirection().isForward(); + var params = request.searchParams(); + var paths = forward ? params.egressPaths() : params.accessPaths(); + return EgressPaths.create(paths, request.profile()); + } +} From 8dbafdb39367fe9dfdc6c76e102dcabe38360a92 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 26 Jul 2024 12:05:31 +0200 Subject: [PATCH 14/36] feature: Add via search to Raptor --- .../model/AbstractAccessEgressDecorator.java | 12 + .../raptor/api/request/SearchParams.java | 4 + .../api/request/SearchParamsBuilder.java | 2 +- .../raptor/api/request/ViaConnection.java | 132 ++---- .../raptor/api/request/ViaLocation.java | 159 +++++++- .../raptor/configure/RaptorConfig.java | 56 +-- .../rangeraptor/DefaultRangeRaptorWorker.java | 8 +- .../raptor/rangeraptor/RangeRaptor.java | 2 +- .../RangeRaptorWorkerComposite.java | 85 ++++ .../rangeraptor/context/SearchContext.java | 61 ++- .../context/SearchContextBuilder.java | 33 +- .../context/SearchContextViaLeg.java | 64 +++ .../multicriteria/McStopArrivals.java | 81 +++- .../multicriteria/StopArrivalParetoSet.java | 4 +- .../multicriteria/arrivals/McStopArrival.java | 6 + .../arrivals/McStopArrivalFactory.java | 24 ++ .../arrivals/c1/AccessStopArrival.java | 12 +- .../arrivals/c1/TransferStopArrival.java | 5 + .../arrivals/c1/TransitStopArrival.java | 5 + .../arrivals/c2/AccessStopArrivalC2.java | 9 + .../arrivals/c2/TransferStopArrivalC2.java | 5 + .../arrivals/c2/TransitStopArrivalC2.java | 5 + .../configure/McRangeRaptorConfig.java | 81 ++-- .../configure/StdRangeRaptorConfig.java | 15 +- .../rangeraptor/transit/AccessPaths.java | 44 +- .../rangeraptor/transit/ViaConnections.java | 23 ++ .../service/ViaRangeRaptorDynamicSearch.java | 297 ++++++++++++++ .../raptor/api/request/ViaConnectionTest.java | 129 ------ .../raptor/api/request/ViaLocationTest.java | 231 ++++++++++- .../raptor/moduletests/J02_ViaSearchTest.java | 376 ++++++++++++++++++ ...ArrivalParetoSetComparatorFactoryTest.java | 5 + .../arrivals/McStopArrivalTest.java | 5 + .../rangeraptor/transit/AccessPathsTest.java | 5 +- 33 files changed, 1618 insertions(+), 367 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextViaLeg.java create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java create mode 100644 src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java delete mode 100644 src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java diff --git a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java index ddb266e0884..eb1a388f91c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java @@ -16,6 +16,18 @@ public AbstractAccessEgressDecorator(RaptorAccessEgress delegate) { this.delegate = delegate; } + public static RaptorAccessEgress accessEgressWithExtraSlack( + RaptorAccessEgress delegate, + int slack + ) { + return new AbstractAccessEgressDecorator(delegate) { + @Override + public int durationInSeconds() { + return super.durationInSeconds() + slack; + } + }; + } + protected RaptorAccessEgress delegate() { return delegate; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index 811f9c39465..db20f892890 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -216,6 +216,10 @@ public List viaLocations() { return viaLocations; } + public boolean hasViaLocations() { + return !viaLocations.isEmpty(); + } + /** * Get the maximum duration of any access or egress path in seconds. */ diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java index 32edd1233ff..dcde29b0709 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java @@ -163,7 +163,7 @@ public SearchParamsBuilder addEgressPaths(RaptorAccessEgress... egressPaths) return addEgressPaths(Arrays.asList(egressPaths)); } - public List viaLocations() { + public Collection viaLocations() { return viaLocations; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java b/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java index b2d001c1f0f..1d1fd6cb2cb 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java @@ -1,30 +1,28 @@ package org.opentripplanner.raptor.api.request; -import java.time.Duration; import java.util.Objects; -import org.opentripplanner.framework.lang.IntUtils; +import javax.annotation.Nullable; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; +import org.opentripplanner.raptor.api.model.RaptorTransfer; /** * A via-connection is used to connect two stops during the raptor routing. The Raptor - * implementation uses these connections to force the path through at least one connection per + * implementation uses these connections to force the path through at least one connection per * via-location. This is not an alternative to transfers. Raptor supports several use-cases through * via-connections: - - *

Route via a single stop with a minimum-wait-time

- - * Raptor will allow a path to go through a single stop, if the from- and to-stop is the same and - * the {@code durationInSeconds} is at least one(1) second. - + * *

Route via a pass-through-stop

- - * Raptor will allow a path to go through a pass-through-stop, if the {@code durationInSeconds} is - * zero and the from and to stop is the same. - + * Raptor will allow a path to go through a pass-through-stop. The stop can be visited on-board + * transit, or at the alight- or board-stop. The from-stop and to-stop is the same, and the + * minimum-wait-time must be zero. + * + *

Route via a single stop with a minimum-wait-time

+ * Raptor will allow a path to go through a single stop, if the from-stop and to-stop is the same. + * *

Route via a coordinate

- + * * To route through a coordinate you need to find all nearby stops, then calculate the "walk" * durations and produce the set of connection. If you want to spend a min-wait-time at the * coordinate, this time must be added to the {@code durationInSeconds}. The calculation of @@ -33,50 +31,18 @@ */ public final class ViaConnection { - private static final int MAX_WAIT_TIME_LIMIT = (int) Duration.ofHours(24).toSeconds(); - private final int fromStop; - private final int toStop; - private final int c1; private final int durationInSeconds; - private ViaConnection(int fromStop, int toStop, int durationInSeconds, int c1) { + @Nullable + private final RaptorTransfer transfer; + + ViaConnection(ViaLocation parent, int fromStop, @Nullable RaptorTransfer transfer) { this.fromStop = fromStop; - this.toStop = toStop; - // To transfer from one stop to another must take at least one second - int minDuration = isSameStop() ? RaptorConstants.ZERO : 1; + this.transfer = transfer; this.durationInSeconds = - IntUtils.requireInRange( - durationInSeconds, - minDuration, - MAX_WAIT_TIME_LIMIT, - "durationInSeconds" - ); - this.c1 = IntUtils.requireNotNegative(c1, "c1"); - } - - /** - * Force the path through a stop, either on-board or as an alight or board stop. - */ - public static ViaConnection passThroughStop(int stop) { - return new ViaConnection(stop, stop, RaptorConstants.ZERO, RaptorConstants.ZERO); - } - - /** - * Force the path through a stop and wait at least the givan {@code duration} before continuing. - * To visit the stop, the path must board or alight transit at the stop. - */ - public static ViaConnection stop(int stop, Duration duration) { - return new ViaConnection(stop, stop, (int) duration.getSeconds(), RaptorConstants.ZERO); - } - - /** - * Force a path through a user provided transfer. This is meant for supporting a coordinate as a - * via point. The path will alight from transit at the {@code fromStop} and board transit at the - * {@code toStop}. - */ - public static ViaConnection stop(int fromStop, int toStop, Duration duration, int c1) { - return new ViaConnection(fromStop, toStop, (int) duration.getSeconds(), c1); + parent.minimumWaitTime() + + (transfer == null ? RaptorConstants.ZERO : transfer.durationInSeconds()); } /** @@ -86,11 +52,16 @@ public int fromStop() { return fromStop; } + @Nullable + public RaptorTransfer transfer() { + return transfer; + } + /** * Stop index where the connection ends. This can be the same as the {@code fromStop}. */ public int toStop() { - return toStop; + return isSameStop() ? fromStop : transfer.stop(); } /** @@ -107,31 +78,23 @@ public int durationInSeconds() { * calculated for each invocation. */ public int c1() { - return c1; - } - - /** - * The path must visit - */ - public boolean allowPassThrough() { - return durationInSeconds == RaptorConstants.ZERO; + return isSameStop() ? RaptorConstants.ZERO : transfer.c1(); } public boolean isSameStop() { - return fromStop == toStop; + return transfer == null; } /** - * This method is used to chack that all connections are unique/provide an optimal path. - * If this connection is better than the other connection, the other connection can be dropped. - *

- * This is the same as being pareto-optimal. + * This method is used to check that all connections are unique/provide an optimal path. + * The method returns {@code true} if this instance is better or equals to the given other + * stop with respect to being pareto-optimal. */ - boolean isBetterThan(ViaConnection other) { - if (fromStop != other.fromStop || toStop != other.toStop) { + boolean isBetterOrEqual(ViaConnection other) { + if (fromStop != other.fromStop || toStop() != other.toStop()) { return false; } - return durationInSeconds <= other.durationInSeconds && c1 <= other.c1; + return durationInSeconds() <= other.durationInSeconds() && c1() <= other.c1(); } /** @@ -143,17 +106,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ViaConnection that = (ViaConnection) o; - return ( - fromStop == that.fromStop && - toStop == that.toStop && - durationInSeconds == that.durationInSeconds && - c1 == that.c1 - ); + return fromStop == that.fromStop && Objects.equals(transfer, that.transfer); } @Override public int hashCode() { - return Objects.hash(fromStop, toStop, durationInSeconds, c1); + return Objects.hash(fromStop, transfer); } @Override @@ -162,20 +120,14 @@ public String toString() { } public String toString(RaptorStopNameResolver stopNameResolver) { - if (allowPassThrough()) { - return "PassThrough(" + stopNameResolver.apply(fromStop) + ")"; + var buf = new StringBuilder(stopNameResolver.apply(fromStop)); + if (transfer != null) { + buf.append("~").append(stopNameResolver.apply(toStop())); } - var buf = new StringBuilder("Via("); - - if (durationInSeconds > RaptorConstants.ZERO) { - buf.append(DurationUtils.durationToStr(durationInSeconds())).append(" "); - } - - buf.append(stopNameResolver.apply(fromStop)); - - if (toStop != fromStop) { - buf.append("~").append(stopNameResolver.apply(toStop)); + int d = durationInSeconds(); + if (d > RaptorConstants.ZERO) { + buf.append(" ").append(DurationUtils.durationToStr(d)); } - return buf.append(")").toString(); + return buf.toString(); } } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java index 0a7a8a78c3b..0fb7bc11714 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java @@ -1,46 +1,185 @@ package org.opentripplanner.raptor.api.request; -import java.util.Collection; +import java.time.Duration; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; +import org.opentripplanner.raptor.api.model.RaptorTransfer; public final class ViaLocation { + private static final int MAX_WAIT_TIME_LIMIT = (int) Duration.ofHours(24).toSeconds(); + private final String label; - private final Collection connections; + private final boolean allowPassThrough; + private final int minimumWaitTime; + private final List connections; - public ViaLocation(String label, Collection connections) { + private ViaLocation( + String label, + boolean allowPassThrough, + Duration minimumWaitTime, + List connections + ) { this.label = label; + this.allowPassThrough = allowPassThrough; + this.minimumWaitTime = + IntUtils.requireInRange( + (int) minimumWaitTime.toSeconds(), + RaptorConstants.ZERO, + MAX_WAIT_TIME_LIMIT, + "minimumWaitTime" + ); this.connections = validateConnections(connections); + + if (allowPassThrough && this.minimumWaitTime > RaptorConstants.ZERO) { + throw new IllegalArgumentException("Pass-through and min-wait-time is not allowed."); + } + } + + /** + * Force the path through a set of stops, either on-board or as an alight or board stop. + */ + public static Builder allowPassThrough(@Nullable String label) { + return new Builder(label, true, Duration.ZERO); } + /** + * Force the path through one of the listed connections. To visit a stop, the path must board or + * alight transit at the given stop, on-board visits do not count, see + * {@link #allowPassThrough(String)}. + */ + public static Builder via(@Nullable String label) { + return new Builder(label, false, Duration.ZERO); + } + + /** + * Force the path through one of the listed connections, and wait the given minimum-wait-time + * before continuing. To visit a stop, the path must board or alight transit at the given stop, + * on-board visits do not count, see {@link #allowPassThrough(String)}. + */ + public static Builder via(@Nullable String label, Duration minimumWaitTime) { + return new Builder(label, false, minimumWaitTime); + } + + @Nullable public String label() { return label; } - public Collection connections() { + public boolean allowPassThrough() { + return allowPassThrough; + } + + public int minimumWaitTime() { + return minimumWaitTime; + } + + public List connections() { return connections; } - @Override public String toString() { - return "ViaLocation{label: " + label + ", connections: " + connections + "}"; + return toString(Integer::toString); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViaLocation that = (ViaLocation) o; + return ( + allowPassThrough == that.allowPassThrough && + minimumWaitTime == that.minimumWaitTime && + Objects.equals(label, that.label) && + Objects.equals(connections, that.connections) + ); + } + + @Override + public int hashCode() { + return Objects.hash(label, allowPassThrough, minimumWaitTime, connections); + } + + public String toString(RaptorStopNameResolver stopNameResolver) { + var buf = new StringBuilder("Via{"); + if (label != null) { + buf.append("label: ").append(label).append(", "); + } + if (allowPassThrough) { + buf.append("allowPassThrough, "); + } + if (minimumWaitTime > RaptorConstants.ZERO) { + buf.append("minWaitTime: ").append(DurationUtils.durationToStr(minimumWaitTime)).append(", "); + } + buf + .append("connections: ") + .append(connections.stream().map(it -> it.toString(stopNameResolver)).toList()); + return buf.append("}").toString(); } - private Collection validateConnections(Collection connections) { - var list = List.copyOf(connections); + private List validateConnections(List connections) { + if (connections.isEmpty()) { + throw new IllegalArgumentException("At least one connection is required."); + } + var list = connections + .stream() + .map(it -> new ViaConnection(this, it.fromStop, it.transfer)) + .toList(); // Compare all pairs to check for duplicates and none optimal connections for (int i = 0; i < list.size(); ++i) { var a = list.get(i); for (int j = i + 1; j < list.size(); ++j) { var b = list.get(j); - if (a.equals(b) || a.isBetterThan(b) || b.isBetterThan(a)) { + if (a.isBetterOrEqual(b) || b.isBetterOrEqual(a)) { throw new IllegalArgumentException( - "All connection need to be pareto-optimal. " + "a: " + a + ", b: " + b + "All connection need to be pareto-optimal: (" + a + ") <-> (" + b + ")" ); } } } return list; } + + public static final class Builder { + + private final String label; + private final boolean allowPassThrough; + private final Duration minimumWaitTime; + private final List connections = new ArrayList<>(); + + public Builder(String label, boolean allowPassThrough, Duration minimumWaitTime) { + this.label = label; + this.allowPassThrough = allowPassThrough; + this.minimumWaitTime = minimumWaitTime; + } + + public Builder addViaStop(int stop) { + this.connections.add(new StopAndTransfer(stop, null)); + return this; + } + + public Builder addViaTransfer(int fromStop, RaptorTransfer transfer) { + this.connections.add(new StopAndTransfer(fromStop, transfer)); + return this; + } + + public ViaLocation build() { + return new ViaLocation(label, allowPassThrough, minimumWaitTime, connections); + } + } + + /** + * Use internally to store connection data, before creating the connection objects. If is + * needed to create the bidirectional relationship between {@link ViaLocation} and + * {@link ViaConnection}. + */ + private record StopAndTransfer(int fromStop, @Nullable RaptorTransfer transfer) {} } diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index 6d65b54bce4..521d69a565e 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -2,7 +2,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.IntPredicate; import javax.annotation.Nullable; import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -10,13 +9,16 @@ import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.rangeraptor.DefaultRangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; +import org.opentripplanner.raptor.rangeraptor.RangeRaptorWorkerComposite; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; +import org.opentripplanner.raptor.rangeraptor.context.SearchContextViaLeg; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McStopArrivals; import org.opentripplanner.raptor.rangeraptor.multicriteria.configure.McRangeRaptorConfig; import org.opentripplanner.raptor.rangeraptor.standard.configure.StdRangeRaptorConfig; import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; @@ -50,7 +52,10 @@ public static RaptorConfig defaultConfigForTes public SearchContext context(RaptorTransitDataProvider transit, RaptorRequest request) { // The passThroughPointsService is needed to create the context, so we initialize it here. this.passThroughPointsService = createPassThroughPointsService(request); - return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination()); + var acceptC2AtDestination = passThroughPointsService.isNoop() + ? null + : passThroughPointsService.acceptC2AtDestination(); + return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination).build(); } public RangeRaptor createStdWorker( @@ -59,7 +64,10 @@ public RangeRaptor createStdWorker( ) { var context = context(transitData, request); var stdConfig = new StdRangeRaptorConfig<>(context); - return createRangeRaptor(context, stdConfig.state(), stdConfig.strategy()); + return createRangeRaptor( + context, + createWorker(context.legs().getFirst(), stdConfig.state(), stdConfig.strategy()) + ); } public RangeRaptor createMcWorker( @@ -68,10 +76,25 @@ public RangeRaptor createMcWorker( Heuristics heuristics ) { var context = context(transitData, request); - var mcConfig = new McRangeRaptorConfig<>(context, passThroughPointsService) - .withHeuristics(heuristics); + RangeRaptorWorker worker = null; + McStopArrivals nextStopArrivals = null; + + if (request.searchParams().hasViaLocations()) { + for (SearchContextViaLeg cxLeg : context.legs().reversed()) { + var c = new McRangeRaptorConfig<>(cxLeg, passThroughPointsService) + .connectWithNextLegArrivals(nextStopArrivals); + var w = createWorker(cxLeg, c.state(), c.strategy()); + worker = RangeRaptorWorkerComposite.of(w, worker); + nextStopArrivals = c.stopArrivals(); + } + } else { + // The first leg is the only leg + var leg = context.legs().getFirst(); + var c = new McRangeRaptorConfig<>(leg, passThroughPointsService).withHeuristics(heuristics); + worker = createWorker(leg, c.state(), c.strategy()); + } - return createRangeRaptor(context, mcConfig.state(), mcConfig.strategy()); + return createRangeRaptor(context, worker); } public RangeRaptor createHeuristicSearch( @@ -115,16 +138,17 @@ private static PassThroughPointsService createPassThroughPointsService(RaptorReq } private RangeRaptorWorker createWorker( - SearchContext ctx, + SearchContextViaLeg ctxLeg, RaptorWorkerState workerState, RoutingStrategy routingStrategy ) { + var ctx = ctxLeg.parent(); return new DefaultRangeRaptorWorker<>( workerState, routingStrategy, ctx.transit(), ctx.slackProvider(), - ctx.accessPaths(), + ctxLeg.accessPaths(), ctx.calculator(), ctx.lifeCycle(), ctx.performanceTimers(), @@ -136,7 +160,7 @@ private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker return new RangeRaptor<>( worker, ctx.transit(), - ctx.accessPaths(), + ctx.legs().getFirst().accessPaths(), ctx.roundTracker(), ctx.calculator(), ctx.createLifeCyclePublisher(), @@ -144,20 +168,6 @@ private RangeRaptor createRangeRaptor(SearchContext ctx, RangeRaptorWorker ); } - private RangeRaptor createRangeRaptor( - SearchContext ctx, - RaptorWorkerState workerState, - RoutingStrategy routingStrategy - ) { - return createRangeRaptor(ctx, createWorker(ctx, workerState, routingStrategy)); - } - - private IntPredicate acceptC2AtDestination() { - return passThroughPointsService.isNoop() - ? null - : passThroughPointsService.acceptC2AtDestination(); - } - @Nullable private ExecutorService createNewThreadPool(int size) { return size > 0 diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 27e267f071f..04f741b7c92 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -1,6 +1,9 @@ package org.opentripplanner.raptor.rangeraptor; +import static org.opentripplanner.raptor.rangeraptor.transit.AccessPaths.calculateMaxNumberOfRides; + import java.util.Collection; +import javax.annotation.Nullable; import org.opentripplanner.raptor.api.debug.RaptorTimers; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -70,6 +73,7 @@ public final class DefaultRangeRaptorWorker private final RaptorTimers timers; + @Nullable private final AccessPaths accessPaths; private final int minNumberOfRounds; @@ -85,7 +89,7 @@ public DefaultRangeRaptorWorker( RoutingStrategy transitWorker, RaptorTransitDataProvider transitData, SlackProvider slackProvider, - AccessPaths accessPaths, + @Nullable AccessPaths accessPaths, RaptorTransitCalculator calculator, WorkerLifeCycle lifeCycle, RaptorTimers timers, @@ -98,7 +102,7 @@ public DefaultRangeRaptorWorker( this.calculator = calculator; this.timers = timers; this.accessPaths = accessPaths; - this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); + this.minNumberOfRounds = calculateMaxNumberOfRides(accessPaths); this.enableTransferConstraints = enableTransferConstraints; lifeCycle.onSetupIteration(time -> this.iterationDepartureTime = time); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 53514f04b24..303ccd1ec19 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -79,7 +79,7 @@ public RangeRaptor( this.calculator = calculator; this.timers = timers; this.accessPaths = accessPaths; - this.minNumberOfRounds = accessPaths.calculateMaxNumberOfRides(); + this.minNumberOfRounds = AccessPaths.calculateMaxNumberOfRides(accessPaths); this.roundTracker = roundTracker; this.lifeCycle = lifeCyclePublisher; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java new file mode 100644 index 00000000000..8c02003e016 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java @@ -0,0 +1,85 @@ +package org.opentripplanner.raptor.rangeraptor; + +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.util.composite.CompositeUtil; + +/** + * Iterate over two RR workers. The head should process the access and the tail should produce the + * result. Paths from the head needs to propagate to the tail - this is NOT part of the + * responsibilities for this class. + */ +public class RangeRaptorWorkerComposite + implements RangeRaptorWorker { + + private final List> children; + + private RangeRaptorWorkerComposite(Collection> children) { + this.children = List.copyOf(children); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static RangeRaptorWorker of( + @Nullable RangeRaptorWorker head, + @Nullable RangeRaptorWorker tail + ) { + return CompositeUtil.of( + RangeRaptorWorkerComposite::new, + it -> it instanceof RangeRaptorWorkerComposite, + it -> ((RangeRaptorWorkerComposite) it).children, + head, + tail + ); + } + + @Override + public RaptorWorkerResult results() { + return tail().results(); + } + + @Override + public boolean hasMoreRounds() { + return children.stream().anyMatch(RangeRaptorWorker::hasMoreRounds); + } + + @Override + public void findTransitForRound() { + for (RangeRaptorWorker child : children) { + child.findTransitForRound(); + } + } + + @Override + public void findTransfersForRound() { + for (RangeRaptorWorker child : children) { + child.findTransfersForRound(); + } + } + + @Override + public boolean isDestinationReachedInCurrentRound() { + return tail().isDestinationReachedInCurrentRound(); + } + + @Override + public void findAccessOnStreetForRound() { + head().findAccessOnStreetForRound(); + } + + @Override + public void findAccessOnBoardForRound() { + head().findAccessOnBoardForRound(); + } + + private RangeRaptorWorker head() { + return children.getFirst(); + } + + private RangeRaptorWorker tail() { + return children.getLast(); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index bcf4a0c021e..c0bf8b9d594 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -4,6 +4,7 @@ import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_DEPARTURE_TIME; import static org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime.USE_TIMETABLE; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -37,13 +38,14 @@ import org.opentripplanner.raptor.rangeraptor.transit.ReverseRaptorTransitCalculator; import org.opentripplanner.raptor.rangeraptor.transit.RoundTracker; import org.opentripplanner.raptor.rangeraptor.transit.SlackProviderAdapter; +import org.opentripplanner.raptor.rangeraptor.transit.ViaConnections; import org.opentripplanner.raptor.spi.RaptorCostCalculator; import org.opentripplanner.raptor.spi.RaptorSlackProvider; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; /** - * The search context is used to hold search scoped instances and to pass these to who ever need - * them. + * The search context is used to hold search scoped instances and to pass these to whom ever need + * them. It is one search-context pr RangeRaptor * * @param The TripSchedule type defined by the user of the raptor API. */ @@ -63,13 +65,13 @@ public class SearchContext { private final RaptorTuningParameters tuningParameters; private final RoundTracker roundTracker; private final DebugHandlerFactory debugFactory; - private final EgressPaths egressPaths; - private final AccessPaths accessPaths; private final LifeCycleSubscriptions lifeCycleSubscriptions = new LifeCycleSubscriptions(); @Nullable private final IntPredicate acceptC2AtDestination; + private final List> legs; + /** Lazy initialized */ private RaptorCostCalculator costCalculator = null; @@ -78,14 +80,14 @@ public SearchContext( RaptorTuningParameters tuningParameters, RaptorTransitDataProvider transit, AccessPaths accessPaths, - EgressPaths egressPaths, + List viaConnections, + @Nullable EgressPaths egressPaths, @Nullable IntPredicate acceptC2AtDestination ) { this.request = request; this.tuningParameters = tuningParameters; this.transit = transit; - this.accessPaths = accessPaths; - this.egressPaths = egressPaths; + this.calculator = createCalculator(request, tuningParameters); this.roundTracker = new RoundTracker( @@ -95,32 +97,24 @@ public SearchContext( ); this.debugFactory = new DebugHandlerFactory<>(debugRequest(request), lifeCycle()); this.acceptC2AtDestination = acceptC2AtDestination; + this.legs = initLegs(accessPaths, viaConnections, egressPaths); } /** * @param acceptC2AtDestination Currently only the pass-through has a constraint on the c2 value * for accepting it at the destination, if not this is {@code null}. */ - public static SearchContext of( + public static SearchContextBuilder of( RaptorRequest request, RaptorTuningParameters tuningParameters, RaptorTransitDataProvider transit, @Nullable IntPredicate acceptC2AtDestination ) { - return new SearchContextBuilder<>(request, tuningParameters, transit, acceptC2AtDestination) - .build(); - } - - public AccessPaths accessPaths() { - return accessPaths; + return new SearchContextBuilder<>(request, tuningParameters, transit, acceptC2AtDestination); } - public EgressPaths egressPaths() { - return egressPaths; - } - - public int[] egressStops() { - return egressPaths().stops(); + public List> legs() { + return legs; } public SearchParams searchParams() { @@ -235,7 +229,7 @@ public RaptorStopNameResolver stopNameResolver() { public TimeBasedBoardingSupport createTimeBasedBoardingSupport() { return new TimeBasedBoardingSupport<>( - accessPaths().hasTimeDependentAccess(), + legs.getFirst().accessPaths().hasTimeDependentAccess(), slackProvider(), calculator(), lifeCycle() @@ -324,6 +318,31 @@ private static ToIntFunction createBoardSlackProvider( : p -> slackProvider.alightSlack(p.slackIndex()); } + private List> initLegs( + AccessPaths accessPaths, + List viaConnections, + EgressPaths egressPaths + ) { + if (viaConnections.isEmpty()) { + return List.of(new SearchContextViaLeg<>(this, accessPaths, null, egressPaths)); + } + var accessEmpty = accessPaths.copyEmpty(); + var list = new ArrayList>(); + for (ViaConnections c : viaConnections) { + list.add( + new SearchContextViaLeg<>( + this, + c == viaConnections.getFirst() ? accessPaths : accessEmpty, + c, + null + ) + ); + } + list.add(new SearchContextViaLeg<>(this, accessEmpty, null, egressPaths)); + + return List.copyOf(list); + } + static ParetoSetTime paretoSetTimeConfig( SearchParams searchParams, SearchDirection searchDirection diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java index a894d43f50c..e0bf4f1df5d 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java @@ -1,12 +1,15 @@ package org.opentripplanner.raptor.rangeraptor.context; +import java.util.List; import java.util.function.IntPredicate; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.raptor.api.request.ViaLocation; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; +import org.opentripplanner.raptor.rangeraptor.transit.ViaConnections; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; public class SearchContextBuilder { @@ -31,24 +34,46 @@ public SearchContextBuilder( } public SearchContext build() { + return createContext(accessPaths(), viaConnections(), egressPaths()); + } + + private SearchContext createContext( + AccessPaths accessPaths, + List viaConnections, + @Nullable EgressPaths egressPaths + ) { return new SearchContext<>( request, tuningParameters, transit, - accessPaths(tuningParameters.iterationDepartureStepInSeconds(), request), - egressPaths(request), + accessPaths, + viaConnections, + egressPaths, acceptC2AtDestination ); } - private static AccessPaths accessPaths(int iterationStep, RaptorRequest request) { + private AccessPaths accessPaths() { + int iterationStep = tuningParameters.iterationDepartureStepInSeconds(); boolean forward = request.searchDirection().isForward(); var params = request.searchParams(); var paths = forward ? params.accessPaths() : params.egressPaths(); return AccessPaths.create(iterationStep, paths, request.profile(), request.searchDirection()); } - private static EgressPaths egressPaths(RaptorRequest request) { + private List viaConnections() { + return request.searchParams().hasViaLocations() + ? request + .searchParams() + .viaLocations() + .stream() + .map(ViaLocation::connections) + .map(ViaConnections::new) + .toList() + : List.of(); + } + + private EgressPaths egressPaths() { boolean forward = request.searchDirection().isForward(); var params = request.searchParams(); var paths = forward ? params.egressPaths() : params.accessPaths(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextViaLeg.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextViaLeg.java new file mode 100644 index 00000000000..364b7e70d28 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextViaLeg.java @@ -0,0 +1,64 @@ +package org.opentripplanner.raptor.rangeraptor.context; + +import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; +import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; +import org.opentripplanner.raptor.rangeraptor.transit.ViaConnections; + +/** + * A search can be split into one or more legs. The {@code parent} search context will have a list + * of legs. The first leg will have a list of {@code accessPaths} and the {@link ViaConnections} + * for transferring to the next leg. The last leg will have {@code egressPaths}. + */ +public class SearchContextViaLeg { + + private final SearchContext parent; + private final AccessPaths accessPaths; + private final ViaConnections viaConnections; + private final EgressPaths egressPaths; + + public SearchContextViaLeg( + SearchContext parent, + AccessPaths accessPaths, + ViaConnections viaConnections, + EgressPaths egressPaths + ) { + this.parent = parent; + this.accessPaths = accessPaths; + this.viaConnections = viaConnections; + this.egressPaths = egressPaths; + } + + /** + * The parent search context this leg is part of. + */ + public SearchContext parent() { + return parent; + } + + /** + * The set of access paths to be used to board this leg. This method returns an empty + * set of access-paths if the leg is not the first leg. Hence, it is null-safe. + */ + public AccessPaths accessPaths() { + return accessPaths; + } + + /** + * The via connections for the via-location this leg ends with. This is {@code null} if this + * leg is the last leg. + */ + @Nullable + public ViaConnections viaConnections() { + return viaConnections; + } + + /** + * The egress path for search, if and only if this is the last leg. + */ + @Nullable + public EgressPaths egressPaths() { + return egressPaths; + } +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java index dc0fbb9baee..c16720ca0c2 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java @@ -4,17 +4,25 @@ import java.util.BitSet; import java.util.Collections; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.ViaConnection; +import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.ArrivalParetoSetComparatorFactory; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; +import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrivalFactory; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; +import org.opentripplanner.raptor.rangeraptor.transit.ViaConnections; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.util.BitSetIterator; +import org.opentripplanner.raptor.util.paretoset.ParetoComparator; +import org.opentripplanner.raptor.util.paretoset.ParetoSetEventListener; /** * This class serve as a wrapper for all stop arrival pareto set, one set for each stop. It also @@ -27,10 +35,12 @@ public final class McStopArrivals { private final StopArrivalParetoSet[] arrivals; private final BitSet touchedStops; + private final McStopArrivals next; - private final ArrivalParetoSetComparatorFactory> comparatorFactory; + //private final ArrivalParetoSetComparatorFactory> comparatorFactory; private final DebugHandlerFactory debugHandlerFactory; private final DebugStopArrivalsStatistics debugStats; + private final ParetoComparator> comparator; /** * Set the time at a transit index iff it is optimal. This sets both the best time and the @@ -38,21 +48,34 @@ public final class McStopArrivals { */ public McStopArrivals( int nStops, - EgressPaths egressPaths, AccessPaths accessPaths, + @Nullable EgressPaths egressPaths, + ViaConnections viaConnections, DestinationArrivalPaths paths, + McStopArrivals next, + McStopArrivalFactory stopArrivalFactory, ArrivalParetoSetComparatorFactory> comparatorFactory, DebugHandlerFactory debugHandlerFactory ) { - this.comparatorFactory = comparatorFactory; + // Assert only-one-of next or egressPaths is set + if (next == null) { + Objects.requireNonNull(egressPaths); + } else if (egressPaths != null) { + throw new IllegalArgumentException( + "Can not delegate to next and at the same have egress paths." + ); + } + //noinspection unchecked this.arrivals = (StopArrivalParetoSet[]) new StopArrivalParetoSet[nStops]; this.touchedStops = new BitSet(nStops); + this.next = next; + this.comparator = comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival(); this.debugHandlerFactory = debugHandlerFactory; this.debugStats = new DebugStopArrivalsStatistics(debugHandlerFactory.debugLogger()); - initAccessArrivals(accessPaths); - glueTogetherEgressStopWithDestinationArrivals(egressPaths, paths); + initViaConnections(viaConnections, stopArrivalFactory); + initEgressStopAndGlueItToDestinationArrivals(egressPaths, paths); } boolean reached(int stopIndex) { @@ -91,6 +114,7 @@ IntIterator stopsTouchedIterator() { void addStopArrival(McStopArrival arrival) { boolean added = findOrCreateSet(arrival.stop()).add(arrival); + if (added) { touchedStops.set(arrival.stop()); } @@ -126,42 +150,65 @@ private StopArrivalParetoSet findOrCreateSet(final int stop) { if (arrivals[stop] == null) { arrivals[stop] = StopArrivalParetoSet - .of(comparatorFactory.compareArrivalTimeRoundAndCost()) + .of(comparator) .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) .build(); } return arrivals[stop]; } - private void initAccessArrivals(AccessPaths accessPaths) { - int maxNRides = accessPaths.calculateMaxNumberOfRides(); - for (int nRides = 0; nRides <= maxNRides; ++nRides) { - for (var access : accessPaths.arrivedOnBoardByNumOfRides(nRides)) { - int stop = access.stop(); - arrivals[stop] = + private void initViaConnections( + @Nullable ViaConnections viaConnections, + McStopArrivalFactory stopArrivalFactory + ) { + if (viaConnections == null) { + return; + } + viaConnections + .byFromStop() + .forEachEntry((stop, list) -> { + this.arrivals[stop] = StopArrivalParetoSet - .of(comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival()) + .of(comparator) .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) + .withNextSearchListener( + new ParetoSetEventListener<>() { + @Override + public void notifyElementAccepted(ArrivalView newElement) { + for (ViaConnection c : list) { + var e = (McStopArrival) newElement; + var n = stopArrivalFactory.createViaStopArrival(e, c); + if (n != null) { + next.addStopArrival(n); + } + } + } + } + ) .build(); - } - } + return true; + }); } /** * This method creates a ParetoSet for the given egress stop. When arrivals are added to the stop, * the "glue" make sure new destination arrivals are added to the destination arrivals. */ - private void glueTogetherEgressStopWithDestinationArrivals( + private void initEgressStopAndGlueItToDestinationArrivals( EgressPaths egressPaths, DestinationArrivalPaths paths ) { + if (egressPaths == null) { + return; + } + egressPaths .byStop() .forEachEntry((stop, list) -> { // The factory is creating the actual "glue" this.arrivals[stop] = StopArrivalParetoSet - .of(comparatorFactory.compareArrivalTimeRoundCostAndOnBoardArrival()) + .of(comparator) .withDebugListener(debugHandlerFactory.paretoSetStopArrivalListener(stop)) .withEgressListener(list, paths) .build(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java index 3d75a0d3438..5492cdbbda4 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/StopArrivalParetoSet.java @@ -42,7 +42,7 @@ static class Builder { private ParetoSetEventListener> nextSearchListener = null; private final ParetoComparator> comparator; - public Builder(ParetoComparator> comparator) { + Builder(ParetoComparator> comparator) { this.comparator = comparator; } @@ -76,7 +76,7 @@ StopArrivalParetoSet build() { // stop to appear before the path is logged (in case both debuggers are enabled). return new StopArrivalParetoSet<>( comparator, - ParetoSetEventListenerComposite.of(debugListener, egressListener, nextSearchListener) + ParetoSetEventListenerComposite.of(debugListener, nextSearchListener, egressListener) ); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrival.java index 895e6aa17e0..e4775c61508 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrival.java @@ -107,6 +107,12 @@ public McStopArrival timeShiftNewArrivalTime(int newArrivalTime) { throw new UnsupportedOperationException("No accessEgress for transfer stop arrival"); } + /** + * Add the given amount of slack to the arrival-time. This is used to add extraordinary + * wait-time to an arrival - for example, in via-search where a minimum-wait-time can be set. + */ + public abstract McStopArrival addSlackToArrivalTime(int slack); + @Override public final int hashCode() { throw new IllegalStateException("Avoid using hashCode() and equals() for this class."); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java index 485410c8797..2d59483d203 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java @@ -3,6 +3,7 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.ViaConnection; import org.opentripplanner.raptor.api.view.PatternRideView; public interface McStopArrivalFactory { @@ -20,4 +21,27 @@ McStopArrival createTransferStopArrival( RaptorTransfer transfer, int arrivalTime ); + + default McStopArrival createViaStopArrival( + McStopArrival previous, + ViaConnection viaConnection + ) { + if (viaConnection.isSameStop()) { + if (viaConnection.durationInSeconds() == 0) { + return previous; + } else { + return previous.addSlackToArrivalTime(viaConnection.durationInSeconds()); + } + } else { + if (previous.arrivedOnBoard()) { + return createTransferStopArrival( + previous, + viaConnection.transfer(), + previous.arrivalTime() + viaConnection.durationInSeconds() + ); + } else { + return null; + } + } + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java index e3787d41f78..cab1cde3c73 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/AccessStopArrival.java @@ -1,5 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c1; +import static org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator.accessEgressWithExtraSlack; import static org.opentripplanner.raptor.api.model.PathLegType.ACCESS; import org.opentripplanner.raptor.api.model.PathLegType; @@ -16,6 +17,7 @@ */ final class AccessStopArrival extends McStopArrival { + private final int departureTime; private final RaptorAccessEgress access; AccessStopArrival(int departureTime, RaptorAccessEgress access) { @@ -26,6 +28,7 @@ final class AccessStopArrival extends McStopArriva access.c1(), access.numberOfRides() ); + this.departureTime = departureTime; this.access = access; } @@ -44,6 +47,11 @@ public AccessPathView accessPath() { return () -> access; } + @Override + public boolean arrivedOnBoard() { + return access.stopReachedOnBoard(); + } + @Override public McStopArrival timeShiftNewArrivalTime(int newRequestedArrivalTime) { int newArrivalTime = access.latestArrivalTime(newRequestedArrivalTime); @@ -62,7 +70,7 @@ public McStopArrival timeShiftNewArrivalTime(int newRequestedArrivalTime) { } @Override - public boolean arrivedOnBoard() { - return access.stopReachedOnBoard(); + public McStopArrival addSlackToArrivalTime(int slack) { + return new AccessStopArrival<>(departureTime, accessEgressWithExtraSlack(access, slack)); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java index 05c165a158b..bca25dbf61c 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransferStopArrival.java @@ -55,4 +55,9 @@ public RaptorTransfer transfer() { public boolean arrivedOnBoard() { return false; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + return new TransferStopArrival<>(previous(), transfer, arrivalTime() + slack); + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java index a30581512a7..ee572175b81 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c1/TransitStopArrival.java @@ -69,4 +69,9 @@ public TransitPathView transitPath() { public boolean arrivedOnBoard() { return true; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + return new TransitStopArrival<>(previous(), stop(), arrivalTime() + slack, c1(), trip); + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java index ed4d9df1415..ecc54110feb 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/AccessStopArrivalC2.java @@ -1,5 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.c2; +import static org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator.accessEgressWithExtraSlack; import static org.opentripplanner.raptor.api.model.PathLegType.ACCESS; import org.opentripplanner.raptor.api.model.PathLegType; @@ -7,6 +8,7 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.view.AccessPathView; +import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; import org.opentripplanner.raptor.spi.RaptorCostCalculator; /** @@ -16,6 +18,7 @@ */ final class AccessStopArrivalC2 extends AbstractStopArrivalC2 { + private final int departureTime; private final RaptorAccessEgress access; AccessStopArrivalC2(int departureTime, RaptorAccessEgress access) { @@ -27,6 +30,7 @@ final class AccessStopArrivalC2 extends AbstractSt access.c1(), RaptorCostCalculator.ZERO_COST ); + this.departureTime = departureTime; this.access = access; } @@ -57,6 +61,11 @@ public AbstractStopArrivalC2 timeShiftNewArrivalTime(int newRequestedArrivalT return new AccessStopArrivalC2<>(newDepartureTime, access); } + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + return new AccessStopArrivalC2<>(departureTime, accessEgressWithExtraSlack(access, slack)); + } + @Override public boolean arrivedOnBoard() { return access.stopReachedOnBoard(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java index 42ee55ff784..888346b30fe 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransferStopArrivalC2.java @@ -46,4 +46,9 @@ public RaptorTransfer transfer() { public boolean arrivedOnBoard() { return false; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + return new TransferStopArrivalC2<>(previous(), transfer, arrivalTime() + slack); + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2.java index c047f484609..e08dde5103a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/c2/TransitStopArrivalC2.java @@ -58,4 +58,9 @@ public TransitPathView transitPath() { public boolean arrivedOnBoard() { return true; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + return new TransitStopArrivalC2<>(previous(), stop(), arrivalTime() + slack, c1(), c2(), trip); + } } 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 5a55eeb424a..e65d180a9d8 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 @@ -8,6 +8,7 @@ import org.opentripplanner.raptor.api.request.RaptorTransitGroupPriorityCalculator; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.context.SearchContext; +import org.opentripplanner.raptor.rangeraptor.context.SearchContextViaLeg; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; @@ -41,21 +42,22 @@ */ public class McRangeRaptorConfig { - private final SearchContext context; + private final SearchContextViaLeg contextLeg; private final PathConfig pathConfig; private final PassThroughPointsService passThroughPointsService; private DestinationArrivalPaths paths; private McRangeRaptorWorkerState state; - private RoutingStrategy strategy; private Heuristics heuristics; + private McStopArrivals arrivals; + private McStopArrivals nextLegArrivals = null; public McRangeRaptorConfig( - SearchContext context, + SearchContextViaLeg contextLeg, PassThroughPointsService passThroughPointsService ) { - this.context = Objects.requireNonNull(context); + this.contextLeg = Objects.requireNonNull(contextLeg); this.passThroughPointsService = Objects.requireNonNull(passThroughPointsService); - this.pathConfig = new PathConfig<>(context); + this.pathConfig = new PathConfig<>(this.contextLeg.parent()); } /** @@ -77,6 +79,18 @@ public McRangeRaptorConfig withHeuristics(Heuristics heuristics) { return this; } + /** + * Set the next leg state. This is used to connect the state created by this config with the + * next leg. If this is the last leg, the next leg should be {@code null}. Calling this method + * is optional - if not called. + */ + public McRangeRaptorConfig connectWithNextLegArrivals( + @Nullable McStopArrivals nextLegArrivals + ) { + this.nextLegArrivals = nextLegArrivals; + return this; + } + /** * Create new multi-criteria worker with optional heuristics. */ @@ -88,6 +102,27 @@ public RaptorWorkerState state() { return createState(heuristics); } + /** + * This is used in the config to chain more than one search together. + */ + public McStopArrivals stopArrivals() { + if (arrivals == null) { + this.arrivals = + new McStopArrivals<>( + context().nStops(), + contextLeg.accessPaths(), + contextLeg.egressPaths(), + contextLeg.viaConnections(), + createDestinationArrivalPaths(), + nextLegArrivals, + createStopArrivalFactory(), + createFactoryParetoComparator(), + context().debugFactory() + ); + } + return arrivals; + } + /* private factory methods */ private RoutingStrategy createTransitWorkerStrategy(McRangeRaptorWorkerState state) { @@ -111,11 +146,11 @@ private > RoutingStrategy createTransitWorkerStrateg ) { return new MultiCriteriaRoutingStrategy<>( state, - context.createTimeBasedBoardingSupport(), + context().createTimeBasedBoardingSupport(), factory, passThroughPointsService, - context.costCalculator(), - context.slackProvider(), + context().costCalculator(), + context().slackProvider(), createPatternRideParetoSet(patternRideComparator) ); } @@ -124,13 +159,13 @@ private McRangeRaptorWorkerState createState(Heuristics heuristics) { if (state == null) { state = new McRangeRaptorWorkerState<>( - createStopArrivals(), + stopArrivals(), createDestinationArrivalPaths(), createHeuristicsProvider(heuristics), createStopArrivalFactory(), - context.costCalculator(), - context.calculator(), - context.lifeCycle() + context().costCalculator(), + context().calculator(), + context().lifeCycle() ); } return state; @@ -140,26 +175,20 @@ private McStopArrivalFactory createStopArrivalFactory() { return includeC2() ? new StopArrivalFactoryC2<>() : new StopArrivalFactoryC1<>(); } - private McStopArrivals createStopArrivals() { - return new McStopArrivals<>( - context.nStops(), - context.egressPaths(), - context.accessPaths(), - createDestinationArrivalPaths(), - createFactoryParetoComparator(), - context.debugFactory() - ); + private SearchContext context() { + return contextLeg.parent(); } private HeuristicsProvider createHeuristicsProvider(Heuristics heuristics) { if (heuristics == null) { return new HeuristicsProvider<>(); } else { + var ctx = contextLeg.parent(); return new HeuristicsProvider<>( heuristics, createDestinationArrivalPaths(), - context.lifeCycle(), - context.debugFactory() + ctx.lifeCycle(), + ctx.debugFactory() ); } } @@ -167,7 +196,7 @@ private HeuristicsProvider createHeuristicsProvider(Heuristics heuristics) { private > ParetoSet createPatternRideParetoSet( ParetoComparator comparator ) { - return new ParetoSet<>(comparator, context.debugFactory().paretoSetPatternRideListener()); + return new ParetoSet<>(comparator, context().debugFactory().paretoSetPatternRideListener()); } private DestinationArrivalPaths createDestinationArrivalPaths() { @@ -183,7 +212,7 @@ private ArrivalParetoSetComparatorFactory> createFactoryParetoC } private MultiCriteriaRequest mcRequest() { - return context.multiCriteria(); + return context().multiCriteria(); } /** @@ -234,7 +263,7 @@ private ParetoSetCost resolveCostConfig() { if (isPassThrough()) { return ParetoSetCost.USE_C1_AND_C2; } - if (context.multiCriteria().relaxCostAtDestination() != null) { + if (context().multiCriteria().relaxCostAtDestination() != null) { return ParetoSetCost.USE_C1_RELAX_DESTINATION; } return ParetoSetCost.USE_C1; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java index 74e0f286992..efb50a38774 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java @@ -32,6 +32,7 @@ import org.opentripplanner.raptor.rangeraptor.standard.stoparrivals.StdStopArrivalsState; import org.opentripplanner.raptor.rangeraptor.standard.stoparrivals.path.EgressArrivalToPathAdapter; import org.opentripplanner.raptor.rangeraptor.standard.stoparrivals.view.StopsCursor; +import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; /** * The responsibility of this class is to wire different standard range raptor worker configurations @@ -72,7 +73,7 @@ public Heuristics createHeuristics(RaptorWorkerResult results) { return oneOf( new HeuristicsAdapter( ctx.nStops(), - ctx.egressPaths(), + egressPaths(), ctx.calculator(), ctx.costCalculator(), results.extractBestOverallArrivals(), @@ -180,7 +181,7 @@ private DestinationArrivalPaths destinationArrivalPaths() { // adapter notify the destination on each new egress stop arrival. var pathsAdapter = createEgressArrivalToPathAdapter(destinationArrivalPaths); - resolveStopArrivals().setupEgressStopStates(ctx.egressPaths(), pathsAdapter); + resolveStopArrivals().setupEgressStopStates(egressPaths(), pathsAdapter); return destinationArrivalPaths; } @@ -249,7 +250,7 @@ private UnknownPathFactory unknownPathFactory() { resolveBestNumberOfTransfers(), ctx.calculator(), ctx.slackProvider().transferSlack(), - ctx.egressPaths(), + egressPaths(), MIN_TRAVEL_DURATION.is(ctx.profile()), paretoComparator(ctx.paretoSetTimeConfig(), ParetoSetCost.NONE, null, null), ctx.lifeCycle() @@ -259,11 +260,15 @@ private UnknownPathFactory unknownPathFactory() { private SimpleArrivedAtDestinationCheck createSimpleArrivedAtDestinationCheck() { return new SimpleArrivedAtDestinationCheck( resolveBestTimes(), - ctx.egressPaths().egressesWitchStartByWalking(), - ctx.egressPaths().egressesWitchStartByARide() + egressPaths().egressesWitchStartByWalking(), + egressPaths().egressesWitchStartByARide() ); } + private EgressPaths egressPaths() { + return ctx.legs().getLast().egressPaths(); + } + private S withBestNumberOfTransfers(S value) { this.bestNumberOfTransfers = oneOf(value, BestNumberOfTransfers.class); return value; diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 3e2a5d4ecb8..2da4832760f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -5,6 +5,7 @@ import static org.opentripplanner.raptor.rangeraptor.transit.AccessEgressFunctions.removeNonOptimalPathsForStandardRaptor; import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -38,17 +39,14 @@ private AccessPaths( int iterationStep, IntUnaryOperator iterationOp, TIntObjectMap> arrivedOnStreetByNumOfRides, - TIntObjectMap> arrivedOnBoardByNumOfRides + TIntObjectMap> arrivedOnBoardByNumOfRides, + int maxTimePenalty ) { this.iterationStep = iterationStep; this.iterationOp = iterationOp; this.arrivedOnStreetByNumOfRides = arrivedOnStreetByNumOfRides; this.arrivedOnBoardByNumOfRides = arrivedOnBoardByNumOfRides; - this.maxTimePenalty = - Math.max( - maxTimePenalty(arrivedOnBoardByNumOfRides), - maxTimePenalty(arrivedOnStreetByNumOfRides) - ); + this.maxTimePenalty = maxTimePenalty; } /** @@ -74,12 +72,28 @@ public static AccessPaths create( } paths = decorateWithTimePenaltyLogic(paths); + var arrivedOnBoardByNumOfRides = groupByRound(paths, RaptorAccessEgress::stopReachedByWalking); + var arrivedOnStreetByNumOfRides = groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard); return new AccessPaths( iterationStep, iterationOp(searchDirection), - groupByRound(paths, RaptorAccessEgress::stopReachedByWalking), - groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard) + arrivedOnBoardByNumOfRides, + arrivedOnStreetByNumOfRides, + Math.max( + maxTimePenalty(arrivedOnBoardByNumOfRides), + maxTimePenalty(arrivedOnStreetByNumOfRides) + ) + ); + } + + public AccessPaths copyEmpty() { + return new AccessPaths( + iterationStep, + iterationOp, + new TIntObjectHashMap<>(), + new TIntObjectHashMap<>(), + maxTimePenalty ); } @@ -103,11 +117,13 @@ public List arrivedOnBoardByNumOfRides(int round) { return filterOnTimePenaltyLimitIfExist(arrivedOnBoardByNumOfRides.get(round)); } - public int calculateMaxNumberOfRides() { - return Math.max( - Arrays.stream(arrivedOnStreetByNumOfRides.keys()).max().orElse(0), - Arrays.stream(arrivedOnBoardByNumOfRides.keys()).max().orElse(0) - ); + public static int calculateMaxNumberOfRides(AccessPaths paths) { + return paths == null + ? 0 + : Math.max( + Arrays.stream(paths.arrivedOnStreetByNumOfRides.keys()).max().orElse(0), + Arrays.stream(paths.arrivedOnBoardByNumOfRides.keys()).max().orElse(0) + ); } /** @@ -151,7 +167,7 @@ public boolean hasTimeDependentAccess() { /* private methods */ - private int maxTimePenalty(TIntObjectMap> col) { + private static int maxTimePenalty(TIntObjectMap> col) { return col .valueCollection() .stream() diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java new file mode 100644 index 00000000000..ae1dd29ce47 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java @@ -0,0 +1,23 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import static java.util.stream.Collectors.groupingBy; + +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import java.util.Collection; +import java.util.List; +import org.opentripplanner.raptor.api.request.ViaConnection; + +public class ViaConnections { + + private final TIntObjectMap> byFromStop; + + public ViaConnections(Collection viaConnections) { + this.byFromStop = new TIntObjectHashMap<>(); + viaConnections.stream().collect(groupingBy(ViaConnection::fromStop)).forEach(byFromStop::put); + } + + public TIntObjectMap> byFromStop() { + return byFromStop; + } +} diff --git a/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java new file mode 100644 index 00000000000..6731517ffc4 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java @@ -0,0 +1,297 @@ +package org.opentripplanner.raptor.service; + +import static org.opentripplanner.raptor.api.model.SearchDirection.FORWARD; +import static org.opentripplanner.raptor.api.model.SearchDirection.REVERSE; +import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA; +import static org.opentripplanner.raptor.service.HeuristicToRunResolver.resolveHeuristicToRunBasedOnOptimizationsAndSearchParameters; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.opentripplanner.framework.application.OTPRequestTimeoutException; +import org.opentripplanner.raptor.RaptorService; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.raptor.api.request.SearchParams; +import org.opentripplanner.raptor.api.request.SearchParamsBuilder; +import org.opentripplanner.raptor.api.response.RaptorResponse; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.RangeRaptor; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This search helps the {@link RaptorService} to configure + * heuristics and set dynamic search parameters like EDT, LAT and raptor-search-window. + *

+ * If possible the forward and reverse heuristics will be run in parallel. + *

+ * Depending on which optimization is enabled and which search parameters are set a forward and/or a + * reverse "single-iteration" raptor search is performed and heuristics are collected. This is used + * to configure the "main" multi-iteration RangeRaptor search. + */ +public class ViaRangeRaptorDynamicSearch { + + private static final Logger LOG = LoggerFactory.getLogger(ViaRangeRaptorDynamicSearch.class); + + private final RaptorConfig config; + private final RaptorTransitDataProvider transitData; + private final RaptorRequest originalRequest; + private final RaptorSearchWindowCalculator dynamicSearchWindowCalculator; + + private final HeuristicSearchTask fwdHeuristics; + private final HeuristicSearchTask revHeuristics; + + public ViaRangeRaptorDynamicSearch( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest originalRequest + ) { + this.config = config; + this.transitData = transitData; + this.originalRequest = originalRequest; + this.dynamicSearchWindowCalculator = + config.searchWindowCalculator().withSearchParams(originalRequest.searchParams()); + + this.fwdHeuristics = new HeuristicSearchTask<>(FORWARD, "Forward", config, transitData); + this.revHeuristics = new HeuristicSearchTask<>(REVERSE, "Reverse", config, transitData); + } + + public RaptorResponse route() { + try { + enableHeuristicSearchBasedOnOptimizationsAndSearchParameters(); + + // Run heuristics, if no destination is reached + runHeuristics(); + + // Set search-window and other dynamic calculated parameters + var dynamicRequest = requestWithDynamicSearchParams(originalRequest); + + return createAndRunDynamicRRWorker(dynamicRequest); + } catch (DestinationNotReachedException e) { + return new RaptorResponse<>( + Collections.emptyList(), + null, + // If a trip exist(forward heuristics succeed), but is outside the calculated + // search-window, then set the search-window params as if the request was + // performed. This enables the client to page to the next window + requestWithDynamicSearchParams(originalRequest), + false + ); + } + } + + /** + * Only exposed for testing purposes + */ + @Nullable + public Heuristics getDestinationHeuristics() { + if (!originalRequest.useDestinationPruning()) { + return null; + } + LOG.debug("RangeRaptor - Destination pruning enabled."); + return revHeuristics.result(); + } + + /** + * Create and prepare heuristic search (both FORWARD and REVERSE) based on optimizations and input + * search parameters. This is done for Standard and Multi-criteria profiles only. + */ + private void enableHeuristicSearchBasedOnOptimizationsAndSearchParameters() { + // We delegate this to a static method to be able to write unit test on this logic + resolveHeuristicToRunBasedOnOptimizationsAndSearchParameters( + originalRequest, + fwdHeuristics::enable, + revHeuristics::enable + ); + } + + /** + * Run standard "singe-iteration" raptor search to calculate heuristics - this should be really + * fast to run compared with a (multi-criteria) range-raptor search. + * + * @throws DestinationNotReachedException if destination is not reached. + */ + private void runHeuristics() { + if (isItPossibleToRunHeuristicsInParallel()) { + runHeuristicsInParallel(); + } else { + runHeuristicsSequentially(); + } + fwdHeuristics.debugCompareResult(revHeuristics); + } + + private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { + LOG.debug("Main request: {}", request); + RangeRaptor raptorWorker; + + // Create worker + if (request.profile().is(MULTI_CRITERIA)) { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createStdWorker(transitData, request); + } + + // Route + var result = raptorWorker.route(); + + // create and return response + return new RaptorResponse<>( + result.extractPaths(), + new DefaultStopArrivals(result), + request, + // This method is not run unless the heuristic reached the destination + true + ); + } + + private boolean isItPossibleToRunHeuristicsInParallel() { + SearchParams s = originalRequest.searchParams(); + return ( + config.isMultiThreaded() && + originalRequest.runInParallel() && + s.isEarliestDepartureTimeSet() && + s.isLatestArrivalTimeSet() && + fwdHeuristics.isEnabled() && + revHeuristics.isEnabled() + ); + } + + /** + * @throws DestinationNotReachedException if destination is not reached + */ + private void runHeuristicsInParallel() { + fwdHeuristics.withRequest(originalRequest); + revHeuristics.withRequest(originalRequest); + Future asyncResult = null; + try { + asyncResult = config.threadPool().submit(fwdHeuristics::run); + revHeuristics.run(); + asyncResult.get(); + LOG.debug( + "Route using RangeRaptor - " + "REVERSE and FORWARD heuristic search performed in parallel." + ); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // propagate interruption to the running task. + asyncResult.cancel(true); + throw new OTPRequestTimeoutException(); + } catch (ExecutionException e) { + if (e.getCause() instanceof DestinationNotReachedException) { + throw new DestinationNotReachedException(); + } + LOG.error(e.getMessage() + ". Request: " + originalRequest, e); + throw new IllegalStateException( + "Failed to run FORWARD/REVERSE heuristic search in parallel. Details: " + e.getMessage() + ); + } + } + + /** + * @throws DestinationNotReachedException if destination is not reached + */ + private void runHeuristicsSequentially() { + List> tasks = listTasksInOrder(); + + if (tasks.isEmpty()) { + return; + } + + // Run the first heuristic search + Heuristics result = runHeuristicSearchTask(tasks.get(0)); + calculateDynamicSearchParametersFromHeuristics(result); + + if (tasks.size() == 1) { + return; + } + + // Run the second heuristic search + runHeuristicSearchTask(tasks.get(1)); + } + + private Heuristics runHeuristicSearchTask(HeuristicSearchTask task) { + RaptorRequest request = task.getDirection().isForward() + ? requestForForwardHeurSearchWithDynamicSearchParams() + : requestForReverseHeurSearchWithDynamicSearchParams(); + + task.withRequest(request).run(); + + return task.result(); + } + + /** + * If the earliest-departure-time(EDT) is set, the task order should be: + *

    + *
  1. {@code FORWARD}
  2. + *
  3. {@code REVERSE}
  4. + *
+ * If not EDT is set, the latest-arrival-time is set, and the order should be the opposite, + * with {@code REVERSE} first + */ + private List> listTasksInOrder() { + boolean performForwardFirst = originalRequest.searchParams().isEarliestDepartureTimeSet(); + + List> list = performForwardFirst + ? List.of(fwdHeuristics, revHeuristics) + : List.of(revHeuristics, fwdHeuristics); + + return list.stream().filter(HeuristicSearchTask::isEnabled).collect(Collectors.toList()); + } + + private RaptorRequest requestForForwardHeurSearchWithDynamicSearchParams() { + if (originalRequest.searchParams().isEarliestDepartureTimeSet()) { + return originalRequest; + } + return originalRequest + .mutate() + .searchParams() + .earliestDepartureTime(transitData.getValidTransitDataStartTime()) + .build(); + } + + private RaptorRequest requestForReverseHeurSearchWithDynamicSearchParams() { + if (originalRequest.searchParams().isLatestArrivalTimeSet()) { + return originalRequest; + } + return originalRequest + .mutate() + .searchParams() + .latestArrivalTime( + transitData.getValidTransitDataEndTime() + + originalRequest.searchParams().accessEgressMaxDurationSeconds() + ) + .build(); + } + + private RaptorRequest requestWithDynamicSearchParams(RaptorRequest request) { + SearchParamsBuilder builder = request.mutate().searchParams(); + + if (!request.searchParams().isEarliestDepartureTimeSet()) { + builder.earliestDepartureTime(dynamicSearchWindowCalculator.getEarliestDepartureTime()); + } + if (!request.searchParams().isSearchWindowSet()) { + builder.searchWindowInSeconds(dynamicSearchWindowCalculator.getSearchWindowSeconds()); + } + // We do not set the latest-arrival-time, because we do not want to limit the forward + // multi-criteria search, it does not have much effect on the performance - we only risk + // loosing optimal results. + return builder.build(); + } + + private void calculateDynamicSearchParametersFromHeuristics(@Nullable Heuristics heuristics) { + if (heuristics != null) { + dynamicSearchWindowCalculator + .withHeuristics( + heuristics.bestOverallJourneyTravelDuration(), + heuristics.minWaitTimeForJourneysReachingDestination() + ) + .calculate(); + } + } +} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java deleted file mode 100644 index 998c71e3da4..00000000000 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaConnectionTest.java +++ /dev/null @@ -1,129 +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.time.Duration; -import java.util.List; -import org.junit.jupiter.api.Assertions; -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.raptor.api.model.RaptorConstants; - -class ViaConnectionTest { - - private static final int STOP_A = 12; - private static final int STOP_B = 13; - private static final int STOP_C = 14; - private static final Duration MIN_DURATION = Duration.ofMinutes(3); - private static final int MIN_DURATION_SEC = (int) MIN_DURATION.toSeconds(); - private static final int C1 = 200; - - @Test - void passThroughStop() { - var subject = ViaConnection.passThroughStop(STOP_C); - assertEquals(STOP_C, subject.fromStop()); - assertEquals(STOP_C, subject.toStop()); - assertTrue(subject.allowPassThrough()); - assertTrue(subject.isSameStop()); - Assertions.assertEquals(RaptorConstants.ZERO, subject.durationInSeconds()); - assertEquals(RaptorConstants.ZERO, subject.c1()); - } - - @Test - void viaSingleStop() { - var subject = ViaConnection.stop(STOP_C, MIN_DURATION); - assertEquals(STOP_C, subject.fromStop()); - assertEquals(STOP_C, subject.toStop()); - assertFalse(subject.allowPassThrough()); - assertTrue(subject.isSameStop()); - assertEquals(MIN_DURATION_SEC, subject.durationInSeconds()); - assertEquals(RaptorConstants.ZERO, subject.c1()); - } - - @Test - void viaCoordinateOrTransfer() { - var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); - assertEquals(STOP_A, subject.fromStop()); - assertEquals(STOP_B, subject.toStop()); - assertFalse(subject.allowPassThrough()); - assertFalse(subject.isSameStop()); - assertEquals(MIN_DURATION_SEC, subject.durationInSeconds()); - assertEquals(C1, subject.c1()); - } - - static List isBetterThanTestCases() { - // Subject is: STOP_A, STOP_B, MIN_DURATION, C1 - return List.of( - Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1, true, "Same"), - Arguments.of(STOP_C, STOP_B, MIN_DURATION_SEC, C1, false, "toStop differ"), - Arguments.of(STOP_A, STOP_C, MIN_DURATION_SEC, C1, false, "fromStop differ"), - Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC + 1, C1, true, "Wait time is better"), - Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC - 1, C1, false, "Wait time is worse"), - Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1 + 1, true, "C1 is better"), - Arguments.of(STOP_A, STOP_B, MIN_DURATION_SEC, C1 - 1, false, "C1 is worse") - ); - } - - @ParameterizedTest - @MethodSource("isBetterThanTestCases") - void isBetterThan( - int fromStop, - int toStop, - int minWaitTime, - int c1, - boolean expected, - String description - ) { - var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); - var candidate = ViaConnection.stop(fromStop, toStop, Duration.ofSeconds(minWaitTime), c1); - assertEquals(subject.isBetterThan(candidate), expected, description); - } - - @Test - void testEqualsAndHashCode() { - var subject = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); - var same = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); - // Slightly less wait-time and slightly larger cost(c1) - var other = ViaConnection.stop( - STOP_A, - STOP_B, - MIN_DURATION.minus(Duration.ofSeconds(1)), - C1 + 1 - ); - - assertEquals(subject, same); - assertNotEquals(subject, other); - assertNotEquals(subject, "Does not match another type"); - - assertEquals(subject.hashCode(), same.hashCode()); - assertNotEquals(subject.hashCode(), other.hashCode()); - } - - @Test - void testToString() { - var viaStopAB = ViaConnection.stop(STOP_A, STOP_B, MIN_DURATION, C1); - var viaStopB = ViaConnection.stop(STOP_B, MIN_DURATION); - var passThroughC = ViaConnection.passThroughStop(STOP_C); - - assertEquals("Via(3m 12~13)", viaStopAB.toString()); - assertEquals("Via(3m A~B)", viaStopAB.toString(ViaConnectionTest::stopName)); - assertEquals("Via(3m 13)", viaStopB.toString()); - assertEquals("Via(3m B)", viaStopB.toString(ViaConnectionTest::stopName)); - assertEquals("PassThrough(14)", passThroughC.toString()); - assertEquals("PassThrough(C)", passThroughC.toString(ViaConnectionTest::stopName)); - } - - private static String stopName(int i) { - return switch (i) { - case 12 -> "A"; - case 13 -> "B"; - case 14 -> "C"; - default -> throw new IllegalArgumentException("Unknown stop: " + i); - }; - } -} diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java index b313152f628..4feba14381f 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java @@ -1,53 +1,248 @@ 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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; import java.util.List; +import org.junit.jupiter.api.Assertions; 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.raptor._data.transit.TestTransfer; +import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.RaptorTransfer; class ViaLocationTest { private static final int STOP_A = 12; private static final int STOP_B = 13; - private static final Duration DURATION = Duration.ofMinutes(3); + private static final int STOP_C = 14; + private static final Duration WAIT_TIME = Duration.ofMinutes(3); + private static final int WAIT_TIME_SEC = (int) WAIT_TIME.toSeconds(); private static final int C1 = 200; + private static final int TX_DURATION = 35; + private static final RaptorTransfer TX = new TestTransfer(STOP_B, TX_DURATION, C1); @Test - void testAssessors() { - var connections = List.of(ViaConnection.stop(STOP_A, STOP_B, DURATION, C1)); - var subject = new ViaLocation("Nnn", connections); + void passThroughStop() { + var subject = ViaLocation.allowPassThrough("PassThrough A").addViaStop(STOP_C).build(); - assertEquals("Nnn", subject.label()); - assertEquals(connections, subject.connections()); + assertEquals("PassThrough A", subject.label()); + assertTrue(subject.allowPassThrough()); + assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); + assertEquals( + "Via{label: PassThrough A, allowPassThrough, connections: [C]}", + subject.toString(ViaLocationTest::stopName) + ); + assertEquals( + "Via{label: PassThrough A, allowPassThrough, connections: [14]}", + subject.toString() + ); + + assertEquals(1, subject.connections().size()); + + var c = subject.connections().getFirst(); + assertEquals(STOP_C, c.fromStop()); + assertEquals(STOP_C, c.toStop()); + assertTrue(c.isSameStop()); + Assertions.assertEquals(RaptorConstants.ZERO, c.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, c.c1()); + } + + @Test + void viaSingleStop() { + var subject = ViaLocation.via("Tx A").addViaStop(STOP_B).build(); + + assertEquals("Tx A", subject.label()); + assertFalse(subject.allowPassThrough()); + assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); + assertEquals("Via{label: Tx A, connections: [B]}", subject.toString(ViaLocationTest::stopName)); + assertEquals("Via{label: Tx A, connections: [13]}", subject.toString()); + assertEquals(1, subject.connections().size()); + + var connection = subject.connections().getFirst(); + assertEquals(STOP_B, connection.fromStop()); + assertEquals(STOP_B, connection.toStop()); + assertTrue(connection.isSameStop()); + Assertions.assertEquals(RaptorConstants.ZERO, connection.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, connection.c1()); + } + + @Test + void testCombinationOfPassThroughAndTransfer() { + var subject = ViaLocation + .allowPassThrough("PassThrough A") + .addViaStop(STOP_C) + .addViaTransfer(STOP_A, TX) + .build(); + + assertEquals("PassThrough A", subject.label()); + assertTrue(subject.allowPassThrough()); + assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); + assertEquals( + "Via{label: PassThrough A, allowPassThrough, connections: [C, A~B 35s]}", + subject.toString(ViaLocationTest::stopName) + ); + assertEquals(2, subject.connections().size()); + + var c = subject.connections().getFirst(); + assertEquals(STOP_C, c.fromStop()); + assertEquals(STOP_C, c.toStop()); + assertTrue(c.isSameStop()); + Assertions.assertEquals(RaptorConstants.ZERO, c.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, c.c1()); + + c = subject.connections().getLast(); + assertEquals(STOP_A, c.fromStop()); + assertEquals(STOP_B, c.toStop()); + assertFalse(c.isSameStop()); + Assertions.assertEquals(TX_DURATION, c.durationInSeconds()); + assertEquals(C1, c.c1()); + } + + @Test + void viaStopAorCWithWaitTime() { + var subject = ViaLocation + .via("Plaza", WAIT_TIME) + .addViaStop(STOP_C) + .addViaTransfer(STOP_A, TX) + .build(); + + assertEquals("Plaza", subject.label()); + assertFalse(subject.allowPassThrough()); + assertEquals(WAIT_TIME_SEC, subject.minimumWaitTime()); + assertEquals( + "Via{label: Plaza, minWaitTime: 3m, connections: [C 3m, A~B 3m35s]}", + subject.toString(ViaLocationTest::stopName) + ); + assertEquals(2, subject.connections().size()); + + var connection = subject.connections().getFirst(); + assertEquals(STOP_C, connection.fromStop()); + assertEquals(STOP_C, connection.toStop()); + assertTrue(connection.isSameStop()); + Assertions.assertEquals(WAIT_TIME_SEC, connection.durationInSeconds()); + assertEquals(RaptorConstants.ZERO, connection.c1()); + + connection = subject.connections().getLast(); + assertEquals(STOP_A, connection.fromStop()); + assertEquals(STOP_B, connection.toStop()); + assertFalse(connection.isSameStop()); + Assertions.assertEquals(WAIT_TIME_SEC + TX.durationInSeconds(), connection.durationInSeconds()); + assertEquals(C1, connection.c1()); + } + + static List isBetterThanTestCases() { + // Subject is: STOP_A, STOP_B, MIN_DURATION, C1 + return List.of( + Arguments.of(STOP_A, STOP_B, TX_DURATION, C1, true, "Same"), + Arguments.of(STOP_C, STOP_B, TX_DURATION, C1, false, "toStop differ"), + Arguments.of(STOP_A, STOP_C, TX_DURATION, C1, false, "fromStop differ"), + Arguments.of(STOP_A, STOP_B, TX_DURATION + 1, C1, true, "Wait time is better"), + Arguments.of(STOP_A, STOP_B, TX_DURATION - 1, C1, false, "Wait time is worse"), + Arguments.of(STOP_A, STOP_B, TX_DURATION, C1 + 1, true, "C1 is better"), + Arguments.of(STOP_A, STOP_B, TX_DURATION, C1 - 1, false, "C1 is worse") + ); + } + + @ParameterizedTest + @MethodSource("isBetterThanTestCases") + void isBetterThan( + int fromStop, + int toStop, + int minWaitTime, + int c1, + boolean expected, + String description + ) { + var subject = ViaLocation + .via("Subject") + .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) + .build() + .connections() + .getFirst(); + + var candidate = ViaLocation + .via("Candidate") + .addViaTransfer(fromStop, new TestTransfer(toStop, minWaitTime, c1)) + .build() + .connections() + .getFirst(); + + assertEquals(subject.isBetterOrEqual(candidate), expected, description); } @Test - void twoNoneParetoOptimalConnectionsAreNotAllowed() { + void throwsExceptionIfConnectionsIsNotParetoOptimal() { var e = assertThrows( IllegalArgumentException.class, () -> - new ViaLocation( - "Via", - List.of(ViaConnection.passThroughStop(STOP_A), ViaConnection.stop(STOP_A, DURATION)) - ) + ViaLocation + .via("S") + .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) + .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) + .build() ); assertEquals( - "All connection need to be pareto-optimal. a: PassThrough(12), b: Via(3m 12)", + "All connection need to be pareto-optimal: (12~13 35s) <-> (12~13 35s)", e.getMessage() ); } + @Test + void testEqualsAndHashCode() { + var subject = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + var same = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + // Slightly less wait-time and slightly larger cost(c1) + var other = ViaLocation.via(null, Duration.ofSeconds(1)).addViaTransfer(STOP_A, TX).build(); + + assertEquals(subject, same); + assertNotEquals(subject, other); + assertNotEquals(subject, "Does not match another type"); + + assertEquals(subject.hashCode(), same.hashCode()); + assertNotEquals(subject.hashCode(), other.hashCode()); + } + @Test void testToString() { + var subject = ViaLocation.via("A|B").addViaStop(STOP_A).addViaStop(STOP_B).build(); + assertEquals("Via{label: A|B, connections: [12, 13]}", subject.toString()); + assertEquals( + "Via{label: A|B, connections: [A, B]}", + subject.toString(ViaLocationTest::stopName) + ); + + subject = ViaLocation.via(null, WAIT_TIME).addViaStop(STOP_B).build(); + assertEquals("Via{minWaitTime: 3m, connections: [13 3m]}", subject.toString()); assertEquals( - "ViaLocation{label: Nnn, connections: [PassThrough(12), PassThrough(13)]}", - new ViaLocation( - "Nnn", - List.of(ViaConnection.passThroughStop(STOP_A), ViaConnection.passThroughStop(STOP_B)) - ) - .toString() + "Via{minWaitTime: 3m, connections: [B 3m]}", + subject.toString(ViaLocationTest::stopName) ); + + subject = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + assertEquals("Via{connections: [12~13 35s]}", subject.toString()); + assertEquals("Via{connections: [A~B 35s]}", subject.toString(ViaLocationTest::stopName)); + + subject = ViaLocation.allowPassThrough(null).addViaStop(STOP_C).build(); + assertEquals("Via{allowPassThrough, connections: [14]}", subject.toString()); + assertEquals( + "Via{allowPassThrough, connections: [C]}", + subject.toString(ViaLocationTest::stopName) + ); + } + + private static String stopName(int i) { + return switch (i) { + case 12 -> "A"; + case 13 -> "B"; + case 14 -> "C"; + default -> throw new IllegalArgumentException("Unknown stop: " + i); + }; } } diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java new file mode 100644 index 00000000000..8210f479bbc --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java @@ -0,0 +1,376 @@ +package org.opentripplanner.raptor.moduletests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.plan.PlanTestConstants.D2m; +import static org.opentripplanner.raptor._data.RaptorTestConstants.D1m; +import static org.opentripplanner.raptor._data.RaptorTestConstants.D30s; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_A; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_C; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_D; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_E; +import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_F; +import static org.opentripplanner.raptor._data.RaptorTestConstants.T00_00; +import static org.opentripplanner.raptor._data.RaptorTestConstants.T01_00; +import static org.opentripplanner.raptor._data.api.PathUtils.pathsToString; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor._data.transit.TestRoute.route; +import static org.opentripplanner.raptor._data.transit.TestTransfer.transfer; +import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; +import static org.opentripplanner.raptor.api.request.ViaLocation.via; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.RaptorService; +import org.opentripplanner.raptor._data.api.PathUtils; +import org.opentripplanner.raptor._data.transit.TestTransitData; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorProfile; +import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.raptor.api.request.ViaLocation; +import org.opentripplanner.raptor.configure.RaptorConfig; + +/** + * FEATURE UNDER TEST + * + * Raptor should be able to handle route request with one or more via locations. + * If a stop is specified as via location in the request, then all the results returned + * from raptor should include the stop. The stop should be a alight, board or intermediate + * stop of one of the trips in the path. + * + * It should be possible to specify more than one connection. The result should include the + * via locations in the order as they were specified in the request. Only alternatives that pass + * through all via locations should be included in the result. + * + * To support stations and other collections of stops, Raptor should also support multiple via + * connections in one via location. + */ +class J02_ViaSearchTest { + + static final List VIA_LOCATION_STOP_B = List.of(viaLocation("B", STOP_B)); + static final List VIA_LOCATION_STOP_C = List.of(viaLocation("C", STOP_C)); + static final List VIA_LOCATION_STOP_A_OR_B = List.of( + viaLocation("B&C", STOP_A, STOP_B) + ); + + static final List VIA_LOCATION_STOP_B_THEN_D = List.of( + viaLocation("B", STOP_B), + viaLocation("D", STOP_D) + ); + static final List VIA_LOCATION_STOP_C_THEN_B = List.of( + viaLocation("B", STOP_C), + viaLocation("D", STOP_B) + ); + + private final RaptorService raptorService = new RaptorService<>( + RaptorConfig.defaultConfigForTest() + ); + + private RaptorRequestBuilder prepareRequest() { + var builder = new RaptorRequestBuilder(); + + builder + .profile(RaptorProfile.MULTI_CRITERIA) + // TODO: 2023-07-24 Currently heuristics does not work with pass-through so we + // have to turn them off. Make sure to re-enable optimization later when it's fixed + .clearOptimizations(); + + builder + .searchParams() + .earliestDepartureTime(T00_00) + .latestArrivalTime(T01_00) + .searchWindow(Duration.ofMinutes(10)) + .timetable(true); + + return builder; + } + + @Test + @DisplayName( + "Basic via search with just one route. You should be forced to get off the " + + "first trip and wait for the next one at the specified via stop." + ) + void viaSearchAlightingAtViaStop() { + var data = new TestTransitData(); + + data.withRoutes( + route("R1", STOP_A, STOP_B, STOP_C, STOP_D) + .withTimetable(schedule("0:02 0:10 0:20 0:30"), schedule("0:12 0:20 0:30 0:40")) + ); + + var requestBuilder = prepareRequest(); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocation(via("C").addViaStop(STOP_C).build()) + .addEgressPaths(walk(STOP_D, D30s)); + + var result = raptorService.route(requestBuilder.build(), data); + + // Verify that we alight the first trip at stop C and board the second trip + assertEquals( + "Walk 30s ~ A ~ BUS R1 0:02 0:20 ~ C ~ BUS R1 0:30 0:40 ~ D ~ Walk 30s [0:01:30 0:40:30 39m Tₓ1 C₁3_600]", + pathsToString(result) + ); + } + + @Test + @DisplayName( + "Basic via search with just two routes. You should be forced to get off the first route, " + + "then transfer and BOARD the second trip at the specified via stop. This test that via works " + + "at the boarding stop. We will add better options for the transfer to see that the given via " + + "stop is used over the alternatives." + ) + void viaSearchArrivingByTransferAtViaStop() { + var data = new TestTransitData(); + + data + .withRoutes( + route("R1", STOP_A, STOP_B, STOP_D, STOP_E).withTimetable(schedule("0:02 0:10 0:20 0:30")), + route("R2", STOP_C, STOP_D, STOP_E).withTimetable(schedule("0:25 0:30 0:40")) + ) + // Walk 1 minute to transfer from D to C - this is the only way to visit stop C + .withTransfer(STOP_D, transfer(STOP_C, D1m)); + + var requestBuilder = prepareRequest(); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocation(via("C").addViaStop(STOP_C).build()) + .addEgressPaths(walk(STOP_E, D30s)); + + var result = raptorService.route(requestBuilder.build(), data); + + // Verify that we alight the first trip at stop C and board the second trip + assertEquals( + "Walk 30s ~ A ~ BUS R1 0:02 0:20 ~ D ~ Walk 1m ~ C ~ BUS R2 0:25 0:40 ~ E ~ Walk 30s " + + "[0:01:30 0:40:30 39m Tₓ1 C₁3_660]", + pathsToString(result) + ); + } + + @Test + @DisplayName( + "Via stop as the first stop in the journey - only the access will be used for the first " + + "part, no transit. Access arrival should be copied over to 'next' worker." + ) + void accessWalkToViaStopWithoutTransit() { + var data = new TestTransitData(); + + data.withRoutes( + route("R1", STOP_A, STOP_B, STOP_C, STOP_D) + .withTimetable( + schedule("0:02 0:05 0:10 0:15"), + // We add another trip to allow riding trip one - via B - then ride trip two, this + // is not a pareto-optimal solution and should only appear if there is something wrong. + schedule("0:12 0:15 0:20 0:25") + ) + ); + + var requestBuilder = prepareRequest(); + + // We will add access to A, B, and C, but since the B stop is the via point we expect that to + // be used + requestBuilder + .searchParams() + .addViaLocations(VIA_LOCATION_STOP_B) + // We allow access to A, B, and C - if the via search works as expected, only access to B + // should be used - access to A would require an extra transfer; C has no valid paths. + .addAccessPaths(walk(STOP_A, D30s)) + .addAccessPaths(walk(STOP_B, D30s)) + .addAccessPaths(walk(STOP_C, D30s)) + .addEgressPaths(walk(STOP_D, D30s)); + + // Verify that the journey start by walking to the via stop, the uses one trip to the destination. + // A combination of trip one and two with a transfer is not expected. + assertEquals( + PathUtils.join( + "Walk 30s ~ B ~ BUS R1 0:05 0:15 ~ D ~ Walk 30s [0:04:30 0:15:30 11m Tₓ0 C₁1_320]", + "Walk 30s ~ B ~ BUS R1 0:15 0:25 ~ D ~ Walk 30s [0:14:30 0:25:30 11m Tₓ0 C₁1_320]" + ), + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + @Test + @DisplayName( + "Via stop as the last stop in the journey - only the egress will be used for the last " + + "part, no transit. The transit arrival at the via stop should be copied over to the " + + "next worker and then this should be used to add the egress - without any transfers or" + + "more transit." + ) + void transitToViaStopThenTakeEgressWalkToDestination() { + var data = new TestTransitData(); + + data.withRoutes( + route("R1", STOP_A, STOP_B, STOP_C, STOP_D) + .withTimetable( + schedule("0:02 0:05 0:10 0:20"), + // We add another trip to check that we do not transfer to the other trip at some point. + schedule("0:12 0:15 0:20 0:25") + ) + ); + + var requestBuilder = prepareRequest(); + + // We will add access to A, B, and C, but since the B stop is the via point we expect that to + // be used + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocations(VIA_LOCATION_STOP_C) + // We allow egress from B, C, and D - if the via search works as expected, only egress from C + // should be used - egress from B has not visited via stop C, and egress from stop D would + // require a transfer at stop C to visit the via stop - this is not an optimal path. + .addEgressPaths(walk(STOP_B, D30s)) + .addEgressPaths(walk(STOP_C, D30s)) + .addEgressPaths(walk(STOP_D, D30s)); + + assertEquals( + PathUtils.join( + "Walk 30s ~ A ~ BUS R1 0:02 0:10 ~ C ~ Walk 30s [0:01:30 0:10:30 9m Tₓ0 C₁1_200]", + "Walk 30s ~ A ~ BUS R1 0:12 0:20 ~ C ~ Walk 30s [0:11:30 0:20:30 9m Tₓ0 C₁1_200]" + ), + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + @Test + @DisplayName("Multiple via points") + void multipleViaPoints() { + var data = new TestTransitData(); + + // Create two routes. + // The first one includes one via stop point. + // The second one includes the second via point. + // Both arrive at the desired destination, so normally there should not be any transfers. + data.withRoutes( + route("R2", STOP_A, STOP_B, STOP_C, STOP_D, STOP_E, STOP_F) + .withTimetable( + schedule("0:02 0:05 0:10 0:15 0:20 0:25"), + schedule("0:12 0:15 0:20 0:25 0:30 0:35"), + schedule("0:22 0:25 0:30 0:35 0:40 0:45") + ) + ); + + data.mcCostParamsBuilder().transferCost(100); + + var requestBuilder = prepareRequest(); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocations(VIA_LOCATION_STOP_B_THEN_D) + .addEgressPaths(walk(STOP_F, D30s)); + + // Verify that both via points are included + assertEquals( + "Walk 30s ~ A " + + "~ BUS R2 0:02 0:05 ~ B " + + "~ BUS R2 0:15 0:25 ~ D " + + "~ BUS R2 0:35 0:45 ~ F " + + "~ Walk 30s " + + "[0:01:30 0:45:30 44m Tₓ2 C₁4_700]", + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + @Test + @DisplayName("Multiple via points works with circular lines, visit stop C than stop B") + void viaSearchWithCircularLine() { + var data = new TestTransitData(); + + data.withRoute( + route("R1", STOP_A, STOP_B, STOP_C, STOP_B, STOP_C, STOP_B, STOP_C, STOP_B, STOP_D) + .withTimetable(schedule("0:05 0:10 0:15 0:20 0:25 0:30 0:35 0:40 0:45")) + ); + + var requestBuilder = prepareRequest(); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocations(VIA_LOCATION_STOP_C_THEN_B) + .addEgressPaths(walk(STOP_D, D30s)); + + assertEquals( + "Walk 30s ~ A " + + "~ BUS R1 0:05 0:15 ~ C " + + "~ BUS R1 0:25 0:30 ~ B " + + "~ BUS R1 0:40 0:45 ~ D " + + "~ Walk 30s " + + "[0:04:30 0:45:30 41m Tₓ2 C₁4_320]", + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + @Test + @DisplayName("Multiple stops in the same via location") + void testViaSearchWithManyStopsInTheViaLocation() { + var data = new TestTransitData(); + + data.withRoutes( + route("R1", STOP_A, STOP_C).withTimetable(schedule("0:04 0:15")), + route("R2", STOP_B, STOP_C).withTimetable(schedule("0:05 0:14")) + ); + + var requestBuilder = prepareRequest(); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addAccessPaths(walk(STOP_B, D2m)) + .addViaLocations(VIA_LOCATION_STOP_A_OR_B) + .addEgressPaths(walk(STOP_C, D30s)); + + // Both routes are pareto optimal. + // Route 2 is faster, but it contains more walking + // Verify that both routes are included as a valid result + assertEquals( + PathUtils.join( + "Walk 2m ~ B ~ BUS R2 0:05 0:14 ~ C ~ Walk 30s [0:03 0:14:30 11m30s Tₓ0 C₁1_440]", + "Walk 30s ~ A ~ BUS R1 0:04 0:15 ~ C ~ Walk 30s [0:03:30 0:15:30 12m Tₓ0 C₁1_380]" + ), + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + @Test + @DisplayName("Test minimum wait time") + void testMinWaitTime() { + var data = new TestTransitData(); + data.withRoutes( + route("R1", STOP_A, STOP_B).withTimetable(schedule("0:02:00 0:04:00")), + route("R2", STOP_B, STOP_C) + .withTimetable(schedule("0:05:44 0:10"), schedule("0:05:45 0:11"), schedule("0:05:46 0:12")) + ); + + var requestBuilder = prepareRequest(); + var minWaitTime = Duration.ofSeconds(45); + + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D30s)) + .addViaLocations(List.of(ViaLocation.via("B", minWaitTime).addViaStop(STOP_B).build())) + .addEgressPaths(walk(STOP_C, D30s)); + + // We expect to bard the second trip at 0:05:45, since the minWaitTime is 45s and the + // transfer slack is 60s. + assertEquals( + "Walk 30s ~ A ~ BUS R1 0:02 0:04 ~ B ~ BUS R2 0:05:45 0:11 ~ C ~ Walk 30s " + + "[0:01:30 0:11:30 10m Tₓ1 C₁1_860]", + pathsToString(raptorService.route(requestBuilder.build(), data)) + ); + } + + private static ViaLocation viaLocation(String label, int... stops) { + var builder = ViaLocation.via(label); + Arrays.stream(stops).forEach(builder::addViaStop); + return builder.build(); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java index 9b6311b58bc..5fd19fc07f5 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/ArrivalParetoSetComparatorFactoryTest.java @@ -305,5 +305,10 @@ public PathLegType arrivedBy() { public boolean arrivedOnBoard() { return arrivedOnBoard; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + throw new UnsupportedOperationException(); + } } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java index 64593a91b2a..9cbb35ca0a6 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalTest.java @@ -149,5 +149,10 @@ public PathLegType arrivedBy() { public boolean arrivedOnBoard() { return arrivedOnBoard; } + + @Override + public McStopArrival addSlackToArrivalTime(int slack) { + throw new UnsupportedOperationException(); + } } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java index 8611a046a5b..505fa67706e 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java @@ -78,8 +78,9 @@ void arrivedOnBoardByNumOfRides() { @Test void calculateMaxNumberOfRides() { - assertEquals(3, create(STANDARD).calculateMaxNumberOfRides()); - assertEquals(3, create(MULTI_CRITERIA).calculateMaxNumberOfRides()); + assertEquals(0, AccessPaths.calculateMaxNumberOfRides(null)); + assertEquals(3, AccessPaths.calculateMaxNumberOfRides(create(STANDARD))); + assertEquals(3, AccessPaths.calculateMaxNumberOfRides(create(MULTI_CRITERIA))); } @Test From d7696121662bdcf8af84cfff244aed59844e5cb0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 19:30:01 +0200 Subject: [PATCH 15/36] refactor: Deprecate old vi search. --- .../transmodel/mapping/ViaLocationMapper.java | 6 ++--- .../transmodel/mapping/ViaRequestMapper.java | 7 ++++-- .../model/plan/ViaLocationInputType.java | 6 ++--- .../algorithm/via/ViaRoutingWorker.java | 6 ++--- .../routing/api/RoutingService.java | 5 ++++ .../routing/api/request/RouteViaRequest.java | 18 ++++++++++---- ...cation.java => ViaLocationDeprecated.java} | 8 +++++-- ...st.java => ViaLocationDeprecatedTest.java} | 24 ++++++++++++------- .../algorithm/via/ViaRoutingWorkerTest.java | 4 ++-- 9 files changed, 56 insertions(+), 28 deletions(-) rename src/main/java/org/opentripplanner/routing/api/request/{ViaLocation.java => ViaLocationDeprecated.java} (82%) rename src/test/java/org/opentripplanner/raptor/api/request/{ViaLocationTest.java => ViaLocationDeprecatedTest.java} (92%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index af39338fb98..e9c7c7ff3b4 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -5,16 +5,16 @@ import java.time.Duration; import java.util.List; import java.util.Map; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; class ViaLocationMapper { - static ViaLocation mapViaLocation(Map viaLocation) { + static ViaLocationDeprecated mapViaLocation(Map viaLocation) { try { - return new ViaLocation( + return new ViaLocationDeprecated( GenericLocationMapper.toGenericLocation(viaLocation), false, (Duration) viaLocation.get("minSlack"), diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index 0781fbe34a6..a16803f6559 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -9,7 +9,7 @@ import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.RouteViaRequest; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.request.request.JourneyRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -27,7 +27,10 @@ public static RouteViaRequest createRouteViaRequest(DataFetchingEnvironment envi RouteRequest request = serverContext.defaultRouteRequest(); List> viaInput = environment.getArgument("via"); - List vias = viaInput.stream().map(ViaLocationMapper::mapViaLocation).toList(); + List vias = viaInput + .stream() + .map(ViaLocationMapper::mapViaLocation) + .toList(); List requests; if (environment.containsArgument("segments")) { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java index 6ce02240817..a45ac58871c 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java @@ -5,7 +5,7 @@ import graphql.schema.GraphQLInputObjectType; import org.opentripplanner.apis.transmodel.model.framework.CoordinateInputType; import org.opentripplanner.apis.transmodel.support.GqlUtil; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.ViaLocationDeprecated; public class ViaLocationInputType { @@ -57,7 +57,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name("minSlack") - .defaultValue(ViaLocation.DEFAULT_MIN_SLACK) + .defaultValue(ViaLocationDeprecated.DEFAULT_MIN_SLACK) .description( "The minimum time the user wants to stay in the via location before continuing his journey" ) @@ -67,7 +67,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name("maxSlack") - .defaultValue(ViaLocation.DEFAULT_MAX_SLACK) + .defaultValue(ViaLocationDeprecated.DEFAULT_MAX_SLACK) .description( "The maximum time the user wants to stay in the via location before continuing his journey" ) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorker.java index 94dff22547b..a4ff6753d5a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorker.java @@ -12,7 +12,7 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.RouteViaRequest; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.response.InputField; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -121,7 +121,7 @@ private ViaRoutingResponse combineRoutingResponse(List routingR private List filterTransits( Itinerary i, List itineraries, - ViaLocation viaLocation + ViaLocationDeprecated viaLocation ) { return itineraries.stream().filter(withinSlackTest(i, viaLocation)).toList(); } @@ -129,7 +129,7 @@ private List filterTransits( /** * Only allow departures within min/max slack time. */ - private Predicate withinSlackTest(Itinerary i, ViaLocation v) { + private Predicate withinSlackTest(Itinerary i, ViaLocationDeprecated v) { var earliestDeparturetime = i.endTime().plus(v.minSlack()); var latestDeparturetime = i.endTime().plus(v.maxSlack()); diff --git a/src/main/java/org/opentripplanner/routing/api/RoutingService.java b/src/main/java/org/opentripplanner/routing/api/RoutingService.java index f840ac27524..fcea16e3991 100644 --- a/src/main/java/org/opentripplanner/routing/api/RoutingService.java +++ b/src/main/java/org/opentripplanner/routing/api/RoutingService.java @@ -8,5 +8,10 @@ public interface RoutingService { RoutingResponse route(RouteRequest request); + /** + * @deprecated We will replace the complex via-search with a simpler version part of the + * existing trip search. + */ + @Deprecated ViaRoutingResponse route(RouteViaRequest request); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteViaRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteViaRequest.java index 97e492ffd98..7d91558cf77 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteViaRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteViaRequest.java @@ -14,7 +14,11 @@ /** * Trip planning request with a list of via points. + * + * @deprecated We will replace the complex via-search with a simpler version part of the + * existing trip search. */ +@Deprecated public class RouteViaRequest implements Serializable { private final GenericLocation from; @@ -27,7 +31,10 @@ public class RouteViaRequest implements Serializable { private final Locale locale; private final Integer numItineraries; - private RouteViaRequest(List viaLocations, List viaJourneys) { + private RouteViaRequest( + List viaLocations, + List viaJourneys + ) { if (viaLocations == null || viaLocations.isEmpty()) { throw new IllegalArgumentException("viaLocations must not be empty"); } @@ -67,7 +74,10 @@ private RouteViaRequest(Builder builder) { this.numItineraries = builder.numItineraries; } - public static Builder of(List viaLocations, List viaJourneys) { + public static Builder of( + List viaLocations, + List viaJourneys + ) { return new Builder(new RouteViaRequest(viaLocations, viaJourneys)); } @@ -230,8 +240,8 @@ public Builder withNumItineraries(Integer numItineraries) { } /** - * ViaSegments contains the {@link JourneyRequest} to the next {@link ViaLocation}. The last + * ViaSegments contains the {@link JourneyRequest} to the next {@link ViaLocationDeprecated}. The last * segment has null viaLocation, as `to` is the destination of that segment. */ - public record ViaSegment(JourneyRequest journeyRequest, ViaLocation viaLocation) {} + public record ViaSegment(JourneyRequest journeyRequest, ViaLocationDeprecated viaLocation) {} } diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java similarity index 82% rename from src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java rename to src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java index 9701095a382..7d864dddb16 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java @@ -11,8 +11,12 @@ * @param passThroughPoint Does the via point represent a pass through * @param minSlack Minimum time that is allowed to wait for interchange. * @param maxSlack Maximum time to wait for next departure. + * + * @deprecated We will replace the complex via-search with a simpler version part of the + * existing trip search. */ -public record ViaLocation( +@Deprecated +public record ViaLocationDeprecated( GenericLocation point, boolean passThroughPoint, Duration minSlack, @@ -21,7 +25,7 @@ public record ViaLocation( public static final Duration DEFAULT_MAX_SLACK = Duration.ofHours(1); public static final Duration DEFAULT_MIN_SLACK = Duration.ofMinutes(5); - public ViaLocation { + public ViaLocationDeprecated { Objects.requireNonNull(minSlack); Objects.requireNonNull(maxSlack); Objects.requireNonNull(point); diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java similarity index 92% rename from src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java rename to src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java index 4feba14381f..8d8ad336cff 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java @@ -17,7 +17,7 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; -class ViaLocationTest { +class ViaLocationDeprecatedTest { private static final int STOP_A = 12; private static final int STOP_B = 13; @@ -37,7 +37,7 @@ void passThroughStop() { assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [C]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [14]}", @@ -61,7 +61,10 @@ void viaSingleStop() { assertEquals("Tx A", subject.label()); assertFalse(subject.allowPassThrough()); assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); - assertEquals("Via{label: Tx A, connections: [B]}", subject.toString(ViaLocationTest::stopName)); + assertEquals( + "Via{label: Tx A, connections: [B]}", + subject.toString(ViaLocationDeprecatedTest::stopName) + ); assertEquals("Via{label: Tx A, connections: [13]}", subject.toString()); assertEquals(1, subject.connections().size()); @@ -86,7 +89,7 @@ void testCombinationOfPassThroughAndTransfer() { assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [C, A~B 35s]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); assertEquals(2, subject.connections().size()); @@ -118,7 +121,7 @@ void viaStopAorCWithWaitTime() { assertEquals(WAIT_TIME_SEC, subject.minimumWaitTime()); assertEquals( "Via{label: Plaza, minWaitTime: 3m, connections: [C 3m, A~B 3m35s]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); assertEquals(2, subject.connections().size()); @@ -215,25 +218,28 @@ void testToString() { assertEquals("Via{label: A|B, connections: [12, 13]}", subject.toString()); assertEquals( "Via{label: A|B, connections: [A, B]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); subject = ViaLocation.via(null, WAIT_TIME).addViaStop(STOP_B).build(); assertEquals("Via{minWaitTime: 3m, connections: [13 3m]}", subject.toString()); assertEquals( "Via{minWaitTime: 3m, connections: [B 3m]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); subject = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); assertEquals("Via{connections: [12~13 35s]}", subject.toString()); - assertEquals("Via{connections: [A~B 35s]}", subject.toString(ViaLocationTest::stopName)); + assertEquals( + "Via{connections: [A~B 35s]}", + subject.toString(ViaLocationDeprecatedTest::stopName) + ); subject = ViaLocation.allowPassThrough(null).addViaStop(STOP_C).build(); assertEquals("Via{allowPassThrough, connections: [14]}", subject.toString()); assertEquals( "Via{allowPassThrough, connections: [C]}", - subject.toString(ViaLocationTest::stopName) + subject.toString(ViaLocationDeprecatedTest::stopName) ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java b/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java index 94c8c7b2f71..4425aa10ccd 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java @@ -17,7 +17,7 @@ import org.opentripplanner.model.plan.TripPlan; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.RouteViaRequest; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.request.request.JourneyRequest; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.routing.api.response.ViaRoutingResponseConnection; @@ -136,7 +136,7 @@ public RouteViaRequest createRouteViaRequest() { int minSlack = 10; int maxSlack = 45; var viaLocations = List.of( - new ViaLocation( + new ViaLocationDeprecated( location(viaC), false, Duration.ofMinutes(minSlack), From fe37d94b0b60485cb23195cbd3acf0d08b254dae Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 19:37:16 +0200 Subject: [PATCH 16/36] refactor: Rename field in PassThroughPoint --- .../transmodel/mapping/PassThroughLocationMapper.java | 2 +- .../transit/mappers/RaptorRequestMapper.java | 4 ++-- .../routing/api/request/PassThroughPoint.java | 10 +++++----- .../apis/transmodel/mapping/TripRequestMapperTest.java | 8 ++++---- .../transit/mappers/RaptorRequestMapperTest.java | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java index 951467e8727..6e2db781dbf 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java @@ -43,6 +43,6 @@ private static PassThroughPoint handlePoint( } return stopLocations.stream(); }) - .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(sls, name))); + .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(name, sls))); } } 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 948b132e408..669abcbc32d 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 @@ -184,8 +184,8 @@ private List mapPassThroughPoints() { .getPassThroughPoints() .stream() .map(p -> { - final int[] stops = p.stopLocations().stream().mapToInt(StopLocation::getIndex).toArray(); - return new PassThroughPoint(p.name(), stops); + final int[] stops = p.locations().stream().mapToInt(StopLocation::getIndex).toArray(); + return new PassThroughPoint(p.label(), stops); }) .toList(); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java index fa63c2e5668..36d48368915 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java +++ b/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java @@ -7,14 +7,14 @@ /** * Defines one pass-through point which the journey must pass through. */ -public record PassThroughPoint(List stopLocations, @Nullable String name) { +public record PassThroughPoint(@Nullable String label, List locations) { /** * Get the one or multiple stops of the pass-through point, of which only one is required to be * passed through. */ @Override - public List stopLocations() { - return stopLocations; + public List locations() { + return locations; } /** @@ -22,7 +22,7 @@ public List stopLocations() { */ @Override @Nullable - public String name() { - return name; + public String label() { + return label; } } diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index ab7e2b62b7c..bbeb3bb66b1 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -324,10 +324,10 @@ void testPassThroughPoints() { final List points = TripRequestMapper .createRequest(executionContext(arguments)) .getPassThroughPoints(); - assertEquals(PTP1, points.get(0).stopLocations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP1", points.get(0).name()); - assertEquals(PTP2, points.get(1).stopLocations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP2", points.get(1).name()); + assertEquals(PTP1, points.get(0).locations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP1", points.get(0).label()); + assertEquals(PTP2, points.get(1).locations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP2", points.get(1).label()); } @Test diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 47542782884..5ffff1b1cfb 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -56,7 +56,7 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { void testPassThroughPoints() { var req = new RouteRequest(); - req.setPassThroughPoints(List.of(new PassThroughPoint(List.of(STOP_A), "Via A"))); + req.setPassThroughPoints(List.of(new PassThroughPoint("Via A", List.of(STOP_A)))); var result = map(req); @@ -72,7 +72,7 @@ void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); // Set pass-through and relax transit-group-priority - req.setPassThroughPoints(List.of(new PassThroughPoint(List.of(STOP_A), "Via A"))); + req.setPassThroughPoints(List.of(new PassThroughPoint("Via A", List.of(STOP_A)))); req.withPreferences(p -> p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) ); From b672ff7f278d8cfc774a483bad090e2b2fe7c104 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 20:07:41 +0200 Subject: [PATCH 17/36] refactor: Deprecate ViaLocationMapper --- ...ViaLocationMapper.java => ViaLocationDeprecatedMapper.java} | 3 ++- .../apis/transmodel/mapping/ViaRequestMapper.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/main/java/org/opentripplanner/apis/transmodel/mapping/{ViaLocationMapper.java => ViaLocationDeprecatedMapper.java} (95%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java similarity index 95% rename from src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java index e9c7c7ff3b4..9ef77dfc847 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java @@ -10,7 +10,8 @@ import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; -class ViaLocationMapper { +@Deprecated +class ViaLocationDeprecatedMapper { static ViaLocationDeprecated mapViaLocation(Map viaLocation) { try { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index a16803f6559..082b96c1add 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -29,7 +29,7 @@ public static RouteViaRequest createRouteViaRequest(DataFetchingEnvironment envi List> viaInput = environment.getArgument("via"); List vias = viaInput .stream() - .map(ViaLocationMapper::mapViaLocation) + .map(ViaLocationDeprecatedMapper::mapViaLocation) .toList(); List requests; From 1249901dd5d8bc7e82ef7b690b4d31a2d37b3a63 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 20:15:41 +0200 Subject: [PATCH 18/36] refactor: Rename PassThroughPoint to ViaLocation --- .../transmodel/mapping/PassThroughLocationMapper.java | 8 ++++---- .../routing/api/request/RouteRequest.java | 10 +++++----- .../{PassThroughPoint.java => ViaLocation.java} | 2 +- .../apis/transmodel/mapping/TripRequestMapperTest.java | 4 ++-- ...{PassThroughPointTest.java => ViaLocationTest.java} | 2 +- .../transit/mappers/RaptorRequestMapperTest.java | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) rename src/main/java/org/opentripplanner/routing/api/request/{PassThroughPoint.java => ViaLocation.java} (87%) rename src/test/java/org/opentripplanner/raptor/api/request/{PassThroughPointTest.java => ViaLocationTest.java} (98%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java index 6e2db781dbf..cf94e50ba81 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java @@ -6,12 +6,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import org.opentripplanner.routing.api.request.PassThroughPoint; +import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.transit.service.TransitService; class PassThroughLocationMapper { - static List toLocations( + static List toLocations( final TransitService transitService, final List> passThroughPoints ) { @@ -23,7 +23,7 @@ static List toLocations( // TODO Propagate an error if a stopplace is unknown and fails lookup. } - private static PassThroughPoint handlePoint( + private static ViaLocation handlePoint( final TransitService transitService, Map map ) { @@ -43,6 +43,6 @@ private static PassThroughPoint handlePoint( } return stopLocations.stream(); }) - .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(name, sls))); + .collect(collectingAndThen(toList(), sls -> new ViaLocation(name, sls))); } } diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index c0873e407ae..b0b6f280520 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -56,7 +56,7 @@ public class RouteRequest implements Cloneable, Serializable { private GenericLocation to; - private List passThroughPoints = Collections.emptyList(); + private List via = Collections.emptyList(); private Instant dateTime = Instant.now(); @@ -278,12 +278,12 @@ public void setTo(GenericLocation to) { this.to = to; } - public List getPassThroughPoints() { - return passThroughPoints; + public List getPassThroughPoints() { + return via; } - public void setPassThroughPoints(final List passThroughPoints) { - this.passThroughPoints = passThroughPoints; + public void setPassThroughPoints(final List via) { + this.via = via; } /** diff --git a/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java similarity index 87% rename from src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java rename to src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java index 36d48368915..7b713bc70b1 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java @@ -7,7 +7,7 @@ /** * Defines one pass-through point which the journey must pass through. */ -public record PassThroughPoint(@Nullable String label, List locations) { +public record ViaLocation(@Nullable String label, List locations) { /** * Get the one or multiple stops of the pass-through point, of which only one is required to be * passed through. diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index bbeb3bb66b1..628bee0d72e 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -39,9 +39,9 @@ import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.routing.api.request.PassThroughPoint; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.preference.StreetPreferences; import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; @@ -321,7 +321,7 @@ void testPassThroughPoints() { List.of(Map.of("name", "PTP1", "placeIds", PTP1), Map.of("placeIds", PTP2, "name", "PTP2")) ); - final List points = TripRequestMapper + final List points = TripRequestMapper .createRequest(executionContext(arguments)) .getPassThroughPoints(); assertEquals(PTP1, points.get(0).locations().stream().map(STOP_TO_ID).toList()); diff --git a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java similarity index 98% rename from src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java rename to src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java index 155e122a7b6..1ae47e930af 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -class PassThroughPointTest { +class ViaLocationTest { private static final int[] STOPS = { 2, 7, 13 }; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 5ffff1b1cfb..c0a04532fe2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -14,8 +14,8 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.request.RaptorRequest; -import org.opentripplanner.routing.api.request.PassThroughPoint; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.StopLocation; @@ -56,7 +56,7 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { void testPassThroughPoints() { var req = new RouteRequest(); - req.setPassThroughPoints(List.of(new PassThroughPoint("Via A", List.of(STOP_A)))); + req.setPassThroughPoints(List.of(new ViaLocation("Via A", List.of(STOP_A)))); var result = map(req); @@ -72,7 +72,7 @@ void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); // Set pass-through and relax transit-group-priority - req.setPassThroughPoints(List.of(new PassThroughPoint("Via A", List.of(STOP_A)))); + req.setPassThroughPoints(List.of(new ViaLocation("Via A", List.of(STOP_A)))); req.withPreferences(p -> p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) ); From bab588e3fa1c61563d27c6a37b4eef2740de0cae Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 20:17:46 +0200 Subject: [PATCH 19/36] refactor: Rename PassThroughLocationMapper to ViaLocationMapper --- .../apis/transmodel/mapping/TripRequestMapper.java | 2 +- .../{PassThroughLocationMapper.java => ViaLocationMapper.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/org/opentripplanner/apis/transmodel/mapping/{PassThroughLocationMapper.java => ViaLocationMapper.java} (97%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index 72cd5fc6260..bd0500ac556 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -44,7 +44,7 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) { callWith.argument( "passThroughPoints", (List> v) -> { - request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)); + request.setPassThroughPoints(ViaLocationMapper.toLocations(transitService, v)); } ); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java similarity index 97% rename from src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index cf94e50ba81..7a0977ffaa9 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -9,7 +9,7 @@ import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.transit.service.TransitService; -class PassThroughLocationMapper { +class ViaLocationMapper { static List toLocations( final TransitService transitService, From add5edce79f2e58594f0b992adcbb91863b230d7 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 9 Sep 2024 21:08:13 +0200 Subject: [PATCH 20/36] refactor: Add parameters to the ViaLocation Parameters for via locations with wait-time and allowAsPassThroughPoint flag are added. --- .../framework/time/DurationUtils.java | 33 ++++++ .../routing/api/request/ViaLocation.java | 107 ++++++++++++++++-- .../framework/time/DurationUtilsTest.java | 24 ++++ 3 files changed, 155 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java index 9d16ed7d269..db7facb9e21 100644 --- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java @@ -186,11 +186,38 @@ public static Duration requireNonNegative(Duration value) { return value; } + /** + * Checks that duration is in positive and less than the given {@code maxLimit}(exclusive). + * + * @param subject used to identify name of the problematic value when throwing an exception. + */ + public static Duration requireNonNegative(Duration value, Duration maxLimit, String subject) { + Objects.requireNonNull(value); + if (value.isNegative()) { + throw new IllegalArgumentException( + "Duration %s can't be negative: %s".formatted(subject, value) + ); + } + if (value.compareTo(maxLimit) >= 0) { + throw new IllegalArgumentException( + "Duration %s can't be longer or equals too %s: %s".formatted( + subject, + durationToStr(maxLimit), + value + ) + ); + } + return value; + } + /** * Checks that duration is not negative and not over 2 days. * * @param subject used to identify name of the problematic value when throwing an exception. + * @deprecated Use {@link #requireNonNegative(Duration, Duration, String)} - This method is + * not generic, it has a hardcoded limit. */ + @Deprecated public static Duration requireNonNegativeLong(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { @@ -210,7 +237,10 @@ public static Duration requireNonNegativeLong(Duration value, String subject) { * Checks that duration is not negative and not over 2 hours. * * @param subject used to identify name of the problematic value when throwing an exception. + * @deprecated Use {@link #requireNonNegative(Duration, Duration, String)} - This method is + * not generic, it has a hardcoded limit. */ + @Deprecated public static Duration requireNonNegativeMedium(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { @@ -230,7 +260,10 @@ public static Duration requireNonNegativeMedium(Duration value, String subject) * Checks that duration is not negative and not over 30 minutes. * * @param subject used to identify name of the problematic value when throwing an exception. + * @deprecated Use {@link #requireNonNegative(Duration, Duration, String)} - This method is + * not generic, it has a hardcoded limit. */ + @Deprecated public static Duration requireNonNegativeShort(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java index 7b713bc70b1..eebd199160e 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java @@ -1,28 +1,117 @@ package org.opentripplanner.routing.api.request; +import java.time.Duration; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.transit.model.site.StopLocation; /** - * Defines one pass-through point which the journey must pass through. + * Defines a via location which the journey must route through. + *

+ * TODO: The list of stop location forces the client to look up the stops before creating + * the request. This duplicates logic in all places using this. Instead this should + * be replaced by passing in ids and the look up the location inside the router. + * To do this properly the routing service must handle lookup errors properly and + * pass the error information properly back to the caller/client. */ -public record ViaLocation(@Nullable String label, List locations) { +public class ViaLocation { + + private static final Duration MINIMUM_WAIT_TIME_MAX_LIMIT = Duration.ofHours(24); + + private final boolean allowAsPassThroughPoint; + private final Duration minimumWaitTime; + private final String label; + private final List locations; + + public ViaLocation( + @Nullable String label, + boolean allowAsPassThroughPoint, + Duration minimumWaitTime, + List locations + ) { + this.label = label; + this.allowAsPassThroughPoint = allowAsPassThroughPoint; + this.minimumWaitTime = + DurationUtils.requireNonNegative( + minimumWaitTime, + MINIMUM_WAIT_TIME_MAX_LIMIT, + "minimumWaitTime" + ); + this.locations = List.copyOf(locations); + + if (allowAsPassThroughPoint && !minimumWaitTime.isZero()) { + throw new IllegalArgumentException( + "AllowAsPassThroughPoint can not be used with minimumWaitTime for " + label + "." + ); + } + } + + public ViaLocation(@Nullable String label, List locations) { + this(label, true, Duration.ZERO, locations); + } + /** - * Get the one or multiple stops of the pass-through point, of which only one is required to be - * passed through. + * If set to {@code true} this location can be visited as a pass-through-point. Only + * collections of stops are supported, not coordinates. Also, the minWaitTime must be + * zero(0). */ - @Override - public List locations() { - return locations; + public boolean allowAsPassThroughPoint() { + return allowAsPassThroughPoint; } /** - * Get an optional name of the pass-through point for debugging and logging. + * The minimum wait time is used to force the trip to stay the given duration at the via + * location before the trip is continued. This cannot be used together with allow-pass-through, + * since a pass-through stop is visited on-board. + */ + public Duration minimumWaitTime() { + return minimumWaitTime; + } + + /** + * Get an optional name/label of for debugging and logging. */ - @Override @Nullable public String label() { return label; } + + /** + * Get the one or multiple stops of which only one is required to route through. + */ + public List locations() { + return locations; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViaLocation that = (ViaLocation) o; + return ( + allowAsPassThroughPoint == that.allowAsPassThroughPoint && + Objects.equals(minimumWaitTime, that.minimumWaitTime) && + Objects.equals(label, that.label) && + Objects.equals(locations, that.locations) + ); + } + + @Override + public int hashCode() { + return Objects.hash(allowAsPassThroughPoint, minimumWaitTime, label, locations); + } + + @Override + public String toString() { + return ToStringBuilder + .of(ViaLocation.class) + .addBoolIfTrue(label, label != null) + .addBoolIfTrue("allowAsPassThroughPoint", allowAsPassThroughPoint) + .addDuration("minimumWaitTime", minimumWaitTime, Duration.ZERO) + .addCol("locations", locations) + .toString(); + } } diff --git a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java index 97b8afc52c0..a045e693b29 100644 --- a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java @@ -23,6 +23,8 @@ public class DurationUtilsTest { + private final Duration NEG_1s = Duration.ofSeconds(-1); + private final Duration D1s = Duration.ofSeconds(1); private final Duration D3d = Duration.ofDays(3); private final Duration D2h = Duration.ofHours(2); private final Duration D5m = Duration.ofMinutes(5); @@ -127,6 +129,28 @@ public void testRequireNonNegative() { assertThrows(IllegalArgumentException.class, () -> requireNonNegative(Duration.ofSeconds(-1))); } + @Test + public void testRequireNonNegativeAndMaxLimit() { + // Firs make sure legal values are accepted + requireNonNegative(Duration.ZERO, D2h, "test"); + requireNonNegative(D2h.minus(D1s), D2h, "test"); + + // null is not supported + assertThrows(NullPointerException.class, () -> requireNonNegative(null, D2h, "test")); + + // Test max limit + var ex = assertThrows( + IllegalArgumentException.class, + () -> requireNonNegative(D2h, D2h, "test") + ); + assertEquals("Duration test can't be longer or equals too 2h: PT2H", ex.getMessage()); + + // Test non-negative + ex = + assertThrows(IllegalArgumentException.class, () -> requireNonNegative(NEG_1s, D2h, "test")); + assertEquals("Duration test can't be negative: PT-1S", ex.getMessage()); + } + @Test public void testRequireNonNegativeLong() { assertThrows(NullPointerException.class, () -> requireNonNegativeLong(null, "test")); From 5e00c29a8d9b70d42038182d27761073310b0056 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 11 Sep 2024 13:35:17 +0200 Subject: [PATCH 21/36] refactor: Rename request getPassThroughPoints() to getViaLocations() --- .../transit/mappers/RaptorRequestMapper.java | 4 ++-- .../routing/api/request/RouteRequest.java | 2 +- .../transmodel/mapping/TripRequestMapperTest.java | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) 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 669abcbc32d..71407f7637b 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 @@ -115,7 +115,7 @@ private RaptorRequest doMap() { var r = pt.raptor(); // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled - if (!request.getPassThroughPoints().isEmpty()) { + if (!request.getViaLocations().isEmpty()) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); } else if (!pt.relaxTransitGroupPriority().isNormal()) { @@ -181,7 +181,7 @@ private RaptorRequest doMap() { private List mapPassThroughPoints() { return request - .getPassThroughPoints() + .getViaLocations() .stream() .map(p -> { final int[] stops = p.locations().stream().mapToInt(StopLocation::getIndex).toArray(); diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index b0b6f280520..26d17fccec3 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -278,7 +278,7 @@ public void setTo(GenericLocation to) { this.to = to; } - public List getPassThroughPoints() { + public List getViaLocations() { return via; } diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index 628bee0d72e..c926a429a22 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -311,7 +311,7 @@ public void testBikeTriangleFactorsHasNoEffect(VehicleRoutingOptimizeType bot) { } @Test - void testPassThroughPoints() { + void testViaLocations() { TransitIdMapper.clearFixedFeedId(); final List PTP1 = List.of(stop1, stop2, stop3).stream().map(STOP_TO_ID).toList(); @@ -321,13 +321,13 @@ void testPassThroughPoints() { List.of(Map.of("name", "PTP1", "placeIds", PTP1), Map.of("placeIds", PTP2, "name", "PTP2")) ); - final List points = TripRequestMapper + final List viaLocations = TripRequestMapper .createRequest(executionContext(arguments)) - .getPassThroughPoints(); - assertEquals(PTP1, points.get(0).locations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP1", points.get(0).label()); - assertEquals(PTP2, points.get(1).locations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP2", points.get(1).label()); + .getViaLocations(); + assertEquals(PTP1, viaLocations.get(0).locations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP1", viaLocations.get(0).label()); + assertEquals(PTP2, viaLocations.get(1).locations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP2", viaLocations.get(1).label()); } @Test From a534f33b45c4d6c0ef17afcd25d8a5574437f375 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 11 Sep 2024 18:16:04 +0200 Subject: [PATCH 22/36] refactor: Refactor PassThroughPoint request arguments and replace it with ViaLocation --- .../apis/transmodel/TransmodelGraph.java | 3 +- .../transmodel/mapping/TripRequestMapper.java | 4 +- .../transmodel/mapping/ViaLocationMapper.java | 42 +++------- .../raptoradapter/router/TransitRouter.java | 21 ++++- .../mappers/LookupStopIndexCallback.java | 35 +++++++++ .../transit/mappers/RaptorRequestMapper.java | 35 +++++---- .../routing/api/request/ViaConnection.java | 78 +++++++++++++++++++ .../routing/api/request/ViaLocation.java | 58 ++++++++------ .../framework/EntityNotFoundException.java | 34 ++++++++ .../transit/service/TransitService.java | 6 ++ .../mapping/TripRequestMapperTest.java | 30 +++---- .../mappers/LookupStopIndexCallbackTest.java | 53 +++++++++++++ .../mappers/RaptorRequestMapperTest.java | 18 +++-- .../mappers/TestLookupStopIndexCallback.java | 25 ++++++ .../api/request/ViaConnectionTest.java | 42 ++++++++++ .../routing/api/request/ViaLocationTest.java | 50 ++++++++++++ .../EntityNotFoundExceptionTest.java | 22 ++++++ 17 files changed, 457 insertions(+), 99 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallback.java create mode 100644 src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java create mode 100644 src/main/java/org/opentripplanner/transit/model/framework/EntityNotFoundException.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallbackTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TestLookupStopIndexCallback.java create mode 100644 src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java create mode 100644 src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java create mode 100644 src/test/java/org/opentripplanner/transit/model/framework/EntityNotFoundExceptionTest.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java index 79e767b1fea..f4241fcbc61 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraph.java @@ -25,6 +25,7 @@ import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.framework.lang.ObjectUtils; import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.transit.model.framework.EntityNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,7 +73,7 @@ Response executeGraphQL( return ExecutionResultMapper.timeoutResponse(); } catch (ResponseTooLargeException rtle) { return ExecutionResultMapper.tooLargeResponse(rtle.getMessage()); - } catch (CoercingParseValueException | UnknownOperationException e) { + } catch (EntityNotFoundException | CoercingParseValueException | UnknownOperationException e) { return ExecutionResultMapper.badRequestResponse(e.getMessage()); } catch (Exception systemError) { LOG.error(systemError.getMessage(), systemError); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index bd0500ac556..0c62b03c6a7 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -16,7 +16,6 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.service.TransitService; public class TripRequestMapper { @@ -40,11 +39,10 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) { "to", (Map v) -> request.setTo(GenericLocationMapper.toGenericLocation(v)) ); - final TransitService transitService = context.getTransitService(); callWith.argument( "passThroughPoints", (List> v) -> { - request.setPassThroughPoints(ViaLocationMapper.toLocations(transitService, v)); + request.setPassThroughPoints(ViaLocationMapper.toPassThroughLocations(v)); } ); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index 7a0977ffaa9..521be3f1052 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -1,48 +1,26 @@ package org.opentripplanner.apis.transmodel.mapping; -import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Map; -import java.util.Objects; import org.opentripplanner.routing.api.request.ViaLocation; -import org.opentripplanner.transit.service.TransitService; +import org.opentripplanner.transit.model.framework.FeedScopedId; class ViaLocationMapper { - static List toLocations( - final TransitService transitService, + static List toPassThroughLocations( final List> passThroughPoints ) { - return passThroughPoints - .stream() - .map(p -> handlePoint(transitService, p)) - .filter(Objects::nonNull) - .collect(toList()); - // TODO Propagate an error if a stopplace is unknown and fails lookup. + return passThroughPoints.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); } - private static ViaLocation handlePoint( - final TransitService transitService, - Map map - ) { - final List stops = (List) map.get("placeIds"); - final String name = (String) map.get("name"); - if (stops == null) { - return null; - } - - return stops - .stream() - .map(TransitIdMapper::mapIDToDomain) - .flatMap(id -> { - var stopLocations = transitService.getStopOrChildStops(id); - if (stopLocations.isEmpty()) { - throw new RuntimeException("No match for %s.".formatted(id)); - } - return stopLocations.stream(); - }) - .collect(collectingAndThen(toList(), sls -> new ViaLocation(name, sls))); + private static ViaLocation mapViaLocation(Map inputMap) { + final String name = (String) inputMap.get("name"); + final List stopLocationIds = + ((List) inputMap.get("placeIds")).stream() + .map(TransitIdMapper::mapIDToDomain) + .toList(); + return ViaLocation.passThroughLocation(name, stopLocationIds); } } 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 37bb7270902..0654a73700d 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 @@ -11,6 +11,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.stream.IntStream; import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; @@ -41,7 +42,10 @@ import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.street.search.TemporaryVerticesContainer; +import org.opentripplanner.transit.model.framework.EntityNotFoundException; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; +import org.opentripplanner.transit.model.site.StopLocation; public class TransitRouter { @@ -133,7 +137,8 @@ private TransitRouterResult route() { serverContext.raptorConfig().isMultiThreaded(), accessEgresses.getAccesses(), accessEgresses.getEgresses(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + this::listStopIndexes ); // Route transit @@ -368,4 +373,18 @@ private TemporaryVerticesContainer createTemporaryVerticesContainer( request.journey().egress().mode() ); } + + private IntStream listStopIndexes(FeedScopedId stopLocationId) { + Collection stops = serverContext + .transitService() + .getStopOrChildStops(stopLocationId); + + if (stops.isEmpty()) { + throw new EntityNotFoundException( + "Stop, station, multimodal station or group of stations", + stopLocationId + ); + } + return stops.stream().mapToInt(StopLocation::getIndex); + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallback.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallback.java new file mode 100644 index 00000000000..1ffd3584777 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallback.java @@ -0,0 +1,35 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; + +import java.util.Collection; +import java.util.stream.IntStream; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * The raptor mapper does not have access to the transit layer, so it need help to + * lookup stop-location indexes (Stop index used by Raptor). There is a one-to-one + * mapping between stops and stop-index, but a station, multimodal-station or group-of-stations + * will most likely contain more than one stop. + */ +@FunctionalInterface +public interface LookupStopIndexCallback { + /** + * The implementation of this method should list all stop indexes part of the entity referenced + * by the given id. + * @return a stream of stop indexes. We return a stream here because we need to merge this with + * the indexes of other stops. + */ + IntStream lookupStopLocationIndexes(FeedScopedId stopLocationId); + + /** + * Take a set of stop location ids and convert them into a sorted distinct list of + * stop indexes. + */ + default int[] lookupStopLocationIndexes(Collection stopLocationIds) { + return stopLocationIds + .stream() + .flatMapToInt(this::lookupStopLocationIndexes) + .sorted() + .distinct() + .toArray(); + } +} 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 71407f7637b..74b2a235d9d 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 @@ -23,9 +23,10 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.ViaConnection; +import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; -import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -35,6 +36,7 @@ public class RaptorRequestMapper { private final long transitSearchTimeZeroEpocSecond; private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final LookupStopIndexCallback lookUpStopIndex; private RaptorRequestMapper( RouteRequest request, @@ -42,7 +44,8 @@ private RaptorRequestMapper( Collection accessPaths, Collection egressPaths, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + LookupStopIndexCallback lookUpStopIndex ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -50,6 +53,7 @@ private RaptorRequestMapper( this.egressPaths = egressPaths; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; + this.lookUpStopIndex = lookUpStopIndex; } public static RaptorRequest mapRequest( @@ -58,7 +62,8 @@ public static RaptorRequest mapRequest( boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + LookupStopIndexCallback lookUpStopIndex ) { return new RaptorRequestMapper( request, @@ -66,7 +71,8 @@ public static RaptorRequest mapRequest( accessPaths, egressPaths, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + lookUpStopIndex ) .doMap(); } @@ -115,7 +121,9 @@ private RaptorRequest doMap() { var r = pt.raptor(); // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled - if (!request.getViaLocations().isEmpty()) { + + // TODO - We need handle via locations that are not pass-through-points here + if (request.getViaLocations().stream().allMatch(ViaLocation::allowAsPassThroughPoint)) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); } else if (!pt.relaxTransitGroupPriority().isNormal()) { @@ -180,14 +188,15 @@ private RaptorRequest doMap() { } private List mapPassThroughPoints() { - return request - .getViaLocations() - .stream() - .map(p -> { - final int[] stops = p.locations().stream().mapToInt(StopLocation::getIndex).toArray(); - return new PassThroughPoint(p.label(), stops); - }) - .toList(); + return request.getViaLocations().stream().map(this::mapPassThroughPoints).toList(); + } + + private PassThroughPoint mapPassThroughPoints(ViaLocation location) { + var feedIds = location.connections().stream().map(ViaConnection::locationId).toList(); + return new PassThroughPoint( + location.label(), + lookUpStopIndex.lookupStopLocationIndexes(feedIds) + ); } static RelaxFunction mapRelaxCost(CostLinearFunction relax) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java b/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java new file mode 100644 index 00000000000..d44d2d90d79 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java @@ -0,0 +1,78 @@ +package org.opentripplanner.routing.api.request; + +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * A ViaConnection is a reference to a location or a coordinate. Supported locations are stop, + * station, multimodal-station and group-of-stations. Either the {@code locationId} or the + * {@code coordinate} must be set. + *

+ * Earlier the coordinate was used as a fallback for the location-id, this is not the case anymore. + * Any inconsistencies in location ids between the client and the server should be detected + * and fixed - not automatically patched. If the client wants to provide a fallback, it could + * fire a new request with the coordinate set instead. + * + */ +public class ViaConnection { + + private final FeedScopedId locationId; + private final WgsCoordinate coordinate; + + private ViaConnection(@Nullable FeedScopedId locationId, @Nullable WgsCoordinate coordinate) { + this.locationId = locationId; + this.coordinate = coordinate; + } + + public ViaConnection(FeedScopedId locationId) { + this(Objects.requireNonNull(locationId), null); + } + + public ViaConnection(WgsCoordinate coordinate) { + this(null, Objects.requireNonNull(coordinate)); + } + + public static List connections(List ids) { + return ids.stream().map(ViaConnection::new).toList(); + } + + public boolean hasLocationId() { + return locationId != null; + } + + public boolean hasCoordinate() { + return coordinate != null; + } + + public FeedScopedId locationId() { + return locationId; + } + + public WgsCoordinate coordinate() { + return coordinate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViaConnection that = (ViaConnection) o; + return ( + Objects.equals(locationId, that.locationId) && Objects.equals(coordinate, that.coordinate) + ); + } + + @Override + public int hashCode() { + return Objects.hash(locationId, coordinate); + } + + @SuppressWarnings("DataFlowIssue") + @Override + public String toString() { + return locationId != null ? locationId.toString() : coordinate.toString(); + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java index eebd199160e..c6c6db73287 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java @@ -1,56 +1,66 @@ package org.opentripplanner.routing.api.request; import java.time.Duration; +import java.util.Collection; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.framework.FeedScopedId; /** * Defines a via location which the journey must route through. - *

- * TODO: The list of stop location forces the client to look up the stops before creating - * the request. This duplicates logic in all places using this. Instead this should - * be replaced by passing in ids and the look up the location inside the router. - * To do this properly the routing service must handle lookup errors properly and - * pass the error information properly back to the caller/client. */ -public class ViaLocation { +public final class ViaLocation { private static final Duration MINIMUM_WAIT_TIME_MAX_LIMIT = Duration.ofHours(24); + private final String label; private final boolean allowAsPassThroughPoint; private final Duration minimumWaitTime; - private final String label; - private final List locations; + private final List connections; - public ViaLocation( + @SuppressWarnings("DataFlowIssue") + private ViaLocation( @Nullable String label, boolean allowAsPassThroughPoint, - Duration minimumWaitTime, - List locations + @Nullable Duration minimumWaitTime, + Collection connections ) { this.label = label; this.allowAsPassThroughPoint = allowAsPassThroughPoint; this.minimumWaitTime = DurationUtils.requireNonNegative( - minimumWaitTime, + minimumWaitTime == null ? Duration.ZERO : minimumWaitTime, MINIMUM_WAIT_TIME_MAX_LIMIT, "minimumWaitTime" ); - this.locations = List.copyOf(locations); + this.connections = List.copyOf(connections); if (allowAsPassThroughPoint && !minimumWaitTime.isZero()) { throw new IllegalArgumentException( - "AllowAsPassThroughPoint can not be used with minimumWaitTime for " + label + "." + "'allowAsPassThroughPoint' can not be used with minimumWaitTime for " + label + "." + ); + } + if (allowAsPassThroughPoint && connections.stream().anyMatch(ViaConnection::hasCoordinate)) { + throw new IllegalArgumentException( + "'allowAsPassThroughPoint' can not be used with coordinates for " + label + "." ); } } - public ViaLocation(@Nullable String label, List locations) { - this(label, true, Duration.ZERO, locations); + /** + * A pass-through-location instructs the router to visit the location either by boarding, + * alighting or on-board a transit. + * @param label The name/label for this location. This is used for debugging and logging and is pass-through information. + * @param locationIds The ID for the stop, station or multimodal station or groupOfStopPlace. + */ + public static ViaLocation passThroughLocation( + @Nullable String label, + List locationIds + ) { + return new ViaLocation(label, true, Duration.ZERO, ViaConnection.connections(locationIds)); } /** @@ -80,10 +90,10 @@ public String label() { } /** - * Get the one or multiple stops of which only one is required to route through. + * Get the one or multiple locations of which only one is required to route through. */ - public List locations() { - return locations; + public List connections() { + return connections; } @Override @@ -95,13 +105,13 @@ public boolean equals(Object o) { allowAsPassThroughPoint == that.allowAsPassThroughPoint && Objects.equals(minimumWaitTime, that.minimumWaitTime) && Objects.equals(label, that.label) && - Objects.equals(locations, that.locations) + Objects.equals(connections, that.connections) ); } @Override public int hashCode() { - return Objects.hash(allowAsPassThroughPoint, minimumWaitTime, label, locations); + return Objects.hash(allowAsPassThroughPoint, minimumWaitTime, label, connections); } @Override @@ -111,7 +121,7 @@ public String toString() { .addBoolIfTrue(label, label != null) .addBoolIfTrue("allowAsPassThroughPoint", allowAsPassThroughPoint) .addDuration("minimumWaitTime", minimumWaitTime, Duration.ZERO) - .addCol("locations", locations) + .addObj("connections", connections) .toString(); } } diff --git a/src/main/java/org/opentripplanner/transit/model/framework/EntityNotFoundException.java b/src/main/java/org/opentripplanner/transit/model/framework/EntityNotFoundException.java new file mode 100644 index 00000000000..362dad1b80b --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/EntityNotFoundException.java @@ -0,0 +1,34 @@ +package org.opentripplanner.transit.model.framework; + +/** + * This exception is used by the main OTP business logic to signal that one of the + * ids passed in is not found. This exception should be handled in a generic way in each + * API. + *

+ * When an entity is not found, it indicates that there is a system integration error. This + * should not be used if the user type in the id, then the client should validate the id + * before it is passed into OTP. + */ +public class EntityNotFoundException extends RuntimeException { + + private final String entityName; + private final FeedScopedId id; + + /** + * Use this if the id can be of more than one type, or you would like to provide an + * alternative name. + */ + public EntityNotFoundException(String entityName, FeedScopedId id) { + this.entityName = entityName; + this.id = id; + } + + public EntityNotFoundException(Class entityType, FeedScopedId id) { + this(entityType.getSimpleName(), id); + } + + @Override + public String getMessage() { + return entityName + " entity not found: " + id; + } +} diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java index 1836b5612d2..ce661f6af10 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -139,6 +139,12 @@ public interface TransitService { StopLocation getStopLocation(FeedScopedId parseId); + /** + * Return all stops associated with the given id. If a Station, a MultiModalStation, or a + * GroupOfStations matches the id, then all child stops are returned. If the id matches a regular + * stops, area stop or stop group, then a list with one item is returned. + * An empty list is if nothing is found. + */ Collection getStopOrChildStops(FeedScopedId id); Collection listStopLocationGroups(); diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index c926a429a22..ead64a1bb99 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -314,8 +314,8 @@ public void testBikeTriangleFactorsHasNoEffect(VehicleRoutingOptimizeType bot) { void testViaLocations() { TransitIdMapper.clearFixedFeedId(); - final List PTP1 = List.of(stop1, stop2, stop3).stream().map(STOP_TO_ID).toList(); - final List PTP2 = List.of(stop2, stop3, stop1).stream().map(STOP_TO_ID).toList(); + final List PTP1 = Stream.of(stop1, stop2, stop3).map(STOP_TO_ID).toList(); + final List PTP2 = Stream.of(stop3, stop2).map(STOP_TO_ID).toList(); final Map arguments = Map.of( "passThroughPoints", List.of(Map.of("name", "PTP1", "placeIds", PTP1), Map.of("placeIds", PTP2, "name", "PTP2")) @@ -324,26 +324,16 @@ void testViaLocations() { final List viaLocations = TripRequestMapper .createRequest(executionContext(arguments)) .getViaLocations(); - assertEquals(PTP1, viaLocations.get(0).locations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP1", viaLocations.get(0).label()); - assertEquals(PTP2, viaLocations.get(1).locations().stream().map(STOP_TO_ID).toList()); - assertEquals("PTP2", viaLocations.get(1).label()); - } - - @Test - void testPassThroughPointsNoMatch() { - TransitIdMapper.clearFixedFeedId(); - - final Map arguments = Map.of( - "passThroughPoints", - List.of(Map.of("placeIds", List.of("F:XX:NonExisting"))) + assertEquals( + "ViaLocation{PTP1, allowAsPassThroughPoint, connections: [F:ST:stop1, F:ST:stop2, F:ST:stop3]}", + viaLocations.get(0).toString() ); - - final RuntimeException ex = assertThrows( - RuntimeException.class, - () -> TripRequestMapper.createRequest(executionContext(arguments)) + assertEquals("PTP1", viaLocations.get(0).label()); + assertEquals( + "ViaLocation{PTP2, allowAsPassThroughPoint, connections: [F:ST:stop3, F:ST:stop2]}", + viaLocations.get(1).toString() ); - assertEquals("No match for F:XX:NonExisting.", ex.getMessage()); + assertEquals("PTP2", viaLocations.get(1).label()); } @Test diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallbackTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallbackTest.java new file mode 100644 index 00000000000..fb39db51cd5 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/LookupStopIndexCallbackTest.java @@ -0,0 +1,53 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.framework.EntityNotFoundException; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class LookupStopIndexCallbackTest { + + private static final FeedScopedId ID_1 = FeedScopedId.ofNullable("F", "1"); + private static final FeedScopedId ID_2 = FeedScopedId.ofNullable("F", "2"); + private static final FeedScopedId ID_3 = FeedScopedId.ofNullable("F", "3"); + + private final LookupStopIndexCallback subject = new TestLookupStopIndexCallback( + Map.of(ID_1, new int[] { 1, 7, 13 }, ID_2, new int[] { 2, 7, 15 }) + ); + + /** + * This mostly verifies that the test is set up correctly, the code tested is the dummy inside + * the test. + */ + void lookupStopLocationIndexesSingleIdInput() { + assertArrayEquals(new int[] { 1, 7, 13 }, subject.lookupStopLocationIndexes(ID_1).toArray()); + var ex = Assertions.assertThrows( + EntityNotFoundException.class, + () -> subject.lookupStopLocationIndexes(ID_3).toArray() + ); + assertEquals("StopLocation does not exist for id F:3", ex.getMessage()); + } + + @Test + void lookupStopLocationIndexesCollectionInput() { + assertArrayEquals(new int[] {}, subject.lookupStopLocationIndexes(List.of())); + assertArrayEquals(new int[] { 1, 7, 13 }, subject.lookupStopLocationIndexes(List.of(ID_1))); + assertArrayEquals( + new int[] { 1, 2, 7, 13, 15 }, + subject.lookupStopLocationIndexes(List.of(ID_1, ID_2)) + ); + + // Should throw exception? + var ex = assertThrows( + EntityNotFoundException.class, + () -> subject.lookupStopLocationIndexes(List.of(ID_1, ID_3)) + ); + assertEquals("StopLocation entity not found: F:3", ex.getMessage()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index c0a04532fe2..30b91a38fbf 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -3,9 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.time.Duration; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -18,6 +19,7 @@ import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; class RaptorRequestMapperTest { @@ -26,12 +28,13 @@ class RaptorRequestMapperTest { private static final StopLocation STOP_A = TEST_MODEL.stop("Stop:A").build(); private static final List ACCESS = List.of(TestAccessEgress.walk(12, 45)); private static final List EGRESS = List.of(TestAccessEgress.walk(144, 54)); - private static final Duration D0s = Duration.ofSeconds(0); private static final CostLinearFunction R1 = CostLinearFunction.of("50 + 1.0x"); private static final CostLinearFunction R2 = CostLinearFunction.of("0 + 1.5x"); private static final CostLinearFunction R3 = CostLinearFunction.of("30 + 2.0x"); + private static final Map STOPS_MAP = Map.of(STOP_A.getId(), STOP_A); + static List testCasesRelaxedCost() { return List.of( Arguments.of(CostLinearFunction.NORMAL, 0, 0), @@ -56,7 +59,9 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { void testPassThroughPoints() { var req = new RouteRequest(); - req.setPassThroughPoints(List.of(new ViaLocation("Via A", List.of(STOP_A)))); + req.setPassThroughPoints( + List.of(ViaLocation.passThroughLocation("Via A", List.of(STOP_A.getId()))) + ); var result = map(req); @@ -72,7 +77,9 @@ void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); // Set pass-through and relax transit-group-priority - req.setPassThroughPoints(List.of(new ViaLocation("Via A", List.of(STOP_A)))); + req.setPassThroughPoints( + List.of(ViaLocation.passThroughLocation("Via A", List.of(STOP_A.getId()))) + ); req.withPreferences(p -> p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) ); @@ -90,7 +97,8 @@ private static RaptorRequest map(RouteRequest request) { false, ACCESS, EGRESS, - null + null, + id -> IntStream.of(STOPS_MAP.get(id).getIndex()) ); } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TestLookupStopIndexCallback.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TestLookupStopIndexCallback.java new file mode 100644 index 00000000000..f1898d69f52 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TestLookupStopIndexCallback.java @@ -0,0 +1,25 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; + +import java.util.Map; +import java.util.stream.IntStream; +import org.opentripplanner.transit.model.framework.EntityNotFoundException; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class TestLookupStopIndexCallback implements LookupStopIndexCallback { + + private final Map index; + + public TestLookupStopIndexCallback(Map index) { + this.index = index; + } + + @Override + public IntStream lookupStopLocationIndexes(FeedScopedId stopLocationId) { + int[] values = index.get(stopLocationId); + if (values == null) { + throw new EntityNotFoundException(StopLocation.class, stopLocationId); + } + return IntStream.of(values); + } +} diff --git a/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java b/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java new file mode 100644 index 00000000000..d500483f84c --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java @@ -0,0 +1,42 @@ +package org.opentripplanner.routing.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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ViaConnectionTest { + + private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); + + private final ViaConnection connectionWithLocationId = new ViaConnection(ID); + private final ViaConnection connectionWithCoordinate = new ViaConnection(WgsCoordinate.GREENWICH); + + @Test + void hasLocationId() { + assertTrue(connectionWithLocationId.hasLocationId()); + assertFalse(connectionWithCoordinate.hasLocationId()); + } + + @Test + void hasCoordinate() { + assertFalse(connectionWithLocationId.hasCoordinate()); + assertTrue(connectionWithCoordinate.hasCoordinate()); + } + + @Test + void locationId() { + assertEquals(ID, connectionWithLocationId.locationId()); + assertNull(connectionWithCoordinate.locationId()); + } + + @Test + void coordinate() { + assertNull(connectionWithLocationId.coordinate()); + assertEquals(WgsCoordinate.GREENWICH, connectionWithCoordinate.coordinate()); + } +} diff --git a/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java new file mode 100644 index 00000000000..12b1e560033 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java @@ -0,0 +1,50 @@ +package org.opentripplanner.routing.api.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ViaLocationTest { + + private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); + + public static final String LABEL = "AName"; + private static final ViaLocation passThroughLocation = ViaLocation.passThroughLocation( + LABEL, + List.of(ID) + ); + + // TODO add cases for none passThroughLocation here when added... + + @Test + void allowAsPassThroughPoint() { + assertTrue(passThroughLocation.allowAsPassThroughPoint()); + } + + @Test + void minimumWaitTime() { + assertEquals(Duration.ZERO, passThroughLocation.minimumWaitTime()); + } + + @Test + void label() { + assertEquals(LABEL, passThroughLocation.label()); + } + + @Test + void connections() { + assertEquals("[F:1]", passThroughLocation.connections().toString()); + } + + @Test + void testToString() { + assertEquals( + "ViaLocation{AName, allowAsPassThroughPoint, connections: [F:1]}", + passThroughLocation.toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/transit/model/framework/EntityNotFoundExceptionTest.java b/src/test/java/org/opentripplanner/transit/model/framework/EntityNotFoundExceptionTest.java new file mode 100644 index 00000000000..f33eb389a37 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/model/framework/EntityNotFoundExceptionTest.java @@ -0,0 +1,22 @@ +package org.opentripplanner.transit.model.framework; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class EntityNotFoundExceptionTest { + + private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); + + @Test + void getMessage() { + assertEquals( + "Integer entity not found: F:1", + new EntityNotFoundException(Integer.class, ID).getMessage() + ); + assertEquals( + "Stop or Station entity not found: F:1", + new EntityNotFoundException("Stop or Station", ID).getMessage() + ); + } +} From 756f7a46dce258b883493bade8a36db53731c3b6 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 13 Sep 2024 17:48:25 +0200 Subject: [PATCH 23/36] refactor: Make to kinds of ViaLocation in request: PassThrough and Visit --- .../transmodel/mapping/ViaLocationMapper.java | 10 +- .../transit/mappers/RaptorRequestMapper.java | 6 +- .../api/request/AbstractViaLocation.java | 44 ++++++ .../api/request/PassThroughViaLocation.java | 37 +++++ .../routing/api/request/ViaLocation.java | 131 ++++-------------- .../routing/api/request/VisitViaLocation.java | 103 ++++++++++++++ .../asserts/AssertEqualsAndHashCode.java | 33 +++++ .../mapping/TripRequestMapperTest.java | 4 +- .../mappers/RaptorRequestMapperTest.java | 10 +- .../request/PassThroughViaLocationTest.java | 64 +++++++++ .../routing/api/request/ViaLocationTest.java | 50 ------- .../api/request/VisitViaLocationTest.java | 77 ++++++++++ 12 files changed, 402 insertions(+), 167 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java create mode 100644 src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java create mode 100644 src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java create mode 100644 src/test/java/org/opentripplanner/_support/asserts/AssertEqualsAndHashCode.java create mode 100644 src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java delete mode 100644 src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java create mode 100644 src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index 521be3f1052..49d079e9ffd 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import org.opentripplanner.routing.api.request.PassThroughViaLocation; import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -12,15 +13,18 @@ class ViaLocationMapper { static List toPassThroughLocations( final List> passThroughPoints ) { - return passThroughPoints.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); + return passThroughPoints + .stream() + .map(ViaLocationMapper::mapPassThroughViaLocation) + .collect(toList()); } - private static ViaLocation mapViaLocation(Map inputMap) { + private static ViaLocation mapPassThroughViaLocation(Map inputMap) { final String name = (String) inputMap.get("name"); final List stopLocationIds = ((List) inputMap.get("placeIds")).stream() .map(TransitIdMapper::mapIDToDomain) .toList(); - return ViaLocation.passThroughLocation(name, stopLocationIds); + return new PassThroughViaLocation(name, stopLocationIds); } } 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 74b2a235d9d..68c6d6f7f2a 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 @@ -23,7 +23,6 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.ViaConnection; import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; @@ -123,7 +122,7 @@ private RaptorRequest doMap() { // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled // TODO - We need handle via locations that are not pass-through-points here - if (request.getViaLocations().stream().allMatch(ViaLocation::allowAsPassThroughPoint)) { + if (request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation)) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); } else if (!pt.relaxTransitGroupPriority().isNormal()) { @@ -192,10 +191,9 @@ private List mapPassThroughPoints() { } private PassThroughPoint mapPassThroughPoints(ViaLocation location) { - var feedIds = location.connections().stream().map(ViaConnection::locationId).toList(); return new PassThroughPoint( location.label(), - lookUpStopIndex.lookupStopLocationIndexes(feedIds) + lookUpStopIndex.lookupStopLocationIndexes(location.stopLocationIds()) ); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java new file mode 100644 index 00000000000..68ee399732c --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java @@ -0,0 +1,44 @@ +package org.opentripplanner.routing.api.request; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public abstract class AbstractViaLocation implements ViaLocation { + + private final String label; + private final List stopLocationIds; + + public AbstractViaLocation(String label, Collection stopLocationIds) { + this.label = label; + this.stopLocationIds = List.copyOf(stopLocationIds); + } + + @Nullable + @Override + public String label() { + return label; + } + + @Override + public List stopLocationIds() { + return stopLocationIds; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractViaLocation that = (AbstractViaLocation) o; + return ( + Objects.equals(label, that.label) && Objects.equals(stopLocationIds, that.stopLocationIds) + ); + } + + @Override + public int hashCode() { + return Objects.hash(label, stopLocationIds); + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java new file mode 100644 index 00000000000..2ec6928fafc --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java @@ -0,0 +1,37 @@ +package org.opentripplanner.routing.api.request; + +import java.util.Collection; +import javax.annotation.Nullable; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * One of the listed stop locations or one of its children must be visited. An on-board + * intermediate stop visit is ok, as well as boarding or alighting at one of the stops. + */ +public class PassThroughViaLocation extends AbstractViaLocation { + + @SuppressWarnings("DataFlowIssue") + public PassThroughViaLocation(@Nullable String label, Collection stopLocationIds) { + super(label, stopLocationIds); + if (stopLocationIds.isEmpty()) { + throw new IllegalArgumentException( + "A pass through via location must have at least one stop location. Label: " + label + ); + } + } + + @Override + public boolean isPassThroughLocation() { + return true; + } + + @Override + public String toString() { + return ToStringBuilder + .of(PassThroughViaLocation.class) + .addObj("label", label()) + .addCol("stopLocationIds", stopLocationIds()) + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java index c6c6db73287..c40f1903fce 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java @@ -1,127 +1,56 @@ package org.opentripplanner.routing.api.request; import java.time.Duration; -import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.Nullable; -import org.opentripplanner.framework.time.DurationUtils; -import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.FeedScopedId; /** - * Defines a via location which the journey must route through. + * Defines a via location which the journey must route through. At least one stop location or + * coordinate must exist. When routing, the via-location is visited if at least one of the stops + * or coordinates is visited, before the journey continues. There is no need to visit any other + * stop location or coordinate. + *

+ * The stop locations and coordinates are distinct locations. In earlier versions of OTP the + * coordinates were used as a fallback for when a stop was not found. But in this version, a + * {@link org.opentripplanner.transit.model.framework.EntityNotFoundException} is thrown if + * one of the stops does not exist. The search does NOT try to be smart and recover from an + * entity no found exception. */ -public final class ViaLocation { - - private static final Duration MINIMUM_WAIT_TIME_MAX_LIMIT = Duration.ofHours(24); - - private final String label; - private final boolean allowAsPassThroughPoint; - private final Duration minimumWaitTime; - private final List connections; - - @SuppressWarnings("DataFlowIssue") - private ViaLocation( - @Nullable String label, - boolean allowAsPassThroughPoint, - @Nullable Duration minimumWaitTime, - Collection connections - ) { - this.label = label; - this.allowAsPassThroughPoint = allowAsPassThroughPoint; - this.minimumWaitTime = - DurationUtils.requireNonNegative( - minimumWaitTime == null ? Duration.ZERO : minimumWaitTime, - MINIMUM_WAIT_TIME_MAX_LIMIT, - "minimumWaitTime" - ); - this.connections = List.copyOf(connections); - - if (allowAsPassThroughPoint && !minimumWaitTime.isZero()) { - throw new IllegalArgumentException( - "'allowAsPassThroughPoint' can not be used with minimumWaitTime for " + label + "." - ); - } - if (allowAsPassThroughPoint && connections.stream().anyMatch(ViaConnection::hasCoordinate)) { - throw new IllegalArgumentException( - "'allowAsPassThroughPoint' can not be used with coordinates for " + label + "." - ); - } - } - +public interface ViaLocation { /** - * A pass-through-location instructs the router to visit the location either by boarding, - * alighting or on-board a transit. - * @param label The name/label for this location. This is used for debugging and logging and is pass-through information. - * @param locationIds The ID for the stop, station or multimodal station or groupOfStopPlace. + * Get an optional name/label of for debugging and logging. Not used in business logic. */ - public static ViaLocation passThroughLocation( - @Nullable String label, - List locationIds - ) { - return new ViaLocation(label, true, Duration.ZERO, ViaConnection.connections(locationIds)); - } + @Nullable + String label(); /** - * If set to {@code true} this location can be visited as a pass-through-point. Only - * collections of stops are supported, not coordinates. Also, the minWaitTime must be - * zero(0). + * The minimum wait time is used to force the trip to stay the given duration at the via location + * before the trip is continued. This cannot be used together with allow-pass-through, since a + * pass-through stop is visited on-board. */ - public boolean allowAsPassThroughPoint() { - return allowAsPassThroughPoint; + default Duration minimumWaitTime() { + return Duration.ZERO; } /** - * The minimum wait time is used to force the trip to stay the given duration at the via - * location before the trip is continued. This cannot be used together with allow-pass-through, - * since a pass-through stop is visited on-board. + * Returns {@code true} if this location is a pass-through-point. Only stops can be visited and + * the {@code minimumWaitTime} must be zero. */ - public Duration minimumWaitTime() { - return minimumWaitTime; - } + boolean isPassThroughLocation(); /** - * Get an optional name/label of for debugging and logging. + * A list of stop witch can be used as via location together with the {@code coordinates}.A stop + * location can be a stop, a station, a multimodal station or a group of stations. */ - @Nullable - public String label() { - return label; - } + List stopLocationIds(); /** - * Get the one or multiple locations of which only one is required to route through. + * Get the one or multiple stop locations. This is optional to implement, an empty + * list is returned if this is not available. */ - public List connections() { - return connections; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ViaLocation that = (ViaLocation) o; - return ( - allowAsPassThroughPoint == that.allowAsPassThroughPoint && - Objects.equals(minimumWaitTime, that.minimumWaitTime) && - Objects.equals(label, that.label) && - Objects.equals(connections, that.connections) - ); - } - - @Override - public int hashCode() { - return Objects.hash(allowAsPassThroughPoint, minimumWaitTime, label, connections); - } - - @Override - public String toString() { - return ToStringBuilder - .of(ViaLocation.class) - .addBoolIfTrue(label, label != null) - .addBoolIfTrue("allowAsPassThroughPoint", allowAsPassThroughPoint) - .addDuration("minimumWaitTime", minimumWaitTime, Duration.ZERO) - .addObj("connections", connections) - .toString(); + default List coordinates() { + return List.of(); } } diff --git a/src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java new file mode 100644 index 00000000000..e7b28f27e84 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java @@ -0,0 +1,103 @@ +package org.opentripplanner.routing.api.request; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * A visit via location is a physical visit to one of the stops or coordinates listed. An + * on-board visit is not accepted, the traveler must alight or board at the given stop + * for it to count. To visit a coordinate, the traveler must walk(bike or drive) to the + * closest point in the street network from a stop and back to another stop to join the + * transit network. + *

+ * TODO: NOTE! Coordinates are NOT supported jet. + */ +public class VisitViaLocation extends AbstractViaLocation { + + private static final Duration MINIMUM_WAIT_TIME_MAX_LIMIT = Duration.ofHours(24); + + private final Duration minimumWaitTime; + private final List coordinates; + + public VisitViaLocation( + @Nullable String label, + @Nullable Duration minimumWaitTime, + List stopLocationIds, + List coordinates + ) { + super(label, stopLocationIds); + this.minimumWaitTime = + DurationUtils.requireNonNegative( + minimumWaitTime == null ? Duration.ZERO : minimumWaitTime, + MINIMUM_WAIT_TIME_MAX_LIMIT, + "minimumWaitTime" + ); + this.coordinates = List.copyOf(coordinates); + + if (stopLocationIds().isEmpty() && coordinates().isEmpty()) { + throw new IllegalArgumentException( + "A via location must have at least one stop location or a coordinate. Label: " + label + ); + } + } + + /** + * The minimum wait time is used to force the trip to stay the given duration at the via location + * before the trip is continued. This cannot be used together with allow-pass-through, since a + * pass-through stop is visited on-board. + */ + @Override + public Duration minimumWaitTime() { + return minimumWaitTime; + } + + @Override + public boolean isPassThroughLocation() { + return false; + } + + @Override + public List coordinates() { + return coordinates; + } + + @Override + public String toString() { + return ToStringBuilder + .of(VisitViaLocation.class) + .addObj("label", label()) + .addDuration("minimumWaitTime", minimumWaitTime, Duration.ZERO) + .addCol("stopLocationIds", stopLocationIds()) + .addObj("coordinates", coordinates) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + VisitViaLocation that = (VisitViaLocation) o; + return ( + Objects.equals(minimumWaitTime, that.minimumWaitTime) && + Objects.equals(coordinates, that.coordinates) + ); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), minimumWaitTime, coordinates); + } +} diff --git a/src/test/java/org/opentripplanner/_support/asserts/AssertEqualsAndHashCode.java b/src/test/java/org/opentripplanner/_support/asserts/AssertEqualsAndHashCode.java new file mode 100644 index 00000000000..7e4fbe91676 --- /dev/null +++ b/src/test/java/org/opentripplanner/_support/asserts/AssertEqualsAndHashCode.java @@ -0,0 +1,33 @@ +package org.opentripplanner._support.asserts; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class AssertEqualsAndHashCode { + + private final Object subject; + + @SuppressWarnings("EqualsWithItself") + public AssertEqualsAndHashCode(Object subject) { + this.subject = subject; + assertEquals(subject, subject); + } + + public static AssertEqualsAndHashCode verify(Object subject) { + return new AssertEqualsAndHashCode(subject); + } + + public AssertEqualsAndHashCode sameAs(Object same) { + assertEquals(subject, same); + assertEquals(subject.hashCode(), same.hashCode()); + return this; + } + + public AssertEqualsAndHashCode differentFrom(Object... others) { + for (Object other : others) { + assertNotEquals(subject, other); + assertNotEquals(subject.hashCode(), other.hashCode()); + } + return this; + } +} diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index ead64a1bb99..dd8a1ac62a9 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -325,12 +325,12 @@ void testViaLocations() { .createRequest(executionContext(arguments)) .getViaLocations(); assertEquals( - "ViaLocation{PTP1, allowAsPassThroughPoint, connections: [F:ST:stop1, F:ST:stop2, F:ST:stop3]}", + "PassThroughViaLocation{label: PTP1, stopLocationIds: [F:ST:stop1, F:ST:stop2, F:ST:stop3]}", viaLocations.get(0).toString() ); assertEquals("PTP1", viaLocations.get(0).label()); assertEquals( - "ViaLocation{PTP2, allowAsPassThroughPoint, connections: [F:ST:stop3, F:ST:stop2]}", + "PassThroughViaLocation{label: PTP2, stopLocationIds: [F:ST:stop3, F:ST:stop2]}", viaLocations.get(1).toString() ); assertEquals("PTP2", viaLocations.get(1).label()); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 30b91a38fbf..8214eb4f043 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -15,8 +15,8 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.request.RaptorRequest; +import org.opentripplanner.routing.api.request.PassThroughViaLocation; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -59,9 +59,7 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { void testPassThroughPoints() { var req = new RouteRequest(); - req.setPassThroughPoints( - List.of(ViaLocation.passThroughLocation("Via A", List.of(STOP_A.getId()))) - ); + req.setPassThroughPoints(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); var result = map(req); @@ -77,9 +75,7 @@ void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); // Set pass-through and relax transit-group-priority - req.setPassThroughPoints( - List.of(ViaLocation.passThroughLocation("Via A", List.of(STOP_A.getId()))) - ); + req.setPassThroughPoints(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); req.withPreferences(p -> p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) ); diff --git a/src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java new file mode 100644 index 00000000000..d81f73b038c --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java @@ -0,0 +1,64 @@ +package org.opentripplanner.routing.api.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class PassThroughViaLocationTest { + + private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); + + private static final String LABEL = "AName"; + + @SuppressWarnings("DataFlowIssue") + private static final ViaLocation subject = new PassThroughViaLocation(LABEL, List.of(ID)); + + @Test + void allowAsPassThroughPoint() { + assertTrue(subject.isPassThroughLocation()); + } + + @Test + void minimumWaitTime() { + assertEquals(Duration.ZERO, subject.minimumWaitTime()); + } + + @Test + void label() { + assertEquals(LABEL, subject.label()); + } + + @Test + void stopLocationIds() { + assertEquals("[F:1]", subject.stopLocationIds().toString()); + } + + @Test + void coordinates() { + assertEquals("[]", subject.coordinates().toString()); + } + + @Test + void testToString() { + assertEquals( + "PassThroughViaLocation{label: AName, stopLocationIds: [F:1]}", + subject.toString() + ); + } + + @Test + void testEqAndHashCode() { + AssertEqualsAndHashCode + .verify(subject) + .sameAs(new PassThroughViaLocation(subject.label(), subject.stopLocationIds())) + .differentFrom( + new PassThroughViaLocation("Other", subject.stopLocationIds()), + new PassThroughViaLocation(subject.label(), List.of(new FeedScopedId("F", "2"))) + ); + } +} diff --git a/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java deleted file mode 100644 index 12b1e560033..00000000000 --- a/src/test/java/org/opentripplanner/routing/api/request/ViaLocationTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.opentripplanner.routing.api.request; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -class ViaLocationTest { - - private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); - - public static final String LABEL = "AName"; - private static final ViaLocation passThroughLocation = ViaLocation.passThroughLocation( - LABEL, - List.of(ID) - ); - - // TODO add cases for none passThroughLocation here when added... - - @Test - void allowAsPassThroughPoint() { - assertTrue(passThroughLocation.allowAsPassThroughPoint()); - } - - @Test - void minimumWaitTime() { - assertEquals(Duration.ZERO, passThroughLocation.minimumWaitTime()); - } - - @Test - void label() { - assertEquals(LABEL, passThroughLocation.label()); - } - - @Test - void connections() { - assertEquals("[F:1]", passThroughLocation.connections().toString()); - } - - @Test - void testToString() { - assertEquals( - "ViaLocation{AName, allowAsPassThroughPoint, connections: [F:1]}", - passThroughLocation.toString() - ); - } -} diff --git a/src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java new file mode 100644 index 00000000000..08ee1b9dbee --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java @@ -0,0 +1,77 @@ +package org.opentripplanner.routing.api.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class VisitViaLocationTest { + + private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); + private static final String LABEL = "AName"; + private static final Duration MINIMUM_WAIT_TIME = Duration.ofMinutes(5); + + @SuppressWarnings("DataFlowIssue") + private static final ViaLocation subject = new VisitViaLocation( + LABEL, + MINIMUM_WAIT_TIME, + List.of(ID), + List.of(WgsCoordinate.GREENWICH) + ); + + @Test + void allowAsPassThroughPoint() { + assertFalse(subject.isPassThroughLocation()); + } + + @Test + void minimumWaitTime() { + assertEquals(MINIMUM_WAIT_TIME, subject.minimumWaitTime()); + } + + @Test + void label() { + assertEquals(LABEL, subject.label()); + } + + @Test + void stopLocationIds() { + assertEquals("[F:1]", subject.stopLocationIds().toString()); + } + + @Test + void coordinates() { + assertEquals("[" + WgsCoordinate.GREENWICH + "]", subject.coordinates().toString()); + } + + @Test + void testToString() { + assertEquals( + "VisitViaLocation{label: AName, minimumWaitTime: 5m, stopLocationIds: [F:1], coordinates: [(51.48, 0.0)]}", + subject.toString() + ); + } + + @Test + void testEqAndHashCode() { + var l = subject.label(); + var mwt = subject.minimumWaitTime(); + var ids = subject.stopLocationIds(); + var cs = subject.coordinates(); + + AssertEqualsAndHashCode + .verify(subject) + .sameAs(new VisitViaLocation(l, mwt, ids, cs)) + .differentFrom( + new VisitViaLocation("other", mwt, ids, cs), + new VisitViaLocation(l, Duration.ZERO, ids, cs), + new VisitViaLocation(l, mwt, List.of(), cs), + new VisitViaLocation(l, mwt, ids, List.of()) + ); + } +} From 26e41f456e05b69a0cbf7b2ed8bd473af2d3f387 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 13 Sep 2024 17:52:09 +0200 Subject: [PATCH 24/36] refactor: Delete unused ViaConnection --- .../routing/api/request/ViaConnection.java | 78 ------------------- .../api/request/ViaConnectionTest.java | 42 ---------- 2 files changed, 120 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java delete mode 100644 src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java b/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java deleted file mode 100644 index d44d2d90d79..00000000000 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaConnection.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.opentripplanner.routing.api.request; - -import java.util.List; -import java.util.Objects; -import javax.annotation.Nullable; -import org.opentripplanner.framework.geometry.WgsCoordinate; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -/** - * A ViaConnection is a reference to a location or a coordinate. Supported locations are stop, - * station, multimodal-station and group-of-stations. Either the {@code locationId} or the - * {@code coordinate} must be set. - *

- * Earlier the coordinate was used as a fallback for the location-id, this is not the case anymore. - * Any inconsistencies in location ids between the client and the server should be detected - * and fixed - not automatically patched. If the client wants to provide a fallback, it could - * fire a new request with the coordinate set instead. - * - */ -public class ViaConnection { - - private final FeedScopedId locationId; - private final WgsCoordinate coordinate; - - private ViaConnection(@Nullable FeedScopedId locationId, @Nullable WgsCoordinate coordinate) { - this.locationId = locationId; - this.coordinate = coordinate; - } - - public ViaConnection(FeedScopedId locationId) { - this(Objects.requireNonNull(locationId), null); - } - - public ViaConnection(WgsCoordinate coordinate) { - this(null, Objects.requireNonNull(coordinate)); - } - - public static List connections(List ids) { - return ids.stream().map(ViaConnection::new).toList(); - } - - public boolean hasLocationId() { - return locationId != null; - } - - public boolean hasCoordinate() { - return coordinate != null; - } - - public FeedScopedId locationId() { - return locationId; - } - - public WgsCoordinate coordinate() { - return coordinate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ViaConnection that = (ViaConnection) o; - return ( - Objects.equals(locationId, that.locationId) && Objects.equals(coordinate, that.coordinate) - ); - } - - @Override - public int hashCode() { - return Objects.hash(locationId, coordinate); - } - - @SuppressWarnings("DataFlowIssue") - @Override - public String toString() { - return locationId != null ? locationId.toString() : coordinate.toString(); - } -} diff --git a/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java b/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java deleted file mode 100644 index d500483f84c..00000000000 --- a/src/test/java/org/opentripplanner/routing/api/request/ViaConnectionTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.opentripplanner.routing.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.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.opentripplanner.framework.geometry.WgsCoordinate; -import org.opentripplanner.transit.model.framework.FeedScopedId; - -class ViaConnectionTest { - - private static final FeedScopedId ID = FeedScopedId.ofNullable("F", "1"); - - private final ViaConnection connectionWithLocationId = new ViaConnection(ID); - private final ViaConnection connectionWithCoordinate = new ViaConnection(WgsCoordinate.GREENWICH); - - @Test - void hasLocationId() { - assertTrue(connectionWithLocationId.hasLocationId()); - assertFalse(connectionWithCoordinate.hasLocationId()); - } - - @Test - void hasCoordinate() { - assertFalse(connectionWithLocationId.hasCoordinate()); - assertTrue(connectionWithCoordinate.hasCoordinate()); - } - - @Test - void locationId() { - assertEquals(ID, connectionWithLocationId.locationId()); - assertNull(connectionWithCoordinate.locationId()); - } - - @Test - void coordinate() { - assertNull(connectionWithLocationId.coordinate()); - assertEquals(WgsCoordinate.GREENWICH, connectionWithCoordinate.coordinate()); - } -} From 496aa5df5c33da4db2dc44dd9c206ec17b5bea07 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 13 Sep 2024 17:54:04 +0200 Subject: [PATCH 25/36] refactor: Move via request classes into via package --- .../apis/transmodel/mapping/ViaLocationMapper.java | 4 ++-- .../raptoradapter/transit/mappers/RaptorRequestMapper.java | 2 +- .../org/opentripplanner/routing/api/request/RouteRequest.java | 1 + .../routing/api/request/{ => via}/AbstractViaLocation.java | 2 +- .../routing/api/request/{ => via}/PassThroughViaLocation.java | 2 +- .../routing/api/request/{ => via}/ViaLocation.java | 2 +- .../routing/api/request/{ => via}/VisitViaLocation.java | 2 +- .../apis/transmodel/mapping/TripRequestMapperTest.java | 2 +- .../transit/mappers/RaptorRequestMapperTest.java | 2 +- .../api/request/{ => via}/PassThroughViaLocationTest.java | 2 +- .../routing/api/request/{ => via}/VisitViaLocationTest.java | 2 +- 11 files changed, 12 insertions(+), 11 deletions(-) rename src/main/java/org/opentripplanner/routing/api/request/{ => via}/AbstractViaLocation.java (95%) rename src/main/java/org/opentripplanner/routing/api/request/{ => via}/PassThroughViaLocation.java (95%) rename src/main/java/org/opentripplanner/routing/api/request/{ => via}/ViaLocation.java (97%) rename src/main/java/org/opentripplanner/routing/api/request/{ => via}/VisitViaLocation.java (98%) rename src/test/java/org/opentripplanner/routing/api/request/{ => via}/PassThroughViaLocationTest.java (96%) rename src/test/java/org/opentripplanner/routing/api/request/{ => via}/VisitViaLocationTest.java (97%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index 49d079e9ffd..1ff09b742d5 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -4,8 +4,8 @@ import java.util.List; import java.util.Map; -import org.opentripplanner.routing.api.request.PassThroughViaLocation; -import org.opentripplanner.routing.api.request.ViaLocation; +import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; +import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; class ViaLocationMapper { 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 68c6d6f7f2a..9220756f8bb 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 @@ -23,8 +23,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.api.request.DebugEventType; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator; public class RaptorRequestMapper { diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 26d17fccec3..1eb24e92d60 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -21,6 +21,7 @@ import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.request.JourneyRequest; +import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.api.response.InputField; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; diff --git a/src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/via/AbstractViaLocation.java similarity index 95% rename from src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java rename to src/main/java/org/opentripplanner/routing/api/request/via/AbstractViaLocation.java index 68ee399732c..2f694df53cb 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/AbstractViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/via/AbstractViaLocation.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import java.util.Collection; import java.util.List; diff --git a/src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java similarity index 95% rename from src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java rename to src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java index 2ec6928fafc..1116031cec6 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/PassThroughViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocation.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import java.util.Collection; import javax.annotation.Nullable; diff --git a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/via/ViaLocation.java similarity index 97% rename from src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java rename to src/main/java/org/opentripplanner/routing/api/request/via/ViaLocation.java index c40f1903fce..c2108886abc 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/via/ViaLocation.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import java.time.Duration; import java.util.List; diff --git a/src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java similarity index 98% rename from src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java rename to src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java index e7b28f27e84..63cf57a538f 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/VisitViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import java.time.Duration; import java.util.List; diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index dd8a1ac62a9..38a411ac1cf 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -41,9 +41,9 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.ViaLocation; import org.opentripplanner.routing.api.request.preference.StreetPreferences; import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; +import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 8214eb4f043..b3c84ada2dd 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -15,9 +15,9 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.request.RaptorRequest; -import org.opentripplanner.routing.api.request.PassThroughViaLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; diff --git a/src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocationTest.java similarity index 96% rename from src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java rename to src/test/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocationTest.java index d81f73b038c..9ed431912ac 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/PassThroughViaLocationTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/via/PassThroughViaLocationTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java b/src/test/java/org/opentripplanner/routing/api/request/via/VisitViaLocationTest.java similarity index 97% rename from src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java rename to src/test/java/org/opentripplanner/routing/api/request/via/VisitViaLocationTest.java index 08ee1b9dbee..e36ed42551e 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/VisitViaLocationTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/via/VisitViaLocationTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.routing.api.request; +package org.opentripplanner.routing.api.request.via; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; From 14b8562accd04ed40fcbd631ed5076d764fda5f7 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 19 Sep 2024 23:32:04 +0200 Subject: [PATCH 26/36] refactor: Hide legacy ViaSearch in Transmodel API in its own package --- .../apis/transmodel/TransmodelGraphQLSchema.java | 8 ++++---- .../apis/transmodel/model/plan/FilterInputType.java | 2 +- .../model/plan/{ => legacyvia}/ViaLocationInputType.java | 2 +- .../model/plan/{ => legacyvia}/ViaSegmentInputType.java | 3 ++- .../model/plan/{ => legacyvia}/ViaTripQuery.java | 3 ++- .../model/plan/{ => legacyvia}/ViaTripType.java | 2 +- .../org/opentripplanner/apis/transmodel/schema.graphql | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) rename src/main/java/org/opentripplanner/apis/transmodel/model/plan/{ => legacyvia}/ViaLocationInputType.java (97%) rename src/main/java/org/opentripplanner/apis/transmodel/model/plan/{ => legacyvia}/ViaSegmentInputType.java (95%) rename src/main/java/org/opentripplanner/apis/transmodel/model/plan/{ => legacyvia}/ViaTripQuery.java (98%) rename src/main/java/org/opentripplanner/apis/transmodel/model/plan/{ => legacyvia}/ViaTripType.java (98%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 9ad43606420..59863e94ba8 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -74,10 +74,10 @@ import org.opentripplanner.apis.transmodel.model.plan.TripPatternType; import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.model.plan.TripType; -import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; -import org.opentripplanner.apis.transmodel.model.plan.ViaSegmentInputType; -import org.opentripplanner.apis.transmodel.model.plan.ViaTripQuery; -import org.opentripplanner.apis.transmodel.model.plan.ViaTripType; +import org.opentripplanner.apis.transmodel.model.plan.legacyvia.ViaLocationInputType; +import org.opentripplanner.apis.transmodel.model.plan.legacyvia.ViaSegmentInputType; +import org.opentripplanner.apis.transmodel.model.plan.legacyvia.ViaTripQuery; +import org.opentripplanner.apis.transmodel.model.plan.legacyvia.ViaTripType; import org.opentripplanner.apis.transmodel.model.siri.et.EstimatedCallType; import org.opentripplanner.apis.transmodel.model.siri.sx.AffectsType; import org.opentripplanner.apis.transmodel.model.siri.sx.PtSituationElementType; diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java index ce792ca3790..1e868eadb86 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/FilterInputType.java @@ -7,7 +7,7 @@ public class FilterInputType { - static final GraphQLInputObjectType INPUT_TYPE = GraphQLInputObjectType + public static final GraphQLInputObjectType INPUT_TYPE = GraphQLInputObjectType .newInputObject() .name("TripFilterInput") .description( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java similarity index 97% rename from src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java index a45ac58871c..b47b00144d6 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.transmodel.model.plan; +package org.opentripplanner.apis.transmodel.model.plan.legacyvia; import graphql.Scalars; import graphql.schema.GraphQLInputObjectField; diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaSegmentInputType.java similarity index 95% rename from src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaSegmentInputType.java index 0d591a1357a..a2ada7487c3 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaSegmentInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaSegmentInputType.java @@ -1,10 +1,11 @@ -package org.opentripplanner.apis.transmodel.model.plan; +package org.opentripplanner.apis.transmodel.model.plan.legacyvia; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.plan.FilterInputType; public class ViaSegmentInputType { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java similarity index 98% rename from src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java index 8641dbc60c2..c3ea87f9fc1 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.transmodel.model.plan; +package org.opentripplanner.apis.transmodel.model.plan.legacyvia; import graphql.Scalars; import graphql.schema.GraphQLArgument; @@ -28,6 +28,7 @@ public static GraphQLFieldDefinition create( .description( "Via trip search. Find trip patterns traveling via one or more intermediate (via) locations." ) + .deprecate("The the regular plan query with via stop instead.") .type(new GraphQLNonNull(viaTripType)) .withDirective(gqlUtil.timingData) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripType.java similarity index 98% rename from src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripType.java index 8b55222f3b8..740664c02f0 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaTripType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripType.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.transmodel.model.plan; +package org.opentripplanner.apis.transmodel.model.plan.legacyvia; import graphql.Scalars; import graphql.schema.DataFetchingEnvironment; diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 0d2bf71dcc6..dfc39cd30d6 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -943,7 +943,7 @@ type QueryType { via: [ViaLocationInput!]!, "Whether the trip must be wheelchair accessible. Supported for the street part to the search, not implemented for the transit yet." wheelchairAccessible: Boolean = false - ): ViaTrip! @timingData + ): ViaTrip! @deprecated(reason : "The the regular plan query with via stop instead.") @timingData } type RentalVehicle implements PlaceInterface { From 38b9d1702bdd66bb4e2d001b96c77c6bbb99022b Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 17:20:48 +0200 Subject: [PATCH 27/36] refactor: Make Transmodel scalars static and move from GqlUtil into TransmodelScalars --- .../transmodel/TransmodelGraphQLSchema.java | 89 +++++++++---------- .../model/framework/AuthorityType.java | 7 +- .../model/framework/OperatorType.java | 7 +- .../framework/PenaltyForStreetModeType.java | 4 +- .../StreetModeDurationInputType.java | 2 +- .../model/framework/TransmodelScalars.java | 40 +++++++++ .../model/network/JourneyPatternType.java | 12 +-- .../model/plan/ItineraryFiltersInputType.java | 5 +- .../apis/transmodel/model/plan/LegType.java | 11 +-- .../apis/transmodel/model/plan/TripQuery.java | 5 +- .../plan/legacyvia/ViaLocationInputType.java | 8 +- .../model/plan/legacyvia/ViaTripQuery.java | 5 +- .../model/siri/et/EstimatedCallType.java | 5 +- .../transmodel/model/siri/sx/AffectsType.java | 8 +- .../apis/transmodel/model/stop/QuayType.java | 7 +- .../transmodel/model/stop/StopPlaceType.java | 5 +- .../timetable/BookingArrangementType.java | 6 +- .../timetable/DatedServiceJourneyQuery.java | 10 +-- .../timetable/DatedServiceJourneyType.java | 8 +- .../model/timetable/ServiceJourneyType.java | 14 +-- .../timetable/TimetabledPassingTimeType.java | 12 +-- .../apis/transmodel/support/GqlUtil.java | 26 ------ 22 files changed, 154 insertions(+), 142 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 59863e94ba8..90708dcd315 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -58,6 +58,7 @@ import org.opentripplanner.apis.transmodel.model.framework.ServerInfoType; import org.opentripplanner.apis.transmodel.model.framework.StreetModeDurationInputType; import org.opentripplanner.apis.transmodel.model.framework.SystemNoticeType; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.framework.ValidityPeriodType; import org.opentripplanner.apis.transmodel.model.network.DestinationDisplayType; import org.opentripplanner.apis.transmodel.model.network.GroupOfLinesType; @@ -167,20 +168,15 @@ private GraphQLSchema create() { GraphQLOutputType multilingualStringType = MultilingualStringType.create(); GraphQLObjectType validityPeriodType = ValidityPeriodType.create(gqlUtil); GraphQLObjectType infoLinkType = InfoLinkType.create(); - GraphQLOutputType bookingArrangementType = BookingArrangementType.create(gqlUtil); + GraphQLOutputType bookingArrangementType = BookingArrangementType.create(); GraphQLOutputType systemNoticeType = SystemNoticeType.create(); GraphQLOutputType linkGeometryType = PointsOnLinkType.create(); GraphQLOutputType serverInfoType = ServerInfoType.create(); GraphQLOutputType authorityType = AuthorityType.create( LineType.REF, - PtSituationElementType.REF, - gqlUtil - ); - GraphQLOutputType operatorType = OperatorType.create( - LineType.REF, - ServiceJourneyType.REF, - gqlUtil + PtSituationElementType.REF ); + GraphQLOutputType operatorType = OperatorType.create(LineType.REF, ServiceJourneyType.REF); GraphQLOutputType brandingType = BrandingType.create(); GraphQLOutputType noticeType = NoticeType.create(); GraphQLOutputType rentalVehicleTypeType = RentalVehicleTypeType.create(); @@ -245,8 +241,7 @@ private GraphQLSchema create() { stopPlaceType, lineType, ServiceJourneyType.REF, - DatedServiceJourneyType.REF, - gqlUtil + DatedServiceJourneyType.REF ); // Timetable @@ -270,8 +265,7 @@ private GraphQLSchema create() { lineType, ServiceJourneyType.REF, stopToStopGeometryType, - ptSituationElementType, - gqlUtil + ptSituationElementType ); GraphQLOutputType estimatedCallType = EstimatedCallType.create( bookingArrangementType, @@ -294,16 +288,14 @@ private GraphQLSchema create() { ptSituationElementType, journeyPatternType, estimatedCallType, - TimetabledPassingTimeType.REF, - gqlUtil + TimetabledPassingTimeType.REF ); GraphQLOutputType datedServiceJourneyType = DatedServiceJourneyType.create( serviceJourneyType, journeyPatternType, estimatedCallType, - quayType, - gqlUtil + quayType ); GraphQLOutputType timetabledPassingTime = TimetabledPassingTimeType.create( @@ -311,8 +303,7 @@ private GraphQLSchema create() { noticeType, quayType, destinationDisplayType, - serviceJourneyType, - gqlUtil + serviceJourneyType ); GraphQLObjectType tripPatternTimePenaltyType = TripPatternTimePenaltyType.create(); @@ -358,7 +349,7 @@ private GraphQLSchema create() { ); GraphQLInputObjectType durationPerStreetModeInput = StreetModeDurationInputType.create(gqlUtil); - GraphQLInputObjectType penaltyForStreetMode = PenaltyForStreetModeType.create(gqlUtil); + GraphQLInputObjectType penaltyForStreetMode = PenaltyForStreetModeType.create(); GraphQLFieldDefinition tripQuery = TripQuery.create( routing, @@ -369,7 +360,7 @@ private GraphQLSchema create() { ); GraphQLOutputType viaTripType = ViaTripType.create(tripPatternType, routingErrorType); - GraphQLInputObjectType viaLocationInputType = ViaLocationInputType.create(gqlUtil); + GraphQLInputObjectType viaLocationInputType = ViaLocationInputType.create(); GraphQLInputObjectType viaSegmentInputType = ViaSegmentInputType.create(); GraphQLFieldDefinition viaTripQuery = ViaTripQuery.create( @@ -435,7 +426,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlace") .description("Get a single stopPlace based on its id)") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(stopPlaceType) .argument( GraphQLArgument @@ -457,7 +448,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlaces") .description("Get all stopPlaces") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(stopPlaceType))) .argument( GraphQLArgument @@ -492,7 +483,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlacesByBbox") .description("Get all stop places within the specified bounding box") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(stopPlaceType))) .argument( GraphQLArgument @@ -573,7 +564,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quay") .description("Get a single quay based on its id)") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(quayType) .argument( GraphQLArgument @@ -594,7 +585,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quays") .description("Get all quays") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(quayType))) .argument( GraphQLArgument @@ -639,7 +630,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quaysByBbox") .description("Get all quays within the specified bounding box") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(quayType))) .argument( GraphQLArgument @@ -722,7 +713,7 @@ private GraphQLSchema create() { "limits for the input parameters, but the query will timeout and return if the parameters " + "are too high." ) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type( relay.connectionType( "quayAtDistance", @@ -808,7 +799,7 @@ private GraphQLSchema create() { .description( "Get all places (quays, stop places, car parks etc. with coordinates) within the specified radius from a location. The returned type has two fields place and distance. The search is done by walking so the distance is according to the network of walkables." ) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type( relay.connectionType( "placeAtDistance", @@ -1008,7 +999,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("authority") .description("Get an authority by ID") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(authorityType) .argument( GraphQLArgument @@ -1029,7 +1020,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("authorities") .description("Get all authorities") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(authorityType))) .dataFetcher(environment -> { return new ArrayList<>(GqlUtil.getTransitService(environment).getAgencies()); @@ -1041,7 +1032,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("operator") .description("Get a operator by ID") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(operatorType) .argument( GraphQLArgument @@ -1062,7 +1053,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("operators") .description("Get all operators") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(operatorType))) .dataFetcher(environment -> { return new ArrayList<>(GqlUtil.getTransitService(environment).getAllOperators()); @@ -1074,7 +1065,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("line") .description("Get a single line based on its id") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(lineType) .argument( GraphQLArgument @@ -1099,7 +1090,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("lines") .description("Get all lines") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .argument( GraphQLArgument @@ -1252,7 +1243,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serviceJourney") .description("Get a single service journey based on its id") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(serviceJourneyType) .argument( GraphQLArgument @@ -1273,7 +1264,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serviceJourneys") .description("Get all service journeys") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(serviceJourneyType))) .argument( GraphQLArgument @@ -1296,7 +1287,7 @@ private GraphQLSchema create() { .newArgument() .name("activeDates") .description("Set of ids of active dates to fetch serviceJourneys for.") - .type(new GraphQLList(gqlUtil.dateScalar)) + .type(new GraphQLList(TransmodelScalars.DATE_SCALAR)) .build() ) .argument( @@ -1350,7 +1341,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStations") .description("Get all bike rental stations") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .argument( GraphQLArgument .newArgument() @@ -1379,7 +1370,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStation") .description("Get all bike rental stations") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(bikeRentalStationType) .argument( GraphQLArgument @@ -1406,7 +1397,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStationsByBbox") .description("Get all bike rental stations within the specified bounding box.") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(bikeRentalStationType))) .argument( GraphQLArgument.newArgument().name("minimumLatitude").type(Scalars.GraphQLFloat).build() @@ -1445,7 +1436,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikePark") .description("Get a single bike park based on its id") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(bikeParkType) .argument( GraphQLArgument @@ -1470,7 +1461,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeParks") .description("Get all bike parks") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(bikeParkType))) .dataFetcher(environment -> GqlUtil @@ -1485,7 +1476,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("routingParameters") .description("Get default routing parameters.") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(this.routing.graphQLType) .dataFetcher(environment -> routing.request) .build() @@ -1495,7 +1486,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("situations") .description("Get all active situations.") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .argument( GraphQLArgument @@ -1571,7 +1562,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("situation") .description("Get a single situation based on its situationNumber") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(ptSituationElementType) .argument( GraphQLArgument @@ -1597,7 +1588,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("leg") .description("Refetch a single leg based on its id") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(LegType.REF) .argument( GraphQLArgument @@ -1624,13 +1615,13 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serverInfo") .description("Get OTP server information") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(serverInfoType)) .dataFetcher(e -> projectInfo()) .build() ) .field(DatedServiceJourneyQuery.createGetById(datedServiceJourneyType)) - .field(DatedServiceJourneyQuery.createQuery(datedServiceJourneyType, gqlUtil)) + .field(DatedServiceJourneyQuery.createQuery(datedServiceJourneyType)) .build(); return GraphQLSchema @@ -1639,7 +1630,7 @@ private GraphQLSchema create() { .additionalType(placeInterface) .additionalType(timetabledPassingTime) .additionalType(Relay.pageInfoType) - .additionalDirective(gqlUtil.timingData) + .additionalDirective(TransmodelScalars.TIMING_DATA) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java index 27d20c8e423..11e2198829b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java @@ -17,8 +17,7 @@ public class AuthorityType { public static GraphQLObjectType create( GraphQLOutputType lineType, - GraphQLOutputType ptSituationElementType, - GqlUtil gqlUtil + GraphQLOutputType ptSituationElementType ) { return GraphQLObjectType .newObject() @@ -65,7 +64,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .dataFetcher(environment -> getTransitService(environment) @@ -80,7 +79,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("situations") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("Get all situations active for the authority.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .dataFetcher(environment -> diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java index d45319b9d9e..94fb19879b1 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java @@ -14,8 +14,7 @@ public class OperatorType { public static GraphQLObjectType create( GraphQLOutputType lineType, - GraphQLOutputType serviceJourneyType, - GqlUtil gqlUtil + GraphQLOutputType serviceJourneyType ) { return GraphQLObjectType .newObject() @@ -48,7 +47,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .dataFetcher(environment -> GqlUtil @@ -64,7 +63,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourney") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(serviceJourneyType))) .dataFetcher(environment -> GqlUtil diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java index 6d945ae912f..b899ada3599 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/PenaltyForStreetModeType.java @@ -47,7 +47,7 @@ public class PenaltyForStreetModeType { private static final String FIELD_TIME_PENALTY = "timePenalty"; private static final String FIELD_COST_FACTOR = "costFactor"; - public static GraphQLInputObjectType create(GqlUtil gqlUtil) { + public static GraphQLInputObjectType create() { return GraphQLInputObjectType .newInputObject() .name("PenaltyForStreetMode") @@ -70,7 +70,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name(FIELD_TIME_PENALTY) - .type(new GraphQLNonNull(gqlUtil.doubleFunctionScalar)) + .type(new GraphQLNonNull(TransmodelScalars.DOUBLE_FUNCTION_SCALAR)) .description( """ Penalty applied to the time for the given list of modes. diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java index bf274688617..182885fdeef 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/StreetModeDurationInputType.java @@ -42,7 +42,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { GraphQLInputObjectField .newInputObjectField() .name(FIELD_DURATION) - .type(new GraphQLNonNull(gqlUtil.durationScalar)) + .type(new GraphQLNonNull(TransmodelScalars.DURATION_SCALAR)) ) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java new file mode 100644 index 00000000000..86592cba99d --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java @@ -0,0 +1,40 @@ +package org.opentripplanner.apis.transmodel.model.framework; + +import graphql.introspection.Introspection; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLScalarType; +import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunctionFactory; +import org.opentripplanner.apis.transmodel.model.scalars.LocalTimeScalarFactory; +import org.opentripplanner.apis.transmodel.model.scalars.TimeScalarFactory; +import org.opentripplanner.framework.graphql.scalar.DateScalarFactory; +import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; + +/** + * This class contains all Transmodel custom scalars, except the + * {@link org.opentripplanner.apis.transmodel.support.GqlUtil#dateTimeScalar}. + */ +public class TransmodelScalars { + + public static final GraphQLScalarType DATE_SCALAR; + public static final GraphQLScalarType DOUBLE_FUNCTION_SCALAR; + public static final GraphQLScalarType LOCAL_TIME_SCALAR; + public static final GraphQLObjectType TIME_SCALAR; + public static final GraphQLScalarType DURATION_SCALAR; + public static final GraphQLDirective TIMING_DATA; + + static { + DATE_SCALAR = DateScalarFactory.createTransmodelDateScalar(); + DOUBLE_FUNCTION_SCALAR = DoubleFunctionFactory.createDoubleFunctionScalar(); + LOCAL_TIME_SCALAR = LocalTimeScalarFactory.createLocalTimeScalar(); + TIME_SCALAR = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); + DURATION_SCALAR = DurationScalarFactory.createDurationScalar(); + TIMING_DATA = + GraphQLDirective + .newDirective() + .name("timingData") + .description("Add timing data to prometheus, if Actuator API is enabled") + .validLocation(Introspection.DirectiveLocation.FIELD_DEFINITION) + .build(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java index 0743b02a3c4..7e6392e29f3 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java @@ -16,6 +16,7 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.apis.transmodel.mapping.GeometryMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.network.TripPattern; @@ -33,8 +34,7 @@ public static GraphQLObjectType create( GraphQLOutputType lineType, GraphQLOutputType serviceJourneyType, GraphQLOutputType stopToStopGeometryType, - GraphQLNamedOutputType ptSituationElementType, - GqlUtil gqlUtil + GraphQLNamedOutputType ptSituationElementType ) { return GraphQLObjectType .newObject() @@ -68,7 +68,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneys") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(serviceJourneyType)))) .dataFetcher(e -> ((TripPattern) e.getSource()).scheduledTripsAsStream().collect(Collectors.toList()) @@ -79,9 +79,11 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneysForDate") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("List of service journeys for the journey pattern for a given date") - .argument(GraphQLArgument.newArgument().name("date").type(gqlUtil.dateScalar).build()) + .argument( + GraphQLArgument.newArgument().name("date").type(TransmodelScalars.DATE_SCALAR).build() + ) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(serviceJourneyType)))) .dataFetcher(environment -> { TIntSet services = GqlUtil diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java index b5d82af5138..7f0d5b10215 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ItineraryFiltersInputType.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.function.Consumer; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunction; import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.apis.transmodel.support.GqlUtil; @@ -26,7 +27,7 @@ public class ItineraryFiltersInputType { private static final String GROUP_SIMILARITY_KEEP_N_ITINERARIES = "groupSimilarityKeepNumOfItineraries"; - public static GraphQLInputObjectType create(GqlUtil gqlUtil, ItineraryFilterPreferences dft) { + public static GraphQLInputObjectType create(ItineraryFilterPreferences dft) { return GraphQLInputObjectType .newInputObject() .name("ItineraryFilters") @@ -59,7 +60,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil, ItineraryFilterPref GraphQLInputObjectField .newInputObjectField() .name("costLimitFunction") - .type(new GraphQLNonNull(gqlUtil.doubleFunctionScalar)) + .type(new GraphQLNonNull(TransmodelScalars.DOUBLE_FUNCTION_SCALAR)) .build() ) .field( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java index 867d08e3933..4109c1f925c 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java @@ -22,6 +22,7 @@ import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.apis.transmodel.model.TripTimeOnDateHelper; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.model.plan.Leg; @@ -268,7 +269,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("fromEstimatedCall") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("EstimatedCall for the quay where the leg originates.") .type(estimatedCallType) .dataFetcher(env -> TripTimeOnDateHelper.getTripTimeOnDateForFromPlace(env.getSource())) @@ -278,7 +279,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("toEstimatedCall") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("EstimatedCall for the quay where the leg ends.") .type(estimatedCallType) .dataFetcher(env -> TripTimeOnDateHelper.getTripTimeOnDateForToPlace(env.getSource())) @@ -318,7 +319,7 @@ public static GraphQLObjectType create( .description( "For transit legs, the service date of the trip. For non-transit legs, null." ) - .type(gqlUtil.dateScalar) + .type(TransmodelScalars.DATE_SCALAR) .dataFetcher(environment -> Optional.of((Leg) environment.getSource()).map(Leg::getServiceDate).orElse(null) ) @@ -352,7 +353,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("intermediateEstimatedCalls") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description( "For ride legs, estimated calls for quays between the Place where the leg originates and the Place where the leg ends. For non-ride legs, empty list." ) @@ -366,7 +367,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneyEstimatedCalls") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description( "For ride legs, all estimated calls for the service journey. For non-ride legs, empty list." ) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index b67b26b90b6..f29c06e533d 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -18,6 +18,7 @@ import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; import org.opentripplanner.apis.transmodel.model.framework.PassThroughPointInputType; import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; @@ -45,7 +46,7 @@ public static GraphQLFieldDefinition create( "trip patterns describing suggested alternatives for the trip." ) .type(new GraphQLNonNull(tripType)) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .argument( GraphQLArgument .newArgument() @@ -572,7 +573,7 @@ Normally this is when the search is performed (now), plus a small grace period t "Configure the itinerary-filter-chain. NOTE! THESE PARAMETERS ARE USED " + "FOR SERVER-SIDE TUNING AND IS AVAILABLE HERE FOR TESTING ONLY." ) - .type(ItineraryFiltersInputType.create(gqlUtil, preferences.itineraryFilter())) + .type(ItineraryFiltersInputType.create(preferences.itineraryFilter())) .build() ) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java index b47b00144d6..1f26d12e7f7 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaLocationInputType.java @@ -4,12 +4,12 @@ import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLInputObjectType; import org.opentripplanner.apis.transmodel.model.framework.CoordinateInputType; -import org.opentripplanner.apis.transmodel.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.routing.api.request.ViaLocationDeprecated; public class ViaLocationInputType { - public static GraphQLInputObjectType create(GqlUtil gqlUtil) { + public static GraphQLInputObjectType create() { return GraphQLInputObjectType .newInputObject() .name("ViaLocationInput") @@ -61,7 +61,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { .description( "The minimum time the user wants to stay in the via location before continuing his journey" ) - .type(gqlUtil.durationScalar) + .type(TransmodelScalars.DURATION_SCALAR) ) .field( GraphQLInputObjectField @@ -71,7 +71,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { .description( "The maximum time the user wants to stay in the via location before continuing his journey" ) - .type(gqlUtil.durationScalar) + .type(TransmodelScalars.DURATION_SCALAR) ) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java index c3ea87f9fc1..27f7df1d19f 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java @@ -11,6 +11,7 @@ import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; public class ViaTripQuery { @@ -30,7 +31,7 @@ public static GraphQLFieldDefinition create( ) .deprecate("The the regular plan query with via stop instead.") .type(new GraphQLNonNull(viaTripType)) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .argument( GraphQLArgument .newArgument() @@ -73,7 +74,7 @@ public static GraphQLFieldDefinition create( "The search-window used is returned to the response metadata as `searchWindowUsed` for " + "debugging purposes." ) - .type(new GraphQLNonNull(gqlUtil.durationScalar)) + .type(new GraphQLNonNull(TransmodelScalars.DURATION_SCALAR)) .build() ) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java index 3587b3dc0f6..87cb23e926e 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java @@ -17,6 +17,7 @@ import java.util.Set; import org.opentripplanner.apis.transmodel.mapping.OccupancyStatusMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.routing.alertpatch.StopCondition; @@ -271,7 +272,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("date") - .type(new GraphQLNonNull(gqlUtil.dateScalar)) + .type(new GraphQLNonNull(TransmodelScalars.DATE_SCALAR)) .description("The date the estimated call is valid for.") .dataFetcher(environment -> ((TripTimeOnDate) environment.getSource()).getServiceDay()) .build() @@ -326,7 +327,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("situations") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .description("Get all relevant situations for this EstimatedCall.") .dataFetcher(environment -> diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java index 4aded08e3e8..58bf9d3f958 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/AffectsType.java @@ -8,6 +8,7 @@ import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLUnionType; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.stop.StopPlaceType; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.alertpatch.EntitySelector; @@ -23,8 +24,7 @@ public static GraphQLOutputType create( GraphQLOutputType stopPlaceType, GraphQLOutputType lineType, GraphQLOutputType serviceJourneyType, - GraphQLOutputType datedServiceJourneyType, - GqlUtil gqlUtil + GraphQLOutputType datedServiceJourneyType ) { GraphQLObjectType affectedStopPlace = GraphQLObjectType .newObject() @@ -96,7 +96,7 @@ public static GraphQLOutputType create( GraphQLFieldDefinition .newFieldDefinition() .name("operatingDay") - .type(gqlUtil.dateScalar) + .type(TransmodelScalars.DATE_SCALAR) .dataFetcher(environment -> environment.getSource().serviceDate()) .build() ) @@ -204,7 +204,7 @@ public static GraphQLOutputType create( GraphQLFieldDefinition .newFieldDefinition() .name("operatingDay") - .type(gqlUtil.dateScalar) + .type(TransmodelScalars.DATE_SCALAR) .dataFetcher(environment -> environment.getSource().serviceDate() ) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java index e146b537c1c..26b7101d076 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java @@ -19,6 +19,7 @@ import java.util.Optional; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; import org.opentripplanner.apis.transmodel.model.scalars.GeoJSONCoordinatesScalar; import org.opentripplanner.apis.transmodel.support.GqlUtil; @@ -169,7 +170,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("List of lines servicing this quay") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(lineType)))) .dataFetcher(env -> @@ -187,7 +188,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("journeyPatterns") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("List of journey patterns servicing this quay") .type(new GraphQLNonNull(new GraphQLList(journeyPatternType))) .dataFetcher(env -> @@ -199,7 +200,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("estimatedCalls") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("List of visits to this quay as part of vehicle journeys.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(estimatedCallType)))) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java index 53621ee9b5a..b8fdf84d449 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java @@ -29,6 +29,7 @@ import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; @@ -227,7 +228,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("quays") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("Returns all quays that are children of this stop place") .type(new GraphQLList(quayType)) .argument( @@ -285,7 +286,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("estimatedCalls") - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description("List of visits to this stop place as part of vehicle journeys.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(estimatedCallType)))) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java index de0f86b1166..727dc37d99b 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java @@ -7,14 +7,14 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import org.opentripplanner.apis.transmodel.model.EnumTypes; -import org.opentripplanner.apis.transmodel.support.GqlUtil; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.transit.model.organization.ContactInfo; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; import org.opentripplanner.transit.model.timetable.booking.BookingTime; public class BookingArrangementType { - public static GraphQLObjectType create(GqlUtil gqlUtil) { + public static GraphQLObjectType create() { GraphQLOutputType contactType = GraphQLObjectType .newObject() .name("Contact") @@ -82,7 +82,7 @@ public static GraphQLObjectType create(GqlUtil gqlUtil) { .newFieldDefinition() .name("latestBookingTime") .description("Latest time the service can be booked. ISO 8601 timestamp") - .type(gqlUtil.localTimeScalar) + .type(TransmodelScalars.LOCAL_TIME_SCALAR) .dataFetcher(environment -> { final BookingTime latestBookingTime = (bookingInfo(environment)).getLatestBookingTime(); return latestBookingTime == null ? null : latestBookingTime.getTime(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java index c3c8ba420e4..0f4557fb755 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyQuery.java @@ -13,6 +13,7 @@ import java.util.stream.Stream; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.timetable.TripAlteration; @@ -38,10 +39,7 @@ public static GraphQLFieldDefinition createGetById(GraphQLOutputType datedServic .build(); } - public static GraphQLFieldDefinition createQuery( - GraphQLOutputType datedServiceJourneyType, - GqlUtil gqlUtil - ) { + public static GraphQLFieldDefinition createQuery(GraphQLOutputType datedServiceJourneyType) { return GraphQLFieldDefinition .newFieldDefinition() .name("datedServiceJourneys") @@ -69,7 +67,9 @@ public static GraphQLFieldDefinition createQuery( GraphQLArgument .newArgument() .name("operatingDays") - .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(gqlUtil.dateScalar)))) + .type( + new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TransmodelScalars.DATE_SCALAR))) + ) ) .argument( GraphQLArgument diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java index faeccf930c6..a7ef432a315 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Optional; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.TripTimeOnDateHelper; import org.opentripplanner.transit.model.network.TripPattern; @@ -33,8 +34,7 @@ public static GraphQLObjectType create( GraphQLOutputType serviceJourneyType, GraphQLOutputType journeyPatternType, GraphQLType estimatedCallType, - GraphQLType quayType, - GqlUtil gqlUtil + GraphQLType quayType ) { return GraphQLObjectType .newObject() @@ -48,7 +48,7 @@ public static GraphQLObjectType create( .description( "The date this service runs. The date used is based on the service date as opposed to calendar date." ) - .type(gqlUtil.dateScalar) + .type(TransmodelScalars.DATE_SCALAR) .dataFetcher(environment -> Optional .of(tripOnServiceDate(environment)) @@ -142,7 +142,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("estimatedCalls") .type(new GraphQLList(estimatedCallType)) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description( "Returns scheduled passingTimes for this dated service journey, " + "updated with real-time-updates (if available). " diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java index c0c96b1e393..dd5b8f89466 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java @@ -17,6 +17,7 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.model.TripTimeOnDate; @@ -41,8 +42,7 @@ public static GraphQLObjectType create( GraphQLOutputType ptSituationElementType, GraphQLOutputType journeyPatternType, GraphQLOutputType estimatedCallType, - GraphQLOutputType timetabledPassingTimeType, - GqlUtil gqlUtil + GraphQLOutputType timetabledPassingTimeType ) { return GraphQLObjectType .newObject() @@ -61,8 +61,8 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("activeDates") - .withDirective(gqlUtil.timingData) - .type(new GraphQLNonNull(new GraphQLList(gqlUtil.dateScalar))) + .withDirective(TransmodelScalars.TIMING_DATA) + .type(new GraphQLNonNull(new GraphQLList(TransmodelScalars.DATE_SCALAR))) .dataFetcher(environment -> GqlUtil .getTransitService(environment) @@ -235,7 +235,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("passingTimes") .type(new GraphQLNonNull(new GraphQLList(timetabledPassingTimeType))) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description( "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" ) @@ -254,7 +254,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("estimatedCalls") .type(new GraphQLList(estimatedCallType)) - .withDirective(gqlUtil.timingData) + .withDirective(TransmodelScalars.TIMING_DATA) .description( "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). " + "NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is " + @@ -264,7 +264,7 @@ public static GraphQLObjectType create( GraphQLArgument .newArgument() .name("date") - .type(gqlUtil.dateScalar) + .type(TransmodelScalars.DATE_SCALAR) .description("Date to get estimated calls for. Defaults to today.") .build() ) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java index 9370ed74d93..0f4a40f0750 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/TimetabledPassingTimeType.java @@ -8,6 +8,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLTypeReference; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.framework.application.OTPFeature; @@ -25,8 +26,7 @@ public static GraphQLObjectType create( GraphQLOutputType noticeType, GraphQLOutputType quayType, GraphQLOutputType destinationDisplayType, - GraphQLOutputType serviceJourneyType, - GqlUtil gqlUtil + GraphQLOutputType serviceJourneyType ) { return GraphQLObjectType .newObject() @@ -44,7 +44,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("arrival") - .type(gqlUtil.timeScalar) + .type(TransmodelScalars.TIME_SCALAR) .description("Scheduled time of arrival at quay") .dataFetcher(environment -> missingValueToNull(((TripTimeOnDate) environment.getSource()).getScheduledArrival()) @@ -55,7 +55,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("departure") - .type(gqlUtil.timeScalar) + .type(TransmodelScalars.TIME_SCALAR) .description("Scheduled time of departure from quay") .dataFetcher(environment -> missingValueToNull(((TripTimeOnDate) environment.getSource()).getScheduledDeparture()) @@ -111,7 +111,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("earliestDepartureTime") - .type(gqlUtil.timeScalar) + .type(TransmodelScalars.TIME_SCALAR) .description( "Earliest possible departure time for a service journey with a service window." ) @@ -131,7 +131,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("latestArrivalTime") - .type(gqlUtil.timeScalar) + .type(TransmodelScalars.TIME_SCALAR) .description( "Latest possible (planned) arrival time for a service journey with a service window." ) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java index 16083085500..78e22e3a6af 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java @@ -1,14 +1,11 @@ package org.opentripplanner.apis.transmodel.support; import graphql.Scalars; -import graphql.introspection.Introspection.DirectiveLocation; import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; -import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLScalarType; import java.time.ZoneId; import java.util.List; @@ -16,12 +13,7 @@ import org.opentripplanner.apis.transmodel.TransmodelRequestContext; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.apis.transmodel.model.scalars.DateTimeScalarFactory; -import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunctionFactory; -import org.opentripplanner.apis.transmodel.model.scalars.LocalTimeScalarFactory; -import org.opentripplanner.apis.transmodel.model.scalars.TimeScalarFactory; import org.opentripplanner.framework.graphql.GraphQLUtils; -import org.opentripplanner.framework.graphql.scalar.DateScalarFactory; -import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; @@ -34,29 +26,11 @@ public class GqlUtil { public final GraphQLScalarType dateTimeScalar; - public final GraphQLScalarType dateScalar; - public final GraphQLScalarType doubleFunctionScalar; - public final GraphQLScalarType localTimeScalar; - public final GraphQLObjectType timeScalar; - public final GraphQLScalarType durationScalar; - public final GraphQLDirective timingData; /** private to prevent util class from instantiation */ public GqlUtil(ZoneId timeZone) { this.dateTimeScalar = DateTimeScalarFactory.createMillisecondsSinceEpochAsDateTimeStringScalar(timeZone); - this.dateScalar = DateScalarFactory.createTransmodelDateScalar(); - this.doubleFunctionScalar = DoubleFunctionFactory.createDoubleFunctionScalar(); - this.localTimeScalar = LocalTimeScalarFactory.createLocalTimeScalar(); - this.timeScalar = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); - this.durationScalar = DurationScalarFactory.createDurationScalar(); - this.timingData = - GraphQLDirective - .newDirective() - .name("timingData") - .description("Add timing data to prometheus, if Actuator API is enabled") - .validLocation(DirectiveLocation.FIELD_DEFINITION) - .build(); } public static TransitService getTransitService(DataFetchingEnvironment environment) { From 2e3b77a48af04d447d84b771b2b959fe4a40f98e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 17:40:14 +0200 Subject: [PATCH 28/36] feature: Add VIA to Transmodel trip request --- .../transmodel/mapping/TripRequestMapper.java | 9 +- .../transmodel/mapping/ViaLocationMapper.java | 68 ++++++++- .../model/framework/OneOfDirective.java | 17 +++ .../apis/transmodel/model/plan/TripQuery.java | 15 ++ .../model/plan/ViaLocationInputType.java | 114 ++++++++++++++ .../framework/collection/CollectionUtils.java | 13 ++ .../routing/api/request/RouteRequest.java | 2 +- .../api/request/via/VisitViaLocation.java | 9 +- .../apis/transmodel/schema.graphql | 62 +++++++- .../mapping/ViaLocationMapperTest.java | 141 ++++++++++++++++++ .../collection/CollectionUtilsTest.java | 18 +++ .../mappers/RaptorRequestMapperTest.java | 4 +- 12 files changed, 459 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java create mode 100644 src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index 0c62b03c6a7..50c56328e2f 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -11,6 +11,7 @@ import java.util.Locale; import java.util.Map; import org.opentripplanner.apis.transmodel.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.RouteRequest; @@ -42,7 +43,13 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) { callWith.argument( "passThroughPoints", (List> v) -> { - request.setPassThroughPoints(ViaLocationMapper.toPassThroughLocations(v)); + request.setViaLocations(ViaLocationMapper.toLegacyPassThroughLocations(v)); + } + ); + callWith.argument( + TripQuery.FIELD_VIA, + (List> v) -> { + request.setViaLocations(ViaLocationMapper.mapToViaLocations(v)); } ); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java index 1ff09b742d5..92f37e023fc 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java @@ -2,24 +2,86 @@ import static java.util.stream.Collectors.toList; +import java.time.Duration; +import java.util.Collection; import java.util.List; import java.util.Map; +import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; +import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; +import org.opentripplanner.routing.api.request.via.VisitViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; class ViaLocationMapper { - static List toPassThroughLocations( + static List mapToViaLocations(final List> via) { + return via.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); + } + + /** + * @deprecated Legacy passThrough, use via instead + */ + @Deprecated + static List toLegacyPassThroughLocations( final List> passThroughPoints ) { return passThroughPoints .stream() - .map(ViaLocationMapper::mapPassThroughViaLocation) + .map(ViaLocationMapper::mapLegacyPassThroughViaLocation) .collect(toList()); } - private static ViaLocation mapPassThroughViaLocation(Map inputMap) { + private static ViaLocation mapViaLocation(Map inputMap) { + Map visit = (Map) inputMap.get( + ViaLocationInputType.FIELD_VISIT + ); + Map passThrough = (Map) inputMap.get( + ViaLocationInputType.FIELD_PASS_THROUGH + ); + + if (CollectionUtils.isEmpty(visit)) { + if (CollectionUtils.isEmpty(passThrough)) { + throw new IllegalArgumentException( + "Either 'visit' or 'passThrough' should be set in 'via' (@oneOf)." + ); + } else { + return mapPassThroughViaLocation(passThrough); + } + } else { + if (CollectionUtils.isEmpty(passThrough)) { + return mapVisitViaLocation(visit); + } else { + throw new IllegalArgumentException( + "Both 'visit' and 'passThrough' can not be set in 'via' (@oneOf)." + ); + } + } + } + + private static VisitViaLocation mapVisitViaLocation(Map inputMap) { + var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); + var minimumWaitTime = (Duration) inputMap.get(ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME); + var stopLocationIds = mapStopLocationIds(inputMap); + return new VisitViaLocation(label, minimumWaitTime, stopLocationIds, List.of()); + } + + private static PassThroughViaLocation mapPassThroughViaLocation(Map inputMap) { + var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); + var stopLocationIds = mapStopLocationIds(inputMap); + return new PassThroughViaLocation(label, stopLocationIds); + } + + private static List mapStopLocationIds(Map map) { + var c = (Collection) map.get(ViaLocationInputType.FIELD_STOP_LOCATION_IDS); + return c.stream().map(TransitIdMapper::mapIDToDomain).toList(); + } + + /** + * @deprecated Legacy passThrough, use via instead + */ + @Deprecated + private static ViaLocation mapLegacyPassThroughViaLocation(Map inputMap) { final String name = (String) inputMap.get("name"); final List stopLocationIds = ((List) inputMap.get("placeIds")).stream() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java new file mode 100644 index 00000000000..7dd035b0924 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java @@ -0,0 +1,17 @@ +package org.opentripplanner.apis.transmodel.model.framework; + +import graphql.introspection.Introspection; +import graphql.schema.GraphQLDirective; + +public class OneOfDirective { + + public static final GraphQLDirective ONE_OF_DIRECTIVE = GraphQLDirective + .newDirective() + .description("One and only one of the fields in the (input) type must be non null.") + .validLocations( + Introspection.DirectiveLocation.INPUT_OBJECT, + Introspection.DirectiveLocation.OBJECT + ) + .name("OneOf") + .build(); +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index f29c06e533d..a5faa14361e 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -28,6 +28,12 @@ public class TripQuery { public static final String ACCESS_EGRESS_PENALTY = "accessEgressPenalty"; public static final String MAX_ACCESS_EGRESS_DURATION_FOR_MODE = "maxAccessEgressDurationForMode"; public static final String MAX_DIRECT_DURATION_FOR_MODE = "maxDirectDurationForMode"; + public static final String FIELD_VIA = "via"; + public static final String DOC_VIA = + """ + The list of via locations the journey is required to visit. All locations are + visited in the order they are listed. + """; public static GraphQLFieldDefinition create( DefaultRouteRequestType routing, @@ -174,10 +180,19 @@ Normally this is when the search is performed (now), plus a small grace period t GraphQLArgument .newArgument() .name("passThroughPoints") + .deprecate("Use via instead") .description("The list of points the journey is required to pass through.") .type(new GraphQLList(new GraphQLNonNull(PassThroughPointInputType.INPUT_TYPE))) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name(FIELD_VIA) + .description(DOC_VIA) + .type(new GraphQLList(new GraphQLNonNull(ViaLocationInputType.VIA_LOCATION_INPUT))) + .build() + ) .argument( GraphQLArgument .newArgument() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java new file mode 100644 index 00000000000..f866f57fe74 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java @@ -0,0 +1,114 @@ +package org.opentripplanner.apis.transmodel.model.plan; + +import static org.opentripplanner.apis.transmodel.model.framework.OneOfDirective.ONE_OF_DIRECTIVE; + +import graphql.Scalars; +import graphql.language.StringValue; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLList; +import java.time.Duration; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; + +public class ViaLocationInputType { + + /* type constants */ + + private static final String INPUT_VIA_LOCATION = "PlanViaLocationInput"; + private static final String INPUT_VISIT_VIA_LOCATION = "PlanVisitViaLocationInput"; + private static final String INPUT_PASS_THROUGH_VIA_LOCATION = "PlanPassThroughViaLocationInput"; + + private static final String DOC_VISIT_VIA_LOCATION = + """ + A visit-via-location is a physical visit to one of the stops or coordinates listed. An + on-board visit does not count, the traveler must alight or board at the given stop for + it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to + the closest point in the street network from a stop and back to another stop to join + the transit network. + + NOTE! Coordinates are NOT supported jet. + """; + private static final String DOC_PASS_THROUGH_VIA_LOCATION = + """ + One of the listed stop locations must be visited on-board a transit vehicle or the journey must + alight or board at the location. + """; + private static final String DOC_VIA_LOCATION = + """ + A via-location is used to specifying a location as an intermediate place the router must + route through. The via-location must be either a pass-through-location or a + visit-via-location. An on-board "visit" is only allowed for pass-through-via-locations, while + the visit-via-location can visit a stop-location or a coordinate and specify a + minimum-wait-time. + """; + + /* field constants */ + + public static final String FIELD_LABEL = "label"; + public static final String FIELD_MINIMUM_WAIT_TIME = "minimumWaitTime"; + public static final String FIELD_STOP_LOCATION_IDS = "stopLocationIds"; + + // TODO : Add coordinates + //private static final String FIELD_COORDINATES = "coordinates"; + public static final String FIELD_VISIT = "visit"; + public static final String FIELD_PASS_THROUGH = "passThrough"; + + private static final String DOC_LABEL = + "The label/name of the location. This is pass-through " + + "information and is not used in routing."; + private static final String DOC_MINIMUM_WAIT_TIME = + """ + The minimum wait time is used to force the trip to stay the given duration at the + via-location before the trip is continued. + """; + private static final String DOC_STOP_LOCATION_IDS = + """ + A list of stop locations. A stop location can be a quay, a stop place, a multimodal + stop place or a group of stop places. It is enough to visit ONE of the locations + listed. + """; + + static final GraphQLInputObjectType VISIT_VIA_LOCATION_INPUT = GraphQLInputObjectType + .newInputObject() + .name(INPUT_VISIT_VIA_LOCATION) + .description(DOC_VISIT_VIA_LOCATION) + .field(b -> b.name(FIELD_LABEL).description(DOC_LABEL).type(Scalars.GraphQLString)) + .field(b -> + b + .name(FIELD_MINIMUM_WAIT_TIME) + .description(DOC_MINIMUM_WAIT_TIME) + .type(TransmodelScalars.DURATION_SCALAR) + .defaultValueLiteral(StringValue.of(Duration.ZERO.toString())) + ) + .field(b -> + b + .name(FIELD_STOP_LOCATION_IDS) + .description(DOC_STOP_LOCATION_IDS) + .type(GraphQLList.list(Scalars.GraphQLString)) + ) + /* + TODO: Add support for coordinates + */ + .build(); + + static final GraphQLInputObjectType PASS_THROUGH_VIA_LOCATION_INPUT = GraphQLInputObjectType + .newInputObject() + .name(INPUT_PASS_THROUGH_VIA_LOCATION) + .description(DOC_PASS_THROUGH_VIA_LOCATION) + .field(b -> b.name(FIELD_LABEL).description(DOC_LABEL).type(Scalars.GraphQLString)) + .field(b -> + b + .name(FIELD_STOP_LOCATION_IDS) + .description(DOC_STOP_LOCATION_IDS) + .type(GraphQLList.list(Scalars.GraphQLString)) + ) + .build(); + + public static final GraphQLInputObjectType VIA_LOCATION_INPUT = GraphQLInputObjectType + .newInputObject() + .name(INPUT_VIA_LOCATION) + .description(DOC_VIA_LOCATION) + .withDirective(ONE_OF_DIRECTIVE) + .field(b -> b.name(FIELD_VISIT).type(VISIT_VIA_LOCATION_INPUT)) + .field(b -> b.name(FIELD_PASS_THROUGH).type(PASS_THROUGH_VIA_LOCATION_INPUT)) + .build(); +} diff --git a/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java b/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java index 1e86f49770f..856542bc87b 100644 --- a/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java +++ b/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java @@ -47,6 +47,19 @@ public static boolean isEmpty(@Nullable Collection c) { return c == null || c.isEmpty(); } + /** + * A null-safe version of isEmpty() for a collection. + *

+ * If the collection is {@code null} then {@code true} is returned. + *

+ * If the collection is empty then {@code true} is returned. + *

+ * Otherwise {@code false} is returned. + */ + public static boolean isEmpty(@Nullable Map m) { + return m == null || m.isEmpty(); + } + /** * Look up the given key in a Map, return null if the key is null. * This prevents a NullPointerException if the underlying implementation of the map does not diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 1eb24e92d60..aa5253a2e94 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -283,7 +283,7 @@ public List getViaLocations() { return via; } - public void setPassThroughPoints(final List via) { + public void setViaLocations(final List via) { this.via = via; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java b/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java index 63cf57a538f..3bd62a32a0b 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java +++ b/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java @@ -10,11 +10,10 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; /** - * A visit via location is a physical visit to one of the stops or coordinates listed. An - * on-board visit is not accepted, the traveler must alight or board at the given stop - * for it to count. To visit a coordinate, the traveler must walk(bike or drive) to the - * closest point in the street network from a stop and back to another stop to join the - * transit network. + * A visit-via-location is a physical visit to one of the stops or coordinates listed. An on-board + * visit does not count , the traveler must alight or board at the given stop for it to to be + * accepted. To visit a coordinate, the traveler must walk(bike or drive) to the closest point in + * the street network from a stop and back to another stop to join the transit network. *

* TODO: NOTE! Coordinates are NOT supported jet. */ diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index dfc39cd30d6..c0b193a0d6e 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -2,6 +2,9 @@ schema { query: QueryType } +"One and only one of the fields in the (input) type must be non null." +directive @OneOf on OBJECT | INPUT_OBJECT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -834,7 +837,7 @@ type QueryType { "Use the cursor to go to the next \"page\" of itineraries. Copy the cursor from the last response and keep the original request as is. This will enable you to search for itineraries in the next or previous time-window." pageCursor: String, "The list of points the journey is required to pass through." - passThroughPoints: [PassThroughPoint!], + passThroughPoints: [PassThroughPoint!] @deprecated(reason : "Use via instead"), """ Relax generalized-cost when comparing trips with a different set of transit-group-priorities. The groups are set server side for service-journey and @@ -900,6 +903,11 @@ type QueryType { triangleFactors: TriangleFactors, "Whether or not bike rental availability information will be used to plan bike rental trips." useBikeRentalAvailabilityInformation: Boolean = false, + """ + The list of via locations the journey is required to visit. All locations are + visited in the order they are listed. + """ + via: [PlanViaLocationInput!], "Wait cost is multiplied by this value. Setting this to a value lower than 1 indicates that waiting is better than staying on a vehicle. This should never be set higher than walkReluctance, since that would lead to walking down a line to avoid waiting." waitReluctance: Float = 1.0, "Walk cost is multiplied by this value. This is the main parameter to use for limiting walking." @@ -2092,6 +2100,58 @@ input PenaltyForStreetMode { timePenalty: DoubleFunction! } +""" +One of the listed stop locations must be visited on-board a transit vehicle or the journey must +alight or board at the location. +""" +input PlanPassThroughViaLocationInput { + "The label/name of the location. This is pass-through information and is not used in routing." + label: String + """ + A list of stop locations. A stop location can be a quay, a stop place, a multimodal + stop place or a group of stop places. It is enough to visit ONE of the locations + listed. + """ + stopLocationIds: [String] +} + +""" +A via-location is used to specifying a location as an intermediate place the router must +route through. The via-location must be either a pass-through-location or a +visit-via-location. An on-board "visit" is only allowed for pass-through-via-locations, while +the visit-via-location can visit a stop-location or a coordinate and specify a +minimum-wait-time. +""" +input PlanViaLocationInput @OneOf { + passThrough: PlanPassThroughViaLocationInput + visit: PlanVisitViaLocationInput +} + +""" +A visit-via-location is a physical visit to one of the stops or coordinates listed. An +on-board visit does not count, the traveler must alight or board at the given stop for +it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to +the closest point in the street network from a stop and back to another stop to join +the transit network. + +NOTE! Coordinates are NOT supported jet. +""" +input PlanVisitViaLocationInput { + "The label/name of the location. This is pass-through information and is not used in routing." + label: String + """ + The minimum wait time is used to force the trip to stay the given duration at the + via-location before the trip is continued. + """ + minimumWaitTime: Duration = "PT0S" + """ + A list of stop locations. A stop location can be a quay, a stop place, a multimodal + stop place or a group of stop places. It is enough to visit ONE of the locations + listed. + """ + stopLocationIds: [String] +} + """ A relax-cost is used to increase the limit when comparing one cost to another cost. This is used to include more results into the result. A `ratio=2.0` means a path(itinerary) diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java new file mode 100644 index 00000000000..7a4bf2f480d --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java @@ -0,0 +1,141 @@ +package org.opentripplanner.apis.transmodel.mapping; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_LABEL; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_PASS_THROUGH; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_STOP_LOCATION_IDS; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_VISIT; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ViaLocationMapperTest { + + public static final String LABEL = "TestLabel"; + public static final Duration MIN_WAIT_TIME = Duration.ofMinutes(5); + public static final List LIST_IDS_INPUT = List.of("F:ID1", "F:ID2"); + public static final String EXPECTED_IDS_AS_STRING = "[F:ID1, F:ID2]"; + + @BeforeEach + void setup() { + TransitIdMapper.clearFixedFeedId(); + } + + @Test + void testMapToVisitViaLocations() { + Map input = Map.ofEntries( + entry(FIELD_VISIT, visitInput(LABEL, MIN_WAIT_TIME, LIST_IDS_INPUT)) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + + var via = result.getFirst(); + + assertEquals(LABEL, via.label()); + assertEquals(MIN_WAIT_TIME, via.minimumWaitTime()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertFalse(via.isPassThroughLocation()); + assertEquals( + "[VisitViaLocation{label: TestLabel, minimumWaitTime: 5m, stopLocationIds: [F:ID1, F:ID2], coordinates: []}]", + result.toString() + ); + } + + @Test + void testMapToVisitViaLocationsWithBareMinimum() { + Map input = Map.of( + FIELD_VISIT, + Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + + var via = result.getFirst(); + + assertNull(via.label()); + assertEquals(Duration.ZERO, via.minimumWaitTime()); + assertEquals("[F:1]", via.stopLocationIds().toString()); + assertFalse(via.isPassThroughLocation()); + } + + @Test + void tetMapToPassThrough() { + Map input = Map.of(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var via = result.getFirst(); + + assertEquals(LABEL, via.label()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + assertEquals( + "PassThroughViaLocation{label: TestLabel, stopLocationIds: [F:ID1, F:ID2]}", + via.toString() + ); + } + + @Test + void tetMapToPassThroughWithBareMinimum() { + Map input = Map.of( + FIELD_PASS_THROUGH, + Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var via = result.getFirst(); + + assertNull(via.label()); + assertEquals("[F:1]", via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + } + + @Test + void testOneOf() { + Map input = Map.ofEntries( + entry(FIELD_VISIT, visitInput("A", Duration.ofMinutes(1), List.of("F:99"))), + entry(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)) + ); + var ex = assertThrows( + IllegalArgumentException.class, + () -> ViaLocationMapper.mapToViaLocations(List.of(input)) + ); + assertEquals( + "Both 'visit' and 'passThrough' can not be set in 'via' (@oneOf).", + ex.getMessage() + ); + + ex = + assertThrows( + IllegalArgumentException.class, + () -> ViaLocationMapper.mapToViaLocations(List.of(Map.of())) + ); + assertEquals( + "Either 'visit' or 'passThrough' should be set in 'via' (@oneOf).", + ex.getMessage() + ); + } + + private Map visitInput(String label, Duration minWaitTime, List ids) { + var map = new HashMap(); + if (label != null) { + map.put(FIELD_LABEL, label); + } + if (minWaitTime != null) { + map.put(FIELD_MINIMUM_WAIT_TIME, minWaitTime); + } + if (ids != null) { + map.put(FIELD_STOP_LOCATION_IDS, ids); + } + return map; + } + + private Map passThroughInput(String label, List ids) { + return visitInput(label, null, ids); + } +} diff --git a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java index ae33e493bcc..51e827fad09 100644 --- a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java @@ -1,13 +1,17 @@ package org.opentripplanner.framework.collection; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.type.Month; import java.time.Duration; import java.util.ArrayList; +import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; @@ -40,4 +44,18 @@ void testToString() { Set set = new HashSet<>(list); assertEquals("[, APRIL, JUNE, PT3H]", CollectionUtils.toString(set, NULL_STRING)); } + + @Test + void testIsEmptyMap() { + assertTrue(CollectionUtils.isEmpty((Map) null)); + assertTrue(CollectionUtils.isEmpty(Map.of())); + assertFalse(CollectionUtils.isEmpty(Map.of(1, 1))); + } + + @Test + void testIsEmptyCollection() { + assertTrue(CollectionUtils.isEmpty((Collection) null)); + assertTrue(CollectionUtils.isEmpty(List.of())); + assertFalse(CollectionUtils.isEmpty(Set.of(1))); + } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index b3c84ada2dd..f716db062d4 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -59,7 +59,7 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { void testPassThroughPoints() { var req = new RouteRequest(); - req.setPassThroughPoints(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); + req.setViaLocations(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); var result = map(req); @@ -75,7 +75,7 @@ void testPassThroughPointsTurnTransitGroupPriorityOff() { var req = new RouteRequest(); // Set pass-through and relax transit-group-priority - req.setPassThroughPoints(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); + req.setViaLocations(List.of(new PassThroughViaLocation("Via A", List.of(STOP_A.getId())))); req.withPreferences(p -> p.withTransit(t -> t.withRelaxTransitGroupPriority(CostLinearFunction.of("30m + 1.2t"))) ); From 05ad9ad5831bc2b64616650a5060d6697ca80826 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 18:22:23 +0200 Subject: [PATCH 29/36] refactor: Move @timingData directive out of Scalars in Transmodel API --- .../transmodel/TransmodelGraphQLSchema.java | 75 +++++++------------ .../model/framework/AuthorityType.java | 4 +- .../model/framework/OperatorType.java | 4 +- .../model/framework/TransmodelDirectives.java | 14 ++++ .../model/framework/TransmodelScalars.java | 10 --- .../model/network/JourneyPatternType.java | 5 +- .../apis/transmodel/model/plan/LegType.java | 9 ++- .../apis/transmodel/model/plan/TripQuery.java | 4 +- .../model/plan/legacyvia/ViaTripQuery.java | 3 +- .../model/siri/et/EstimatedCallType.java | 3 +- .../apis/transmodel/model/stop/QuayType.java | 8 +- .../transmodel/model/stop/StopPlaceType.java | 6 +- .../timetable/DatedServiceJourneyType.java | 3 +- .../model/timetable/ServiceJourneyType.java | 7 +- .../apis/transmodel/schema.graphql | 5 +- 15 files changed, 74 insertions(+), 86 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelDirectives.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 90708dcd315..ed69cb44e59 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -58,6 +58,7 @@ import org.opentripplanner.apis.transmodel.model.framework.ServerInfoType; import org.opentripplanner.apis.transmodel.model.framework.StreetModeDurationInputType; import org.opentripplanner.apis.transmodel.model.framework.SystemNoticeType; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.model.framework.ValidityPeriodType; import org.opentripplanner.apis.transmodel.model.network.DestinationDisplayType; @@ -142,28 +143,8 @@ public static GraphQLSchema create(RouteRequest defaultRequest, GqlUtil gqlUtil) return new TransmodelGraphQLSchema(defaultRequest, gqlUtil).create(); } - // private BookingArrangement getBookingArrangementForTripTimeShort(TripTimeShort tripTimeShort) { - // Trip trip = index.tripForId.get(tripTimeShort.tripId); - // if (trip == null) { - // return null; - // } - // TripPattern tripPattern = index.patternForTrip.get(trip); - // if (tripPattern == null || tripPattern.stopPattern == null) { - // return null; - // } - // return tripPattern.stopPattern.bookingArrangements[tripTimeShort.stopIndex]; - // } - @SuppressWarnings("unchecked") private GraphQLSchema create() { - /* - multilingualStringType, validityPeriodType, infoLinkType, bookingArrangementType, systemNoticeType, - linkGeometryType, serverInfoType, authorityType, operatorType, noticeType - - - - */ - // Framework GraphQLOutputType multilingualStringType = MultilingualStringType.create(); GraphQLObjectType validityPeriodType = ValidityPeriodType.create(gqlUtil); @@ -426,7 +407,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlace") .description("Get a single stopPlace based on its id)") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(stopPlaceType) .argument( GraphQLArgument @@ -448,7 +429,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlaces") .description("Get all stopPlaces") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(stopPlaceType))) .argument( GraphQLArgument @@ -483,7 +464,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("stopPlacesByBbox") .description("Get all stop places within the specified bounding box") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(stopPlaceType))) .argument( GraphQLArgument @@ -564,7 +545,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quay") .description("Get a single quay based on its id)") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(quayType) .argument( GraphQLArgument @@ -585,7 +566,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quays") .description("Get all quays") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(quayType))) .argument( GraphQLArgument @@ -630,7 +611,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("quaysByBbox") .description("Get all quays within the specified bounding box") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(quayType))) .argument( GraphQLArgument @@ -713,7 +694,7 @@ private GraphQLSchema create() { "limits for the input parameters, but the query will timeout and return if the parameters " + "are too high." ) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type( relay.connectionType( "quayAtDistance", @@ -799,7 +780,7 @@ private GraphQLSchema create() { .description( "Get all places (quays, stop places, car parks etc. with coordinates) within the specified radius from a location. The returned type has two fields place and distance. The search is done by walking so the distance is according to the network of walkables." ) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type( relay.connectionType( "placeAtDistance", @@ -999,7 +980,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("authority") .description("Get an authority by ID") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(authorityType) .argument( GraphQLArgument @@ -1020,7 +1001,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("authorities") .description("Get all authorities") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(authorityType))) .dataFetcher(environment -> { return new ArrayList<>(GqlUtil.getTransitService(environment).getAgencies()); @@ -1032,7 +1013,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("operator") .description("Get a operator by ID") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(operatorType) .argument( GraphQLArgument @@ -1053,7 +1034,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("operators") .description("Get all operators") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(operatorType))) .dataFetcher(environment -> { return new ArrayList<>(GqlUtil.getTransitService(environment).getAllOperators()); @@ -1065,7 +1046,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("line") .description("Get a single line based on its id") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(lineType) .argument( GraphQLArgument @@ -1090,7 +1071,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("lines") .description("Get all lines") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .argument( GraphQLArgument @@ -1243,7 +1224,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serviceJourney") .description("Get a single service journey based on its id") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(serviceJourneyType) .argument( GraphQLArgument @@ -1264,7 +1245,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serviceJourneys") .description("Get all service journeys") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(serviceJourneyType))) .argument( GraphQLArgument @@ -1341,7 +1322,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStations") .description("Get all bike rental stations") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .argument( GraphQLArgument .newArgument() @@ -1370,7 +1351,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStation") .description("Get all bike rental stations") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(bikeRentalStationType) .argument( GraphQLArgument @@ -1397,7 +1378,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeRentalStationsByBbox") .description("Get all bike rental stations within the specified bounding box.") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(bikeRentalStationType))) .argument( GraphQLArgument.newArgument().name("minimumLatitude").type(Scalars.GraphQLFloat).build() @@ -1436,7 +1417,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikePark") .description("Get a single bike park based on its id") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(bikeParkType) .argument( GraphQLArgument @@ -1461,7 +1442,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("bikeParks") .description("Get all bike parks") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(bikeParkType))) .dataFetcher(environment -> GqlUtil @@ -1476,7 +1457,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("routingParameters") .description("Get default routing parameters.") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(this.routing.graphQLType) .dataFetcher(environment -> routing.request) .build() @@ -1486,7 +1467,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("situations") .description("Get all active situations.") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .argument( GraphQLArgument @@ -1562,7 +1543,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("situation") .description("Get a single situation based on its situationNumber") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(ptSituationElementType) .argument( GraphQLArgument @@ -1588,7 +1569,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("leg") .description("Refetch a single leg based on its id") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(LegType.REF) .argument( GraphQLArgument @@ -1615,7 +1596,7 @@ private GraphQLSchema create() { .newFieldDefinition() .name("serverInfo") .description("Get OTP server information") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(serverInfoType)) .dataFetcher(e -> projectInfo()) .build() @@ -1630,7 +1611,7 @@ private GraphQLSchema create() { .additionalType(placeInterface) .additionalType(timetabledPassingTime) .additionalType(Relay.pageInfoType) - .additionalDirective(TransmodelScalars.TIMING_DATA) + .additionalDirective(TransmodelDirectives.TIMING_DATA) .build(); } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java index 11e2198829b..baae947e345 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/AuthorityType.java @@ -64,7 +64,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .dataFetcher(environment -> getTransitService(environment) @@ -79,7 +79,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("situations") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("Get all situations active for the authority.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .dataFetcher(environment -> diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java index 94fb19879b1..8f55bea52ab 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OperatorType.java @@ -47,7 +47,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .dataFetcher(environment -> GqlUtil @@ -63,7 +63,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourney") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(serviceJourneyType))) .dataFetcher(environment -> GqlUtil diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelDirectives.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelDirectives.java new file mode 100644 index 00000000000..98ee99a8a64 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelDirectives.java @@ -0,0 +1,14 @@ +package org.opentripplanner.apis.transmodel.model.framework; + +import graphql.introspection.Introspection; +import graphql.schema.GraphQLDirective; + +public class TransmodelDirectives { + + public static final GraphQLDirective TIMING_DATA = GraphQLDirective + .newDirective() + .name("timingData") + .description("Add timing data to prometheus, if Actuator API is enabled") + .validLocation(Introspection.DirectiveLocation.FIELD_DEFINITION) + .build(); +} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java index 86592cba99d..404fbd4a5ba 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/TransmodelScalars.java @@ -1,7 +1,5 @@ package org.opentripplanner.apis.transmodel.model.framework; -import graphql.introspection.Introspection; -import graphql.schema.GraphQLDirective; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLScalarType; import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunctionFactory; @@ -21,7 +19,6 @@ public class TransmodelScalars { public static final GraphQLScalarType LOCAL_TIME_SCALAR; public static final GraphQLObjectType TIME_SCALAR; public static final GraphQLScalarType DURATION_SCALAR; - public static final GraphQLDirective TIMING_DATA; static { DATE_SCALAR = DateScalarFactory.createTransmodelDateScalar(); @@ -29,12 +26,5 @@ public class TransmodelScalars { LOCAL_TIME_SCALAR = LocalTimeScalarFactory.createLocalTimeScalar(); TIME_SCALAR = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); DURATION_SCALAR = DurationScalarFactory.createDurationScalar(); - TIMING_DATA = - GraphQLDirective - .newDirective() - .name("timingData") - .description("Add timing data to prometheus, if Actuator API is enabled") - .validLocation(Introspection.DirectiveLocation.FIELD_DEFINITION) - .build(); } } diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java index 7e6392e29f3..4ed3871ff8c 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/JourneyPatternType.java @@ -16,6 +16,7 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.apis.transmodel.mapping.GeometryMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; @@ -68,7 +69,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneys") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(serviceJourneyType)))) .dataFetcher(e -> ((TripPattern) e.getSource()).scheduledTripsAsStream().collect(Collectors.toList()) @@ -79,7 +80,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneysForDate") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("List of service journeys for the journey pattern for a given date") .argument( GraphQLArgument.newArgument().name("date").type(TransmodelScalars.DATE_SCALAR).build() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java index 4109c1f925c..ec7b736f7a6 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java @@ -22,6 +22,7 @@ import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.apis.transmodel.model.TripTimeOnDateHelper; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; @@ -269,7 +270,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("fromEstimatedCall") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("EstimatedCall for the quay where the leg originates.") .type(estimatedCallType) .dataFetcher(env -> TripTimeOnDateHelper.getTripTimeOnDateForFromPlace(env.getSource())) @@ -279,7 +280,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("toEstimatedCall") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("EstimatedCall for the quay where the leg ends.") .type(estimatedCallType) .dataFetcher(env -> TripTimeOnDateHelper.getTripTimeOnDateForToPlace(env.getSource())) @@ -353,7 +354,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("intermediateEstimatedCalls") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description( "For ride legs, estimated calls for quays between the Place where the leg originates and the Place where the leg ends. For non-ride legs, empty list." ) @@ -367,7 +368,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("serviceJourneyEstimatedCalls") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description( "For ride legs, all estimated calls for the service journey. For non-ride legs, empty list." ) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java index a5faa14361e..24acaecda4a 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java @@ -18,7 +18,7 @@ import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; import org.opentripplanner.apis.transmodel.model.framework.PassThroughPointInputType; import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType; -import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; @@ -52,7 +52,7 @@ public static GraphQLFieldDefinition create( "trip patterns describing suggested alternatives for the trip." ) .type(new GraphQLNonNull(tripType)) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .argument( GraphQLArgument .newArgument() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java index 27f7df1d19f..e0616354a99 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/legacyvia/ViaTripQuery.java @@ -11,6 +11,7 @@ import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.framework.LocationInputType; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; @@ -31,7 +32,7 @@ public static GraphQLFieldDefinition create( ) .deprecate("The the regular plan query with via stop instead.") .type(new GraphQLNonNull(viaTripType)) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .argument( GraphQLArgument .newArgument() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java index 87cb23e926e..612a0faa4b0 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/siri/et/EstimatedCallType.java @@ -17,6 +17,7 @@ import java.util.Set; import org.opentripplanner.apis.transmodel.mapping.OccupancyStatusMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; @@ -327,7 +328,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("situations") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ptSituationElementType)))) .description("Get all relevant situations for this EstimatedCall.") .dataFetcher(environment -> diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java index 26b7101d076..ae01a9013f6 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayType.java @@ -19,7 +19,7 @@ import java.util.Optional; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.apis.transmodel.model.EnumTypes; -import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; import org.opentripplanner.apis.transmodel.model.scalars.GeoJSONCoordinatesScalar; import org.opentripplanner.apis.transmodel.support.GqlUtil; @@ -170,7 +170,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("List of lines servicing this quay") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(lineType)))) .dataFetcher(env -> @@ -188,7 +188,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("journeyPatterns") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("List of journey patterns servicing this quay") .type(new GraphQLNonNull(new GraphQLList(journeyPatternType))) .dataFetcher(env -> @@ -200,7 +200,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("estimatedCalls") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("List of visits to this quay as part of vehicle journeys.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(estimatedCallType)))) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java index b8fdf84d449..57a1a81bcde 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java @@ -29,7 +29,7 @@ import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; -import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.plan.JourneyWhiteListed; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.graphql.GraphQLUtils; @@ -228,7 +228,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("quays") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("Returns all quays that are children of this stop place") .type(new GraphQLList(quayType)) .argument( @@ -286,7 +286,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("estimatedCalls") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description("List of visits to this stop place as part of vehicle journeys.") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(estimatedCallType)))) .argument( diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java index a7ef432a315..7fcce88c4ee 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Optional; import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.routing.TripTimeOnDateHelper; @@ -142,7 +143,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("estimatedCalls") .type(new GraphQLList(estimatedCallType)) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description( "Returns scheduled passingTimes for this dated service journey, " + "updated with real-time-updates (if available). " diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java index dd5b8f89466..e61d0a12edc 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java @@ -17,6 +17,7 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; +import org.opentripplanner.apis.transmodel.model.framework.TransmodelDirectives; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.apis.transmodel.support.GqlUtil; import org.opentripplanner.framework.geometry.EncodedPolyline; @@ -61,7 +62,7 @@ public static GraphQLObjectType create( GraphQLFieldDefinition .newFieldDefinition() .name("activeDates") - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(TransmodelScalars.DATE_SCALAR))) .dataFetcher(environment -> GqlUtil @@ -235,7 +236,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("passingTimes") .type(new GraphQLNonNull(new GraphQLList(timetabledPassingTimeType))) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description( "Returns scheduled passing times only - without real-time-updates, for realtime-data use 'estimatedCalls'" ) @@ -254,7 +255,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("estimatedCalls") .type(new GraphQLList(estimatedCallType)) - .withDirective(TransmodelScalars.TIMING_DATA) + .withDirective(TransmodelDirectives.TIMING_DATA) .description( "Returns scheduled passingTimes for this ServiceJourney for a given date, updated with real-time-updates (if available). " + "NB! This takes a date as argument (default=today) and returns estimatedCalls for that date and should only be used if the date is " + diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index c0b193a0d6e..fa013e40eb2 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -2,9 +2,6 @@ schema { query: QueryType } -"One and only one of the fields in the (input) type must be non null." -directive @OneOf on OBJECT | INPUT_OBJECT - "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -2122,7 +2119,7 @@ visit-via-location. An on-board "visit" is only allowed for pass-through-via-loc the visit-via-location can visit a stop-location or a coordinate and specify a minimum-wait-time. """ -input PlanViaLocationInput @OneOf { +input PlanViaLocationInput @oneOf { passThrough: PlanPassThroughViaLocationInput visit: PlanVisitViaLocationInput } From f1e2f9f9e365b2335653f563d64ec2233bc48800 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 18:26:26 +0200 Subject: [PATCH 30/36] feature: Add @oneO directive to Transmodel Schema --- .../transmodel/mapping/TripRequestMapper.java | 4 +- ...Mapper.java => TripViaLocationMapper.java} | 41 +++++-------- .../model/framework/OneOfDirective.java | 17 ------ .../model/plan/ViaLocationInputType.java | 4 +- .../support/OneOfInputValidator.java | 60 +++++++++++++++++++ ...st.java => TripViaLocationMapperTest.java} | 18 +++--- .../support/OneOfInputValidatorTest.java | 53 ++++++++++++++++ 7 files changed, 142 insertions(+), 55 deletions(-) rename src/main/java/org/opentripplanner/apis/transmodel/mapping/{ViaLocationMapper.java => TripViaLocationMapper.java} (71%) delete mode 100644 src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java create mode 100644 src/main/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidator.java rename src/test/java/org/opentripplanner/apis/transmodel/mapping/{ViaLocationMapperTest.java => TripViaLocationMapperTest.java} (87%) create mode 100644 src/test/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidatorTest.java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index 50c56328e2f..a0438ef00d6 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -43,13 +43,13 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) { callWith.argument( "passThroughPoints", (List> v) -> { - request.setViaLocations(ViaLocationMapper.toLegacyPassThroughLocations(v)); + request.setViaLocations(TripViaLocationMapper.toLegacyPassThroughLocations(v)); } ); callWith.argument( TripQuery.FIELD_VIA, (List> v) -> { - request.setViaLocations(ViaLocationMapper.mapToViaLocations(v)); + request.setViaLocations(TripViaLocationMapper.mapToViaLocations(v)); } ); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java similarity index 71% rename from src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java rename to src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java index 92f37e023fc..ec4846bf514 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapper.java @@ -6,17 +6,18 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.opentripplanner.apis.transmodel.model.plan.TripQuery; import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; -import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.apis.transmodel.support.OneOfInputValidator; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.api.request.via.VisitViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; -class ViaLocationMapper { +class TripViaLocationMapper { static List mapToViaLocations(final List> via) { - return via.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); + return via.stream().map(TripViaLocationMapper::mapViaLocation).collect(toList()); } /** @@ -28,35 +29,25 @@ static List toLegacyPassThroughLocations( ) { return passThroughPoints .stream() - .map(ViaLocationMapper::mapLegacyPassThroughViaLocation) + .map(TripViaLocationMapper::mapLegacyPassThroughViaLocation) .collect(toList()); } private static ViaLocation mapViaLocation(Map inputMap) { - Map visit = (Map) inputMap.get( - ViaLocationInputType.FIELD_VISIT - ); - Map passThrough = (Map) inputMap.get( + var fieldName = OneOfInputValidator.validateOneOf( + inputMap, + TripQuery.FIELD_VIA, + ViaLocationInputType.FIELD_VISIT, ViaLocationInputType.FIELD_PASS_THROUGH ); - if (CollectionUtils.isEmpty(visit)) { - if (CollectionUtils.isEmpty(passThrough)) { - throw new IllegalArgumentException( - "Either 'visit' or 'passThrough' should be set in 'via' (@oneOf)." - ); - } else { - return mapPassThroughViaLocation(passThrough); - } - } else { - if (CollectionUtils.isEmpty(passThrough)) { - return mapVisitViaLocation(visit); - } else { - throw new IllegalArgumentException( - "Both 'visit' and 'passThrough' can not be set in 'via' (@oneOf)." - ); - } - } + Map value = (Map) inputMap.get(fieldName); + + return switch (fieldName) { + case ViaLocationInputType.FIELD_VISIT -> mapVisitViaLocation(value); + case ViaLocationInputType.FIELD_PASS_THROUGH -> mapPassThroughViaLocation(value); + default -> throw new IllegalArgumentException("Unknown field: " + fieldName); + }; } private static VisitViaLocation mapVisitViaLocation(Map inputMap) { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java deleted file mode 100644 index 7dd035b0924..00000000000 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/framework/OneOfDirective.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opentripplanner.apis.transmodel.model.framework; - -import graphql.introspection.Introspection; -import graphql.schema.GraphQLDirective; - -public class OneOfDirective { - - public static final GraphQLDirective ONE_OF_DIRECTIVE = GraphQLDirective - .newDirective() - .description("One and only one of the fields in the (input) type must be non null.") - .validLocations( - Introspection.DirectiveLocation.INPUT_OBJECT, - Introspection.DirectiveLocation.OBJECT - ) - .name("OneOf") - .build(); -} diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java index f866f57fe74..efe4585aa96 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/ViaLocationInputType.java @@ -1,6 +1,6 @@ package org.opentripplanner.apis.transmodel.model.plan; -import static org.opentripplanner.apis.transmodel.model.framework.OneOfDirective.ONE_OF_DIRECTIVE; +import static graphql.Directives.OneOfDirective; import graphql.Scalars; import graphql.language.StringValue; @@ -107,7 +107,7 @@ it to to be accepted. To visit a coordinate, the traveler must walk(bike or driv .newInputObject() .name(INPUT_VIA_LOCATION) .description(DOC_VIA_LOCATION) - .withDirective(ONE_OF_DIRECTIVE) + .withDirective(OneOfDirective) .field(b -> b.name(FIELD_VISIT).type(VISIT_VIA_LOCATION_INPUT)) .field(b -> b.name(FIELD_PASS_THROUGH).type(PASS_THROUGH_VIA_LOCATION_INPUT)) .build(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidator.java b/src/main/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidator.java new file mode 100644 index 00000000000..47fca16630e --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidator.java @@ -0,0 +1,60 @@ +package org.opentripplanner.apis.transmodel.support; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * Validate @oneOf directive, this validation is NOT done by the Java GraphQL library at the + * moment(v22.1). Remove this when enforced by the library. The {@code @oneOf} is an experimental + * feature in this version of the library. + *

+ * See {@link graphql.Directives#OneOfDirective} + */ +public class OneOfInputValidator { + + /** + * Validate that the {@code parent} {@code map} only has one entry. + * + * @return the field with a value set. + */ + public static String validateOneOf( + Map map, + String parent, + String... definedFields + ) { + var fieldsInInput = Arrays + .stream(definedFields) + .map(k -> map.containsKey(k) ? k : null) + .filter(Objects::nonNull) + .toList(); + + if (fieldsInInput.isEmpty()) { + throw new IllegalArgumentException( + "No entries in '%s @oneOf'. One of '%s' must be set.".formatted( + parent, + String.join("', '", definedFields) + ) + ); + } + if (fieldsInInput.size() > 1) { + throw new IllegalArgumentException( + "Only one entry in '%s @oneOf' is allowed. Set: '%s'".formatted( + parent, + String.join("', '", fieldsInInput) + ) + ); + } + + var field = fieldsInInput.getFirst(); + if (map.get(field) instanceof Collection c) { + if (c.isEmpty()) { + throw new IllegalArgumentException( + "'%s' can not be empty in '%s @oneOf'.".formatted(field, parent) + ); + } + } + return field; + } +} diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java similarity index 87% rename from src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java rename to src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java index 7a4bf2f480d..49304ee1e05 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripViaLocationMapperTest.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ViaLocationMapperTest { +class TripViaLocationMapperTest { public static final String LABEL = "TestLabel"; public static final Duration MIN_WAIT_TIME = Duration.ofMinutes(5); @@ -36,7 +36,7 @@ void testMapToVisitViaLocations() { Map input = Map.ofEntries( entry(FIELD_VISIT, visitInput(LABEL, MIN_WAIT_TIME, LIST_IDS_INPUT)) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); @@ -56,7 +56,7 @@ void testMapToVisitViaLocationsWithBareMinimum() { FIELD_VISIT, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); @@ -69,7 +69,7 @@ void testMapToVisitViaLocationsWithBareMinimum() { @Test void tetMapToPassThrough() { Map input = Map.of(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); assertEquals(LABEL, via.label()); @@ -87,7 +87,7 @@ void tetMapToPassThroughWithBareMinimum() { FIELD_PASS_THROUGH, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = TripViaLocationMapper.mapToViaLocations(List.of(input)); var via = result.getFirst(); assertNull(via.label()); @@ -103,20 +103,20 @@ void testOneOf() { ); var ex = assertThrows( IllegalArgumentException.class, - () -> ViaLocationMapper.mapToViaLocations(List.of(input)) + () -> TripViaLocationMapper.mapToViaLocations(List.of(input)) ); assertEquals( - "Both 'visit' and 'passThrough' can not be set in 'via' (@oneOf).", + "Only one entry in 'via @oneOf' is allowed. Set: 'visit', 'passThrough'", ex.getMessage() ); ex = assertThrows( IllegalArgumentException.class, - () -> ViaLocationMapper.mapToViaLocations(List.of(Map.of())) + () -> TripViaLocationMapper.mapToViaLocations(List.of(Map.of())) ); assertEquals( - "Either 'visit' or 'passThrough' should be set in 'via' (@oneOf).", + "No entries in 'via @oneOf'. One of 'visit', 'passThrough' must be set.", ex.getMessage() ); } diff --git a/src/test/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidatorTest.java b/src/test/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidatorTest.java new file mode 100644 index 00000000000..e641a1cc855 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/transmodel/support/OneOfInputValidatorTest.java @@ -0,0 +1,53 @@ +package org.opentripplanner.apis.transmodel.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class OneOfInputValidatorTest { + + @Test + void testValidateOneReturnsTheFieldName() { + assertEquals( + "two", + OneOfInputValidator.validateOneOf(Map.of("two", "X"), "parent", "one", "two") + ); + } + + @Test + void testValidateOneOfWithEmptySetOfArguments() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> OneOfInputValidator.validateOneOf(Map.of(), "parent", "one", "two") + ); + assertEquals( + "No entries in 'parent @oneOf'. One of 'one', 'two' must be set.", + ex.getMessage() + ); + } + + @Test + void testValidateOneOfWithTooManyArguments() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> + OneOfInputValidator.validateOneOf(Map.of("one", "X", "two", "Y"), "parent", "one", "two") + ); + assertEquals( + "Only one entry in 'parent @oneOf' is allowed. Set: 'one', 'two'", + ex.getMessage() + ); + } + + @Test + void testValidateOneOfWithEmptyCollection() { + var ex = assertThrows( + IllegalArgumentException.class, + () -> OneOfInputValidator.validateOneOf(Map.of("one", List.of()), "parent", "one", "two") + ); + assertEquals("'one' can not be empty in 'parent @oneOf'.", ex.getMessage()); + } +} From adff75e96d50613d35d560deb1bf6eaf4da65bd0 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 18:33:27 +0200 Subject: [PATCH 31/36] refactor: Rename raptor request classes to avoid using same name as in OTP domain --- ...nnection.java => RaptorViaConnection.java} | 8 ++--- ...iaLocation.java => RaptorViaLocation.java} | 23 +++++++------- .../raptor/api/request/SearchParams.java | 4 +-- .../api/request/SearchParamsBuilder.java | 8 ++--- .../context/SearchContextBuilder.java | 4 +-- .../multicriteria/McStopArrivals.java | 4 +-- .../arrivals/McStopArrivalFactory.java | 4 +-- .../rangeraptor/transit/ViaConnections.java | 13 +++++--- .../request/ViaLocationDeprecatedTest.java | 31 ++++++++++--------- .../raptor/moduletests/J02_ViaSearchTest.java | 20 ++++++------ 10 files changed, 62 insertions(+), 57 deletions(-) rename src/main/java/org/opentripplanner/raptor/api/request/{ViaConnection.java => RaptorViaConnection.java} (94%) rename src/main/java/org/opentripplanner/raptor/api/request/{ViaLocation.java => RaptorViaLocation.java} (90%) diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorViaConnection.java similarity index 94% rename from src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java rename to src/main/java/org/opentripplanner/raptor/api/request/RaptorViaConnection.java index 1d1fd6cb2cb..4017d8feee0 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/ViaConnection.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorViaConnection.java @@ -29,7 +29,7 @@ * {@code c1} need to include the walk time, but not the wait time (assuming all connections * have the same minimum wait time). */ -public final class ViaConnection { +public final class RaptorViaConnection { private final int fromStop; private final int durationInSeconds; @@ -37,7 +37,7 @@ public final class ViaConnection { @Nullable private final RaptorTransfer transfer; - ViaConnection(ViaLocation parent, int fromStop, @Nullable RaptorTransfer transfer) { + RaptorViaConnection(RaptorViaLocation parent, int fromStop, @Nullable RaptorTransfer transfer) { this.fromStop = fromStop; this.transfer = transfer; this.durationInSeconds = @@ -90,7 +90,7 @@ public boolean isSameStop() { * The method returns {@code true} if this instance is better or equals to the given other * stop with respect to being pareto-optimal. */ - boolean isBetterOrEqual(ViaConnection other) { + boolean isBetterOrEqual(RaptorViaConnection other) { if (fromStop != other.fromStop || toStop() != other.toStop()) { return false; } @@ -105,7 +105,7 @@ boolean isBetterOrEqual(ViaConnection other) { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ViaConnection that = (ViaConnection) o; + RaptorViaConnection that = (RaptorViaConnection) o; return fromStop == that.fromStop && Objects.equals(transfer, that.transfer); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorViaLocation.java similarity index 90% rename from src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java rename to src/main/java/org/opentripplanner/raptor/api/request/RaptorViaLocation.java index 0fb7bc11714..3a8df33b37f 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/ViaLocation.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorViaLocation.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.function.Predicate; import javax.annotation.Nullable; import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.framework.time.DurationUtils; @@ -12,16 +11,16 @@ import org.opentripplanner.raptor.api.model.RaptorStopNameResolver; import org.opentripplanner.raptor.api.model.RaptorTransfer; -public final class ViaLocation { +public final class RaptorViaLocation { private static final int MAX_WAIT_TIME_LIMIT = (int) Duration.ofHours(24).toSeconds(); private final String label; private final boolean allowPassThrough; private final int minimumWaitTime; - private final List connections; + private final List connections; - private ViaLocation( + private RaptorViaLocation( String label, boolean allowPassThrough, Duration minimumWaitTime, @@ -81,7 +80,7 @@ public int minimumWaitTime() { return minimumWaitTime; } - public List connections() { + public List connections() { return connections; } @@ -93,7 +92,7 @@ public String toString() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ViaLocation that = (ViaLocation) o; + RaptorViaLocation that = (RaptorViaLocation) o; return ( allowPassThrough == that.allowPassThrough && minimumWaitTime == that.minimumWaitTime && @@ -124,13 +123,13 @@ public String toString(RaptorStopNameResolver stopNameResolver) { return buf.append("}").toString(); } - private List validateConnections(List connections) { + private List validateConnections(List connections) { if (connections.isEmpty()) { throw new IllegalArgumentException("At least one connection is required."); } var list = connections .stream() - .map(it -> new ViaConnection(this, it.fromStop, it.transfer)) + .map(it -> new RaptorViaConnection(this, it.fromStop, it.transfer)) .toList(); // Compare all pairs to check for duplicates and none optimal connections @@ -171,15 +170,15 @@ public Builder addViaTransfer(int fromStop, RaptorTransfer transfer) { return this; } - public ViaLocation build() { - return new ViaLocation(label, allowPassThrough, minimumWaitTime, connections); + public RaptorViaLocation build() { + return new RaptorViaLocation(label, allowPassThrough, minimumWaitTime, connections); } } /** * Use internally to store connection data, before creating the connection objects. If is - * needed to create the bidirectional relationship between {@link ViaLocation} and - * {@link ViaConnection}. + * needed to create the bidirectional relationship between {@link RaptorViaLocation} and + * {@link RaptorViaConnection}. */ private record StopAndTransfer(int fromStop, @Nullable RaptorTransfer transfer) {} } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index db20f892890..bd32c025eda 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -32,7 +32,7 @@ public class SearchParams { private final boolean constrainedTransfers; private final Collection accessPaths; private final Collection egressPaths; - private final List viaLocations; + private final List viaLocations; /** * Default values are defined in the default constructor. @@ -212,7 +212,7 @@ public Collection egressPaths() { * *

* * Required, at least one egress path must exist. */ - public List viaLocations() { + public List viaLocations() { return viaLocations; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java index dcde29b0709..7db4ce5d0f7 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java @@ -30,7 +30,7 @@ public class SearchParamsBuilder { private boolean constrainedTransfers; private final Collection accessPaths = new ArrayList<>(); private final Collection egressPaths = new ArrayList<>(); - private final List viaLocations = new ArrayList<>(); + private final List viaLocations = new ArrayList<>(); public SearchParamsBuilder(RaptorRequestBuilder parent, SearchParams defaults) { this.parent = parent; @@ -163,16 +163,16 @@ public SearchParamsBuilder addEgressPaths(RaptorAccessEgress... egressPaths) return addEgressPaths(Arrays.asList(egressPaths)); } - public Collection viaLocations() { + public Collection viaLocations() { return viaLocations; } - public SearchParamsBuilder addViaLocation(ViaLocation location) { + public SearchParamsBuilder addViaLocation(RaptorViaLocation location) { viaLocations.add(location); return this; } - public SearchParamsBuilder addViaLocations(Collection locations) { + public SearchParamsBuilder addViaLocations(Collection locations) { viaLocations.addAll(locations); return this; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java index e0bf4f1df5d..592688ad52d 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContextBuilder.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; -import org.opentripplanner.raptor.api.request.ViaLocation; +import org.opentripplanner.raptor.api.request.RaptorViaLocation; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.EgressPaths; import org.opentripplanner.raptor.rangeraptor.transit.ViaConnections; @@ -67,7 +67,7 @@ private List viaConnections() { .searchParams() .viaLocations() .stream() - .map(ViaLocation::connections) + .map(RaptorViaLocation::connections) .map(ViaConnections::new) .toList() : List.of(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java index c16720ca0c2..7bea413584a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java @@ -9,7 +9,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.request.ViaConnection; +import org.opentripplanner.raptor.api.request.RaptorViaConnection; import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.ArrivalParetoSetComparatorFactory; @@ -175,7 +175,7 @@ private void initViaConnections( new ParetoSetEventListener<>() { @Override public void notifyElementAccepted(ArrivalView newElement) { - for (ViaConnection c : list) { + for (RaptorViaConnection c : list) { var e = (McStopArrival) newElement; var n = stopArrivalFactory.createViaStopArrival(e, c); if (n != null) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java index 2d59483d203..7825057c376 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/arrivals/McStopArrivalFactory.java @@ -3,7 +3,7 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.request.ViaConnection; +import org.opentripplanner.raptor.api.request.RaptorViaConnection; import org.opentripplanner.raptor.api.view.PatternRideView; public interface McStopArrivalFactory { @@ -24,7 +24,7 @@ McStopArrival createTransferStopArrival( default McStopArrival createViaStopArrival( McStopArrival previous, - ViaConnection viaConnection + RaptorViaConnection viaConnection ) { if (viaConnection.isSameStop()) { if (viaConnection.durationInSeconds() == 0) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java index ae1dd29ce47..053c1da4354 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ViaConnections.java @@ -6,18 +6,21 @@ import gnu.trove.map.hash.TIntObjectHashMap; import java.util.Collection; import java.util.List; -import org.opentripplanner.raptor.api.request.ViaConnection; +import org.opentripplanner.raptor.api.request.RaptorViaConnection; public class ViaConnections { - private final TIntObjectMap> byFromStop; + private final TIntObjectMap> byFromStop; - public ViaConnections(Collection viaConnections) { + public ViaConnections(Collection viaConnections) { this.byFromStop = new TIntObjectHashMap<>(); - viaConnections.stream().collect(groupingBy(ViaConnection::fromStop)).forEach(byFromStop::put); + viaConnections + .stream() + .collect(groupingBy(RaptorViaConnection::fromStop)) + .forEach(byFromStop::put); } - public TIntObjectMap> byFromStop() { + public TIntObjectMap> byFromStop() { return byFromStop; } } diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java index 8d8ad336cff..68c49cd3426 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java @@ -30,7 +30,7 @@ class ViaLocationDeprecatedTest { @Test void passThroughStop() { - var subject = ViaLocation.allowPassThrough("PassThrough A").addViaStop(STOP_C).build(); + var subject = RaptorViaLocation.allowPassThrough("PassThrough A").addViaStop(STOP_C).build(); assertEquals("PassThrough A", subject.label()); assertTrue(subject.allowPassThrough()); @@ -56,7 +56,7 @@ void passThroughStop() { @Test void viaSingleStop() { - var subject = ViaLocation.via("Tx A").addViaStop(STOP_B).build(); + var subject = RaptorViaLocation.via("Tx A").addViaStop(STOP_B).build(); assertEquals("Tx A", subject.label()); assertFalse(subject.allowPassThrough()); @@ -78,7 +78,7 @@ void viaSingleStop() { @Test void testCombinationOfPassThroughAndTransfer() { - var subject = ViaLocation + var subject = RaptorViaLocation .allowPassThrough("PassThrough A") .addViaStop(STOP_C) .addViaTransfer(STOP_A, TX) @@ -110,7 +110,7 @@ void testCombinationOfPassThroughAndTransfer() { @Test void viaStopAorCWithWaitTime() { - var subject = ViaLocation + var subject = RaptorViaLocation .via("Plaza", WAIT_TIME) .addViaStop(STOP_C) .addViaTransfer(STOP_A, TX) @@ -163,14 +163,14 @@ void isBetterThan( boolean expected, String description ) { - var subject = ViaLocation + var subject = RaptorViaLocation .via("Subject") .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) .build() .connections() .getFirst(); - var candidate = ViaLocation + var candidate = RaptorViaLocation .via("Candidate") .addViaTransfer(fromStop, new TestTransfer(toStop, minWaitTime, c1)) .build() @@ -185,7 +185,7 @@ void throwsExceptionIfConnectionsIsNotParetoOptimal() { var e = assertThrows( IllegalArgumentException.class, () -> - ViaLocation + RaptorViaLocation .via("S") .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) .addViaTransfer(STOP_A, new TestTransfer(STOP_B, TX_DURATION, C1)) @@ -199,10 +199,13 @@ void throwsExceptionIfConnectionsIsNotParetoOptimal() { @Test void testEqualsAndHashCode() { - var subject = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); - var same = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + var subject = RaptorViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + var same = RaptorViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); // Slightly less wait-time and slightly larger cost(c1) - var other = ViaLocation.via(null, Duration.ofSeconds(1)).addViaTransfer(STOP_A, TX).build(); + var other = RaptorViaLocation + .via(null, Duration.ofSeconds(1)) + .addViaTransfer(STOP_A, TX) + .build(); assertEquals(subject, same); assertNotEquals(subject, other); @@ -214,28 +217,28 @@ void testEqualsAndHashCode() { @Test void testToString() { - var subject = ViaLocation.via("A|B").addViaStop(STOP_A).addViaStop(STOP_B).build(); + var subject = RaptorViaLocation.via("A|B").addViaStop(STOP_A).addViaStop(STOP_B).build(); assertEquals("Via{label: A|B, connections: [12, 13]}", subject.toString()); assertEquals( "Via{label: A|B, connections: [A, B]}", subject.toString(ViaLocationDeprecatedTest::stopName) ); - subject = ViaLocation.via(null, WAIT_TIME).addViaStop(STOP_B).build(); + subject = RaptorViaLocation.via(null, WAIT_TIME).addViaStop(STOP_B).build(); assertEquals("Via{minWaitTime: 3m, connections: [13 3m]}", subject.toString()); assertEquals( "Via{minWaitTime: 3m, connections: [B 3m]}", subject.toString(ViaLocationDeprecatedTest::stopName) ); - subject = ViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); + subject = RaptorViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); assertEquals("Via{connections: [12~13 35s]}", subject.toString()); assertEquals( "Via{connections: [A~B 35s]}", subject.toString(ViaLocationDeprecatedTest::stopName) ); - subject = ViaLocation.allowPassThrough(null).addViaStop(STOP_C).build(); + subject = RaptorViaLocation.allowPassThrough(null).addViaStop(STOP_C).build(); assertEquals("Via{allowPassThrough, connections: [14]}", subject.toString()); assertEquals( "Via{allowPassThrough, connections: [C]}", diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java index 8210f479bbc..581cd117cb1 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J02_ViaSearchTest.java @@ -17,7 +17,7 @@ import static org.opentripplanner.raptor._data.transit.TestRoute.route; import static org.opentripplanner.raptor._data.transit.TestTransfer.transfer; import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; -import static org.opentripplanner.raptor.api.request.ViaLocation.via; +import static org.opentripplanner.raptor.api.request.RaptorViaLocation.via; import java.time.Duration; import java.util.Arrays; @@ -30,7 +30,7 @@ import org.opentripplanner.raptor._data.transit.TestTripSchedule; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.api.request.ViaLocation; +import org.opentripplanner.raptor.api.request.RaptorViaLocation; import org.opentripplanner.raptor.configure.RaptorConfig; /** @@ -50,17 +50,17 @@ */ class J02_ViaSearchTest { - static final List VIA_LOCATION_STOP_B = List.of(viaLocation("B", STOP_B)); - static final List VIA_LOCATION_STOP_C = List.of(viaLocation("C", STOP_C)); - static final List VIA_LOCATION_STOP_A_OR_B = List.of( + static final List VIA_LOCATION_STOP_B = List.of(viaLocation("B", STOP_B)); + static final List VIA_LOCATION_STOP_C = List.of(viaLocation("C", STOP_C)); + static final List VIA_LOCATION_STOP_A_OR_B = List.of( viaLocation("B&C", STOP_A, STOP_B) ); - static final List VIA_LOCATION_STOP_B_THEN_D = List.of( + static final List VIA_LOCATION_STOP_B_THEN_D = List.of( viaLocation("B", STOP_B), viaLocation("D", STOP_D) ); - static final List VIA_LOCATION_STOP_C_THEN_B = List.of( + static final List VIA_LOCATION_STOP_C_THEN_B = List.of( viaLocation("B", STOP_C), viaLocation("D", STOP_B) ); @@ -356,7 +356,7 @@ void testMinWaitTime() { requestBuilder .searchParams() .addAccessPaths(walk(STOP_A, D30s)) - .addViaLocations(List.of(ViaLocation.via("B", minWaitTime).addViaStop(STOP_B).build())) + .addViaLocations(List.of(RaptorViaLocation.via("B", minWaitTime).addViaStop(STOP_B).build())) .addEgressPaths(walk(STOP_C, D30s)); // We expect to bard the second trip at 0:05:45, since the minWaitTime is 45s and the @@ -368,8 +368,8 @@ void testMinWaitTime() { ); } - private static ViaLocation viaLocation(String label, int... stops) { - var builder = ViaLocation.via(label); + private static RaptorViaLocation viaLocation(String label, int... stops) { + var builder = RaptorViaLocation.via(label); Arrays.stream(stops).forEach(builder::addViaStop); return builder.build(); } From 593656a3d897dc470ec357d963fd91e655d76301 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 18:35:58 +0200 Subject: [PATCH 32/36] refactor: Deprecate Raptor PassThroughPoint --- .../opentripplanner/raptor/api/request/PassThroughPoint.java | 3 +++ .../{ViaLocationTest.java => PassThroughPointTest.java} | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) rename src/test/java/org/opentripplanner/raptor/api/request/{ViaLocationTest.java => PassThroughPointTest.java} (98%) 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 b17fab84347..187436907fe 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java @@ -10,7 +10,10 @@ /** * A collection of stop indexes used to define a pass through-point. + * + * @deprecated This will be replaced by ViaLocation */ +@Deprecated public class PassThroughPoint { private final String name; diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java similarity index 98% rename from src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java rename to src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java index 1ae47e930af..155e122a7b6 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -class ViaLocationTest { +class PassThroughPointTest { private static final int[] STOPS = { 2, 7, 13 }; From 145ddd9d2501615d5c5d37c42eb0f84d1f9dd2d9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 18:37:15 +0200 Subject: [PATCH 33/36] refactor: Rename ViaLocationDeprecatedTest to ViaLocationTest ViaLocationTest --- ...precatedTest.java => ViaLocationTest.java} | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) rename src/test/java/org/opentripplanner/raptor/api/request/{ViaLocationDeprecatedTest.java => ViaLocationTest.java} (93%) diff --git a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java similarity index 93% rename from src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java rename to src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java index 68c49cd3426..2aca0845ff8 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationDeprecatedTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/ViaLocationTest.java @@ -17,7 +17,7 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; -class ViaLocationDeprecatedTest { +class ViaLocationTest { private static final int STOP_A = 12; private static final int STOP_B = 13; @@ -37,7 +37,7 @@ void passThroughStop() { assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [C]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [14]}", @@ -61,10 +61,7 @@ void viaSingleStop() { assertEquals("Tx A", subject.label()); assertFalse(subject.allowPassThrough()); assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); - assertEquals( - "Via{label: Tx A, connections: [B]}", - subject.toString(ViaLocationDeprecatedTest::stopName) - ); + assertEquals("Via{label: Tx A, connections: [B]}", subject.toString(ViaLocationTest::stopName)); assertEquals("Via{label: Tx A, connections: [13]}", subject.toString()); assertEquals(1, subject.connections().size()); @@ -89,7 +86,7 @@ void testCombinationOfPassThroughAndTransfer() { assertEquals(RaptorConstants.ZERO, subject.minimumWaitTime()); assertEquals( "Via{label: PassThrough A, allowPassThrough, connections: [C, A~B 35s]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); assertEquals(2, subject.connections().size()); @@ -121,7 +118,7 @@ void viaStopAorCWithWaitTime() { assertEquals(WAIT_TIME_SEC, subject.minimumWaitTime()); assertEquals( "Via{label: Plaza, minWaitTime: 3m, connections: [C 3m, A~B 3m35s]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); assertEquals(2, subject.connections().size()); @@ -221,28 +218,25 @@ void testToString() { assertEquals("Via{label: A|B, connections: [12, 13]}", subject.toString()); assertEquals( "Via{label: A|B, connections: [A, B]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); subject = RaptorViaLocation.via(null, WAIT_TIME).addViaStop(STOP_B).build(); assertEquals("Via{minWaitTime: 3m, connections: [13 3m]}", subject.toString()); assertEquals( "Via{minWaitTime: 3m, connections: [B 3m]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); subject = RaptorViaLocation.via(null).addViaTransfer(STOP_A, TX).build(); assertEquals("Via{connections: [12~13 35s]}", subject.toString()); - assertEquals( - "Via{connections: [A~B 35s]}", - subject.toString(ViaLocationDeprecatedTest::stopName) - ); + assertEquals("Via{connections: [A~B 35s]}", subject.toString(ViaLocationTest::stopName)); subject = RaptorViaLocation.allowPassThrough(null).addViaStop(STOP_C).build(); assertEquals("Via{allowPassThrough, connections: [14]}", subject.toString()); assertEquals( "Via{allowPassThrough, connections: [C]}", - subject.toString(ViaLocationDeprecatedTest::stopName) + subject.toString(ViaLocationTest::stopName) ); } From d1853d0eb28469512225d254f7f3b328618e225f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Sep 2024 23:40:47 +0200 Subject: [PATCH 34/36] feature: Map OTP request to Raptor request - last step to make the via-search work! --- .../routing/algorithm/RoutingWorker.java | 10 +++- .../FilterTransitWhenDirectModeIsEmpty.java | 2 +- .../transit/mappers/RaptorRequestMapper.java | 55 ++++++++++++++++++- .../routing/api/request/RouteRequest.java | 7 +++ .../mappers/RaptorRequestMapperTest.java | 20 +++++++ 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index 7ac3dd1caf6..1e2dd3f2d4e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -230,10 +230,18 @@ private Duration searchWindowUsed() { : Duration.ofSeconds(raptorSearchParamsUsed.searchWindowInSeconds()); } - private Void routeDirectStreet( + private List routeDirectStreet( List itineraries, Collection routingErrors ) { + // TODO: Add support for via search to the direct-street search and remove this. + // The direct search is used to prune away silly transit results and it + // would be nice to also support via as a feature in the direct-street + // search. + if (request.isViaSearch()) { + return null; + } + debugTimingAggregator.startedDirectStreetRouter(); try { itineraries.addAll(DirectStreetRouter.route(serverContext, request)); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java index 01a8c9ab112..09431f0be19 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java @@ -55,7 +55,7 @@ public class FilterTransitWhenDirectModeIsEmpty { private final StreetMode originalDirectMode; public FilterTransitWhenDirectModeIsEmpty(RequestModes modes) { - this.originalDirectMode = modes.directMode; + this(modes.directMode); } public FilterTransitWhenDirectModeIsEmpty(StreetMode originalDirectMode) { 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 9220756f8bb..dc602b2071f 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 @@ -7,6 +7,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -18,6 +19,7 @@ import org.opentripplanner.raptor.api.request.PassThroughPoint; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.raptor.api.request.RaptorViaLocation; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; @@ -82,6 +84,13 @@ private RaptorRequest doMap() { var preferences = request.preferences(); + // TODO Fix the Raptor search so pass-through and via search can be used together. + if (hasViaLocationsAndPassThroughLocations()) { + throw new IllegalArgumentException( + "A mix of via-locations and pass-through is not allowed in this versionP." + ); + } + if (request.pageCursor() == null) { int time = relativeTime(request.dateTime()); @@ -122,7 +131,7 @@ private RaptorRequest doMap() { // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled // TODO - We need handle via locations that are not pass-through-points here - if (request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation)) { + if (hasPassThroughOnly()) { mcBuilder.withPassThroughPoints(mapPassThroughPoints()); r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination); } else if (!pt.relaxTransitGroupPriority().isNormal()) { @@ -151,6 +160,10 @@ private RaptorRequest doMap() { .addAccessPaths(accessPaths) .addEgressPaths(egressPaths); + if (hasViaLocationsOnly()) { + builder.searchParams().addViaLocations(mapViaLocations()); + } + var raptorDebugging = request.journey().transit().raptorDebugging(); if (raptorDebugging.isEnabled()) { @@ -182,10 +195,48 @@ private RaptorRequest doMap() { ) ); } - return builder.build(); } + private boolean hasPassThroughOnly() { + return request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation); + } + + private boolean hasViaLocationsOnly() { + return request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation); + } + + private boolean hasViaLocationsAndPassThroughLocations() { + var c = request.getViaLocations(); + return ( + request.isViaSearch() && + c.stream().anyMatch(ViaLocation::isPassThroughLocation) && + c.stream().anyMatch(Predicate.not(ViaLocation::isPassThroughLocation)) + ); + } + + private List mapViaLocations() { + return request.getViaLocations().stream().map(this::mapViaLocation).toList(); + } + + private RaptorViaLocation mapViaLocation(ViaLocation input) { + if (input.isPassThroughLocation()) { + var builder = RaptorViaLocation.allowPassThrough(input.label()); + for (int stopIndex : lookUpStopIndex.lookupStopLocationIndexes(input.stopLocationIds())) { + builder.addViaStop(stopIndex); + } + return builder.build(); + } + // Visit Via location + else { + var builder = RaptorViaLocation.via(input.label(), input.minimumWaitTime()); + for (int stopIndex : lookUpStopIndex.lookupStopLocationIndexes(input.stopLocationIds())) { + builder.addViaStop(stopIndex); + } + return builder.build(); + } + } + private List mapPassThroughPoints() { return request.getViaLocations().stream().map(this::mapPassThroughPoints).toList(); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index aa5253a2e94..328ecb73e52 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -279,6 +279,13 @@ public void setTo(GenericLocation to) { this.to = to; } + /** + * Return {@code true} if at least one via location is set! + */ + public boolean isViaSearch() { + return !via.isEmpty(); + } + public List getViaLocations() { return via; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index f716db062d4..87e90e61b2e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; +import org.opentripplanner.routing.api.request.via.VisitViaLocation; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; @@ -55,6 +57,24 @@ void mapRelaxCost(CostLinearFunction input, int cost, int expected) { assertEquals(expected, calcCost.relax(cost)); } + @Test + void testViaLocation() { + var req = new RouteRequest(); + var minWaitTime = Duration.ofMinutes(13); + + req.setViaLocations( + List.of(new VisitViaLocation("Via A", minWaitTime, List.of(STOP_A.getId()), List.of())) + ); + + var result = map(req); + + assertTrue(result.searchParams().hasViaLocations()); + assertEquals( + "[Via{label: Via A, minWaitTime: 13m, connections: [0 13m]}]", + result.searchParams().viaLocations().toString() + ); + } + @Test void testPassThroughPoints() { var req = new RouteRequest(); From ff643d1cca91516fa720d9ef3cea65dba8bfbc43 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 22 Sep 2024 18:13:46 +0200 Subject: [PATCH 35/36] refactor: Move EncodedPolylineBeanWithStops from utils to framework. --- .../opentripplanner/apis/transmodel/mapping/GeometryMapper.java | 2 +- .../model/{util => framework}/EncodedPolylineBeanWithStops.java | 2 +- .../apis/transmodel/model/network/StopToStopGeometryType.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/org/opentripplanner/apis/transmodel/model/{util => framework}/EncodedPolylineBeanWithStops.java (80%) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java index e2bf687e3b8..664da59d522 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/GeometryMapper.java @@ -2,7 +2,7 @@ import java.util.ArrayList; import java.util.List; -import org.opentripplanner.apis.transmodel.model.util.EncodedPolylineBeanWithStops; +import org.opentripplanner.apis.transmodel.model.framework.EncodedPolylineBeanWithStops; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.network.TripPattern; diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/EncodedPolylineBeanWithStops.java similarity index 80% rename from src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java rename to src/main/java/org/opentripplanner/apis/transmodel/model/framework/EncodedPolylineBeanWithStops.java index 92262a17381..23508bf2ef7 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/util/EncodedPolylineBeanWithStops.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/framework/EncodedPolylineBeanWithStops.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.transmodel.model.util; +package org.opentripplanner.apis.transmodel.model.framework; import org.opentripplanner.framework.geometry.EncodedPolyline; import org.opentripplanner.transit.model.site.StopLocation; diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java index 800eda04cb2..98a965e50be 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/network/StopToStopGeometryType.java @@ -3,7 +3,7 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; -import org.opentripplanner.apis.transmodel.model.util.EncodedPolylineBeanWithStops; +import org.opentripplanner.apis.transmodel.model.framework.EncodedPolylineBeanWithStops; public class StopToStopGeometryType { From 68e8867ddc03de97e9de172f084059f02564802c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 23 Sep 2024 11:19:30 +0200 Subject: [PATCH 36/36] Add RaptorRouter interface for to allow top level instrumentation This also includes a little naming cleanup. --- .../opentripplanner/raptor/RaptorService.java | 4 ++-- .../raptor/configure/RaptorConfig.java | 12 ++++++------ .../rangeraptor/DefaultRangeRaptorWorker.java | 4 ++-- .../raptor/rangeraptor/RangeRaptor.java | 13 ++++--------- .../rangeraptor/RangeRaptorWorkerComposite.java | 4 ++-- .../internalapi/RangeRaptorWorker.java | 2 +- .../rangeraptor/internalapi/RaptorRouter.java | 17 +++++++++++++++++ ...orkerResult.java => RaptorRouterResult.java} | 2 +- .../internalapi/RaptorWorkerState.java | 2 +- .../multicriteria/McRangeRaptorWorkerState.java | 6 +++--- ...kerResult.java => McRaptorRouterResult.java} | 6 +++--- .../standard/StdRangeRaptorWorkerState.java | 6 +++--- ...erResult.java => StdRaptorRouterResult.java} | 6 +++--- .../configure/StdRangeRaptorConfig.java | 4 ++-- .../rangeraptor/transit/RoundTracker.java | 6 +++--- .../raptor/service/DefaultStopArrivals.java | 6 +++--- .../raptor/service/HeuristicSearchTask.java | 6 +++--- .../service/RangeRaptorDynamicSearch.java | 9 +++++---- .../service/ViaRangeRaptorDynamicSearch.java | 9 +++++---- 19 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouter.java rename src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/{RaptorWorkerResult.java => RaptorRouterResult.java} (93%) rename src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/{McRaptorWorkerResult.java => McRaptorRouterResult.java} (91%) rename src/main/java/org/opentripplanner/raptor/rangeraptor/standard/{StdRaptorWorkerResult.java => StdRaptorRouterResult.java} (92%) diff --git a/src/main/java/org/opentripplanner/raptor/RaptorService.java b/src/main/java/org/opentripplanner/raptor/RaptorService.java index d8e7fcd3dcd..70156cbbfbe 100644 --- a/src/main/java/org/opentripplanner/raptor/RaptorService.java +++ b/src/main/java/org/opentripplanner/raptor/RaptorService.java @@ -68,8 +68,8 @@ private RaptorResponse routeUsingStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { - var worker = config.createStdWorker(transitData, request); - var result = worker.route(); + var rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); + var result = rangeRaptorRouter.route(); var arrivals = new DefaultStopArrivals(result); return new RaptorResponse<>(result.extractPaths(), arrivals, request, false); } diff --git a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java index 521d69a565e..cc488448304 100644 --- a/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/configure/RaptorConfig.java @@ -15,7 +15,7 @@ import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.PassThroughPointsService; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.multicriteria.McStopArrivals; @@ -58,7 +58,7 @@ public SearchContext context(RaptorTransitDataProvider transit, RaptorRequ return SearchContext.of(request, tuningParameters, transit, acceptC2AtDestination).build(); } - public RangeRaptor createStdWorker( + public RangeRaptor createRangeRaptorWithStdWorker( RaptorTransitDataProvider transitData, RaptorRequest request ) { @@ -70,7 +70,7 @@ public RangeRaptor createStdWorker( ); } - public RangeRaptor createMcWorker( + public RangeRaptor createRangeRaptorWithMcWorker( RaptorTransitDataProvider transitData, RaptorRequest request, Heuristics heuristics @@ -97,17 +97,17 @@ public RangeRaptor createMcWorker( return createRangeRaptor(context, worker); } - public RangeRaptor createHeuristicSearch( + public RangeRaptor createRangeRaptorWithHeuristicSearch( RaptorTransitDataProvider transitData, RaptorRequest request ) { - return createStdWorker(transitData, request); + return createRangeRaptorWithStdWorker(transitData, request); } public Heuristics createHeuristic( RaptorTransitDataProvider transitData, RaptorRequest request, - RaptorWorkerResult results + RaptorRouterResult results ) { var context = context(transitData, request); return new StdRangeRaptorConfig<>(context).createHeuristics(results); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 04f741b7c92..7db9e4aac85 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -9,7 +9,7 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.internalapi.SlackProvider; @@ -110,7 +110,7 @@ public DefaultRangeRaptorWorker( } @Override - public RaptorWorkerResult results() { + public RaptorRouterResult results() { return state.results(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java index 303ccd1ec19..873767b92a5 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptor.java @@ -5,7 +5,8 @@ import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouter; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.lifecycle.LifeCycleEventPublisher; import org.opentripplanner.raptor.rangeraptor.transit.AccessPaths; import org.opentripplanner.raptor.rangeraptor.transit.RaptorTransitCalculator; @@ -43,7 +44,7 @@ * @param The TripSchedule type defined by the user of the raptor API. */ @SuppressWarnings("Duplicates") -public final class RangeRaptor { +public final class RangeRaptor implements RaptorRouter { private final RangeRaptorWorker worker; @@ -84,13 +85,7 @@ public RangeRaptor( this.lifeCycle = lifeCyclePublisher; } - /** - * For each iteration (minute), calculate the minimum travel time to each transit stop in - * seconds. - *

- * Run the scheduled search, round 0 is the street search. - */ - public RaptorWorkerResult route() { + public RaptorRouterResult route() { timers.route(() -> { int iterationDepartureTime = RaptorConstants.TIME_NOT_SET; lifeCycle.notifyRouteSearchStart(calculator.searchForward()); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java index 8c02003e016..8d20525da6b 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/RangeRaptorWorkerComposite.java @@ -5,7 +5,7 @@ import javax.annotation.Nullable; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.internalapi.RangeRaptorWorker; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.util.composite.CompositeUtil; /** @@ -37,7 +37,7 @@ public static RangeRaptorWorker of( } @Override - public RaptorWorkerResult results() { + public RaptorRouterResult results() { return tail().results(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java index 3a911125630..858c0e7124b 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RangeRaptorWorker.java @@ -12,7 +12,7 @@ public interface RangeRaptorWorker { /** * Fetch the result after the search is performed. */ - RaptorWorkerResult results(); + RaptorRouterResult results(); /** * Check if the RangeRaptor should continue with a new round. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouter.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouter.java new file mode 100644 index 00000000000..3b5fb711b06 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouter.java @@ -0,0 +1,17 @@ +package org.opentripplanner.raptor.rangeraptor.internalapi; + +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; + +/** + * Interface for Raptor Router. Allow instrumentation/wrapping the router. This is not + * currently used in the main branch of OTP, but it is used in Entur fork to extend the + * router functionality. + */ +public interface RaptorRouter { + /** + * Perform the routing request and return the result. A range-raptor request will + * iterate over the minutes in the search-window, while a plain raptor search will + * just do one iteration. + */ + RaptorRouterResult route(); +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouterResult.java similarity index 93% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouterResult.java index ce6f7deb673..df073b796ec 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorRouterResult.java @@ -7,7 +7,7 @@ /** * This is the result of a RangeRaptor route call. */ -public interface RaptorWorkerResult { +public interface RaptorRouterResult { /** * Return all paths found. */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java index cb73d668450..2add95f9a79 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/internalapi/RaptorWorkerState.java @@ -45,5 +45,5 @@ public interface RaptorWorkerState { */ void transferToStops(int fromStop, Iterator transfers); - RaptorWorkerResult results(); + RaptorRouterResult results(); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRangeRaptorWorkerState.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRangeRaptorWorkerState.java index eccb009aeaa..1dcada83419 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRangeRaptorWorkerState.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRangeRaptorWorkerState.java @@ -6,7 +6,7 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.McStopArrival; @@ -110,9 +110,9 @@ public void transferToStops(int fromStop, Iterator tra } @Override - public RaptorWorkerResult results() { + public RaptorRouterResult results() { arrivals.debugStateInfo(); - return new McRaptorWorkerResult(arrivals, paths); + return new McRaptorRouterResult(arrivals, paths); } Iterable> listStopArrivalsPreviousRound(int stop) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorRouterResult.java similarity index 91% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorRouterResult.java index a664c89e0bd..2339c2a8df6 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorRouterResult.java @@ -3,16 +3,16 @@ import java.util.Collection; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths; -public class McRaptorWorkerResult implements RaptorWorkerResult { +public class McRaptorRouterResult implements RaptorRouterResult { private final McStopArrivals stopArrivals; private final DestinationArrivalPaths paths; - public McRaptorWorkerResult(McStopArrivals arrivals, DestinationArrivalPaths paths) { + public McRaptorRouterResult(McStopArrivals arrivals, DestinationArrivalPaths paths) { stopArrivals = arrivals; this.paths = paths; } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRangeRaptorWorkerState.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRangeRaptorWorkerState.java index e3aba5c36f9..bcb2ca0f798 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRangeRaptorWorkerState.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRangeRaptorWorkerState.java @@ -5,7 +5,7 @@ import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.TransitArrival; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.standard.besttimes.BestTimes; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.ArrivedAtDestinationCheck; import org.opentripplanner.raptor.rangeraptor.standard.internalapi.BestNumberOfTransfers; @@ -209,8 +209,8 @@ private void transferToStop(int arrivalTimeTransit, int fromStop, RaptorTransfer } @Override - public RaptorWorkerResult results() { - return new StdRaptorWorkerResult<>( + public RaptorRouterResult results() { + return new StdRaptorRouterResult<>( bestTimes, stopArrivalsState::extractPaths, bestNumberOfTransfers::extractBestNumberOfTransfers diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorRouterResult.java similarity index 92% rename from src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java rename to src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorRouterResult.java index 7a6812c9c95..1612234b185 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorRouterResult.java @@ -4,14 +4,14 @@ import java.util.function.Supplier; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.RaptorPath; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; import org.opentripplanner.raptor.rangeraptor.standard.besttimes.BestTimes; /** * Result for Standard Range Raptor route call. */ -public class StdRaptorWorkerResult implements RaptorWorkerResult { +public class StdRaptorRouterResult implements RaptorRouterResult { private final BestTimes bestTimes; private final Supplier>> pathSupplier; @@ -23,7 +23,7 @@ public class StdRaptorWorkerResult implements Rapt */ private Collection> paths = null; - public StdRaptorWorkerResult( + public StdRaptorRouterResult( BestTimes bestTimes, Supplier>> pathSupplier, Supplier bestNumberOfTransfersSupplier diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java index efb50a38774..5bd0b7c5077 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/configure/StdRangeRaptorConfig.java @@ -9,7 +9,7 @@ import org.opentripplanner.raptor.rangeraptor.context.SearchContext; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerState; import org.opentripplanner.raptor.rangeraptor.internalapi.RoutingStrategy; import org.opentripplanner.raptor.rangeraptor.path.DestinationArrivalPaths; @@ -69,7 +69,7 @@ public RoutingStrategy strategy() { return strategy; } - public Heuristics createHeuristics(RaptorWorkerResult results) { + public Heuristics createHeuristics(RaptorRouterResult results) { return oneOf( new HeuristicsAdapter( ctx.nStops(), diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java index f355bc72ebb..4a185db0b43 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RoundTracker.java @@ -5,8 +5,8 @@ /** * Round tracker to keep track of round index and when to stop exploring new rounds. *

- * In round 0 the access paths with one leg are added. In round 1 the first transit and transfers is - * added, ... + * In round zero(0), the access paths with one leg are added. In round one(1) the first transit and + * transfers is added, ... */ public class RoundTracker { @@ -30,7 +30,7 @@ public class RoundTracker { * This is default set to the maximum number of rounds limit, but as soon as the destination is * reach the {@link #numberOfAdditionalTransfers} is used to update the limit. *

- * The limit is inclusive, indicating the the last round to process. + * The limit is inclusive, indicating the last round to process. */ private int roundMaxLimit; diff --git a/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java b/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java index 2d3e3795cd9..244dca1c81c 100644 --- a/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java @@ -1,7 +1,7 @@ package org.opentripplanner.raptor.service; import org.opentripplanner.raptor.api.response.StopArrivals; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; /** @@ -13,9 +13,9 @@ public class DefaultStopArrivals implements StopArrivals { private SingleCriteriaStopArrivals bestTransitArrivalTime = null; private SingleCriteriaStopArrivals bestNumberOfTransfers = null; - private final RaptorWorkerResult results; + private final RaptorRouterResult results; - public DefaultStopArrivals(RaptorWorkerResult results) { + public DefaultStopArrivals(RaptorRouterResult results) { this.results = results; } diff --git a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java index cabc03fcf3a..78bb9a646bd 100644 --- a/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java +++ b/src/main/java/org/opentripplanner/raptor/service/HeuristicSearchTask.java @@ -10,7 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.raptor.rangeraptor.RangeRaptor; import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; -import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorRouterResult; import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +36,7 @@ public class HeuristicSearchTask { private RangeRaptor search = null; private RaptorRequest originalRequest; private RaptorRequest heuristicRequest; - private RaptorWorkerResult result = null; + private RaptorRouterResult result = null; public HeuristicSearchTask( RaptorRequest request, @@ -144,7 +144,7 @@ private void createHeuristicSearchIfNotExist(RaptorRequest request) { ); heuristicRequest = builder.build(); - search = config.createHeuristicSearch(transitData, heuristicRequest); + search = config.createRangeRaptorWithHeuristicSearch(transitData, heuristicRequest); } } } diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index 922f89db792..5353804f414 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -129,17 +129,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor raptorWorker; + RangeRaptor rangeRaptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + rangeRaptorRouter = + config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - raptorWorker = config.createStdWorker(transitData, request); + rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = raptorWorker.route(); + var result = rangeRaptorRouter.route(); // create and return response return new RaptorResponse<>( diff --git a/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java index 6731517ffc4..aedbb5cbeef 100644 --- a/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/ViaRangeRaptorDynamicSearch.java @@ -129,17 +129,18 @@ private void runHeuristics() { private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); - RangeRaptor raptorWorker; + RangeRaptor rangeRaptorRouter; // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + rangeRaptorRouter = + config.createRangeRaptorWithMcWorker(transitData, request, getDestinationHeuristics()); } else { - raptorWorker = config.createStdWorker(transitData, request); + rangeRaptorRouter = config.createRangeRaptorWithStdWorker(transitData, request); } // Route - var result = raptorWorker.route(); + var result = rangeRaptorRouter.route(); // create and return response return new RaptorResponse<>(