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 f71283b572e..6e13b05a7e6 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -92,6 +92,12 @@ public enum OTPFeature { false, "Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads." ), + WaitForGraphUpdateInPollingUpdaters( + true, + false, + "Make all polling updaters wait for graph updates to complete before finishing. " + + "If this is not enabled, the updaters will finish after submitting the task to update the graph." + ), Co2Emissions(false, true, "Enable the emissions sandbox module."), DataOverlay( false, diff --git a/application/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java b/application/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java index a5be5ef4185..b217c60a05b 100644 --- a/application/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/alert/GtfsRealtimeAlertsUpdater.java @@ -2,6 +2,7 @@ import com.google.transit.realtime.GtfsRealtime.FeedMessage; import java.net.URI; +import java.util.concurrent.ExecutionException; import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.routing.impl.TransitAlertServiceImpl; @@ -26,7 +27,6 @@ public class GtfsRealtimeAlertsUpdater extends PollingGraphUpdater implements Tr private final TransitAlertService transitAlertService; private final HttpHeaders headers; private final OtpHttpClient otpHttpClient; - private WriteToGraphCallback saveResultOnGraph; private Long lastTimestamp = Long.MIN_VALUE; public GtfsRealtimeAlertsUpdater( @@ -48,11 +48,6 @@ public GtfsRealtimeAlertsUpdater( LOG.info("Creating real-time alert updater running every {}: {}", pollingPeriod(), url); } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - public TransitAlertService getTransitAlertService() { return transitAlertService; } @@ -63,32 +58,26 @@ public String toString() { } @Override - protected void runPolling() { - try { - final FeedMessage feed = otpHttpClient.getAndMap( - URI.create(url), - this.headers.asMap(), - FeedMessage.PARSER::parseFrom - ); + protected void runPolling() throws InterruptedException, ExecutionException { + final FeedMessage feed = otpHttpClient.getAndMap( + URI.create(url), + this.headers.asMap(), + FeedMessage.PARSER::parseFrom + ); - long feedTimestamp = feed.getHeader().getTimestamp(); - if (feedTimestamp == lastTimestamp) { - LOG.debug("Ignoring feed with a timestamp that has not been updated from {}", url); - return; - } - if (feedTimestamp < lastTimestamp) { - LOG.info("Ignoring feed with older than previous timestamp from {}", url); - return; - } + long feedTimestamp = feed.getHeader().getTimestamp(); + if (feedTimestamp == lastTimestamp) { + LOG.debug("Ignoring feed with a timestamp that has not been updated from {}", url); + return; + } + if (feedTimestamp < lastTimestamp) { + LOG.info("Ignoring feed with older than previous timestamp from {}", url); + return; + } - // Handle update in graph writer runnable - saveResultOnGraph.execute(context -> - updateHandler.update(feed, context.gtfsRealtimeFuzzyTripMatcher()) - ); + // Handle update in graph writer runnable + updateGraph(context -> updateHandler.update(feed, context.gtfsRealtimeFuzzyTripMatcher())); - lastTimestamp = feedTimestamp; - } catch (Exception e) { - LOG.error("Failed to process GTFS-RT Alerts feed from {}", url, e); - } + lastTimestamp = feedTimestamp; } } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdater.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdater.java index 663d3ab906b..8901a49faef 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdater.java @@ -30,10 +30,6 @@ public class SiriETUpdater extends PollingGraphUpdater { * Feed id that is used for the trip ids in the TripUpdates */ private final String feedId; - /** - * Parent update manager. Is used to execute graph writer runnables. - */ - protected WriteToGraphCallback saveResultOnGraph; private final EstimatedTimetableHandler estimatedTimetableHandler; @@ -60,11 +56,6 @@ public SiriETUpdater( this.metricsConsumer = metricsConsumer; } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - /** * Repeatedly makes blocking calls to an UpdateStreamer to retrieve new stop time updates, and * applies those updates to the graph. diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdater.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdater.java index edfdf0d878d..f089ea69bfd 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdater.java @@ -14,7 +14,6 @@ import org.opentripplanner.updater.siri.SiriAlertsUpdateHandler; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.updater.trip.UrlUpdaterParameters; import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; @@ -36,7 +35,6 @@ public class SiriSXUpdater extends PollingGraphUpdater implements TransitAlertPr // TODO RT_AB: Document why SiriAlertsUpdateHandler is a separate instance that persists across // many graph update operations. private final SiriAlertsUpdateHandler updateHandler; - private WriteToGraphCallback writeToGraphCallback; private ZonedDateTime lastTimestamp = ZonedDateTime.now().minusWeeks(1); private String requestorRef; /** @@ -80,11 +78,6 @@ public SiriSXUpdater( LOG.info("Creating SIRI-SX updater running every {}: {}", pollingPeriod(), url); } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.writeToGraphCallback = writeToGraphCallback; - } - public TransitAlertService getTransitAlertService() { return transitAlertService; } @@ -148,7 +141,7 @@ private void updateSiri() { // All that said, out of all the update types, Alerts (and SIRI SX) are probably the ones // that would be most tolerant of non-versioned application-wide storage since they don't // participate in routing and are tacked on to already-completed routing responses. - writeToGraphCallback.execute(context -> { + saveResultOnGraph.execute(context -> { updateHandler.update(serviceDelivery, context); if (markPrimed) { primed = true; diff --git a/application/src/main/java/org/opentripplanner/updater/spi/PollingGraphUpdater.java b/application/src/main/java/org/opentripplanner/updater/spi/PollingGraphUpdater.java index e0859371de8..e8faa4b6aba 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/PollingGraphUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/PollingGraphUpdater.java @@ -2,6 +2,9 @@ import java.time.Duration; import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.updater.GraphWriterRunnable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +37,10 @@ public abstract class PollingGraphUpdater implements GraphUpdater { * removed that. */ protected volatile boolean primed; + /** + * Parent update manager. Is used to execute graph writer runnables. + */ + protected WriteToGraphCallback saveResultOnGraph; /** Shared configuration code for all polling graph updaters. */ protected PollingGraphUpdater(PollingGraphUpdaterParameters config) { @@ -95,9 +102,22 @@ public String getConfigRef() { return configRef; } + @Override + public final void setup(WriteToGraphCallback writeToGraphCallback) { + this.saveResultOnGraph = writeToGraphCallback; + } + /** * Mirrors GraphUpdater.run method. Only difference is that runPolling will be run multiple times * with pauses in between. The length of the pause is defined in the preference frequency. */ protected abstract void runPolling() throws Exception; + + protected final void updateGraph(GraphWriterRunnable task) + throws ExecutionException, InterruptedException { + var result = saveResultOnGraph.execute(task); + if (OTPFeature.WaitForGraphUpdateInPollingUpdaters.isOn()) { + result.get(); + } + } } diff --git a/application/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java b/application/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java index c725c8b1088..8331bf19d3b 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/PollingTripUpdater.java @@ -2,10 +2,10 @@ import com.google.transit.realtime.GtfsRealtime.TripUpdate; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.UpdateResult; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.updater.trip.metrics.BatchTripUpdateMetrics; import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; @@ -33,10 +33,6 @@ public class PollingTripUpdater extends PollingGraphUpdater { private final BackwardsDelayPropagationType backwardsDelayPropagationType; private final Consumer recordMetrics; - /** - * Parent update manager. Is used to execute graph writer runnables. - */ - private WriteToGraphCallback saveResultOnGraph; /** * Set only if we should attempt to match the trip_id from other data in TripDescriptor */ @@ -63,17 +59,12 @@ public PollingTripUpdater( ); } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - /** * Repeatedly makes blocking calls to an UpdateStreamer to retrieve new stop time updates, and * applies those updates to the graph. */ @Override - public void runPolling() { + public void runPolling() throws InterruptedException, ExecutionException { // Get update lists from update source List updates = updateSource.getUpdates(); var incrementality = updateSource.incrementalityOfLastUpdates(); @@ -89,7 +80,7 @@ public void runPolling() { feedId, recordMetrics ); - saveResultOnGraph.execute(runnable); + updateGraph(runnable); } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index e548d5d75be..29e820ff952 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.stream.Collectors; import org.opentripplanner.service.vehicleparking.VehicleParkingRepository; @@ -12,7 +13,6 @@ import org.opentripplanner.updater.RealTimeUpdateContext; import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.PollingGraphUpdater; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,8 +27,6 @@ public class VehicleParkingAvailabilityUpdater extends PollingGraphUpdater { VehicleParkingAvailabilityUpdater.class ); private final DataSource source; - private WriteToGraphCallback saveResultOnGraph; - private final VehicleParkingRepository repository; public VehicleParkingAvailabilityUpdater( @@ -44,17 +42,12 @@ public VehicleParkingAvailabilityUpdater( } @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - - @Override - protected void runPolling() { + protected void runPolling() throws InterruptedException, ExecutionException { if (source.update()) { var updates = source.getUpdates(); var graphWriterRunnable = new AvailabilityUpdater(updates); - saveResultOnGraph.execute(graphWriterRunnable); + updateGraph(graphWriterRunnable); } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdater.java index 8e4cf8a862b..5513e224f57 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdater.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.stream.Collectors; import org.opentripplanner.routing.graph.Graph; @@ -26,7 +27,6 @@ import org.opentripplanner.updater.RealTimeUpdateContext; import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.PollingGraphUpdater; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +42,6 @@ public class VehicleParkingUpdater extends PollingGraphUpdater { private final Map> tempEdgesByPark = new HashMap<>(); private final DataSource source; private final List oldVehicleParkings = new ArrayList<>(); - private WriteToGraphCallback saveResultOnGraph; private final VertexLinker linker; private final VehicleParkingRepository parkingRepository; @@ -64,12 +63,7 @@ public VehicleParkingUpdater( } @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - - @Override - protected void runPolling() { + protected void runPolling() throws InterruptedException, ExecutionException { LOG.debug("Updating vehicle parkings from {}", source); if (!source.update()) { LOG.debug("No updates"); @@ -81,7 +75,7 @@ protected void runPolling() { VehicleParkingGraphWriterRunnable graphWriterRunnable = new VehicleParkingGraphWriterRunnable( vehicleParkings ); - saveResultOnGraph.execute(graphWriterRunnable); + updateGraph(graphWriterRunnable); } private class VehicleParkingGraphWriterRunnable implements GraphWriterRunnable { diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index 4c487ac997b..f53d3010e59 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -3,11 +3,11 @@ import com.google.transit.realtime.GtfsRealtime.VehiclePosition; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutionException; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.updater.spi.PollingGraphUpdater; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,10 +27,6 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { private final GtfsRealtimeHttpVehiclePositionSource vehiclePositionSource; private final Set vehiclePositionFeatures; - /** - * Parent update manager. Is used to execute graph writer runnables. - */ - private WriteToGraphCallback saveResultOnGraph; private final String feedId; private final RealtimeVehicleRepository realtimeVehicleRepository; private final boolean fuzzyTripMatching; @@ -54,17 +50,12 @@ public PollingVehiclePositionUpdater( ); } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - /** * Repeatedly makes blocking calls to an UpdateStreamer to retrieve new stop time updates, and * applies those updates to the graph. */ @Override - public void runPolling() { + public void runPolling() throws InterruptedException, ExecutionException { // Get update lists from update source List updates = vehiclePositionSource.getPositions(); @@ -77,7 +68,7 @@ public void runPolling() { fuzzyTripMatching, updates ); - saveResultOnGraph.execute(runnable); + updateGraph(runnable); } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java index 24686edce6c..8419d004138 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; import org.opentripplanner.routing.linking.DisposableEdgeCollection; @@ -30,7 +31,6 @@ import org.opentripplanner.updater.RealTimeUpdateContext; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.UpdaterConstructionException; -import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDatasource; import org.opentripplanner.utils.lang.ObjectUtils; import org.opentripplanner.utils.logging.Throttle; @@ -53,8 +53,6 @@ public class VehicleRentalUpdater extends PollingGraphUpdater { private final VehicleRentalDatasource source; private final String nameForLogging; - private WriteToGraphCallback saveResultOnGraph; - private Map latestModifiedEdges = Map.of(); private Set latestAppliedGeofencingZones = Set.of(); private final Map verticesByStation = new HashMap<>(); @@ -108,11 +106,6 @@ public VehicleRentalUpdater( } } - @Override - public void setup(WriteToGraphCallback writeToGraphCallback) { - this.saveResultOnGraph = writeToGraphCallback; - } - @Override public String toString() { return ToStringBuilder.of(VehicleRentalUpdater.class).addObj("source", source).toString(); @@ -124,7 +117,7 @@ public String getConfigRef() { } @Override - protected void runPolling() { + protected void runPolling() throws InterruptedException, ExecutionException { LOG.debug("Updating vehicle rental stations from {}", nameForLogging); if (!source.update()) { LOG.debug("No updates from {}", nameForLogging); @@ -138,7 +131,7 @@ protected void runPolling() { stations, geofencingZones ); - saveResultOnGraph.execute(graphWriterRunnable); + updateGraph(graphWriterRunnable); } private class VehicleRentalGraphWriterRunnable implements GraphWriterRunnable { diff --git a/application/src/test/java/org/opentripplanner/updater/spi/PollingGraphUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/spi/PollingGraphUpdaterTest.java new file mode 100644 index 00000000000..6132988d92b --- /dev/null +++ b/application/src/test/java/org/opentripplanner/updater/spi/PollingGraphUpdaterTest.java @@ -0,0 +1,78 @@ +package org.opentripplanner.updater.spi; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.updater.GraphWriterRunnable; + +public class PollingGraphUpdaterTest { + + private static final PollingGraphUpdaterParameters config = new PollingGraphUpdaterParameters() { + @Override + public Duration frequency() { + return Duration.ZERO; + } + + @Override + public String configRef() { + return ""; + } + }; + + private static final PollingGraphUpdater subject = new PollingGraphUpdater(config) { + @Override + protected void runPolling() {} + }; + + private boolean updateCompleted; + + @BeforeAll + static void beforeAll() { + subject.setup(runnable -> CompletableFuture.runAsync(() -> runnable.run(null))); + } + + @BeforeEach + void setUp() { + updateCompleted = false; + } + + private final GraphWriterRunnable graphWriterRunnable = context -> { + try { + Thread.sleep(100); + updateCompleted = true; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + @Test + void testUpdateGraphWithWaitFeatureOn() { + OTPFeature.WaitForGraphUpdateInPollingUpdaters.testOn(() -> { + callUpdater(); + assertTrue(updateCompleted); + }); + } + + @Test + void testProcessGraphUpdaterResultWithWaitFeatureOff() { + OTPFeature.WaitForGraphUpdateInPollingUpdaters.testOff(() -> { + callUpdater(); + assertFalse(updateCompleted); + }); + } + + private void callUpdater() { + try { + subject.updateGraph(graphWriterRunnable); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index bec637fe51c..326ccfa8a77 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -221,39 +221,40 @@ Here is a list of all features which can be toggled on/off and their default val -| Feature | Description | Enabled by default | Sandbox | -|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| -| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | -| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | -| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | -| `IncludeEmptyRailStopsInTransfers` | Turning this on guarantees that Rail stops without scheduled departures still get included when generating transfers using `ConsiderPatternsForDirectTransfers`. It is common for stops to be assign at real-time for Rail. Turning this on will help to avoid dropping transfers which are needed, when the stop is in use later. Turning this on, if ConsiderPatternsForDirectTransfers is off has no effect. | | | -| `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. | ✓️ | | -| `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | -| `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | -| `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. | | | -| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | -| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | -| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_ (GTFS) and Interchanges (NeTEx). Turning this _off_ will increase the routing performance a little. | ✓️ | | -| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | -| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | -| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | -| `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | -| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | -| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | -| `FlexRouting` | Enable FLEX routing. | | ✓️ | -| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | -| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | | ✓️ | -| `MultiCriteriaGroupMaxFilter` | Keep the best itinerary with respect to each criteria used in the transit-routing search. For example the itinerary with the lowest cost, fewest transfers, and each unique transit-group (transit-group-priority) is kept, even if the max-limit is exceeded. This is turned off by default for now, until this feature is well tested. | | | -| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | -| `ReportApi` | Enable the report API. | | ✓️ | -| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | -| `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 searching in south of Norway. Only relevant in Norway. | | ✓️ | -| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | +| Feature | Description | Enabled by default | Sandbox | +|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------:|:-------:| +| `APIBikeRental` | Enable the bike rental endpoint. | ✓️ | | +| `APIServerInfo` | Enable the server info endpoint. | ✓️ | | +| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | | +| `IncludeEmptyRailStopsInTransfers` | Turning this on guarantees that Rail stops without scheduled departures still get included when generating transfers using `ConsiderPatternsForDirectTransfers`. It is common for stops to be assign at real-time for Rail. Turning this on will help to avoid dropping transfers which are needed, when the stop is in use later. Turning this on, if ConsiderPatternsForDirectTransfers is off has no effect. | | | +| `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. | ✓️ | | +| `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | +| `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | +| `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. | | | +| `OptimizeTransfers` | OTP will inspect all itineraries found and optimize where (which stops) the transfer will happen. Waiting time, priority and guaranteed transfers are taken into account. | ✓️ | | +| `ParallelRouting` | Enable performing parts of the trip planning in parallel. | | | +| `TransferConstraints` | Enforce transfers to happen according to the _transfers.txt_ (GTFS) and Interchanges (NeTEx). Turning this _off_ will increase the routing performance a little. | ✓️ | | +| `TransmodelGraphQlApi` | Enable the [Transmodel (NeTEx) GraphQL API](apis/TransmodelApi.md). | ✓️ | ✓️ | +| `ActuatorAPI` | Endpoint for actuators (service health status). | | ✓️ | +| `AsyncGraphQLFetchers` | Whether the @async annotation in the GraphQL schema should lead to the fetch being executed asynchronously. This allows batch or alias queries to run in parallel at the cost of consuming extra threads. | | | +| `WaitForGraphUpdateInPollingUpdaters` | Make all polling updaters wait for graph updates to complete before finishing. If this is not enabled, the updaters will finish after submitting the task to update the graph. | ✓️ | | +| `Co2Emissions` | Enable the emissions sandbox module. | | ✓️ | +| `DataOverlay` | Enable usage of data overlay when calculating costs for the street network. | | ✓️ | +| `FaresV2` | Enable import of GTFS-Fares v2 data. | | ✓️ | +| `FlexRouting` | Enable FLEX routing. | | ✓️ | +| `GoogleCloudStorage` | Enable Google Cloud Storage integration. | | ✓️ | +| `LegacyRestApi` | Enable legacy REST API. This API will be removed in the future. | | ✓️ | +| `MultiCriteriaGroupMaxFilter` | Keep the best itinerary with respect to each criteria used in the transit-routing search. For example the itinerary with the lowest cost, fewest transfers, and each unique transit-group (transit-group-priority) is kept, even if the max-limit is exceeded. This is turned off by default for now, until this feature is well tested. | | | +| `RealtimeResolver` | When routing with ignoreRealtimeUpdates=true, add an extra step which populates results with real-time data | | ✓️ | +| `ReportApi` | Enable the report API. | | ✓️ | +| `RestAPIPassInDefaultConfigAsJson` | Enable a default RouteRequest to be passed in as JSON on the REST API - FOR DEBUGGING ONLY! | | | +| `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 searching in south of Norway. Only relevant in Norway. | | ✓️ | +| `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ |