diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java new file mode 100644 index 00000000000..b7ce446d430 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java @@ -0,0 +1,79 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorTransferConstraint; +import org.opentripplanner.raptor.spi.RaptorCostCalculator; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; +import org.opentripplanner.transit.model.basic.TransitMode; + + +/** + * This cost calculator increases the cost on mode coach by adding an extra reluctance. The + * reluctance is hardcoded in this class and cannot be configured. + */ +class CoachCostCalculator implements RaptorCostCalculator { + + private static final int EXTRA_RELUCTANCE_ON_COACH = RaptorCostConverter.toRaptorCost(0.6); + + private final RaptorCostCalculator delegate; + + CoachCostCalculator(RaptorCostCalculator delegate) { + this.delegate = delegate; + } + + @Override + public int boardingCost( + boolean firstBoarding, + int prevArrivalTime, + int boardStop, + int boardTime, + T trip, + RaptorTransferConstraint transferConstraints + ) { + return delegate.boardingCost( + firstBoarding, + prevArrivalTime, + boardStop, + boardTime, + trip, + transferConstraints + ); + } + + @Override + public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { + return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + } + + @Override + public int transitArrivalCost( + int boardCost, + int alightSlack, + int transitTime, + T trip, + int toStop + ) { + int cost = delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStop); + if(trip.transitReluctanceFactorIndex() == TransitMode.COACH.ordinal()) { + cost += transitTime * EXTRA_RELUCTANCE_ON_COACH; + } + return cost; + } + + @Override + public int waitCost(int waitTimeInSeconds) { + return delegate.waitCost(waitTimeInSeconds); + } + + @Override + public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop) { + return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStop); + } + + @Override + public int costEgress(RaptorAccessEgress egress) { + return delegate.costEgress(egress); + } + +} diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturSorlandsbanenService.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturSorlandsbanenService.java new file mode 100644 index 00000000000..a973617f257 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/EnturSorlandsbanenService.java @@ -0,0 +1,103 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.function.BiFunction; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.raptor.spi.ExtraMcRouterSearch; +import org.opentripplanner.raptor.spi.RaptorTransitDataProvider; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgresses; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.StopLocation; + +/** + * This is basically a big hack to produce results containing "Sørlandsbanen" in Norway. This + * railroad line is slow and goes inland fare from where people live. Despite this, people and the + * operator want to show it in the results for log travel along the southern part of Norway where + * ii is an option. Tuning the search has proven to be challenging. It is solved here by doing + * two searches. One normal search and one where the rail is given a big cost advantage over coach. + * If train results are found in the second search, then it is added to the results of the first + * search. Everything found in the first search is always returned. + */ +public class EnturSorlandsbanenService { + + private static final double SOUTH_BOARDER_LIMIT = 59.1; + private static final int MIN_DISTANCE_LIMIT = 120_000; + + + public ExtraMcRouterSearch createMcRouterFactory(RouteRequest request, AccessEgresses accessEgresses, TransitLayer transitLayer) { + WgsCoordinate from = findStopCoordinate( + request.from(), + accessEgresses.getAccesses(), + transitLayer + ); + WgsCoordinate to = findStopCoordinate(request.to(), accessEgresses.getEgresses(), transitLayer); + + if (from.latitude() > SOUTH_BOARDER_LIMIT && to.latitude() > SOUTH_BOARDER_LIMIT) { + return null; + } + + double distance = from.distanceTo(to); + if (distance < MIN_DISTANCE_LIMIT) { + return null; + } + + return new ExtraMcRouterSearch<>() { + @Override + public RaptorTransitDataProvider createTransitDataAlternativeSearch(RaptorTransitDataProvider transitDataMainSearch) { + return new RaptorRoutingRequestTransitData( + (RaptorRoutingRequestTransitData)transitDataMainSearch, + new CoachCostCalculator<>(transitDataMainSearch.multiCriteriaCostCalculator()) + ); + } + + @Override + public BiFunction>, Collection>, Collection>> merger() { + return new MergePaths<>(); + } + }; + } + + /** + * 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 (RoutingAccessEgress 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/MergePaths.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/MergePaths.java new file mode 100644 index 00000000000..b5f94019b72 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/MergePaths.java @@ -0,0 +1,52 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; +import org.opentripplanner.raptor.api.model.RaptorTripSchedule; +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.TripScheduleWithOffset; +import org.opentripplanner.transit.model.basic.TransitMode; + +/** + * Strategy for merging the main results and the extra rail results from Sorlandsbanen. + * Everything from the main result is kept, and any additional rail results from the alternative + * search are added. + */ +class MergePaths implements BiFunction>, Collection>, Collection>> { + + @Override + public Collection> apply(Collection> main, Collection> railAlternatives) { + Map> result = new HashMap<>(); + addAllToMap(result, main); + addRailToMap(result, railAlternatives); + return result.values(); + } + + private void addAllToMap(Map> map, Collection> paths) { + for (var it : paths) { + map.put(new PathKey(it), it); + } + } + + private void addRailToMap(Map> map, Collection> paths) { + for (var it : paths) { + if (hasRail(it)) { + map.put(new PathKey(it), it); + } + } + } + + 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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java new file mode 100644 index 00000000000..28c1b1eac25 --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/PathKey.java @@ -0,0 +1,58 @@ +package org.opentripplanner.ext.sorlandsbanen; + +import org.opentripplanner.raptor.api.path.PathLeg; +import org.opentripplanner.raptor.api.path.RaptorPath; + + +/** + * Uses a hash to create a key for access, egress and transit legs in a path. Transfers + * are not included. The key is used to exclude duplicates. This approach may drop valid results + * when there is a hash collision, but this whole sandbox feature is a hack - so we can tolerate + * this here. + */ +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/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/configure/EnturSorlandsbanenModule.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/configure/EnturSorlandsbanenModule.java new file mode 100644 index 00000000000..b1208f2c68b --- /dev/null +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/configure/EnturSorlandsbanenModule.java @@ -0,0 +1,17 @@ +package org.opentripplanner.ext.sorlandsbanen.configure; + +import dagger.Module; +import dagger.Provides; +import javax.annotation.Nullable; +import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; +import org.opentripplanner.framework.application.OTPFeature; + +@Module +public class EnturSorlandsbanenModule { + + @Provides + @Nullable + EnturSorlandsbanenService providesEnturSorlandsbanenService() { + return OTPFeature.Sorlandsbanen.isOn() ? new EnturSorlandsbanenService() : null; + } +} diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 324f5397673..c4cb86ebfb9 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -118,6 +118,11 @@ public enum OTPFeature { SandboxAPIGeocoder(false, true, "Enable the Geocoder API."), SandboxAPIMapboxVectorTilesApi(false, true, "Enable Mapbox vector tiles API."), SandboxAPIParkAndRideApi(false, true, "Enable park-and-ride endpoint."), + Sorlandsbanen( + false, + true, + "Include train Sørlandsbanen in results when searchig in south of Norway. Only relevant in Norway." + ), TransferAnalyzer(false, true, "Analyze transfers during graph build."); private static final Object TEST_LOCK = new Object(); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index e72d8ee1427..d6c03c4d773 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -12,12 +12,14 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.stream.IntStream; +import javax.annotation.Nullable; import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.api.response.RaptorResponse; +import org.opentripplanner.raptor.spi.ExtraMcRouterSearch; import org.opentripplanner.routing.algorithm.mapping.RaptorPathToItineraryMapper; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressPenaltyDecorator; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter; @@ -142,7 +144,10 @@ private TransitRouterResult route() { ); // Route transit - var raptorService = new RaptorService<>(serverContext.raptorConfig()); + var raptorService = new RaptorService<>( + serverContext.raptorConfig(), + createMcRouterFactory(accessEgresses, transitLayer) + ); var transitResponse = raptorService.route(raptorRequest, requestTransitDataProvider); checkIfTransitConnectionExists(transitResponse); @@ -387,4 +392,21 @@ private IntStream listStopIndexes(FeedScopedId stopLocationId) { } return stops.stream().mapToInt(StopLocation::getIndex); } + + /** + * An optional factory for creating a decorator around the multi-criteria RangeRaptor instance. + */ + @Nullable + private ExtraMcRouterSearch createMcRouterFactory( + AccessEgresses accessEgresses, + TransitLayer transitLayer + ) { + if (OTPFeature.Sorlandsbanen.isOff()) { + return null; + } + var service = serverContext.enturSorlandsbanenService(); + return service == null + ? null + : service.createMcRouterFactory(request, accessEgresses, transitLayer); + } } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java index 0182598ca35..f1b462e031a 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java @@ -128,6 +128,23 @@ public RaptorRoutingRequestTransitData( ); } + public RaptorRoutingRequestTransitData( + RaptorRoutingRequestTransitData original, + RaptorCostCalculator newCostCalculator + ) { + 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 = newCostCalculator; + this.slackProvider = original.slackProvider(); + } + @Override public Iterator getTransfersFromStop(int stopIndex) { return transferIndex.getForwardTransfers(stopIndex).iterator(); diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 7ec71e589c7..765262840ee 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -10,6 +10,7 @@ import org.opentripplanner.ext.flex.FlexParameters; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.ridehailing.RideHailingService; +import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.inspector.raster.TileRendererManager; @@ -101,16 +102,10 @@ public interface OtpServerRequestContext { List rideHailingServices(); - @Nullable - StopConsolidationService stopConsolidationService(); - StreetLimitationParametersService streetLimitationParametersService(); MeterRegistry meterRegistry(); - @Nullable - EmissionsService emissionsService(); - /** Inspector/debug services */ TileRendererManager tileRendererManager(); @@ -129,6 +124,8 @@ default GraphFinder graphFinder() { VectorTileConfig vectorTileConfig(); + /* Sandbox modules */ + @Nullable default DataOverlayContext dataOverlayContext(RouteRequest request) { return OTPFeature.DataOverlay.isOnElseNull(() -> @@ -139,6 +136,15 @@ default DataOverlayContext dataOverlayContext(RouteRequest request) { ); } + @Nullable + EmissionsService emissionsService(); + @Nullable LuceneIndex lucenceIndex(); + + @Nullable + StopConsolidationService stopConsolidationService(); + + @Nullable + EnturSorlandsbanenService enturSorlandsbanenService(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index b307776ef52..c76595a3eed 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -10,6 +10,8 @@ import org.opentripplanner.ext.geocoder.configure.GeocoderModule; import org.opentripplanner.ext.interactivelauncher.configuration.InteractiveLauncherModule; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; +import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; +import org.opentripplanner.ext.sorlandsbanen.configure.EnturSorlandsbanenModule; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; import org.opentripplanner.ext.stopconsolidation.configure.StopConsolidationServiceModule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; @@ -55,6 +57,7 @@ ConstructApplicationModule.class, RideHailingServicesModule.class, EmissionsServiceModule.class, + EnturSorlandsbanenModule.class, StopConsolidationServiceModule.class, InteractiveLauncherModule.class, StreetLimitationParametersServiceModule.class, @@ -90,6 +93,9 @@ public interface ConstructApplicationFactory { StreetLimitationParameters streetLimitationParameters(); + @Nullable + EnturSorlandsbanenService enturSorlandsbanenService(); + @Nullable LuceneIndex luceneIndex(); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index 6c830054c49..75e2d0940a3 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -10,6 +10,7 @@ import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator; import org.opentripplanner.ext.ridehailing.RideHailingService; +import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; @@ -41,6 +42,7 @@ OtpServerRequestContext providesServerContext( StreetLimitationParametersService streetLimitationParametersService, @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, + @Nullable EnturSorlandsbanenService enturSorlandsbanenService, LauncherRequestDecorator launcherRequestDecorator, @Nullable LuceneIndex luceneIndex ) { @@ -58,6 +60,7 @@ OtpServerRequestContext providesServerContext( realtimeVehicleService, vehicleRentalService, emissionsService, + enturSorlandsbanenService, routerConfig.flexParameters(), rideHailingServices, stopConsolidationService, diff --git a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 0e81193d787..2c35dfe6263 100644 --- a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -9,6 +9,7 @@ import org.opentripplanner.ext.flex.FlexParameters; import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.ridehailing.RideHailingService; +import org.opentripplanner.ext.sorlandsbanen.EnturSorlandsbanenService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.inspector.raster.TileRendererManager; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; @@ -48,6 +49,10 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final RealtimeVehicleService realtimeVehicleService; private final VehicleRentalService vehicleRentalService; private final EmissionsService emissionsService; + + @Nullable + private final EnturSorlandsbanenService enturSorlandsbanenService; + private final StopConsolidationService stopConsolidationService; private final StreetLimitationParametersService streetLimitationParametersService; private final LuceneIndex luceneIndex; @@ -67,12 +72,13 @@ private DefaultServerRequestContext( WorldEnvelopeService worldEnvelopeService, RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, - EmissionsService emissionsService, + @Nullable EmissionsService emissionsService, + @Nullable EnturSorlandsbanenService enturSorlandsbanenService, List rideHailingServices, - StopConsolidationService stopConsolidationService, + @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, FlexParameters flexParameters, - TraverseVisitor traverseVisitor, + @Nullable TraverseVisitor traverseVisitor, @Nullable LuceneIndex luceneIndex ) { this.graph = graph; @@ -90,6 +96,7 @@ private DefaultServerRequestContext( this.realtimeVehicleService = realtimeVehicleService; this.rideHailingServices = rideHailingServices; this.emissionsService = emissionsService; + this.enturSorlandsbanenService = enturSorlandsbanenService; this.stopConsolidationService = stopConsolidationService; this.streetLimitationParametersService = streetLimitationParametersService; this.luceneIndex = luceneIndex; @@ -110,6 +117,7 @@ public static DefaultServerRequestContext create( RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, @Nullable EmissionsService emissionsService, + @Nullable EnturSorlandsbanenService enturSorlandsbanenService, FlexParameters flexParameters, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, @@ -130,6 +138,7 @@ public static DefaultServerRequestContext create( realtimeVehicleService, vehicleRentalService, emissionsService, + enturSorlandsbanenService, rideHailingServices, stopConsolidationService, streetLimitationParametersService, @@ -251,4 +260,9 @@ public LuceneIndex lucenceIndex() { public EmissionsService emissionsService() { return emissionsService; } + + @Nullable + public EnturSorlandsbanenService enturSorlandsbanenService() { + return enturSorlandsbanenService; + } } diff --git a/application/src/test/java/org/opentripplanner/TestServerContext.java b/application/src/test/java/org/opentripplanner/TestServerContext.java index 90dca6ff840..b403e9432fe 100644 --- a/application/src/test/java/org/opentripplanner/TestServerContext.java +++ b/application/src/test/java/org/opentripplanner/TestServerContext.java @@ -51,6 +51,7 @@ public static OtpServerRequestContext createServerContext( createRealtimeVehicleService(transitService), createVehicleRentalService(), createEmissionsService(), + null, routerConfig.flexParameters(), List.of(), null, diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index 38a411ac1cf..d71a11aad68 100644 --- a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -141,6 +141,7 @@ void setup() { new DefaultRealtimeVehicleService(transitService), new DefaultVehicleRentalService(), new DefaultEmissionsService(new EmissionsDataModel()), + null, RouterConfig.DEFAULT.flexParameters(), List.of(), null, diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 85a33281f81..087843644b1 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -117,6 +117,7 @@ public SpeedTest( TestServerContext.createRealtimeVehicleService(transitService), TestServerContext.createVehicleRentalService(), TestServerContext.createEmissionsService(), + null, config.flexConfig, List.of(), null, diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..a45e4253a06 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -250,6 +250,7 @@ Here is a list of all features which can be toggled on/off and their default val | `SandboxAPIGeocoder` | Enable the Geocoder API. | | ✓️ | | `SandboxAPIMapboxVectorTilesApi` | Enable Mapbox vector tiles API. | | ✓️ | | `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | +| `Sorlandsbanen` | Include train Sørlandsbanen in results when searchig in south of Norway. Only relevant in Norway. | | ✓️ | | `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | diff --git a/doc/user/sandbox/Sorlandsbanen.md b/doc/user/sandbox/Sorlandsbanen.md new file mode 100644 index 00000000000..4898a29af37 --- /dev/null +++ b/doc/user/sandbox/Sorlandsbanen.md @@ -0,0 +1,44 @@ +# Sørlandsbanen - The southern railroad in Norway + +**This sandbox module is only working in Norway**, in particular only in the south of Norway. The +feature flag to turn it *on* should only be enabled if you are routing using the norwegian data set. + +The railroad in southern Norway is very slow and does not go by the cost where most people live. It +is easily beaten by coaches in the area. Despite this, we need to include it in results where it is +relevant. + +When the feature flag is enabled, two Raptor searches are performed. The first is the regular +search - unmodified, as requested by the user. The second search is modified to include train +results with Sørlandsbanen. This is achieved by setting a high COACH reluctance. We then take any +rail results(if they exist) from the second search and add it two to the results from the first +search. The new set of results will contain everything we found in the first search, plus the train +results in the second results. + +Note! This is a hack and the logic to enable this look at the origin and destination coordinates +in addition to the feature flag. + + +## Contact Info + +- Entur, Norway + +## Changelog + +- 2024-10-14: We have used this feature for som time, but now want it in the Sandbox so we do not + need to merge it everytime we create a new entur release. + + +### Configuration + +This is turned _off_ by default. To turn it on enable the `Sorlandsbanen` feature. + +```json +// otp-config.json +{ + "otpFeatures": { + "Sorlandsbanen": true + } +} +``` + + diff --git a/mkdocs.yml b/mkdocs.yml index 1364be7be1f..7d1c25bd45d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,3 +122,4 @@ nav: - Ride Hailing: 'sandbox/RideHailing.md' - Emissions: 'sandbox/Emissions.md' - Stop Consolidation: 'sandbox/StopConsolidation.md' + - Sørlandsbanen: 'sandbox/Sorlandsbanen.md'