From f6adb13a34c3e57949b64cbb58f0e65430f7f269 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 17 Oct 2024 17:11:23 +0300 Subject: [PATCH] Add ability to create special transfer requests for cars and bikes with the 'carsAllowedStopMaxTransferDurationsForMode' build config parameter. --- .../api/parameter/QualifiedModeSet.java | 6 +- .../module/DirectTransferGenerator.java | 125 +++++++-- .../module/configure/GraphBuilderModules.java | 3 +- .../nearbystops/StreetNearbyStopFinder.java | 30 ++- .../request/framework/DurationForEnum.java | 4 + .../standalone/config/BuildConfig.java | 10 + ...llowedStopMaxTransferDurationsForMode.java | 55 ++++ .../module/DirectTransferGeneratorTest.java | 239 +++++++++++++++++- .../StreetNearbyStopFinderTest.java | 72 ++++++ doc/user/BuildConfiguration.md | 222 +++++++++------- 10 files changed, 634 insertions(+), 132 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java diff --git a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java index c6f1a3d74ec..c13fb576ad5 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java @@ -126,10 +126,8 @@ public RequestModes getRequestModes() { mBuilder.withEgressMode(StreetMode.CAR_HAILING); mBuilder.withDirectMode(StreetMode.WALK); } else { - mBuilder.withAccessMode(StreetMode.WALK); - mBuilder.withTransferMode(StreetMode.WALK); - mBuilder.withEgressMode(StreetMode.WALK); - mBuilder.withDirectMode(StreetMode.CAR); + // This is used in transfer cache request calculations. + mBuilder.withAllStreetModes(StreetMode.CAR); } } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index a1a0796c66a..ad2cc7c57c3 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -3,10 +3,13 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimaps; import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -18,6 +21,8 @@ import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.model.PathTransfer; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.edge.Edge; @@ -44,6 +49,7 @@ public class DirectTransferGenerator implements GraphBuilderModule { private final Duration radiusByDuration; private final List transferRequests; + private final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; private final Graph graph; private final TimetableRepository timetableRepository; private final DataImportIssueStore issueStore; @@ -60,6 +66,23 @@ public DirectTransferGenerator( this.issueStore = issueStore; this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; + this.carsAllowedStopMaxTransferDurationsForMode = DurationForEnum.of(StreetMode.class).build(); + } + + public DirectTransferGenerator( + Graph graph, + TimetableRepository timetableRepository, + DataImportIssueStore issueStore, + Duration radiusByDuration, + List transferRequests, + DurationForEnum carsAllowedStopMaxTransferDurationsForMode + ) { + this.graph = graph; + this.timetableRepository = timetableRepository; + this.issueStore = issueStore; + this.radiusByDuration = radiusByDuration; + this.transferRequests = transferRequests; + this.carsAllowedStopMaxTransferDurationsForMode = carsAllowedStopMaxTransferDurationsForMode; } @Override @@ -68,9 +91,16 @@ public void buildGraph() { timetableRepository.index(); /* The linker will use streets if they are available, or straight-line distance otherwise. */ - NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(); + NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(radiusByDuration); List stops = graph.getVerticesOfType(TransitStopVertex.class); + Set carsAllowedStops = timetableRepository + .getStopLocationsUsedForCarsAllowedTrips() + .stream() + .map(StopLocation::getId) + .map(graph::getStopVertexForStopId) + .filter(TransitStopVertex.class::isInstance) // filter out null values if no TransitStopVertex is found for ID + .collect(Collectors.toSet()); ProgressTracker progress = ProgressTracker.track( "Create transfer edges for stops", @@ -86,6 +116,34 @@ public void buildGraph() { HashMultimap.create() ); + List filteredTransferRequests = new ArrayList(); + List carsAllowedStopTransferRequests = new ArrayList(); + HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); + + // Split transfer requests into normal and carsAllowedStop requests. + for (RouteRequest transferProfile : transferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); + if (carsAllowedStopMaxTransferDurationsForMode.containsKey(mode)) { + carsAllowedStopNearbyStopFinders.put( + mode, + createNearbyStopFinder(carsAllowedStopMaxTransferDurationsForMode.valueOf(mode)) + ); + + carsAllowedStopTransferRequests.add(transferProfile); + // For bikes, also normal transfer requests are wanted. + if (mode == StreetMode.BIKE) { + filteredTransferRequests.add(transferProfile); + } + } else if (mode == StreetMode.CAR) { + // Special transfers are always created for cars. + // If a duration is not specified for cars, the default is used. + carsAllowedStopNearbyStopFinders.put(mode, nearbyStopFinder); + carsAllowedStopTransferRequests.add(transferProfile); + } else { + filteredTransferRequests.add(transferProfile); + } + } + stops .stream() .parallel() @@ -101,25 +159,8 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); - for (RouteRequest transferProfile : transferRequests) { - for (NearbyStop sd : nearbyStopFinder.findNearbyStops( - ts0, - transferProfile, - transferProfile.journey().transfer(), - false - )) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop.transfersNotAllowed()) { - continue; - } - distinctTransfers.put( - new TransferKey(stop, sd.stop, sd.edges), - new PathTransfer(stop, sd.stop, sd.distance, sd.edges) - ); - } + for (RouteRequest transferProfile : filteredTransferRequests) { + findNearbyStops(nearbyStopFinder, ts0, transferProfile, stop, distinctTransfers); if (OTPFeature.FlexRouting.isOn()) { // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. @@ -143,6 +184,21 @@ public void buildGraph() { } } } + // This calculates transfers between stops that can use trips with cars. + for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); + if ( + carsAllowedStops.contains(ts0) && carsAllowedStopNearbyStopFinders.containsKey(mode) + ) { + findNearbyStops( + carsAllowedStopNearbyStopFinders.get(mode), + ts0, + transferProfile, + stop, + distinctTransfers + ); + } + } LOG.debug( "Linked stop {} with {} transfers to stops with different patterns.", @@ -179,7 +235,7 @@ public void buildGraph() { * whether the graph has a street network and if ConsiderPatternsForDirectTransfers feature is * enabled. */ - private NearbyStopFinder createNearbyStopFinder() { + private NearbyStopFinder createNearbyStopFinder(Duration radiusByDuration) { var transitService = new DefaultTransitService(timetableRepository); NearbyStopFinder finder; if (!graph.hasStreets) { @@ -199,5 +255,32 @@ private NearbyStopFinder createNearbyStopFinder() { } } + private void findNearbyStops( + NearbyStopFinder nearbyStopFinder, + TransitStopVertex ts0, + RouteRequest transferProfile, + RegularStop stop, + Map distinctTransfers + ) { + for (NearbyStop sd : nearbyStopFinder.findNearbyStops( + ts0, + transferProfile, + transferProfile.journey().transfer(), + false + )) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + distinctTransfers.put( + new TransferKey(stop, sd.stop, sd.edges), + new PathTransfer(stop, sd.stop, sd.distance, sd.edges) + ); + } + } + private record TransferKey(StopLocation source, StopLocation target, List edges) {} } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 080d69c571e..03ea13e9f11 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -237,7 +237,8 @@ static DirectTransferGenerator provideDirectTransferGenerator( timetableRepository, issueStore, config.maxTransferDuration, - config.transferRequests + config.transferRequests, + config.carsAllowedStopMaxTransferDurationsForMode ); } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java index e54c27249e1..97a265942d4 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java @@ -30,8 +30,6 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.StreetSearchBuilder; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.street.search.request.StreetSearchRequestMapper; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.transit.model.site.AreaStop; @@ -42,6 +40,7 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { private final int maxStopCount; private final DataOverlayContext dataOverlayContext; private final Set ignoreVertices; + private final Set findOnlyVertices; /** * Construct a NearbyStopFinder for the given graph and search radius. @@ -54,7 +53,7 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext ) { - this(durationLimit, maxStopCount, dataOverlayContext, Set.of()); + this(durationLimit, maxStopCount, dataOverlayContext, Set.of(), Set.of()); } /** @@ -69,11 +68,31 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext, Set ignoreVertices + ) { + this(durationLimit, maxStopCount, dataOverlayContext, ignoreVertices, Set.of()); + } + + /** + * Construct a NearbyStopFinder for the given graph and search radius. + * + * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount + * we will always return all the directly connected stops. + * @param ignoreVertices A set of stop vertices to ignore and not return NearbyStops for. + * + * @param findOnlyVertices Only return NearbyStops that are in this set. + */ + public StreetNearbyStopFinder( + Duration durationLimit, + int maxStopCount, + DataOverlayContext dataOverlayContext, + Set ignoreVertices, + Set findOnlyVertices ) { this.dataOverlayContext = dataOverlayContext; this.durationLimit = durationLimit; this.maxStopCount = maxStopCount; this.ignoreVertices = ignoreVertices; + this.findOnlyVertices = findOnlyVertices; } /** @@ -146,7 +165,10 @@ public Collection findNearbyStops( continue; } if (targetVertex instanceof TransitStopVertex tsv && state.isFinal()) { - stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); + // If a set of findOnlyVertices is provided, only add stops that are in the set. + if (findOnlyVertices.isEmpty() || findOnlyVertices.contains(targetVertex)) { + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); + } } if ( OTPFeature.FlexRouting.isOn() && diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java index e6dacaac19d..69b15ef4624 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java @@ -49,6 +49,10 @@ public Duration defaultValue() { return defaultValue; } + public boolean containsKey(E key) { + return valueForEnum.containsKey(key); + } + /** * Utility method to get {@link #defaultValue} as an number in unit seconds. Equivalent to * {@code (int) defaultValue.toSeconds()}. The downcast is safe since we only allow days, hours, diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index ef2931b987e..6482868d578 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -33,7 +33,10 @@ import org.opentripplanner.model.calendar.ServiceDateInterval; import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.fares.FareServiceFactory; +import org.opentripplanner.standalone.config.buildconfig.CarsAllowedStopMaxTransferDurationsForMode; import org.opentripplanner.standalone.config.buildconfig.DemConfig; import org.opentripplanner.standalone.config.buildconfig.GtfsConfig; import org.opentripplanner.standalone.config.buildconfig.IslandPruningConfig; @@ -151,6 +154,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final IslandPruningConfig islandPruning; public final Duration maxTransferDuration; + public final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; public final NetexFeedParameters netexDefaults; public final GtfsFeedParameters gtfsDefaults; @@ -287,6 +291,12 @@ When set to true (it is false by default), the elevation module will include the "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); + carsAllowedStopMaxTransferDurationsForMode = + CarsAllowedStopMaxTransferDurationsForMode.map( + root, + "carsAllowedStopMaxTransferDurationsForMode", + maxTransferDuration + ); maxStopToShapeSnapDistance = root .of("maxStopToShapeSnapDistance") diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java new file mode 100644 index 00000000000..322ed32d682 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java @@ -0,0 +1,55 @@ +package org.opentripplanner.standalone.config.buildconfig; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import java.time.Duration; +import java.util.Map; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class CarsAllowedStopMaxTransferDurationsForMode { + + public static DurationForEnum map( + NodeAdapter root, + String parameterName, + Duration maxTransferDuration + ) { + Map values = root + .of(parameterName) + .since(V2_7) + .summary( + "This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars." + ) + .description( + """ +This is a special parameter that only works on transfers between stops that have trips that allow cars. +The duration can be set for either 'BIKE' or 'CAR'. +For cars, transfers are only calculated between stops that have trips that allow cars. +For cars, this overrides the default `maxTransferDuration`. +For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. + +**Example** + +```JSON +// build-config.json +{ + "carsAllowedStopMaxTransferDurationsForMode": { + "CAR": "2h", + "BIKE": "3h" + } +} +``` +""" + ) + .asEnumMap(StreetMode.class, Duration.class); + for (StreetMode mode : values.keySet()) { + if (mode != StreetMode.BIKE && mode != StreetMode.CAR) { + throw new IllegalArgumentException( + "Only the CAR and BIKE modes are allowed in the carsAllowedStopMaxTransferDurationsForMode parameter." + ); + } + } + return DurationForEnum.of(StreetMode.class).withValues(values).build(); + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index bb3144aa904..30bc5f78f0f 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -22,15 +22,18 @@ import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.CarAccess; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; /** * This creates a graph with trip patterns @@ -196,7 +199,7 @@ public void testMultipleRequestsWithoutPatterns() { reqWalk.journey().transfer().setMode(StreetMode.WALK); var reqBike = new RouteRequest(); - reqWalk.journey().transfer().setMode(StreetMode.BIKE); + reqBike.journey().transfer().setMode(StreetMode.BIKE); var transferRequests = List.of(reqWalk, reqBike); @@ -223,7 +226,7 @@ public void testMultipleRequestsWithPatterns() { reqWalk.journey().transfer().setMode(StreetMode.WALK); var reqBike = new RouteRequest(); - reqWalk.journey().transfer().setMode(StreetMode.BIKE); + reqBike.journey().transfer().setMode(StreetMode.BIKE); var transferRequests = List.of(reqWalk, reqBike); @@ -252,7 +255,7 @@ public void testMultipleRequestsWithPatterns() { @Test public void testTransferOnIsolatedStations() { - var otpModel = model(true, false, true); + var otpModel = model(true, false, true, false); var graph = otpModel.graph(); graph.hasStreets = false; @@ -273,18 +276,174 @@ public void testTransferOnIsolatedStations() { assertTrue(timetableRepository.getAllPathTransfers().isEmpty()); } + @Test + public void testRequestWithCarsAllowedPatterns() { + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqCar); + + var otpModel = model(false, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 200, List.of(V0, V12), S12) + ); + } + + @Test + public void testRequestWithCarsAllowedPatternsWithDurationLimit() { + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqCar); + + var otpModel = model(false, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(10)).build() + ) + .buildGraph(); + + assertTransfers(timetableRepository.getAllPathTransfers(), tr(S0, 100, List.of(V0, V11), S11)); + } + + @Test + public void testMultipleRequestsWithPatternsAndWithCarsAllowedPatterns() { + var reqWalk = new RouteRequest(); + reqWalk.journey().transfer().setMode(StreetMode.WALK); + + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqWalk, reqBike, reqCar); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 100, List.of(V11, V21), S21), + tr(S11, 110, List.of(V11, V22), S22), + tr(S11, 100, List.of(V11, V12), S12) + ); + } + + @Test + public void testBikeRequestWithPatternsAndWithCarsAllowedPatterns() { + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var transferRequests = List.of(reqBike); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + Duration.ofSeconds(30), + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.BIKE, Duration.ofSeconds(120)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 110, List.of(V11, V22), S22), + tr(S11, 100, List.of(V11, V12), S12) + ); + } + + @Test + public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTransferRequests() { + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var transferRequests = List.of(reqBike); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + Duration.ofSeconds(30), + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(120)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S11, 110, List.of(V11, V22), S22) + ); + } + private TestOtpModel model(boolean addPatterns) { return model(addPatterns, false); } private TestOtpModel model(boolean addPatterns, boolean withBoardingConstraint) { - return model(addPatterns, withBoardingConstraint, false); + return model(addPatterns, withBoardingConstraint, false, false); } private TestOtpModel model( boolean addPatterns, boolean withBoardingConstraint, - boolean withNoTransfersOnStations + boolean withNoTransfersOnStations, + boolean addCarsAllowedPatterns ) { return modelOf( new Builder() { @@ -352,6 +511,76 @@ public void build() { .build() ); } + + if (addCarsAllowedPatterns) { + var agency = TimetableRepositoryForTest.agency("FerryAgency"); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP3")) + .withRoute(route("R3", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S11), st(S21)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP4")) + .withRoute(route("R4", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S0), st(S13)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP5")) + .withRoute(route("R5", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S12), st(S22)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + } } } ); diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java index 261a40454f0..f6197e9b56c 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java @@ -165,6 +165,78 @@ void testIgnoreStopsWithMaxStops() { assertStopAtDistance(stopC, 200, sortedNearbyStops.get(0)); } + @Test + void testFindOnlyVerticesStops() { + var durationLimit = Duration.ofMinutes(10); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + Set.of(), + findOnlyStops + ); + + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(3); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); + assertStopAtDistance(stopC, 200, sortedNearbyStops.get(2)); + } + + @Test + void testFindOnlyVerticesStopsWithIgnore() { + var durationLimit = Duration.ofMinutes(10); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + Set ignore = Set.of(stopB); + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + ignore, + findOnlyStops + ); + + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(2); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopC, 200, sortedNearbyStops.get(1)); + } + + @Test + void testFindOnlyVerticesStopsWithDurationLimit() { + // If we only allow walk for 101 seconds and speed is 1 m/s we should only be able to reach + // one extra stop. + var durationLimit = Duration.ofSeconds(101); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + var routeRequest = new RouteRequest() + .withPreferences(b -> b.withWalk(walkPreferences -> walkPreferences.withSpeed(1.0))); + + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + Set.of(), + findOnlyStops + ); + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, routeRequest, new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(2); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); + } + private List sort(Collection stops) { return stops.stream().sorted(Comparator.comparing(x -> x.distance)).toList(); } diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 99e98066e73..e2d46e98d73 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -17,103 +17,104 @@ Sections follow that describe particular settings in more depth. -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|--------------------------------------------------------------------------|:------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| -| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | -| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | -| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | -| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | -| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | -| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | -| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | -| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | -| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | -| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | -| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | -| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | -| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | -| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | -| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | -| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | -| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | -| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | -| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | -| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | -| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | -| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | -| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | -| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | -| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | -| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | -| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | -| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | -| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | -| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | -|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | -|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | -| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | -| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | -| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | -|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | -|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | -|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | -|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | -|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | -|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | -|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | -|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | -|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | -| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | -|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | -|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | -|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | -|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | -| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | -|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | -|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|    [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | -|    [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | -| [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 | -|       [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 | -| osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | -|    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 | -|    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 | -| [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | -| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | -|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | -|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | -|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | -|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | -|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | -|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | -| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-------------------------------------------------------------------------------------------|:----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| +| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | +| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | +| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | +| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | +| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | +| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | +| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | +| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | +| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | +| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | +| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | +| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | +| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | +| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | +| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | +| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | +| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | +| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | +| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | +| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | +| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | +| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | +| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | +| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | +| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | +| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | +| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | +| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | +| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | +| [carsAllowedStopMaxTransferDurationsForMode](#carsAllowedStopMaxTransferDurationsForMode) | `enum map of duration` | This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. | *Optional* | | 2.7 | +| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | +| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | +|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | +|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | +| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | +| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | +| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | +| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | +|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | +|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | +|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | +|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | +|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | +| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | +|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | +|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | +|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | +|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | +| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | +|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | +|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | +|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | +|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | +| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | +|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | +|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|    [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | +|    [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | +| [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 | +|       [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 | +| osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | +|    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 | +|    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 | +| [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | +| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | +|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | +|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | +|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | +|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | +|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | +|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | +| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | @@ -653,6 +654,33 @@ What OSM tags should be looked on for the source of matching stops to platforms [Detailed documentation](BoardingLocations.md) +

carsAllowedStopMaxTransferDurationsForMode

+ +**Since version:** `2.7` ∙ **Type:** `enum map of duration` ∙ **Cardinality:** `Optional` +**Path:** / +**Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` + +This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. + +This is a special parameter that only works on transfers between stops that have trips that allow cars. +The duration can be set for either 'BIKE' or 'CAR'. +For cars, transfers are only calculated between stops that have trips that allow cars. +For cars, this overrides the default `maxTransferDuration`. +For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. + +**Example** + +```JSON +// build-config.json +{ + "carsAllowedStopMaxTransferDurationsForMode": { + "CAR": "2h", + "BIKE": "3h" + } +} +``` + +

dem

**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional`