diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java index ac69b43e275..a71d5ee40f8 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/UpdatersConfig.java @@ -8,7 +8,9 @@ import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_AZURE_ET_UPDATER; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_AZURE_SX_UPDATER; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_ET_GOOGLE_PUBSUB_UPDATER; +import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_ET_LITE; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_ET_UPDATER; +import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_SX_LITE; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.SIRI_SX_UPDATER; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.STOP_TIME_UPDATER; import static org.opentripplanner.standalone.config.routerconfig.UpdatersConfig.Type.VEHICLE_PARKING; @@ -30,7 +32,9 @@ import org.opentripplanner.standalone.config.routerconfig.updaters.MqttGtfsRealtimeUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.PollingTripUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.SiriETGooglePubsubUpdaterConfig; +import org.opentripplanner.standalone.config.routerconfig.updaters.SiriETLiteUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.SiriETUpdaterConfig; +import org.opentripplanner.standalone.config.routerconfig.updaters.SiriSXLiteUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.SiriSXUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.VehicleParkingUpdaterConfig; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; @@ -44,6 +48,8 @@ import org.opentripplanner.updater.siri.updater.SiriETUpdaterParameters; import org.opentripplanner.updater.siri.updater.SiriSXUpdaterParameters; import org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriETLiteUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriSXLiteUpdaterParameters; import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdaterParameters; import org.opentripplanner.updater.trip.PollingTripUpdaterParameters; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; @@ -182,6 +188,16 @@ public List getSiriSXUpdaterParameters() { return getParameters(SIRI_SX_UPDATER); } + @Override + public List getSiriETLiteUpdaterParameters() { + return getParameters(SIRI_ET_LITE); + } + + @Override + public List getSiriSXLiteUpdaterParameters() { + return getParameters(SIRI_SX_LITE); + } + @Override public List getMqttGtfsRealtimeUpdaterParameters() { return getParameters(MQTT_GTFS_RT_UPDATER); @@ -218,8 +234,10 @@ public enum Type { REAL_TIME_ALERTS(GtfsRealtimeAlertsUpdaterConfig::create), VEHICLE_POSITIONS(VehiclePositionsUpdaterConfig::create), SIRI_ET_UPDATER(SiriETUpdaterConfig::create), + SIRI_ET_LITE(SiriETLiteUpdaterConfig::create), SIRI_ET_GOOGLE_PUBSUB_UPDATER(SiriETGooglePubsubUpdaterConfig::create), SIRI_SX_UPDATER(SiriSXUpdaterConfig::create), + SIRI_SX_LITE(SiriSXLiteUpdaterConfig::create), SIRI_AZURE_ET_UPDATER(SiriAzureETUpdaterConfig::create), SIRI_AZURE_SX_UPDATER(SiriAzureSXUpdaterConfig::create); diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETLiteUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETLiteUpdaterConfig.java new file mode 100644 index 00000000000..b26bdd2a727 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETLiteUpdaterConfig.java @@ -0,0 +1,39 @@ +package org.opentripplanner.standalone.config.routerconfig.updaters; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import java.time.Duration; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.updater.siri.updater.lite.SiriETLiteUpdaterParameters; + +public class SiriETLiteUpdaterConfig { + + public static SiriETLiteUpdaterParameters create(String configRef, NodeAdapter c) { + return new SiriETLiteUpdaterParameters( + configRef, + c.of("feedId").since(V2_7).summary("The ID of the feed to apply the updates to.").asString(), + c + .of("url") + .since(V2_7) + .summary("The URL to send the HTTP requests to.") + .description(SiriSXUpdaterConfig.URL_DESCRIPTION) + .asUri(), + c + .of("frequency") + .since(V2_7) + .summary("How often the updates should be retrieved.") + .asDuration(Duration.ofMinutes(1)), + c + .of("timeout") + .since(V2_7) + .summary("The HTTP timeout to download the updates.") + .asDuration(Duration.ofSeconds(15)), + c + .of("fuzzyTripMatching") + .since(V2_7) + .summary("If the fuzzy trip matcher should be used to match trips.") + .asBoolean(false), + HttpHeadersConfig.headers(c, V2_7) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriSXLiteUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriSXLiteUpdaterConfig.java new file mode 100644 index 00000000000..27c1bcba0be --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriSXLiteUpdaterConfig.java @@ -0,0 +1,46 @@ +package org.opentripplanner.standalone.config.routerconfig.updaters; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import java.time.Duration; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.updater.siri.updater.lite.SiriSXLiteUpdaterParameters; + +public class SiriSXLiteUpdaterConfig { + + public static SiriSXLiteUpdaterParameters create(String configRef, NodeAdapter c) { + return new SiriSXLiteUpdaterParameters( + configRef, + c.of("feedId").since(V2_7).summary("The ID of the feed to apply the updates to.").asString(), + c + .of("url") + .since(V2_7) + .summary("The URL to send the HTTP requests to.") + .description(SiriSXUpdaterConfig.URL_DESCRIPTION) + .asUri(), + c + .of("frequency") + .since(V2_7) + .summary("How often the updates should be retrieved.") + .asDuration(Duration.ofMinutes(1)), + c + .of("timeout") + .since(V2_7) + .summary("The HTTP timeout to download the updates.") + .asDuration(Duration.ofSeconds(15)), + c + .of("earlyStart") + .since(V2_0) + .summary("This value is subtracted from the actual validity defined in the message.") + .description( + """ + Normally the planned departure time is used, so setting this to 10s will cause the + SX-message to be included in trip-results 10 seconds before the the planned departure + time.""" + ) + .asDuration(Duration.ZERO), + HttpHeadersConfig.headers(c, V2_7) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/updater/UpdatersParameters.java b/application/src/main/java/org/opentripplanner/updater/UpdatersParameters.java index 312fa3e2fbc..cb613580495 100644 --- a/application/src/main/java/org/opentripplanner/updater/UpdatersParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/UpdatersParameters.java @@ -8,6 +8,8 @@ import org.opentripplanner.updater.siri.updater.SiriETUpdaterParameters; import org.opentripplanner.updater.siri.updater.SiriSXUpdaterParameters; import org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriETLiteUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriSXLiteUpdaterParameters; import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdaterParameters; import org.opentripplanner.updater.trip.PollingTripUpdaterParameters; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; @@ -33,6 +35,10 @@ public interface UpdatersParameters { List getSiriSXUpdaterParameters(); + List getSiriETLiteUpdaterParameters(); + + List getSiriSXLiteUpdaterParameters(); + List getMqttGtfsRealtimeUpdaterParameters(); List getVehicleParkingUpdaterParameters(); diff --git a/application/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/application/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 1106d621873..11be185fa2a 100644 --- a/application/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/application/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -20,9 +20,11 @@ import org.opentripplanner.updater.UpdatersParameters; import org.opentripplanner.updater.alert.GtfsRealtimeAlertsUpdater; import org.opentripplanner.updater.siri.SiriTimetableSnapshotSource; -import org.opentripplanner.updater.siri.updater.SiriETUpdater; +import org.opentripplanner.updater.siri.updater.SiriHttpLoader; import org.opentripplanner.updater.siri.updater.SiriSXUpdater; +import org.opentripplanner.updater.siri.updater.configure.SiriUpdaterModule; import org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdater; +import org.opentripplanner.updater.siri.updater.lite.SiriLiteHttpLoader; import org.opentripplanner.updater.spi.GraphUpdater; import org.opentripplanner.updater.spi.TimetableSnapshotFlush; import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdater; @@ -182,13 +184,23 @@ private List createUpdatersFromConfig() { updaters.add(new PollingVehiclePositionUpdater(configItem, realtimeVehicleRepository)); } for (var configItem : updatersParameters.getSiriETUpdaterParameters()) { - updaters.add(new SiriETUpdater(configItem, provideSiriTimetableSnapshot())); + updaters.add( + SiriUpdaterModule.createSiriETUpdater(configItem, provideSiriTimetableSnapshot()) + ); + } + for (var configItem : updatersParameters.getSiriETLiteUpdaterParameters()) { + updaters.add( + SiriUpdaterModule.createSiriETUpdater(configItem, provideSiriTimetableSnapshot()) + ); } for (var configItem : updatersParameters.getSiriETGooglePubsubUpdaterParameters()) { updaters.add(new SiriETGooglePubsubUpdater(configItem, provideSiriTimetableSnapshot())); } for (var configItem : updatersParameters.getSiriSXUpdaterParameters()) { - updaters.add(new SiriSXUpdater(configItem, timetableRepository)); + updaters.add(SiriUpdaterModule.createSiriSXUpdater(configItem, timetableRepository)); + } + for (var configItem : updatersParameters.getSiriSXLiteUpdaterParameters()) { + updaters.add(SiriUpdaterModule.createSiriSXUpdater(configItem, timetableRepository)); } for (var configItem : updatersParameters.getMqttGtfsRealtimeUpdaterParameters()) { updaters.add(new MqttGtfsRealtimeUpdater(configItem, provideGtfsTimetableSnapshot())); diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/EstimatedTimetableSource.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/EstimatedTimetableSource.java index f8a1b549c96..3636fd2b96f 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/EstimatedTimetableSource.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/EstimatedTimetableSource.java @@ -23,6 +23,4 @@ public interface EstimatedTimetableSource { * {@link UpdateIncrementality} */ UpdateIncrementality incrementalityOfLastUpdates(); - - String getFeedId(); } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETHttpTripUpdateSource.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETHttpTripUpdateSource.java index fec6e2885a7..9d0373e1a16 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETHttpTripUpdateSource.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETHttpTripUpdateSource.java @@ -11,6 +11,7 @@ import org.opentripplanner.framework.io.OtpHttpClientException; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.trip.UpdateIncrementality; +import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.org.siri.siri20.Siri; @@ -19,11 +20,6 @@ public class SiriETHttpTripUpdateSource implements EstimatedTimetableSource { private static final Logger LOG = LoggerFactory.getLogger(SiriETHttpTripUpdateSource.class); - /** - * Feed id that is used to match trip ids in the TripUpdates - */ - private final String feedId; - private final String url; private final SiriLoader siriLoader; @@ -35,8 +31,7 @@ public class SiriETHttpTripUpdateSource implements EstimatedTimetableSource { private UpdateIncrementality updateIncrementality = FULL_DATASET; private ZonedDateTime lastTimestamp = ZonedDateTime.now().minusMonths(1); - public SiriETHttpTripUpdateSource(Parameters parameters) { - this.feedId = parameters.feedId(); + public SiriETHttpTripUpdateSource(Parameters parameters, SiriLoader siriLoader) { this.url = parameters.url(); this.requestorRef = @@ -44,7 +39,7 @@ public SiriETHttpTripUpdateSource(Parameters parameters) { ? "otp-" + UUID.randomUUID() : parameters.requestorRef(); - this.siriLoader = createLoader(url, parameters); + this.siriLoader = siriLoader; } @Override @@ -82,28 +77,8 @@ public UpdateIncrementality incrementalityOfLastUpdates() { } @Override - public String getFeedId() { - return this.feedId; - } - public String toString() { - return "SiriETHttpTripUpdateSource(" + url + ")"; - } - - private static SiriLoader createLoader(String url, Parameters parameters) { - // Load real-time updates from a file. - if (SiriFileLoader.matchesUrl(url)) { - return new SiriFileLoader(url); - } - // Fallback to default loader - else { - return new SiriHttpLoader( - url, - parameters.timeout(), - parameters.httpRequestHeaders(), - parameters.previewInterval() - ); - } + return ToStringBuilder.of(SiriETHttpTripUpdateSource.class).addStr("url", url).toString(); } public interface Parameters { 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 087bf28e875..663d3ab906b 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 @@ -4,10 +4,12 @@ import java.util.function.Consumer; import org.opentripplanner.updater.siri.SiriTimetableSnapshotSource; import org.opentripplanner.updater.spi.PollingGraphUpdater; +import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.WriteToGraphCallback; -import org.opentripplanner.updater.trip.metrics.TripUpdateMetrics; +import org.opentripplanner.updater.trip.UrlUpdaterParameters; +import org.opentripplanner.utils.tostring.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure; @@ -35,30 +37,27 @@ public class SiriETUpdater extends PollingGraphUpdater { private final EstimatedTimetableHandler estimatedTimetableHandler; - private final Consumer recordMetrics; + private final Consumer metricsConsumer; public SiriETUpdater( - SiriETUpdaterParameters config, - SiriTimetableSnapshotSource timetableSnapshotSource + Parameters config, + SiriTimetableSnapshotSource timetableSnapshotSource, + EstimatedTimetableSource source, + Consumer metricsConsumer ) { super(config); - // Create update streamer from preferences this.feedId = config.feedId(); - this.updateSource = new SiriETHttpTripUpdateSource(config.sourceParameters()); + this.updateSource = source; this.blockReadinessUntilInitialized = config.blockReadinessUntilInitialized(); - LOG.info( - "Creating stop time updater (SIRI ET) running every {} seconds : {}", - pollingPeriod(), - updateSource - ); + LOG.info("Creating SIRI-ET updater running every {}: {}", pollingPeriod(), updateSource); estimatedTimetableHandler = new EstimatedTimetableHandler(timetableSnapshotSource, config.fuzzyTripMatching(), feedId); - recordMetrics = TripUpdateMetrics.streaming(config); + this.metricsConsumer = metricsConsumer; } @Override @@ -87,7 +86,7 @@ public void runPolling() { saveResultOnGraph.execute(context -> { var result = estimatedTimetableHandler.applyUpdate(etds, incrementality, context); ResultLogger.logUpdateResult(feedId, "siri-et", result); - recordMetrics.accept(result); + metricsConsumer.accept(result); if (markPrimed) { primed = true; } @@ -97,8 +96,20 @@ public void runPolling() { } while (moreData); } + @Override public String toString() { - String s = (updateSource == null) ? "NONE" : updateSource.toString(); - return "Polling SIRI ET updater with update source = " + s; + return ToStringBuilder + .of(SiriETUpdater.class) + .addStr("source", updateSource.toString()) + .addDuration("frequency", pollingPeriod()) + .toString(); + } + + public interface Parameters extends UrlUpdaterParameters, PollingGraphUpdaterParameters { + String url(); + + boolean blockReadinessUntilInitialized(); + + boolean fuzzyTripMatching(); } } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java index dc479c034e1..71217225da1 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java @@ -2,8 +2,6 @@ import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; -import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; -import org.opentripplanner.updater.trip.UrlUpdaterParameters; public record SiriETUpdaterParameters( String configRef, @@ -18,8 +16,7 @@ public record SiriETUpdaterParameters( HttpHeaders httpRequestHeaders, boolean producerMetrics ) - implements - PollingGraphUpdaterParameters, UrlUpdaterParameters, SiriETHttpTripUpdateSource.Parameters { + implements SiriETUpdater.Parameters, SiriETHttpTripUpdateSource.Parameters { public SiriETHttpTripUpdateSource.Parameters sourceParameters() { return this; } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriHttpLoader.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriHttpLoader.java index 0ad9309e354..29de28efaee 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriHttpLoader.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriHttpLoader.java @@ -11,7 +11,8 @@ import uk.org.siri.siri20.Siri; /** - * Load real-time updates from SIRI-SX and SIRI-ET feeds over HTTP. + * Load real-time updates from SIRI-SX and SIRI-ET feeds over HTTP using the request/response + * flow, which asks the server to only send the latest updates for a given requestor ref. */ public class SiriHttpLoader implements SiriLoader { 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 83200db30d3..edfdf0d878d 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 @@ -13,7 +13,10 @@ import org.opentripplanner.updater.alert.TransitAlertProvider; 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; import org.slf4j.LoggerFactory; import uk.org.siri.siri20.ServiceDelivery; @@ -40,12 +43,15 @@ public class SiriSXUpdater extends PollingGraphUpdater implements TransitAlertPr * Global retry counter used to create a new unique requestorRef after each retry. */ private int retryCount = 0; - private final SiriHttpLoader siriHttpLoader; + private final SiriLoader siriHttpLoader; private final OtpRetry retry; - public SiriSXUpdater(SiriSXUpdaterParameters config, TimetableRepository timetableRepository) { + public SiriSXUpdater( + Parameters config, + TimetableRepository timetableRepository, + SiriLoader siriLoader + ) { super(config); - // TODO: add options to choose different patch services this.url = config.url(); this.requestorRef = config.requestorRef(); @@ -59,7 +65,7 @@ public SiriSXUpdater(SiriSXUpdaterParameters config, TimetableRepository timetab this.transitAlertService = new TransitAlertServiceImpl(timetableRepository); this.updateHandler = new SiriAlertsUpdateHandler(config.feedId(), transitAlertService, config.earlyStart()); - siriHttpLoader = new SiriHttpLoader(url, config.timeout(), config.requestHeaders()); + siriHttpLoader = siriLoader; retry = new OtpRetryBuilder() @@ -71,11 +77,7 @@ public SiriSXUpdater(SiriSXUpdaterParameters config, TimetableRepository timetab .withOnRetry(this::updateRequestorRef) .build(); - LOG.info( - "Creating real-time alert updater (SIRI SX) running every {} seconds : {}", - pollingPeriod(), - url - ); + LOG.info("Creating SIRI-SX updater running every {}: {}", pollingPeriod(), url); } @Override @@ -87,15 +89,20 @@ public TransitAlertService getTransitAlertService() { return transitAlertService; } - public String toString() { - return "SiriSXUpdater (" + url + ")"; - } - @Override protected void runPolling() throws InterruptedException { retry.execute(this::updateSiri); } + @Override + public String toString() { + return ToStringBuilder + .of(SiriSXUpdater.class) + .addStr("url", url) + .addDuration("frequency", pollingPeriod()) + .toString(); + } + /** * This part of the update process has been factored out to allow repeated retries of the HTTP * fetching operation in case the connection fails or some other disruption happens. @@ -200,4 +207,12 @@ private void updateRequestorRef() { retryCount++; requestorRef = originalRequestorRef + "-retry-" + retryCount; } + + public interface Parameters extends PollingGraphUpdaterParameters, UrlUpdaterParameters { + String requestorRef(); + + boolean blockReadinessUntilInitialized(); + + Duration earlyStart(); + } } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdaterParameters.java index f51584f86f5..d1796557658 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriSXUpdaterParameters.java @@ -2,7 +2,6 @@ import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; -import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; public record SiriSXUpdaterParameters( String configRef, @@ -15,4 +14,4 @@ public record SiriSXUpdaterParameters( boolean blockReadinessUntilInitialized, HttpHeaders requestHeaders ) - implements PollingGraphUpdaterParameters {} + implements SiriSXUpdater.Parameters {} diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/configure/SiriUpdaterModule.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/configure/SiriUpdaterModule.java new file mode 100644 index 00000000000..f39fbbb3134 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/configure/SiriUpdaterModule.java @@ -0,0 +1,112 @@ +package org.opentripplanner.updater.siri.updater.configure; + +import java.util.function.Consumer; +import org.opentripplanner.transit.service.TimetableRepository; +import org.opentripplanner.updater.siri.SiriTimetableSnapshotSource; +import org.opentripplanner.updater.siri.updater.EstimatedTimetableSource; +import org.opentripplanner.updater.siri.updater.SiriETHttpTripUpdateSource; +import org.opentripplanner.updater.siri.updater.SiriETUpdater; +import org.opentripplanner.updater.siri.updater.SiriETUpdaterParameters; +import org.opentripplanner.updater.siri.updater.SiriFileLoader; +import org.opentripplanner.updater.siri.updater.SiriHttpLoader; +import org.opentripplanner.updater.siri.updater.SiriLoader; +import org.opentripplanner.updater.siri.updater.SiriSXUpdater; +import org.opentripplanner.updater.siri.updater.SiriSXUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriETLiteHttpTripUpdateSource; +import org.opentripplanner.updater.siri.updater.lite.SiriETLiteUpdaterParameters; +import org.opentripplanner.updater.siri.updater.lite.SiriLiteHttpLoader; +import org.opentripplanner.updater.siri.updater.lite.SiriSXLiteUpdaterParameters; +import org.opentripplanner.updater.spi.UpdateResult; +import org.opentripplanner.updater.trip.metrics.TripUpdateMetrics; + +/** + * Dependency injection for instantiating SIRI-ET and SX updaters. + */ +public class SiriUpdaterModule { + + public static SiriETUpdater createSiriETUpdater( + SiriETUpdater.Parameters params, + SiriTimetableSnapshotSource timetableSnapshotSource + ) { + return new SiriETUpdater( + params, + timetableSnapshotSource, + createSource(params), + createMetricsConsumer(params) + ); + } + + public static SiriSXUpdater createSiriSXUpdater( + SiriSXUpdater.Parameters params, + TimetableRepository timetableRepository + ) { + return new SiriSXUpdater(params, timetableRepository, createLoader(params)); + } + + private static EstimatedTimetableSource createSource(SiriETUpdater.Parameters params) { + return switch (params) { + case SiriETUpdaterParameters p -> new SiriETHttpTripUpdateSource( + p.sourceParameters(), + createLoader(params) + ); + case SiriETLiteUpdaterParameters p -> new SiriETLiteHttpTripUpdateSource( + p.sourceParameters(), + createLoader(params) + ); + default -> throw new IllegalArgumentException("Unexpected value: " + params); + }; + } + + private static SiriLoader createLoader(SiriSXUpdater.Parameters params) { + // Load real-time updates from a file. + if (SiriFileLoader.matchesUrl(params.url())) { + return new SiriFileLoader(params.url()); + } + // Fallback to default loader + return switch (params) { + case SiriSXUpdaterParameters p -> new SiriHttpLoader( + p.url(), + p.timeout(), + p.requestHeaders() + ); + case SiriSXLiteUpdaterParameters p -> new SiriLiteHttpLoader( + p.uri(), + p.timeout(), + p.requestHeaders() + ); + default -> throw new IllegalArgumentException("Unexpected value: " + params); + }; + } + + private static SiriLoader createLoader(SiriETUpdater.Parameters params) { + // Load real-time updates from a file. + if (SiriFileLoader.matchesUrl(params.url())) { + return new SiriFileLoader(params.url()); + } + // Fallback to default loader + else { + return switch (params) { + case SiriETUpdaterParameters p -> new SiriHttpLoader( + p.url(), + p.timeout(), + p.httpRequestHeaders(), + p.previewInterval() + ); + case SiriETLiteUpdaterParameters p -> new SiriLiteHttpLoader( + p.uri(), + p.timeout(), + p.httpRequestHeaders() + ); + default -> throw new IllegalArgumentException("Unexpected value: " + params); + }; + } + } + + private static Consumer createMetricsConsumer(SiriETUpdater.Parameters params) { + return switch (params) { + case SiriETUpdaterParameters p -> TripUpdateMetrics.streaming(p); + case SiriETLiteUpdaterParameters p -> TripUpdateMetrics.batch(p); + default -> throw new IllegalArgumentException("Unexpected value: " + params); + }; + } +} diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteHttpTripUpdateSource.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteHttpTripUpdateSource.java new file mode 100644 index 00000000000..30801a64c7f --- /dev/null +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteHttpTripUpdateSource.java @@ -0,0 +1,76 @@ +package org.opentripplanner.updater.siri.updater.lite; + +import static org.opentripplanner.updater.trip.UpdateIncrementality.FULL_DATASET; + +import java.net.URI; +import java.time.Duration; +import java.util.Optional; +import org.opentripplanner.framework.io.OtpHttpClientException; +import org.opentripplanner.updater.siri.updater.EstimatedTimetableSource; +import org.opentripplanner.updater.siri.updater.SiriLoader; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.opentripplanner.updater.trip.UpdateIncrementality; +import org.opentripplanner.utils.tostring.ToStringBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.org.siri.siri20.Siri; + +/** + * SIRI Lite periodically downloads all messages as a single GET request. + */ +public class SiriETLiteHttpTripUpdateSource implements EstimatedTimetableSource { + + private static final Logger LOG = LoggerFactory.getLogger(SiriETLiteHttpTripUpdateSource.class); + /** + * The framework code requires a requestor ref but in SIRI Lite this is not used. + */ + private static final String DUMMY_REQUESTOR_REF = "OpenTripPlanner"; + + private final Parameters parameters; + + private final SiriLoader siriLoader; + + public SiriETLiteHttpTripUpdateSource(Parameters parameters, SiriLoader siriLoader) { + this.parameters = parameters; + this.siriLoader = siriLoader; + } + + @Override + public Optional getUpdates() { + try { + var siri = siriLoader.fetchETFeed(DUMMY_REQUESTOR_REF); + if (siri.map(Siri::getServiceDelivery).isEmpty()) { + return Optional.empty(); + } + return siri; + } catch (OtpHttpClientException e) { + LOG.warn("Could not get SIRI-ET data from {}", parameters.uri(), e); + } catch (Exception e) { + LOG.warn("Failed to parse SIRI-ET feed from {}", parameters.uri(), e); + } + return Optional.empty(); + } + + @Override + public UpdateIncrementality incrementalityOfLastUpdates() { + return FULL_DATASET; + } + + public String toString() { + return ToStringBuilder + .of(this.getClass()) + .addStr("feedId", parameters.feedId()) + .addStr("uri", parameters.uri().toString()) + .toString(); + } + + public interface Parameters { + URI uri(); + + String feedId(); + + Duration timeout(); + + HttpHeaders httpRequestHeaders(); + } +} diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteUpdaterParameters.java new file mode 100644 index 00000000000..1eed702e625 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriETLiteUpdaterParameters.java @@ -0,0 +1,31 @@ +package org.opentripplanner.updater.siri.updater.lite; + +import java.net.URI; +import java.time.Duration; +import org.opentripplanner.updater.siri.updater.SiriETUpdater; +import org.opentripplanner.updater.spi.HttpHeaders; + +public record SiriETLiteUpdaterParameters( + String configRef, + String feedId, + URI uri, + Duration frequency, + Duration timeout, + boolean fuzzyTripMatching, + HttpHeaders httpRequestHeaders +) + implements SiriETUpdater.Parameters, SiriETLiteHttpTripUpdateSource.Parameters { + public SiriETLiteHttpTripUpdateSource.Parameters sourceParameters() { + return this; + } + + @Override + public String url() { + return uri.toString(); + } + + @Override + public boolean blockReadinessUntilInitialized() { + return false; + } +} diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoader.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoader.java new file mode 100644 index 00000000000..c9b35be7b19 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoader.java @@ -0,0 +1,58 @@ +package org.opentripplanner.updater.siri.updater.lite; + +import java.net.URI; +import java.time.Duration; +import java.util.Optional; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.framework.io.OtpHttpClientFactory; +import org.opentripplanner.updater.siri.updater.SiriHelper; +import org.opentripplanner.updater.siri.updater.SiriLoader; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.org.siri.siri20.Siri; + +/** + * Load real-time updates from SIRI-SX and SIRI-ET feeds over HTTP via a single request + * that contains all updates. + */ +public class SiriLiteHttpLoader implements SiriLoader { + + private static final Logger LOG = LoggerFactory.getLogger(SiriLiteHttpLoader.class); + private final HttpHeaders headers; + private final URI uri; + private final Duration timeout; + private final OtpHttpClient otpHttpClient; + + public SiriLiteHttpLoader(URI uri, Duration timeout, HttpHeaders headers) { + this.uri = uri; + this.timeout = timeout; + this.headers = HttpHeaders.of().acceptApplicationXML().add(headers).build(); + this.otpHttpClient = new OtpHttpClientFactory(timeout, timeout).create(LOG); + } + + /** + * Send a HTTP GET request and unmarshal the response as JAXB. + */ + @Override + public Optional fetchSXFeed(String ignored) { + return fetchFeed(); + } + + /** + * Send a HTTP GET service request and unmarshal the response as JAXB. + */ + @Override + public Optional fetchETFeed(String ignored) { + return fetchFeed(); + } + + private Optional fetchFeed() { + return otpHttpClient.getAndMap( + uri, + timeout, + headers.asMap(), + is -> Optional.of(SiriHelper.unmarshal(is)) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriSXLiteUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriSXLiteUpdaterParameters.java new file mode 100644 index 00000000000..5c48ca3e04b --- /dev/null +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/lite/SiriSXLiteUpdaterParameters.java @@ -0,0 +1,32 @@ +package org.opentripplanner.updater.siri.updater.lite; + +import java.net.URI; +import java.time.Duration; +import org.opentripplanner.updater.siri.updater.SiriSXUpdater; +import org.opentripplanner.updater.spi.HttpHeaders; + +public record SiriSXLiteUpdaterParameters( + String configRef, + String feedId, + URI uri, + Duration frequency, + Duration earlyStart, + Duration timeout, + HttpHeaders requestHeaders +) + implements SiriSXUpdater.Parameters { + @Override + public String requestorRef() { + return "OpenTripPlanner"; + } + + @Override + public boolean blockReadinessUntilInitialized() { + return false; + } + + @Override + public String url() { + return uri.toString(); + } +} diff --git a/application/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java b/application/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java index b53c5370b53..5b65ca228c9 100644 --- a/application/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java +++ b/application/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java @@ -29,7 +29,12 @@ public class SiriConfigDocTest { private static final File OUT_FILE = new File(USER_DOC_PATH, "sandbox/siri/SiriUpdater.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; - private static final Set INCLUDE_UPDATERS = Set.of("siri-et-updater", "siri-sx-updater"); + private static final Set INCLUDE_UPDATERS = Set.of( + "siri-et-updater", + "siri-sx-updater", + "siri-et-lite", + "siri-sx-lite" + ); private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); public static final ObjectMapper mapper = new ObjectMapper(); diff --git a/application/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java b/application/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java index 234d4c68b33..b88b0a38e80 100644 --- a/application/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java +++ b/application/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java @@ -35,7 +35,9 @@ public class UpdaterConfigDocTest { "vehicle-parking", "siri-et-updater", "siri-et-google-pubsub-updater", - "siri-sx-updater" + "siri-sx-updater", + "siri-et-lite", + "siri-sx-lite" ); private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); public static final ObjectMapper mapper = new ObjectMapper(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoaderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoaderTest.java new file mode 100644 index 00000000000..c05e248747a --- /dev/null +++ b/application/src/test/java/org/opentripplanner/updater/siri/updater/lite/SiriLiteHttpLoaderTest.java @@ -0,0 +1,33 @@ +package org.opentripplanner.updater.siri.updater.lite; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.test.support.ResourceLoader; +import org.opentripplanner.updater.spi.HttpHeaders; +import uk.org.siri.siri20.NaturalLanguageStringStructure; + +class SiriLiteHttpLoaderTest { + + private static final Duration ONE_MIN = Duration.ofMinutes(1); + + @Test + void test() { + var uri = ResourceLoader.of(this).uri("siri-sx.xml"); + var loader = new SiriLiteHttpLoader(uri, ONE_MIN, HttpHeaders.empty()); + var siri = loader.fetchETFeed("OTP"); + var delivery = siri.get().getServiceDelivery().getSituationExchangeDeliveries().getFirst(); + var element = delivery.getSituations().getPtSituationElements().getFirst(); + assertEquals( + List.of( + "Hindernis auf Strecke", + "Obstacle on the route", + "Ostacolo sul percorso", + "Ostacul su la via" + ), + element.getReasonNames().stream().map(NaturalLanguageStringStructure::getValue).toList() + ); + } +} diff --git a/application/src/test/resources/org/opentripplanner/updater/siri/updater/lite/siri-sx.xml b/application/src/test/resources/org/opentripplanner/updater/siri/updater/lite/siri-sx.xml new file mode 100644 index 00000000000..be63b158bc6 --- /dev/null +++ b/application/src/test/resources/org/opentripplanner/updater/siri/updater/lite/siri-sx.xml @@ -0,0 +1,243 @@ + + + + 2024-11-27T11:48:04.308988Z + DDIP-M + + 2024-11-27T11:48:04.308988Z + + + 2024-10-04T13:11:34.547+02:00 + STA + 2024-3443167 + 6 + + directReport + + published + + 2024-10-04T13:08:00+02:00 + 2024-11-30T13:08:00+01:00 + + + 2024-10-04T00:00:00+02:00 + 2024-11-30T23:59:00+01:00 + + routeBlockage + Hindernis auf Strecke + Obstacle on the route + Ostacolo sul percorso + Ostacul su la via + 3 + line + de + Straßen-/Streckenhindernis + The busroute: 120 obstacle on the track + 120 Linea 120 . Ostacolo su strada/pista + + + + + it:apb:Line:80120_.24a + 120 + + 28554 + Salorno, Autostazione + + + + + + + + + 2024-10-04T13:08:00+02:00 + 2024-11-30T13:08:00+01:00 + + tripCancellation + normal + + + + + it:apb:Line:80120_.24a + 120 + + 28554 + Salorno, Autostazione + + + + + + + + + + + + line + + + + + it:apb:Line:80120_.24a + 120 + + 28554 + Salorno, Autostazione + + + + + + + + + published + 2024-3443167 + 2024-10-04T13:11:34.547+02:00 + 6 + ControlCenter + STA + general + 3 + + L + + Straßen-/Streckenhindernis + + The busroute: 120 obstacle on the + track + + 120 Linea 120 . Ostacolo su + strada/pista + + + + Hindernis auf Strecke + Obstacle on the route + Ostacolo sul percorso + Ostacul su la via + + + Freitext + Free text + Testo libero + + + + Umsteigen auf andere + Linie + + Change to another line + + Cambiare linea + + + + Dauer der Beeinträchtigung bis + vsl. 2024-11-30 13:08:00 + + + + + + M + + Straßen-/Streckenhindernis + + The busroute: 120 obstacle on the + track + + 120 Linea 120 . Ostacolo su + strada/pista + + + + Hindernis auf Strecke + Obstacle on the route + Ostacolo sul percorso + Ostacul su la via + + + Freitext + Free text + Testo libero + + + + Umsteigen auf andere + Linie + + Change to another line + + Cambiare linea + + + + Dauer der Beeinträchtigung bis + vsl. 2024-11-30 13:08:00 + + + + + + S + + Straßen-/Streckenhindernis + + The busroute: 120 obstacle on the + track + + 120 Linea 120 . Ostacolo su + strada/pista + + + + Hindernis auf Strecke + Obstacle on the route + Ostacolo sul percorso + Ostacul su la via + + + Aufgrund Stand: 04.10.2024 - + 30.11.2024 ; 13:08 + + Due to Update: 04.10.2024 ; + 13:08 + + A causa di Aggiornamento: + 04.10.2024 ; 13:08 + + + + Freitext + Free text + Testo libero + + + + Umsteigen auf andere + Linie + + Change to another line + + Cambiare linea + + + + Dauer der Beeinträchtigung bis + vsl. 2024-11-30 13:08:00 + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/test/resources/standalone/config/router-config.json b/application/src/test/resources/standalone/config/router-config.json index 3616007c087..77c67d85742 100644 --- a/application/src/test/resources/standalone/config/router-config.json +++ b/application/src/test/resources/standalone/config/router-config.json @@ -434,6 +434,17 @@ "feedId": "parking", "sourceType": "siri-fm", "url": "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + }, + { + "type": "siri-et-lite", + "feedId": "sta", + "url": "https://example.com/siri-lite/estimated-timetable/xml", + "fuzzyTripMatching": true + }, + { + "type": "siri-sx-lite", + "feedId": "sta", + "url": "https://example.com/siri-lite/situation-exchange/xml" } ], "rideHailingServices": [ diff --git a/doc/templates/StopConsolidation.md b/doc/templates/StopConsolidation.md index 70866882bd1..ef32a3c29d6 100644 --- a/doc/templates/StopConsolidation.md +++ b/doc/templates/StopConsolidation.md @@ -32,7 +32,7 @@ This has the following consequences - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. - You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. + You can only reference a stop by its sequence, which only works in GTFS-RT, not SIRI. - Fare calculation and transfers are unlikely to work as expected. diff --git a/doc/templates/UpdaterConfig.md b/doc/templates/UpdaterConfig.md index aab5631e6e2..21db7bdb6a4 100644 --- a/doc/templates/UpdaterConfig.md +++ b/doc/templates/UpdaterConfig.md @@ -82,8 +82,8 @@ GBFS form factors: ## Other updaters in sandboxes - [Vehicle parking](sandbox/VehicleParking.md) -- [Siri over HTTP](sandbox/siri/SiriUpdater.md) -- [Siri over Google Cloud PubSub](sandbox/siri/SiriGooglePubSubUpdater.md) -- [Siri over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) +- [SIRI over HTTP](sandbox/siri/SiriUpdater.md) +- [SIRI over Google Cloud PubSub](sandbox/siri/SiriGooglePubSubUpdater.md) +- [SIRI over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) diff --git a/doc/templates/sandbox/siri/SiriAzureUpdater.md b/doc/templates/sandbox/siri/SiriAzureUpdater.md index 85e22e30bda..5e9e1065519 100644 --- a/doc/templates/sandbox/siri/SiriAzureUpdater.md +++ b/doc/templates/sandbox/siri/SiriAzureUpdater.md @@ -1,6 +1,6 @@ -# Siri Azure Updater +# SIRI Azure Updater -This is a sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages +This is a sandbox extension developed by Skånetrafiken that allows OTP to fetch SIRI ET & SX messages through *Azure Service Bus*. It also enables OTP to download historical data from en HTTP endpoint on startup. @@ -17,11 +17,11 @@ Documentation available [here](../../examples/skanetrafiken/Readme.md). To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. -### Siri Azure ET Updater +### SIRI Azure ET Updater -### Siri Azure SX Updater +### SIRI Azure SX Updater diff --git a/doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md index 09fd3996bef..9d3fff5e00d 100644 --- a/doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md +++ b/doc/templates/sandbox/siri/SiriGooglePubSubUpdater.md @@ -1,4 +1,4 @@ -# Siri-ET Google PubSub Updater +# SIRI-ET Google PubSub Updater Support for consuming SIRI-ET messages over a Google Cloud PubSub subscription. Similarly to the SIRI-ET HTTP updater, this updater is developed to support the Nordic SIRI profile @@ -23,7 +23,7 @@ the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pa To enable the SIRI-ET Google PubSub updater you need to add it to the updaters section of the `router-config.json`. -### Siri-ET via Google PubSub +### SIRI-ET via Google PubSub diff --git a/doc/templates/sandbox/siri/SiriUpdater.md b/doc/templates/sandbox/siri/SiriUpdater.md index f695d304475..bed90ded3a3 100644 --- a/doc/templates/sandbox/siri/SiriUpdater.md +++ b/doc/templates/sandbox/siri/SiriUpdater.md @@ -1,35 +1,50 @@ -# Siri Updater +# SIRI Updaters -Support for consuming SIRI ET and SX messages. The updater is developed to support the Nordic -SIRI profile which is a subset of the SIRI specification. +Support for consuming SIRI ET and SX messages via HTTPS. The updaters aim to support the [Nordic +and EPIP SIRI profiles](../../features-explained/Netex-Siri-Compatibility.md) which +are subsets of the SIRI specification. + +For more documentation about the Norwegian profile and data, go to +the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and +the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile). ## Contact Info - Lasse Tyrihjell, Entur, Norway - -## Documentation - -This updater consumes SIRI real time information. It is developed by Entur and supports the Nordic -Profile for SIRI. It should be possible to develop it further to support a broader set of the SIRI -specification. - -For more documentation goto -the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and -the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile) -. +- Leonard Ehrenfried, Germany ## Configuration -To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. +To enable the SIRI updater you need to add it to the `updaters` section of the `router-config.json`. -### Siri-ET via HTTPS +### SIRI-ET Request/Response via HTTPS -### Siri-SX via HTTPS +### SIRI-SX Request/Response via HTTPS +### SIRI-ET Lite + +SIRI Lite is [not very well](https://nextcloud.leonard.io/s/2tdYdmYBGtLQMfi/download?path=&files=Proposition-Profil-SIRI-Lite-initial-v1-3%20en.pdf) +[specified](https://normes.transport.data.gouv.fr/normes/siri/profil-france/#protocoles-d%C3%A9change-des-donn%C3%A9es-siri), +but this updater supports the following definition: + +Fetching XML-formatted SIRI messages as single GET request rather than the more common request/response +flow. This means that the XML feed must contain all updates for all trips, just like it is the case +for GTFS-RT TripUpdates. + + + +### SIRI-SX Lite + +This updater follows the same definition of SIRI Lite as the SIRI-ET one: it downloads the entire +feed in a single HTTP GET request. + + + + ## Changelog - Initial version of SIRI updater (October 2019) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 56a58be86b2..e25207e5e0e 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -405,7 +405,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Preserve language in SIRI/GTFS-RT alert messages [#4117](https://github.com/opentripplanner/OpenTripPlanner/pull/4117) - Use board/alight cost only for transits [#4079](https://github.com/opentripplanner/OpenTripPlanner/pull/4079) - Improve SIRI real-time performance by reducing stopPattern duplicates [#4038](https://github.com/opentripplanner/OpenTripPlanner/pull/4038) -- Siri updaters for Azure ServiceBus [#4106](https://github.com/opentripplanner/OpenTripPlanner/pull/4106) +- SIRI updaters for Azure ServiceBus [#4106](https://github.com/opentripplanner/OpenTripPlanner/pull/4106) - Fallback to recorded/expected arrival/departure time if other one is missing in SIRI-ET [#4055](https://github.com/opentripplanner/OpenTripPlanner/pull/4055) - Allow overriding GBFS system_id with configuration [#4147](https://github.com/opentripplanner/OpenTripPlanner/pull/4147) - Fix error with transfer-slack and GTFS minTransferTime [#4120](https://github.com/opentripplanner/OpenTripPlanner/pull/4120) @@ -413,7 +413,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Don't indicate stop has been updated when NO_DATA is defined [#3962](https://github.com/opentripplanner/OpenTripPlanner/pull/3962) - Implement nearby searches for car and bicycle parking [#4165](https://github.com/opentripplanner/OpenTripPlanner/pull/4165) - Do not link cars to stop vertices in routing [#4166](https://github.com/opentripplanner/OpenTripPlanner/pull/4166) -- Add Siri real-time occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) +- Add SIRI real-time occupancy info [#4180](https://github.com/opentripplanner/OpenTripPlanner/pull/4180) - Add gtfs stop description translations [#4158](https://github.com/opentripplanner/OpenTripPlanner/pull/4158) - Add option to discard min transfer times [#4195](https://github.com/opentripplanner/OpenTripPlanner/pull/4195) - Use negative delay from first stop in a GTFS RT update in previous stop times when required [#4035](https://github.com/opentripplanner/OpenTripPlanner/pull/4035) diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 7dae97fd74c..82d14f36392 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -878,6 +878,17 @@ Used to group requests when monitoring OTP. "feedId" : "parking", "sourceType" : "siri-fm", "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + }, + { + "type" : "siri-et-lite", + "feedId" : "sta", + "url" : "https://example.com/siri-lite/estimated-timetable/xml", + "fuzzyTripMatching" : true + }, + { + "type" : "siri-sx-lite", + "feedId" : "sta", + "url" : "https://example.com/siri-lite/situation-exchange/xml" } ], "rideHailingServices" : [ diff --git a/doc/user/UpdaterConfig.md b/doc/user/UpdaterConfig.md index f61cce12f54..068b61f112e 100644 --- a/doc/user/UpdaterConfig.md +++ b/doc/user/UpdaterConfig.md @@ -427,8 +427,8 @@ This is temporary and will be removed in a future version of OTP. Use this to sp ## Other updaters in sandboxes - [Vehicle parking](sandbox/VehicleParking.md) -- [Siri over HTTP](sandbox/siri/SiriUpdater.md) -- [Siri over Google Cloud PubSub](sandbox/siri/SiriGooglePubSubUpdater.md) -- [Siri over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) +- [SIRI over HTTP](sandbox/siri/SiriUpdater.md) +- [SIRI over Google Cloud PubSub](sandbox/siri/SiriGooglePubSubUpdater.md) +- [SIRI over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) diff --git a/doc/user/examples/skanetrafiken/Readme.md b/doc/user/examples/skanetrafiken/Readme.md index a611c839140..acaf212719a 100644 --- a/doc/user/examples/skanetrafiken/Readme.md +++ b/doc/user/examples/skanetrafiken/Readme.md @@ -38,7 +38,7 @@ To reduce graph size, only data for northern part of Denmark is used. ## Real-time The **Azure Service Bus** is used to propagate SIRI SX and ET real-time messages to OTP. -This is solved through Siri Azure updaters that Skånetrafiken had implemented in OTP. There are +This is solved through SIRI Azure updaters that Skånetrafiken had implemented in OTP. There are separate updaters for SIRI SX and ET. Those updaters are used to provide data for Swedish traffic (NeTEx). Right now, there is no connection to any real-time source for danish traffic (GTFS data). @@ -77,7 +77,7 @@ Those two parameters are used to define time boundaries for the messages. Both endpoints generate XML response which is an SIRI object containing SX or ET messages. Messages are -formatted according to Siri Nordic Profile. +formatted according to SIRI Nordic Profile. Since in SIRI ET standard each messages contains all necessary data, Skånetrafikens implementation of the endpoint returns only the last message diff --git a/doc/user/features-explained/Netex-Siri-Compatibility.md b/doc/user/features-explained/Netex-Siri-Compatibility.md index 731011be8e0..10c64f6a5c0 100644 --- a/doc/user/features-explained/Netex-Siri-Compatibility.md +++ b/doc/user/features-explained/Netex-Siri-Compatibility.md @@ -12,7 +12,7 @@ Different countries are currently using different incompatible "profiles" of NeT underway to converge on a single European standard profile. This is based in large part on the Nordic profile used by Entur. -The Nordic profile is the only profile that has been thoroughly tested in production in OTP and is +The Nordic profile is the only one that has been thoroughly tested in production in OTP and is used in Norway, Finland and Sweden. ### EPIP @@ -22,7 +22,7 @@ is an attempt to unify other country profiles and support in OTP is adequate, bu to tell how much of EPIP is supported since it is a very large profile. The current status of the support is tracked on [Github](https://github.com/opentripplanner/OpenTripPlanner/issues/3640). -Sometimes it is difficult to tell if a file conforms to EPIP so to find out, you can run the following +Sometimes it is not easy to figure out if a file conforms to EPIP so to find out, you can run the following commands: ``` diff --git a/doc/user/sandbox/StopConsolidation.md b/doc/user/sandbox/StopConsolidation.md index d0e18a9ce30..547f3200296 100644 --- a/doc/user/sandbox/StopConsolidation.md +++ b/doc/user/sandbox/StopConsolidation.md @@ -32,7 +32,7 @@ This has the following consequences - It makes real-time trip updates referencing a stop id much more complicated and in many cases impossible to resolve. - You can only reference a stop by its sequence, which only works in GTFS-RT, not Siri. + You can only reference a stop by its sequence, which only works in GTFS-RT, not SIRI. - Fare calculation and transfers are unlikely to work as expected. diff --git a/doc/user/sandbox/siri/SiriAzureUpdater.md b/doc/user/sandbox/siri/SiriAzureUpdater.md index 898e70d7b84..02d602c48d2 100644 --- a/doc/user/sandbox/siri/SiriAzureUpdater.md +++ b/doc/user/sandbox/siri/SiriAzureUpdater.md @@ -1,6 +1,6 @@ -# Siri Azure Updater +# SIRI Azure Updater -This is a sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages +This is a sandbox extension developed by Skånetrafiken that allows OTP to fetch SIRI ET & SX messages through *Azure Service Bus*. It also enables OTP to download historical data from en HTTP endpoint on startup. @@ -17,7 +17,7 @@ Documentation available [here](../../examples/skanetrafiken/Readme.md). To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. -### Siri Azure ET Updater +### SIRI Azure ET Updater @@ -105,7 +105,7 @@ Has to be present for authenticationMethod SharedAccessKey. This should be Prima -### Siri Azure SX Updater +### SIRI Azure SX Updater diff --git a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md index 5232696ad9b..ed09522b224 100644 --- a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md +++ b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md @@ -1,4 +1,4 @@ -# Siri-ET Google PubSub Updater +# SIRI-ET Google PubSub Updater Support for consuming SIRI-ET messages over a Google Cloud PubSub subscription. Similarly to the SIRI-ET HTTP updater, this updater is developed to support the Nordic SIRI profile @@ -23,7 +23,7 @@ the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pa To enable the SIRI-ET Google PubSub updater you need to add it to the updaters section of the `router-config.json`. -### Siri-ET via Google PubSub +### SIRI-ET via Google PubSub diff --git a/doc/user/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md index 28f2f9a85db..a6f7781e293 100644 --- a/doc/user/sandbox/siri/SiriUpdater.md +++ b/doc/user/sandbox/siri/SiriUpdater.md @@ -1,28 +1,23 @@ -# Siri Updater +# SIRI Updaters -Support for consuming SIRI ET and SX messages. The updater is developed to support the Nordic -SIRI profile which is a subset of the SIRI specification. +Support for consuming SIRI ET and SX messages via HTTPS. The updaters aim to support the [Nordic +and EPIP SIRI profiles](../../features-explained/Netex-Siri-Compatibility.md) which +are subsets of the SIRI specification. + +For more documentation about the Norwegian profile and data, go to +the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and +the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile). ## Contact Info - Lasse Tyrihjell, Entur, Norway - -## Documentation - -This updater consumes SIRI real time information. It is developed by Entur and supports the Nordic -Profile for SIRI. It should be possible to develop it further to support a broader set of the SIRI -specification. - -For more documentation goto -the [Entur Real-Time Data](https://developer.entur.org/pages-real-time-intro) documentation and -the [Norwegian SIRI profile](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/637370420/Norwegian+SIRI+profile) -. +- Leonard Ehrenfried, Germany ## Configuration -To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. +To enable the SIRI updater you need to add it to the `updaters` section of the `router-config.json`. -### Siri-ET via HTTPS +### SIRI-ET Request/Response via HTTPS @@ -89,7 +84,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. -### Siri-SX via HTTPS +### SIRI-SX Request/Response via HTTPS @@ -166,6 +161,148 @@ HTTP headers to add to the request. Any header key, value can be inserted. +### SIRI-ET Lite + +SIRI Lite is [not very well](https://nextcloud.leonard.io/s/2tdYdmYBGtLQMfi/download?path=&files=Proposition-Profil-SIRI-Lite-initial-v1-3%20en.pdf) +[specified](https://normes.transport.data.gouv.fr/normes/siri/profil-france/#protocoles-d%C3%A9change-des-donn%C3%A9es-siri), +but this updater supports the following definition: + +Fetching XML-formatted SIRI messages as single GET request rather than the more common request/response +flow. This means that the XML feed must contain all updates for all trips, just like it is the case +for GTFS-RT TripUpdates. + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "siri-et-lite" | `enum` | The type of the updater. | *Required* | | 1.5 | +| feedId | `string` | The ID of the feed to apply the updates to. | *Required* | | 2.7 | +| frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.7 | +| fuzzyTripMatching | `boolean` | If the fuzzy trip matcher should be used to match trips. | *Optional* | `false` | 2.7 | +| timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.7 | +| [url](#u__15__url) | `uri` | The URL to send the HTTP requests to. | *Required* | | 2.7 | +| [headers](#u__15__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.7 | + + +##### Parameter details + +

url

+ +**Since version:** `2.7` ∙ **Type:** `uri` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +The URL to send the HTTP requests to. + +Use the file protocol to set a directory for reading updates from a directory. The file +loader will look for xml files: '*.xml' in the configured directory. The files are +renamed by the loader when processed: + +    _a.xml_   ➞   _a.xml.inProgress_   ➞   _a.xml.ok_   or   _a.xml.failed_ + + + +

headers

+ +**Since version:** `2.7` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[15] + +HTTP headers to add to the request. Any header key, value can be inserted. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "siri-et-lite", + "feedId" : "sta", + "url" : "https://example.com/siri-lite/estimated-timetable/xml", + "fuzzyTripMatching" : true + } + ] +} +``` + + + +### SIRI-SX Lite + +This updater follows the same definition of SIRI Lite as the SIRI-ET one: it downloads the entire +feed in a single HTTP GET request. + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "siri-sx-lite" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [earlyStart](#u__16__earlyStart) | `duration` | This value is subtracted from the actual validity defined in the message. | *Optional* | `"PT0S"` | 2.0 | +| feedId | `string` | The ID of the feed to apply the updates to. | *Required* | | 2.7 | +| frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.7 | +| timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.7 | +| [url](#u__16__url) | `uri` | The URL to send the HTTP requests to. | *Required* | | 2.7 | +| [headers](#u__16__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.7 | + + +##### Parameter details + +

earlyStart

+ +**Since version:** `2.0` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` +**Path:** /updaters/[16] + +This value is subtracted from the actual validity defined in the message. + +Normally the planned departure time is used, so setting this to 10s will cause the +SX-message to be included in trip-results 10 seconds before the the planned departure +time. + +

url

+ +**Since version:** `2.7` ∙ **Type:** `uri` ∙ **Cardinality:** `Required` +**Path:** /updaters/[16] + +The URL to send the HTTP requests to. + +Use the file protocol to set a directory for reading updates from a directory. The file +loader will look for xml files: '*.xml' in the configured directory. The files are +renamed by the loader when processed: + +    _a.xml_   ➞   _a.xml.inProgress_   ➞   _a.xml.ok_   or   _a.xml.failed_ + + + +

headers

+ +**Since version:** `2.7` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[16] + +HTTP headers to add to the request. Any header key, value can be inserted. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "siri-sx-lite", + "feedId" : "sta", + "url" : "https://example.com/siri-lite/situation-exchange/xml" + } + ] +} +``` + + + + ## Changelog - Initial version of SIRI updater (October 2019)