diff --git a/docs/Configuration.md b/docs/Configuration.md index 05611e23628..2f34b598fa7 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -227,6 +227,7 @@ Here is a list of all features which can be toggled on/off and their default val | `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | | | `DebugUi` | Enable the debug GraphQL client and web UI and located at the root of the web server as well as the debug map tiles it uses. Be aware that the map tiles are not a stable API and can change without notice. Use the [vector tiles feature if](sandbox/MapboxVectorTilesApi.md) you want a stable map tiles API. | ✓️ | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `HackSorlandsbanen` | Includ Sørlandsbanen | | ✓️ | | `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | | `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 0fd9ec47f89..790984ac02c 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -23,6 +23,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | +| extraSearchCoachReluctance | `double` | TODO | *Optional* | `0.0` | 2.1 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index 6248f71e214..f178af9abc0 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java @@ -132,6 +132,7 @@ private BoardAndAlightSlack mapTransit() { v -> tr.withRaptor(r -> r.withRelaxGeneralizedCostAtDestination(v)) ); } + setIfNotNull(req.extraSearchCoachReluctance, tr::setExtraSearchCoachReluctance); }); return new BoardAndAlightSlack( diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java index 3ae029fdb2d..b4912aacb1d 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java @@ -203,6 +203,9 @@ public abstract class RoutingResource { @QueryParam("carReluctance") protected Double carReluctance; + @QueryParam("extraSearchCoachReluctance") + protected Double extraSearchCoachReluctance; + /** * How much worse is waiting for a transit vehicle than being on a transit vehicle, as a * multiplier. The default value treats wait and on-vehicle time as the same. diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java new file mode 100644 index 00000000000..e80e106ce1e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/ConcurrentCompositeWorker.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.api.response.StopArrivals; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.multicriteria.McRaptorWorkerResult; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConcurrentCompositeWorker implements RaptorWorker { + + private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeWorker.class); + + private final RaptorWorker mainWorker; + private final RaptorWorker alternativeWorker; + + ConcurrentCompositeWorker(RaptorWorker mainWorker, RaptorWorker alternativeWorker) { + this.mainWorker = mainWorker; + this.alternativeWorker = alternativeWorker; + } + + @Override + public RaptorWorkerResult route() { + if (OTPFeature.ParallelRouting.isOn()) { + var mainResultFuture = CompletableFuture.supplyAsync(mainWorker::route); + var alternativeResultFuture = CompletableFuture.supplyAsync(alternativeWorker::route); + + try { + return new RaptorWorkerResultComposite<>( + mainResultFuture.get(), + alternativeResultFuture.get() + ); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } else { + var mainResult = mainWorker.route(); + var alternativeResult = alternativeWorker.route(); + return new RaptorWorkerResultComposite<>(mainResult, alternativeResult); + } + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java new file mode 100644 index 00000000000..9be309650aa --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturHackSorlandsBanen.java @@ -0,0 +1,141 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.Function; +import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +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.configure.RaptorConfig; +import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.IndexBasedFactorStrategy; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +public class EnturHackSorlandsBanen { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + public static boolean match(RaptorRequest mcRequest) { + return mcRequest.extraSearchCoachReluctance > 0.1; + } + + public static RaptorWorker worker( + RaptorConfig config, + RaptorTransitDataProvider transitData, + RaptorRequest mcRequest, + Heuristics destinationHeuristics + ) { + //noinspection unchecked + RaptorTransitDataProvider altTransitData = (RaptorTransitDataProvider) ( + (RaptorRoutingRequestTransitData) transitData + ).enturHackSorlandsbanen(mapFactors(mcRequest.extraSearchCoachReluctance)); + + return new ConcurrentCompositeWorker<>( + config.createMcWorker(transitData, mcRequest, destinationHeuristics), + config.createMcWorker(altTransitData, mcRequest, destinationHeuristics) + ); + } + + public static RaptorRequest enableHack( + RaptorRequest raptorRequest, + RouteRequest request, + TransitLayer transitLayer + ) { + if (request.preferences().transit().extraSearchCoachReluctance() < 0.1) { + return raptorRequest; + } + + SearchParams params = raptorRequest.searchParams(); + + WgsCoordinate from = findStopCoordinate(request.from(), params.accessPaths(), transitLayer); + WgsCoordinate to = findStopCoordinate(request.to(), params.egressPaths(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return raptorRequest; + } + + double distanceMeters = SphericalDistanceLibrary.distance( + from.latitude(), + from.longitude(), + to.latitude(), + to.longitude() + ); + + if (distanceMeters < MIN_DISTANCE_LIMIT) { + return raptorRequest; + } + + raptorRequest.extraSearchCoachReluctance = + request.preferences().transit().extraSearchCoachReluctance(); + return raptorRequest; + } + + /* private methods */ + + private static Function mapFactors( + final double extraSearchCoachReluctance + ) { + return (FactorStrategy originalFactors) -> { + int[] modeReluctance = new int[TransitMode.values().length]; + for (TransitMode mode : TransitMode.values()) { + int index = mode.ordinal(); + int originalFactor = originalFactors.factor(index); + modeReluctance[index] = + mode == TransitMode.COACH + ? (int) (extraSearchCoachReluctance * originalFactor + 0.5) + : originalFactor; + } + return new IndexBasedFactorStrategy(modeReluctance); + }; + } + + /** + * Find a coordinate matching the given location, in order: + * - First return the coordinate of the location if it exists. + * - Then loop through the access/egress stops and try to find the + * stop or station given by the location id, return the stop/station coordinate. + * - Return the fist stop in the access/egress list coordinate. + */ + @SuppressWarnings("ConstantConditions") + private static WgsCoordinate findStopCoordinate( + GenericLocation location, + Collection accessEgress, + TransitLayer transitLayer + ) { + if (location.lat != null) { + return new WgsCoordinate(location.lat, location.lng); + } + + StopLocation firstStop = null; + for (RaptorAccessEgress it : accessEgress) { + StopLocation stop = transitLayer.getStopByIndex(it.stop()); + if (stop.getId().equals(location.stopId)) { + return stop.getCoordinate(); + } + if (idIsParentStation(stop, location.stopId)) { + return stop.getParentStation().getCoordinate(); + } + if (firstStop == null) { + firstStop = stop; + } + } + return firstStop.getCoordinate(); + } + + private static boolean idIsParentStation(StopLocation stop, FeedScopedId pId) { + return stop.getParentStation() != null && stop.getParentStation().getId().equals(pId); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..94267b3e7c2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,51 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + +final class PathKey { + + private final int hash; + + PathKey(RaptorPath path) { + this.hash = hash(path); + } + + private static int hash(RaptorPath path) { + if (path == null) { + return 0; + } + int result = 1; + + PathLeg leg = path.accessLeg(); + + while (!leg.isEgressLeg()) { + result = 31 * result + leg.toStop(); + result = 31 * result + leg.toTime(); + + if (leg.isTransitLeg()) { + result = 31 * result + leg.asTransitLeg().trip().pattern().debugInfo().hashCode(); + } + leg = leg.nextLeg(); + } + result = 31 * result + leg.toTime(); + + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o.getClass() != PathKey.class) { + return false; + } + return hash == ((PathKey) o).hash; + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java new file mode 100644 index 00000000000..094e652fc4c --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/sorlandsbanen/RaptorWorkerResultComposite.java @@ -0,0 +1,88 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorkerResult; +import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaptorWorkerResultComposite + implements RaptorWorkerResult { + + private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerResultComposite.class); + + private RaptorWorkerResult mainResult; + private RaptorWorkerResult alternativeResult; + + public RaptorWorkerResultComposite( + RaptorWorkerResult mainResult, + RaptorWorkerResult alternativeResult + ) { + this.mainResult = mainResult; + this.alternativeResult = alternativeResult; + } + + @Override + public Collection> extractPaths() { + Map> paths = new HashMap<>(); + addAll(paths, mainResult.extractPaths()); + addExtraRail(paths, alternativeResult.extractPaths()); + return paths.values(); + } + + @Override + public SingleCriteriaStopArrivals extractBestOverallArrivals() { + return mainResult.extractBestOverallArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestTransitArrivals() { + return mainResult.extractBestTransitArrivals(); + } + + @Override + public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { + return mainResult.extractBestNumberOfTransfers(); + } + + @Override + public boolean isDestinationReached() { + return mainResult.isDestinationReached(); + } + + private void addExtraRail(Map> map, Collection> paths) { + paths.forEach(p -> { + if (hasRail(p)) { + var v = map.put(new PathKey(p), p); + LOG.debug("Ex.Rail {} : {}", (v == null ? "ADD " : "SKIP"), p); + } else { + LOG.debug("Ex. NOT Rail : {}", p); + } + }); + } + + private void addAll(Map> map, Collection> paths) { + paths.forEach(p -> { + var v = map.put(new PathKey(p), p); + LOG.debug("Normal {} : {}", (v == null ? "ADD " : "SKIP"), p); + }); + } + + private static boolean hasRail(RaptorPath path) { + return path + .legStream() + .filter(PathLeg::isTransitLeg) + .anyMatch(leg -> { + var trip = (TripScheduleWithOffset) leg.asTransitLeg().trip(); + var mode = trip.getOriginalTripPattern().getMode(); + return mode == TransitMode.RAIL; + }); + } +} 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 5ed3264a4fd..26676d96c7b 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 @@ -616,6 +616,14 @@ Normally this is when the search is performed (now), plus a small grace period t ) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("extraSearchCoachReluctance") + .description("FOR TESTING ONLY") + .type(Scalars.GraphQLFloat) + .build() + ) .dataFetcher(environment -> new TransmodelGraphQLPlanner().plan(environment)) .build(); } diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 29489de19f2..d7bd15922fc 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -32,6 +32,7 @@ public enum OTPFeature { """ ), FloatingBike(true, false, "Enable floating bike routing."), + HackSorlandsbanen(false, true, "Includ Sørlandsbanen"), GtfsGraphQlApi(true, false, "Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md)."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java index 010bff4bc08..c53f0f90ae5 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequest.java @@ -29,6 +29,9 @@ public class RaptorRequest { private final DebugRequest debug; private final RaptorTimers performanceTimers; + // HACK SØRLANDSBANEN + public double extraSearchCoachReluctance = 0.0; + private RaptorRequest() { searchParams = SearchParams.defaults(); profile = RaptorProfile.MULTI_CRITERIA; @@ -49,6 +52,7 @@ private RaptorRequest() { this.multiCriteria = builder.multiCriteria(); this.performanceTimers = builder.performanceTimers(); this.debug = builder.debug().build(); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; verify(); } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java index ed2aab3de20..7bb8b538843 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/RaptorRequestBuilder.java @@ -34,6 +34,9 @@ public class RaptorRequestBuilder { // Performance monitoring private RaptorTimers performanceTimers; + /** HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance; + // Algorithm private RaptorProfile profile; @@ -60,6 +63,9 @@ public RaptorRequestBuilder() { // Debug this.debug = new DebugRequestBuilder(defaults.debug()); + + // HACK SØRLANDSBANEN + this.extraSearchCoachReluctance = defaults.extraSearchCoachReluctance; } public SearchParamsBuilder searchParams() { diff --git a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java index fb96b7a8724..2e50dc2042d 100644 --- a/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java +++ b/src/main/java/org/opentripplanner/raptor/service/RangeRaptorDynamicSearch.java @@ -11,6 +11,8 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; @@ -134,7 +136,13 @@ private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) // Create worker if (request.profile().is(MULTI_CRITERIA)) { - raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + // HACK SØRLANDSBANEN + if (OTPFeature.HackSorlandsbanen.isOn() && EnturHackSorlandsBanen.match(request)) { + raptorWorker = + EnturHackSorlandsBanen.worker(config, transitData, request, getDestinationHeuristics()); + } else { + raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); + } } else { raptorWorker = config.createStdWorker(transitData, request); } 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 6a1404c3039..eaab9741570 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 @@ -127,7 +127,8 @@ private TransitRouterResult route() { serverContext.raptorConfig().isMultiThreaded(), accessEgresses.getAccesses(), accessEgresses.getEgresses(), - serverContext.meterRegistry() + serverContext.meterRegistry(), + transitLayer ); // Route transit diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 43faa3f0c40..c3bde3e1cc7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -1,5 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost; +import java.util.function.Function; import javax.annotation.Nullable; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -230,4 +231,19 @@ private int boardingCostConstrainedTransfer( // fallback to regular transfer return boardingCostRegularTransfer(firstBoarding, prevArrivalTime, boardStop, boardTime); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + public DefaultCostCalculator( + DefaultCostCalculator original, + Function modeReluctanceMapper + ) { + this.boardCostOnly = original.boardCostOnly; + this.boardAndTransferCost = original.boardAndTransferCost; + this.waitFactor = original.waitFactor; + this.transferCostOnly = original.transferCostOnly; + this.transitFactors = modeReluctanceMapper.apply(original.transitFactors); + this.stopBoardAlightTransferCosts = original.stopBoardAlightTransferCosts; + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java index fba2a7cfe38..8a14c27566d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/FactorStrategy.java @@ -6,7 +6,7 @@ *

* The class and methods are {@code final} to help the JIT compiler optimize the use of this class. */ -interface FactorStrategy { +public interface FactorStrategy { /** * Return the factor for the given index. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java index 152f199248c..4c66b5b452e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java @@ -6,12 +6,12 @@ * This class keep a facto for each index and the minimum factor for fast retrieval during Raptor * search. */ -final class IndexBasedFactorStrategy implements FactorStrategy { +public final class IndexBasedFactorStrategy implements FactorStrategy { private final int[] factors; private final int minFactor; - private IndexBasedFactorStrategy(int[] factors) { + public IndexBasedFactorStrategy(int[] factors) { this.factors = factors; this.minFactor = findMinimumFactor(factors); } 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 879536fdcd0..17b96790a5f 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 org.opentripplanner.ext.sorlandsbanen.EnturHackSorlandsBanen; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; @@ -20,6 +21,8 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; import org.opentripplanner.routing.api.request.DebugEventType; @@ -36,13 +39,16 @@ public class RaptorRequestMapper { private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; + private final TransitLayer transitLayer; + private RaptorRequestMapper( RouteRequest request, boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, long transitSearchTimeZeroEpocSecond, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { this.request = request; this.isMultiThreadedEnbled = isMultiThreaded; @@ -50,6 +56,7 @@ private RaptorRequestMapper( this.egressPaths = egressPaths; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; + this.transitLayer = transitLayer; } public static RaptorRequest mapRequest( @@ -58,7 +65,8 @@ public static RaptorRequest mapRequest( boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, - MeterRegistry meterRegistry + MeterRegistry meterRegistry, + TransitLayer transitLayer ) { return new RaptorRequestMapper( request, @@ -66,7 +74,8 @@ public static RaptorRequest mapRequest( accessPaths, egressPaths, transitSearchTimeZero.toEpochSecond(), - meterRegistry + meterRegistry, + transitLayer ) .doMap(); } @@ -176,7 +185,7 @@ private RaptorRequest doMap() { ); } - return builder.build(); + return EnturHackSorlandsBanen.enableHack(builder.build(), request, transitLayer); } private List mapPassThroughPoints() { 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 8c310206a01..d4785dc6f5b 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 @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Iterator; import java.util.List; +import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.application.OTPFeature; @@ -27,6 +28,8 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedBoardingSearch; import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.CostCalculatorFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.DefaultCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.FactorStrategy; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.GeneralizedCostParametersMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -253,4 +256,34 @@ private PriorityGroupConfigurator createTransitGroupPriorityConfigurator(RouteRe transitRequest.priorityGroupsGlobal() ); } + + /*-- HACK SØRLANDSBANEN :: BEGIN --*/ + + private RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + Function mapFactors + ) { + this.transitLayer = original.transitLayer; + this.transitSearchTimeZero = original.transitSearchTimeZero; + this.activeTripPatternsPerStop = original.activeTripPatternsPerStop; + this.patternIndex = original.patternIndex; + this.transferIndex = original.transferIndex; + this.transferService = original.transferService; + this.constrainedTransfers = original.constrainedTransfers; + this.validTransitDataStartTime = original.validTransitDataStartTime; + this.validTransitDataEndTime = original.validTransitDataEndTime; + this.generalizedCostCalculator = + new DefaultCostCalculator<>( + (DefaultCostCalculator) original.generalizedCostCalculator, + mapFactors + ); + this.slackProvider = original.slackProvider(); + } + + public RaptorTransitDataProvider enturHackSorlandsbanen( + Function mapFactors + ) { + return new RaptorRoutingRequestTransitData(this, mapFactors); + } + /*-- HACK SØRLANDSBANEN :: END --*/ } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java index 78f30277e72..94a4a458915 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java @@ -31,6 +31,7 @@ public final class TransitPreferences implements Serializable { private final boolean includePlannedCancellations; private final boolean includeRealtimeCancellations; private final RaptorPreferences raptor; + private final double extraSearchCoachReluctance; private TransitPreferences() { this.boardSlack = this.alightSlack = DurationForEnum.of(TransitMode.class).build(); @@ -42,6 +43,7 @@ private TransitPreferences() { this.includePlannedCancellations = false; this.includeRealtimeCancellations = false; this.raptor = RaptorPreferences.DEFAULT; + this.extraSearchCoachReluctance = 0.0; } private TransitPreferences(Builder builder) { @@ -55,6 +57,7 @@ private TransitPreferences(Builder builder) { this.includePlannedCancellations = builder.includePlannedCancellations; this.includeRealtimeCancellations = builder.includeRealtimeCancellations; this.raptor = requireNonNull(builder.raptor); + this.extraSearchCoachReluctance = builder.extraSearchCoachReluctance; } public static Builder of() { @@ -165,6 +168,11 @@ public RaptorPreferences raptor() { return raptor; } + /** Zero means turned off. HACK SØRLANDSBANEN */ + public double extraSearchCoachReluctance() { + return extraSearchCoachReluctance; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -180,6 +188,7 @@ public boolean equals(Object o) { ignoreRealtimeUpdates == that.ignoreRealtimeUpdates && includePlannedCancellations == that.includePlannedCancellations && includeRealtimeCancellations == that.includeRealtimeCancellations && + extraSearchCoachReluctance == that.extraSearchCoachReluctance && raptor.equals(that.raptor) ); } @@ -196,6 +205,7 @@ public int hashCode() { ignoreRealtimeUpdates, includePlannedCancellations, includeRealtimeCancellations, + extraSearchCoachReluctance, raptor ); } @@ -245,6 +255,7 @@ public static class Builder { private boolean includePlannedCancellations; private boolean includeRealtimeCancellations; private RaptorPreferences raptor; + private double extraSearchCoachReluctance; public Builder(TransitPreferences original) { this.original = original; @@ -258,6 +269,7 @@ public Builder(TransitPreferences original) { this.includePlannedCancellations = original.includePlannedCancellations; this.includeRealtimeCancellations = original.includeRealtimeCancellations; this.raptor = original.raptor; + this.extraSearchCoachReluctance = original.extraSearchCoachReluctance; } public TransitPreferences original() { @@ -327,6 +339,11 @@ public Builder withRaptor(Consumer body) { return this; } + public Builder setExtraSearchCoachReluctance(double extraSearchCoachReluctance) { + this.extraSearchCoachReluctance = extraSearchCoachReluctance; + return this; + } + public Builder apply(Consumer body) { body.accept(this); return this; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index f9ea136fbe2..df1ee7bb0eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -316,13 +316,14 @@ The board time is added to the time when going from the stop (offboard) to onboa } // TODO REMOVE THIS - builder.withRaptor(it -> - c - .of("relaxTransitSearchGeneralizedCostAtDestination") - .since(V2_3) - .summary("Whether non-optimal transit paths at the destination should be returned") - .description( - """ + builder + .withRaptor(it -> + c + .of("relaxTransitSearchGeneralizedCostAtDestination") + .since(V2_3) + .summary("Whether non-optimal transit paths at the destination should be returned") + .description( + """ Let c be the existing minimum pareto optimal generalized cost to beat. Then a trip with cost c' is accepted if the following is true: `c' < Math.round(c * relaxRaptorCostCriteria)`. @@ -332,10 +333,17 @@ The board time is added to the time when going from the stop (offboard) to onboa Values equals or less than zero is not allowed. Values greater than 2.0 are not supported, due to performance reasons. """ - ) - .asDoubleOptional() - .ifPresent(it::withRelaxGeneralizedCostAtDestination) - ); + ) + .asDoubleOptional() + .ifPresent(it::withRelaxGeneralizedCostAtDestination) + ) + .setExtraSearchCoachReluctance( + c + .of("extraSearchCoachReluctance") + .since(V2_1) + .summary("TODO") + .asDouble(dft.extraSearchCoachReluctance()) + ); } private static void mapBikePreferences(NodeAdapter root, BikePreferences.Builder builder) { diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index fbaa2418d7e..3462c67e458 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -805,6 +805,8 @@ type QueryType { dateTime: DateTime, "Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters." debugItineraryFilter: Boolean = false @deprecated(reason : "Use `itineraryFilter.debug` instead."), + "FOR TESTING ONLY" + extraSearchCoachReluctance: Float, "A list of filters for which trips should be included. A trip will be included if it matches with at least one filter. An empty list of filters means that all trips should be included. If a search include this parameter, \"whiteListed\", \"banned\" & \"modes.transportModes\" filters will be ignored." filters: [TripFilterInput!], "The start location" diff --git a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java index 39022da42ca..aeca5a36ebf 100644 --- a/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java +++ b/src/test/java/org/opentripplanner/raptor/RaptorArchitectureTest.java @@ -39,6 +39,9 @@ public class RaptorArchitectureTest { private static final Package RR_STANDARD = RANGE_RAPTOR.subPackage("standard"); private static final Package RR_STD_CONFIGURE = RR_STANDARD.subPackage("configure"); private static final Package RR_CONTEXT = RANGE_RAPTOR.subPackage("context"); + private static final Package EXT_SORLANDSBANAN_HACK = Package.of( + "org.opentripplanner.ext.sorlandsbanen" + ); /** * Packages used by standard-range-raptor and multi-criteria-range-raptor. @@ -200,7 +203,8 @@ void enforcePackageDependenciesInRaptorService() { RAPTOR_UTIL, CONFIGURE, RR_INTERNAL_API, - RR_TRANSIT + RR_TRANSIT, + EXT_SORLANDSBANAN_HACK ) .verify(); } 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..e3fbbc0df21 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 @@ -90,6 +90,7 @@ private static RaptorRequest map(RouteRequest request) { false, ACCESS, EGRESS, + null, null ); }