From 46133837081419414c729ee2902f037479923e5d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 14:29:24 +0200 Subject: [PATCH 01/20] First implementation of SIRI-FM updater --- .../bikeep/BikeepUpdaterParameters.java | 7 ++ .../bikely/BikelyUpdaterParameters.java | 7 ++ .../hslpark/HslParkUpdaterParameters.java | 7 ++ .../noi/NoiUpdaterParameters.java | 7 ++ .../parkapi/ParkAPIUpdaterParameters.java | 9 +- .../vehicleparking/sirifm/SiriFmUpdater.java | 37 ++++++ .../sirifm/SiriFmUpdaterParameters.java | 34 ++++++ .../vehicle_parking/VehicleParking.java | 33 +++-- .../updaters/VehicleParkingUpdaterConfig.java | 12 ++ .../configure/UpdaterConfigurator.java | 37 ++++-- .../AvailabilityDatasourceFactory.java | 23 ++++ .../vehicle_parking/AvailabiltyUpdate.java | 12 ++ .../VehicleParkingAvailabilityUpdater.java | 114 ++++++++++++++++++ .../VehicleParkingDataSourceFactory.java | 1 + .../VehicleParkingSourceType.java | 1 + .../VehicleParkingUpdaterParameters.java | 6 + .../VehicleParkingUpdaterTest.java | 5 + 17 files changed, 334 insertions(+), 18 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java index be937ecdd5e..86e03731dd8 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikeep; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikeepUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKEEP; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java index 26e40f4ec4a..73f26a43aa4 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikely; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikelyUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKELY; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java index 4b75d38ea6f..b4ad24ae080 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.hslpark; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; @@ -25,4 +27,9 @@ public record HslParkUpdaterParameters( public Duration frequency() { return Duration.ofSeconds(utilizationsFrequencySec); } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java index 371ffdc9f33..b34769c9dd2 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.noi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record NoiUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.NOI_OPEN_DATA_HUB; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java index 263987c60d0..3014d932e08 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.parkapi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import java.util.List; @@ -21,4 +23,9 @@ public record ParkAPIUpdaterParameters( VehicleParkingSourceType sourceType, ZoneId timeZone ) - implements VehicleParkingUpdaterParameters {} + implements VehicleParkingUpdaterParameters { + @Override + public UpdateType updateType() { + return FULL; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java new file mode 100644 index 00000000000..a30249b2b92 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -0,0 +1,37 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import java.util.List; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SiriFmUpdater implements DataSource { + + private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); + private final SiriFmUpdaterParameters params; + + public SiriFmUpdater(SiriFmUpdaterParameters params) { + this.params = params; + } + + @Override + public String toString() { + return ToStringBuilder + .of(this.getClass()) + .addStr("url", this.params.url().toString()) + .toString(); + } + + @Override + public boolean update() { + LOG.error("RUNNING {}", this); + return true; + } + + @Override + public List getUpdates() { + return List.of(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java new file mode 100644 index 00000000000..ef4015029ff --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java @@ -0,0 +1,34 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType.SIRI_FM; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY; + +import java.net.URI; +import java.time.Duration; +import org.opentripplanner.ext.vehicleparking.noi.NoiUpdater; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; + +/** + * Class that extends {@link VehicleParkingUpdaterParameters} with parameters required by {@link + * NoiUpdater}. + */ +public record SiriFmUpdaterParameters( + String configRef, + URI url, + String feedId, + Duration frequency, + HttpHeaders httpHeaders +) + implements VehicleParkingUpdaterParameters { + @Override + public VehicleParkingSourceType sourceType() { + return SIRI_FM; + } + + @Override + public UpdateType updateType() { + return AVAILABILITY; + } +} diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index d5dfc4ce8c1..69f7f08eaa5 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -239,23 +239,40 @@ public boolean hasRealTimeDataForMode( return false; } - switch (traverseMode) { - case BICYCLE: - return availability.getBicycleSpaces() != null; - case CAR: + return switch (traverseMode) { + case BICYCLE -> availability.getBicycleSpaces() != null; + case CAR -> { var places = wheelchairAccessibleCarPlaces ? availability.getWheelchairAccessibleCarSpaces() : availability.getCarSpaces(); - return places != null; - default: - return false; - } + yield places != null; + } + default -> false; + }; } + /** + * The only mutable method in this class: it allows to update the available parking spaces during + * real-time updates. + */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; } + public void close() { + var builder = VehicleParkingSpaces.builder(); + if (hasCarPlaces()) { + builder.carSpaces(0); + } + if (hasWheelchairAccessibleCarPlaces()) { + builder.wheelchairAccessibleCarSpaces(0); + } + if (hasBicyclePlaces()) { + builder.bicycleSpaces(0); + } + updateAvailability(builder.build()); + } + @Override public int hashCode() { return Objects.hash( diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index c687899009f..6ef8c465bc3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -13,6 +13,7 @@ import org.opentripplanner.ext.vehicleparking.hslpark.HslParkUpdaterParameters; import org.opentripplanner.ext.vehicleparking.noi.NoiUpdaterParameters; import org.opentripplanner.ext.vehicleparking.parkapi.ParkAPIUpdaterParameters; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; @@ -100,6 +101,17 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .asDuration(Duration.ofMinutes(1)), HttpHeadersConfig.headers(c, V2_6) ); + case SIRI_FM -> new SiriFmUpdaterParameters( + updaterRef, + c.of("url").since(V2_6).summary("URL of the SIRI-FM Light endpoint.").asUri(), + feedId, + c + .of("frequency") + .since(V2_6) + .summary("How often to update the source.") + .asDuration(Duration.ofMinutes(1)), + HttpHeadersConfig.headers(c, V2_6) + ); }; } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 103120b7ecb..4726ab8b4ff 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -25,6 +25,8 @@ import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdater; import org.opentripplanner.updater.trip.PollingTripUpdater; import org.opentripplanner.updater.trip.TimetableSnapshotSource; +import org.opentripplanner.updater.vehicle_parking.AvailabilityDatasourceFactory; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingAvailabilityUpdater; import org.opentripplanner.updater.vehicle_parking.VehicleParkingDataSourceFactory; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdater; import org.opentripplanner.updater.vehicle_position.PollingVehiclePositionUpdater; @@ -187,15 +189,32 @@ private List createUpdatersFromConfig() { ); } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { - var source = VehicleParkingDataSourceFactory.create(configItem, openingHoursCalendarService); - updaters.add( - new VehicleParkingUpdater( - configItem, - source, - graph.getLinker(), - graph.getVehicleParkingService() - ) - ); + switch (configItem.updateType()) { + case AVAILABILITY -> { + var source = VehicleParkingDataSourceFactory.create( + configItem, + openingHoursCalendarService + ); + updaters.add( + new VehicleParkingUpdater( + configItem, + source, + graph.getLinker(), + graph.getVehicleParkingService() + ) + ); + } + case FULL -> { + var source = AvailabilityDatasourceFactory.create(configItem); + updaters.add( + new VehicleParkingAvailabilityUpdater( + configItem, + source, + graph.getVehicleParkingService() + ) + ); + } + } } for (var configItem : updatersParameters.getSiriAzureETUpdaterParameters()) { updaters.add( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java new file mode 100644 index 00000000000..cb62332747b --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java @@ -0,0 +1,23 @@ +package org.opentripplanner.updater.vehicle_parking; + +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdater; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; +import org.opentripplanner.updater.spi.DataSource; + +/** + * Class that can be used to return a custom vehicle parking {@link DataSource}. + */ +public class AvailabilityDatasourceFactory { + + public static DataSource create(VehicleParkingUpdaterParameters parameters) { + return switch (parameters.sourceType()) { + case SIRI_FM -> new SiriFmUpdater((SiriFmUpdaterParameters) parameters); + case PARK_API, + BICYCLE_PARK_API, + HSL_PARK, + BIKEEP, + NOI_OPEN_DATA_HUB, + BIKELY -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java new file mode 100644 index 00000000000..404fcb9f47b --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -0,0 +1,12 @@ +package org.opentripplanner.updater.vehicle_parking; + +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public sealed interface AvailabiltyUpdate { + FeedScopedId vehicleParkingId(); + + record AvailabilityUpdated(FeedScopedId vehicleParkingId, int spacesAvailable) + implements AvailabiltyUpdate {} + + record ParkingClosed(FeedScopedId vehicleParkingId) implements AvailabiltyUpdate {} +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java new file mode 100644 index 00000000000..cf890213d76 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -0,0 +1,114 @@ +package org.opentripplanner.updater.vehicle_parking; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.PollingGraphUpdater; +import org.opentripplanner.updater.spi.WriteToGraphCallback; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.AvailabilityUpdated; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.ParkingClosed; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Graph updater that dynamically sets availability information on vehicle parking lots. This + * updater fetches data from a single {@link DataSource}. + */ +public class VehicleParkingAvailabilityUpdater extends PollingGraphUpdater { + + private static final Logger LOG = LoggerFactory.getLogger( + VehicleParkingAvailabilityUpdater.class + ); + private final DataSource source; + private WriteToGraphCallback saveResultOnGraph; + + private final VehicleParkingService vehicleParkingService; + + public VehicleParkingAvailabilityUpdater( + VehicleParkingUpdaterParameters parameters, + DataSource source, + VehicleParkingService vehicleParkingService + ) { + super(parameters); + this.source = source; + this.vehicleParkingService = vehicleParkingService; + + LOG.info("Creating vehicle-parking updater running every {}: {}", pollingPeriod(), source); + } + + @Override + public void setup(WriteToGraphCallback writeToGraphCallback) { + this.saveResultOnGraph = writeToGraphCallback; + } + + @Override + protected void runPolling() { + LOG.debug("Updating parking availability from {}", source); + if (!source.update()) { + LOG.debug("No updates"); + } else { + var updates = source.getUpdates(); + + var graphWriterRunnable = new VehicleParkingGraphWriterRunnable(updates); + saveResultOnGraph.execute(graphWriterRunnable); + } + } + + private class VehicleParkingGraphWriterRunnable implements GraphWriterRunnable { + + private final List updates; + private final Map parkingById; + + private VehicleParkingGraphWriterRunnable(List updates) { + this.updates = List.copyOf(updates); + this.parkingById = + vehicleParkingService + .getVehicleParkings() + .collect(Collectors.toUnmodifiableMap(VehicleParking::getId, Function.identity())); + } + + @Override + public void run(Graph graph, TransitModel ignored) { + updates.forEach(this::handleUpdate); + } + + private void handleUpdate(AvailabiltyUpdate update) { + if (!parkingById.containsKey(update.vehicleParkingId())) { + LOG.error( + "Parking with id {} does not exist. Skipping availability update.", + update.vehicleParkingId() + ); + } + var parking = parkingById.get(update.vehicleParkingId()); + + switch (update) { + case ParkingClosed closed -> parking.close(); + case AvailabilityUpdated availabilityUpdated -> { + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(availabilityUpdated.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } + parking.updateAvailability(builder.build()); + } + } + } + } + + @Override + public String toString() { + return ToStringBuilder.of(this.getClass()).addObj("source", source).toString(); + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java index a956fda0d87..09ecb67a54d 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java @@ -42,6 +42,7 @@ public static DataSource create( case BIKELY -> new BikelyUpdater((BikelyUpdaterParameters) parameters); case NOI_OPEN_DATA_HUB -> new NoiUpdater((NoiUpdaterParameters) parameters); case BIKEEP -> new BikeepUpdater((BikeepUpdaterParameters) parameters); + case SIRI_FM -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); }; } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java index f6a28177d8e..1601245b16c 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java @@ -7,4 +7,5 @@ public enum VehicleParkingSourceType { BIKELY, NOI_OPEN_DATA_HUB, BIKEEP, + SIRI_FM, } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java index 3422ec3e300..500a0ff6bcc 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java @@ -8,4 +8,10 @@ */ public interface VehicleParkingUpdaterParameters extends PollingGraphUpdaterParameters { VehicleParkingSourceType sourceType(); + UpdateType updateType(); + + enum UpdateType { + FULL, + AVAILABILITY, + } } diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java index a31b5cfb387..f49299eb4ea 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java @@ -54,6 +54,11 @@ public VehicleParkingSourceType sourceType() { return null; } + @Override + public UpdateType updateType() { + return UpdateType.FULL; + } + @Override public Duration frequency() { return Duration.ZERO; From 67c97e939dc84c4efe7264819361422fa51051ac Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 14:48:01 +0200 Subject: [PATCH 02/20] Flesh out Siri importer --- .../vehicleparking/sirifm/SiriFmUpdater.java | 38 +++++++++++++++++-- .../configure/UpdaterConfigurator.java | 4 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index a30249b2b92..366179d73d0 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -1,9 +1,14 @@ package org.opentripplanner.ext.vehicleparking.sirifm; import java.util.List; +import java.util.Map; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; +import org.rutebanken.siri20.util.SiriXml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,9 +16,14 @@ public class SiriFmUpdater implements DataSource { private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); private final SiriFmUpdaterParameters params; + private final OtpHttpClient httpClient; + private final Map headers; + private List updates = List.of(); - public SiriFmUpdater(SiriFmUpdaterParameters params) { - this.params = params; + public SiriFmUpdater(SiriFmUpdaterParameters parameters) { + params = parameters; + headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); + httpClient = new OtpHttpClientFactory().create(LOG); } @Override @@ -27,11 +37,33 @@ public String toString() { @Override public boolean update() { LOG.error("RUNNING {}", this); + + updates = + httpClient.getAndMap( + params.url(), + headers, + resp -> { + var siri = SiriXml.parseXml(resp); + + var conditions = siri + .getServiceDelivery() + .getFacilityMonitoringDeliveries() + .stream() + .flatMap(d -> d.getFacilityConditions().stream()) + .toList(); + + conditions.forEach(c -> { + LOG.error("{}", c.getFacilityRef().getValue()); + }); + + return List.of(); + } + ); return true; } @Override public List getUpdates() { - return List.of(); + return updates; } } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 4726ab8b4ff..f95c485f8d2 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -190,7 +190,7 @@ private List createUpdatersFromConfig() { } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { switch (configItem.updateType()) { - case AVAILABILITY -> { + case FULL -> { var source = VehicleParkingDataSourceFactory.create( configItem, openingHoursCalendarService @@ -204,7 +204,7 @@ private List createUpdatersFromConfig() { ) ); } - case FULL -> { + case AVAILABILITY -> { var source = AvailabilityDatasourceFactory.create(configItem); updaters.add( new VehicleParkingAvailabilityUpdater( From 84cd9be82570c618ef274103c8fc7327ef33a098 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 17:33:28 +0200 Subject: [PATCH 03/20] Implement Siri code --- .../vehicleparking/sirifm/SiriFmUpdater.java | 69 +++++++++++++++---- .../sirifm/SiriFmUpdaterParameters.java | 4 +- .../configure/UpdaterConfigurator.java | 2 +- .../VehicleParkingAvailabilityUpdater.java | 27 ++++---- .../VehicleParkingUpdaterParameters.java | 2 +- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index 366179d73d0..ada96edff58 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -1,16 +1,26 @@ package org.opentripplanner.ext.vehicleparking.sirifm; +import static uk.org.siri.siri21.CountingTypeEnumeration.PRESENT_COUNT; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.stream.Stream; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; -import org.rutebanken.siri20.util.SiriXml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import uk.org.siri.siri21.FacilityConditionStructure; +import uk.org.siri.siri21.Siri; public class SiriFmUpdater implements DataSource { @@ -20,6 +30,16 @@ public class SiriFmUpdater implements DataSource { private final Map headers; private List updates = List.of(); + private static final JAXBContext jaxbContext; + + static { + try { + jaxbContext = JAXBContext.newInstance(Siri.class); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + public SiriFmUpdater(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); @@ -43,25 +63,50 @@ public boolean update() { params.url(), headers, resp -> { - var siri = SiriXml.parseXml(resp); + var siri = parseXml(resp); - var conditions = siri - .getServiceDelivery() - .getFacilityMonitoringDeliveries() - .stream() + return Stream + .ofNullable(siri.getServiceDelivery()) + .flatMap(sd -> sd.getFacilityMonitoringDeliveries().stream()) .flatMap(d -> d.getFacilityConditions().stream()) + .filter(this::conformsToItalianProfile) + .map(this::mapToUpdate) .toList(); - - conditions.forEach(c -> { - LOG.error("{}", c.getFacilityRef().getValue()); - }); - - return List.of(); } ); return true; } + private AvailabiltyUpdate mapToUpdate(FacilityConditionStructure c) { + var id = new FeedScopedId(params.feedId(), c.getFacilityRef().getValue()); + var available = c.getMonitoredCountings().getFirst().getCount().intValue(); + return new AvailabiltyUpdate.AvailabilityUpdated(id, available); + } + + /** + * Checks if the {@link FacilityConditionStructure} contains all the necessary information that + * are required by the Italian Siri-FM profile. + */ + private boolean conformsToItalianProfile(FacilityConditionStructure c) { + return ( + c.getFacilityRef() != null && + c.getFacilityRef().getValue() != null && + c.getMonitoredCountings().size() == 1 && + c.getMonitoredCountings().stream().anyMatch(mc -> mc.getCountingType() == PRESENT_COUNT) + ); + } + + private Siri parseXml(InputStream stream) { + try { + var xmlif = XMLInputFactory.newInstance(); + var jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + var streamReader = xmlif.createXMLStreamReader(stream); + return (Siri) jaxbUnmarshaller.unmarshal(streamReader); + } catch (JAXBException | XMLStreamException e) { + throw new RuntimeException(e); + } + } + @Override public List getUpdates() { return updates; diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java index ef4015029ff..5afdffb7067 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vehicleparking.sirifm; import static org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType.SIRI_FM; -import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY_ONLY; import java.net.URI; import java.time.Duration; @@ -29,6 +29,6 @@ public VehicleParkingSourceType sourceType() { @Override public UpdateType updateType() { - return AVAILABILITY; + return AVAILABILITY_ONLY; } } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index f95c485f8d2..6662bae362b 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -204,7 +204,7 @@ private List createUpdatersFromConfig() { ) ); } - case AVAILABILITY -> { + case AVAILABILITY_ONLY -> { var source = AvailabilityDatasourceFactory.create(configItem); updaters.add( new VehicleParkingAvailabilityUpdater( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index cf890213d76..8d38890b16c 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -88,20 +88,21 @@ private void handleUpdate(AvailabiltyUpdate update) { "Parking with id {} does not exist. Skipping availability update.", update.vehicleParkingId() ); - } - var parking = parkingById.get(update.vehicleParkingId()); - - switch (update) { - case ParkingClosed closed -> parking.close(); - case AvailabilityUpdated availabilityUpdated -> { - var builder = VehicleParkingSpaces.builder(); - if (parking.hasCarPlaces()) { - builder.carSpaces(availabilityUpdated.spacesAvailable()); - } - if (parking.hasBicyclePlaces()) { - builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } else { + var parking = parkingById.get(update.vehicleParkingId()); + + switch (update) { + case ParkingClosed closed -> parking.close(); + case AvailabilityUpdated availabilityUpdated -> { + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(availabilityUpdated.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } + parking.updateAvailability(builder.build()); } - parking.updateAvailability(builder.build()); } } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java index 500a0ff6bcc..bff0022383f 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java @@ -12,6 +12,6 @@ public interface VehicleParkingUpdaterParameters extends PollingGraphUpdaterPara enum UpdateType { FULL, - AVAILABILITY, + AVAILABILITY_ONLY, } } From e80ec16d686b554872206848e6c0fdc0b6272003 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 17:42:25 +0200 Subject: [PATCH 04/20] Make code more robust --- .../vehicleparking/sirifm/SiriFmUpdater.java | 2 +- .../vehicle_parking/AvailabiltyUpdate.java | 9 +----- .../VehicleParkingAvailabilityUpdater.java | 28 +++++++------------ 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index ada96edff58..1413df407e1 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -80,7 +80,7 @@ public boolean update() { private AvailabiltyUpdate mapToUpdate(FacilityConditionStructure c) { var id = new FeedScopedId(params.feedId(), c.getFacilityRef().getValue()); var available = c.getMonitoredCountings().getFirst().getCount().intValue(); - return new AvailabiltyUpdate.AvailabilityUpdated(id, available); + return new AvailabiltyUpdate(id, available); } /** diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java index 404fcb9f47b..17d273ac237 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -2,11 +2,4 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; -public sealed interface AvailabiltyUpdate { - FeedScopedId vehicleParkingId(); - - record AvailabilityUpdated(FeedScopedId vehicleParkingId, int spacesAvailable) - implements AvailabiltyUpdate {} - - record ParkingClosed(FeedScopedId vehicleParkingId) implements AvailabiltyUpdate {} -} +public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) {} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 8d38890b16c..77790b1a084 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -15,8 +15,6 @@ import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.WriteToGraphCallback; -import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.AvailabilityUpdated; -import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.ParkingClosed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,17 +57,17 @@ protected void runPolling() { } else { var updates = source.getUpdates(); - var graphWriterRunnable = new VehicleParkingGraphWriterRunnable(updates); + var graphWriterRunnable = new UpdateAvailabilities(updates); saveResultOnGraph.execute(graphWriterRunnable); } } - private class VehicleParkingGraphWriterRunnable implements GraphWriterRunnable { + private class UpdateAvailabilities implements GraphWriterRunnable { private final List updates; private final Map parkingById; - private VehicleParkingGraphWriterRunnable(List updates) { + private UpdateAvailabilities(List updates) { this.updates = List.copyOf(updates); this.parkingById = vehicleParkingService @@ -90,20 +88,14 @@ private void handleUpdate(AvailabiltyUpdate update) { ); } else { var parking = parkingById.get(update.vehicleParkingId()); - - switch (update) { - case ParkingClosed closed -> parking.close(); - case AvailabilityUpdated availabilityUpdated -> { - var builder = VehicleParkingSpaces.builder(); - if (parking.hasCarPlaces()) { - builder.carSpaces(availabilityUpdated.spacesAvailable()); - } - if (parking.hasBicyclePlaces()) { - builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); - } - parking.updateAvailability(builder.build()); - } + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(update.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(update.spacesAvailable()); } + parking.updateAvailability(builder.build()); } } } From ad8b74d8afc193ade38cc41fb3d1698e588e9300 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 18:07:22 +0200 Subject: [PATCH 05/20] Add test for Siri-FM parking updater --- .../sirifm/SiriFmUpdaterTest.java | 28 ++++++++++ .../ext/vehicleparking/sirifm/siri-fm.xml | 56 +++++++++++++++++++ .../vehicleparking/sirifm/SiriFmUpdater.java | 4 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java create mode 100644 src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java new file mode 100644 index 00000000000..5078eedb427 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java @@ -0,0 +1,28 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.test.support.ResourceLoader; +import org.opentripplanner.updater.spi.HttpHeaders; + +class SiriFmUpdaterTest { + + @Test + void parse() { + var uri = ResourceLoader.of(this).uri("siri-fm.xml"); + var parameters = new SiriFmUpdaterParameters( + "noi", + uri, + "noi", + Duration.ofSeconds(30), + HttpHeaders.empty() + ); + var updater = new SiriFmUpdater(parameters); + updater.update(); + var updates = updater.getUpdates(); + + assertEquals(4, updates.size()); + } +} diff --git a/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml new file mode 100644 index 00000000000..f4595488284 --- /dev/null +++ b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml @@ -0,0 +1,56 @@ + + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + IT:ITH10:Parking:105 + + available + + + presentCount + bays + 33 + + + + IT:ITH10:Parking:TRENTO_areaexsitviacanestrinip1 + + notAvailable + + + presentCount + bays + 300 + + + + IT:ITH10:Parking:TRENTO_autosilobuonconsigliop3 + + notAvailable + + + presentCount + bays + 633 + + + + IT:ITH10:Parking:TRENTO_cteviabomportop6 + + notAvailable + + + presentCount + bays + 250 + + + + + \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index 1413df407e1..d4ccefa1845 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -56,8 +56,6 @@ public String toString() { @Override public boolean update() { - LOG.error("RUNNING {}", this); - updates = httpClient.getAndMap( params.url(), @@ -92,7 +90,7 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { c.getFacilityRef() != null && c.getFacilityRef().getValue() != null && c.getMonitoredCountings().size() == 1 && - c.getMonitoredCountings().stream().anyMatch(mc -> mc.getCountingType() == PRESENT_COUNT) + c.getMonitoredCountings().getFirst().getCountingType() == PRESENT_COUNT ); } From c228692c9518ec2d4ea4f2b1bb761393c813fd90 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 22:04:24 +0200 Subject: [PATCH 06/20] Simplify Siri updater --- .../vehicleparking/sirifm/SiriFmUpdater.java | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index d4ccefa1845..96e8e455185 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -2,14 +2,10 @@ import static uk.org.siri.siri21.CountingTypeEnumeration.PRESENT_COUNT; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.stream.Stream; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; +import org.entur.siri21.util.SiriXml; import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; @@ -20,8 +16,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.org.siri.siri21.FacilityConditionStructure; -import uk.org.siri.siri21.Siri; +/** + * Parses SIRI 2.1 XML data into parking availability updates. The data needs to conform to the + * Italian profile of SIRI-FM. + */ public class SiriFmUpdater implements DataSource { private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); @@ -30,16 +29,6 @@ public class SiriFmUpdater implements DataSource { private final Map headers; private List updates = List.of(); - private static final JAXBContext jaxbContext; - - static { - try { - jaxbContext = JAXBContext.newInstance(Siri.class); - } catch (JAXBException e) { - throw new RuntimeException(e); - } - } - public SiriFmUpdater(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); @@ -61,7 +50,7 @@ public boolean update() { params.url(), headers, resp -> { - var siri = parseXml(resp); + var siri = SiriXml.parseXml(resp); return Stream .ofNullable(siri.getServiceDelivery()) @@ -94,16 +83,6 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { ); } - private Siri parseXml(InputStream stream) { - try { - var xmlif = XMLInputFactory.newInstance(); - var jaxbUnmarshaller = jaxbContext.createUnmarshaller(); - var streamReader = xmlif.createXMLStreamReader(stream); - return (Siri) jaxbUnmarshaller.unmarshal(streamReader); - } catch (JAXBException | XMLStreamException e) { - throw new RuntimeException(e); - } - } @Override public List getUpdates() { From 64f6fc62f0fc45f462ee0af122b8713e33ce3b39 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 13:15:48 +0200 Subject: [PATCH 07/20] Add test for SIRI parking updater --- docs/sandbox/VehicleParking.md | 10 +- .../sirifm/SiriFmUpdaterTest.java | 2 +- ...riFmUpdater.java => SiriFmDatasource.java} | 7 +- .../vehicle_parking/VehicleParking.java | 14 --- .../AvailabilityDatasourceFactory.java | 4 +- .../vehicle_parking/AvailabiltyUpdate.java | 9 +- ...VehicleParkingAvailabilityUpdaterTest.java | 108 ++++++++++++++++++ 7 files changed, 127 insertions(+), 27 deletions(-) rename src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/{SiriFmUpdater.java => SiriFmDatasource.java} (94%) create mode 100644 src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index a5adde1d4c2..ae83dba2b41 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -61,7 +61,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[2] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -131,7 +131,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[3] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -216,7 +216,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[4] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -281,7 +281,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[5] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -342,7 +342,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[14] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java index 5078eedb427..07502b597d9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java @@ -19,7 +19,7 @@ void parse() { Duration.ofSeconds(30), HttpHeaders.empty() ); - var updater = new SiriFmUpdater(parameters); + var updater = new SiriFmDatasource(parameters); updater.update(); var updates = updater.getUpdates(); diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java rename to src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java index 96e8e455185..abfdf4d29be 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java @@ -21,15 +21,15 @@ * Parses SIRI 2.1 XML data into parking availability updates. The data needs to conform to the * Italian profile of SIRI-FM. */ -public class SiriFmUpdater implements DataSource { +public class SiriFmDatasource implements DataSource { - private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); + private static final Logger LOG = LoggerFactory.getLogger(SiriFmDatasource.class); private final SiriFmUpdaterParameters params; private final OtpHttpClient httpClient; private final Map headers; private List updates = List.of(); - public SiriFmUpdater(SiriFmUpdaterParameters parameters) { + public SiriFmDatasource(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); httpClient = new OtpHttpClientFactory().create(LOG); @@ -83,7 +83,6 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { ); } - @Override public List getUpdates() { return updates; diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 69f7f08eaa5..b50c583c79a 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -259,20 +259,6 @@ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; } - public void close() { - var builder = VehicleParkingSpaces.builder(); - if (hasCarPlaces()) { - builder.carSpaces(0); - } - if (hasWheelchairAccessibleCarPlaces()) { - builder.wheelchairAccessibleCarSpaces(0); - } - if (hasBicyclePlaces()) { - builder.bicycleSpaces(0); - } - updateAvailability(builder.build()); - } - @Override public int hashCode() { return Objects.hash( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java index cb62332747b..876cd303c78 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.updater.vehicle_parking; -import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdater; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmDatasource; import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; import org.opentripplanner.updater.spi.DataSource; @@ -11,7 +11,7 @@ public class AvailabilityDatasourceFactory { public static DataSource create(VehicleParkingUpdaterParameters parameters) { return switch (parameters.sourceType()) { - case SIRI_FM -> new SiriFmUpdater((SiriFmUpdaterParameters) parameters); + case SIRI_FM -> new SiriFmDatasource((SiriFmUpdaterParameters) parameters); case PARK_API, BICYCLE_PARK_API, HSL_PARK, diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java index 17d273ac237..a9d352cbaf2 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -1,5 +1,12 @@ package org.opentripplanner.updater.vehicle_parking; +import java.util.Objects; +import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.transit.model.framework.FeedScopedId; -public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) {} +public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) { + public AvailabiltyUpdate { + Objects.requireNonNull(vehicleParkingId); + IntUtils.requireNotNegative(spacesAvailable); + } +} diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java new file mode 100644 index 00000000000..60cfc3faa1c --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -0,0 +1,108 @@ +package org.opentripplanner.updater.vehicle_parking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import com.google.common.util.concurrent.Futures; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.Future; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphUpdaterManager; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.GraphUpdater; + +class VehicleParkingAvailabilityUpdaterTest { + + private static final VehicleParkingUpdaterParameters PARAMETERS = new VehicleParkingUpdaterParameters() { + @Override + public VehicleParkingSourceType sourceType() { + return VehicleParkingSourceType.SIRI_FM; + } + + @Override + public UpdateType updateType() { + return UpdateType.AVAILABILITY_ONLY; + } + + @Override + public Duration frequency() { + return Duration.ZERO; + } + + @Override + public String configRef() { + return null; + } + }; + private static final FeedScopedId ID = id("parking1"); + + @Test + void updateAvailability() { + var service = new VehicleParkingService(); + + var parking = VehicleParking + .builder() + .id(ID) + .name(I18NString.of("parking")) + .coordinate(WgsCoordinate.GREENWICH) + .carPlaces(true) + .capacity(VehicleParkingSpaces.builder().carSpaces(10).build()) + .build(); + service.updateVehicleParking(List.of(parking), List.of()); + + var updater = new VehicleParkingAvailabilityUpdater(PARAMETERS, new StubDatasource(), service); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getCarSpaces()); + assertNull(updated.getAvailability().getBicycleSpaces()); + } + + private void runUpdaterOnce(VehicleParkingAvailabilityUpdater updater) { + class GraphUpdaterMock extends GraphUpdaterManager { + + private static final Graph GRAPH = new Graph(); + private static final TransitModel TRANSIT_MODEL = new TransitModel(); + + public GraphUpdaterMock(List updaters) { + super(GRAPH, TRANSIT_MODEL, updaters); + } + + @Override + public Future execute(GraphWriterRunnable runnable) { + runnable.run(GRAPH, TRANSIT_MODEL); + return Futures.immediateVoidFuture(); + } + } + + var graphUpdaterManager = new GraphUpdaterMock(List.of(updater)); + graphUpdaterManager.startUpdaters(); + graphUpdaterManager.stop(false); + } + + private static class StubDatasource implements DataSource { + + @Override + public boolean update() { + return true; + } + + @Override + public List getUpdates() { + return List.of(new AvailabiltyUpdate(ID, 8)); + } + } +} From cbddcc4ca8a012e94dde7ed530acd07d8274f32b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 15:01:01 +0200 Subject: [PATCH 08/20] Improve test --- .../VehicleParkingAvailabilityUpdater.java | 5 +- ...VehicleParkingAvailabilityUpdaterTest.java | 82 +++++++++++++++---- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 77790b1a084..86e7b72c9ff 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -51,10 +51,7 @@ public void setup(WriteToGraphCallback writeToGraphCallback) { @Override protected void runPolling() { - LOG.debug("Updating parking availability from {}", source); - if (!source.update()) { - LOG.debug("No updates"); - } else { + if (source.update()) { var updates = source.getUpdates(); var graphWriterRunnable = new UpdateAvailabilities(updates); diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 60cfc3faa1c..713b6d01664 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -46,22 +46,16 @@ public String configRef() { } }; private static final FeedScopedId ID = id("parking1"); + private static final AvailabiltyUpdate DEFAULT_UPDATE = new AvailabiltyUpdate(ID, 8); @Test - void updateAvailability() { - var service = new VehicleParkingService(); - - var parking = VehicleParking - .builder() - .id(ID) - .name(I18NString.of("parking")) - .coordinate(WgsCoordinate.GREENWICH) - .carPlaces(true) - .capacity(VehicleParkingSpaces.builder().carSpaces(10).build()) - .build(); - service.updateVehicleParking(List.of(parking), List.of()); - - var updater = new VehicleParkingAvailabilityUpdater(PARAMETERS, new StubDatasource(), service); + void updateCarAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().carSpaces(10).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); runUpdaterOnce(updater); @@ -71,6 +65,58 @@ void updateAvailability() { assertNull(updated.getAvailability().getBicycleSpaces()); } + @Test + void updateBicycleAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getBicycleSpaces()); + assertNull(updated.getAvailability().getCarSpaces()); + } + @Test + void notFound() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(new AvailabiltyUpdate(id("not-found"), 100)), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertNull(updated.getAvailability()); + } + + private static VehicleParkingService buildParkingService(VehicleParkingSpaces capacity) { + var service = new VehicleParkingService(); + + var parking = parkingBuilder() + .carPlaces(capacity.getCarSpaces() != null) + .bicyclePlaces(capacity.getBicycleSpaces() != null) + .capacity(capacity) + .build(); + service.updateVehicleParking(List.of(parking), List.of()); + return service; + } + + private static VehicleParking.VehicleParkingBuilder parkingBuilder() { + return VehicleParking + .builder() + .id(ID) + .name(I18NString.of("parking")) + .coordinate(WgsCoordinate.GREENWICH); + } + private void runUpdaterOnce(VehicleParkingAvailabilityUpdater updater) { class GraphUpdaterMock extends GraphUpdaterManager { @@ -95,6 +141,12 @@ public Future execute(GraphWriterRunnable runnable) { private static class StubDatasource implements DataSource { + private final AvailabiltyUpdate update; + + private StubDatasource(AvailabiltyUpdate update) { + this.update = update; + } + @Override public boolean update() { return true; @@ -102,7 +154,7 @@ public boolean update() { @Override public List getUpdates() { - return List.of(new AvailabiltyUpdate(ID, 8)); + return List.of(update); } } } From b70dc7473fcb9c3d44a1fca6d5d42b381b5f7bd2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 15:10:54 +0200 Subject: [PATCH 09/20] Add documentation --- doc-templates/VehicleParking.md | 9 ++ docs/RouterConfiguration.md | 6 ++ docs/sandbox/VehicleParking.md | 89 +++++++++++++++++-- .../updaters/VehicleParkingUpdaterConfig.java | 16 +++- ...VehicleParkingAvailabilityUpdaterTest.java | 1 + .../standalone/config/router-config.json | 7 ++ 6 files changed, 121 insertions(+), 7 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 5d149e40f9a..507894b97e8 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -48,6 +48,15 @@ All updaters have the following parameters in common: +## SIRI-FM + +The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index a3042e7b91f..0f91875e542 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -875,6 +875,12 @@ Used to group requests when monitoring OTP. "feedId" : "bikeep", "sourceType" : "bikeep", "url" : "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices" : [ diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index ae83dba2b41..06f520b0972 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -55,7 +55,7 @@ All updaters have the following parameters in common: The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -125,7 +125,7 @@ Used for converting abstract opening hours into concrete points in time. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -210,7 +210,7 @@ Tags to add to the parking lots. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -275,7 +275,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -336,7 +336,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -373,6 +373,85 @@ HTTP headers to add to the request. Any header key, value can be inserted. +## SIRI-FM + +The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [feedId](#u__15__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 | +| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 | +| [sourceType](#u__15__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | +| [url](#u__15__url) | `uri` | URL of the SIRI-FM Light endpoint. | *Required* | | 2.6 | +| [headers](#u__15__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 | + + +#### Details + +

feedId

+ +**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +The id of the data source, which will be the prefix of the parking lot's id. + +This will end up in the API responses as the feed id of the parking lot. + +

sourceType

+ +**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` + +The source of the vehicle updates. + +

url

+ +**Since version:** `2.6` ∙ **Type:** `uri` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +URL of the SIRI-FM Light endpoint. + +SIRI Light means that it must be available as a HTTP GET request rather than the usual +SIRI request mechanism of HTTP POST. + +The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + + +

headers

+ +**Since version:** `2.6` ∙ **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" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + ] +} +``` + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index 6ef8c465bc3..60f794f2f2a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -30,7 +30,7 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .of("feedId") .since(V2_2) .summary("The id of the data source, which will be the prefix of the parking lot's id.") - .description("This will end up in the API responses as the feed id of of the parking lot.") + .description("This will end up in the API responses as the feed id of the parking lot.") .asString(); return switch (sourceType) { case HSL_PARK -> new HslParkUpdaterParameters( @@ -103,7 +103,19 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap ); case SIRI_FM -> new SiriFmUpdaterParameters( updaterRef, - c.of("url").since(V2_6).summary("URL of the SIRI-FM Light endpoint.").asUri(), + c + .of("url") + .since(V2_6) + .summary("URL of the SIRI-FM Light endpoint.") + .description( + """ + SIRI Light means that it must be available as a HTTP GET request rather than the usual + SIRI request mechanism of HTTP POST. + + The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + """ + ) + .asUri(), feedId, c .of("frequency") diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 713b6d01664..0af58378f90 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -81,6 +81,7 @@ void updateBicycleAvailability() { assertEquals(8, updated.getAvailability().getBicycleSpaces()); assertNull(updated.getAvailability().getCarSpaces()); } + @Test void notFound() { var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 3a3ef9b4cf0..c526de423c1 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -438,6 +438,13 @@ "feedId": "bikeep", "sourceType": "bikeep", "url": "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + // SIRI-FM vehicle parking updater + { + "type": "vehicle-parking", + "feedId": "parking", + "sourceType": "siri-fm", + "url": "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices": [ From 7363899c074dfc8bebb03caf32e166414169e073 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 2 Aug 2024 10:23:15 +0200 Subject: [PATCH 10/20] Mention SIRI 2.1 --- doc-templates/VehicleParking.md | 3 ++- docs/sandbox/VehicleParking.md | 6 ++++-- .../routerconfig/updaters/VehicleParkingUpdaterConfig.java | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 507894b97e8..9efb6b66522 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -53,7 +53,8 @@ All updaters have the following parameters in common: The SIRI-FM updaters works slighly differently from the other in that it only updates the availability of parking but does not create new lots in realtime. -The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +requires SIRI 2.1. diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 06f520b0972..45a2dc28ea8 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -378,7 +378,8 @@ HTTP headers to add to the request. Any header key, value can be inserted. The SIRI-FM updaters works slighly differently from the other in that it only updates the availability of parking but does not create new lots in realtime. -The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +requires SIRI 2.1. @@ -422,7 +423,8 @@ URL of the SIRI-FM Light endpoint. SIRI Light means that it must be available as a HTTP GET request rather than the usual SIRI request mechanism of HTTP POST. -The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). +The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) +which requires SIRI 2.1.

headers

diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index 60f794f2f2a..88b47aac4eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -112,7 +112,8 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap SIRI Light means that it must be available as a HTTP GET request rather than the usual SIRI request mechanism of HTTP POST. - The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) + which requires SIRI 2.1. """ ) .asUri(), From 3a76485ba656f3c6e1547c75504c8a2be93894b4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 2 Aug 2024 20:49:32 +0200 Subject: [PATCH 11/20] Update doc-templates/VehicleParking.md Co-authored-by: Joel Lappalainen --- doc-templates/VehicleParking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 9efb6b66522..a8a46d7875d 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -50,7 +50,7 @@ All updaters have the following parameters in common: ## SIRI-FM -The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +The SIRI-FM updaters works slighly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile From 1ba10c05df1d030adc7cba3c5566aa58b893ebd4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 14:13:04 +0200 Subject: [PATCH 12/20] Compile docs --- docs/sandbox/VehicleParking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 45a2dc28ea8..9ca4dcc4ee3 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -375,7 +375,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ## SIRI-FM -The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +The SIRI-FM updaters works slighly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile From a2214eb5882b494c0754f097ff7694f619fbf879 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 14:18:01 +0200 Subject: [PATCH 13/20] Rename class --- .../vehicle_parking/VehicleParkingAvailabilityUpdater.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 86e7b72c9ff..2a5de121893 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -54,17 +54,17 @@ protected void runPolling() { if (source.update()) { var updates = source.getUpdates(); - var graphWriterRunnable = new UpdateAvailabilities(updates); + var graphWriterRunnable = new AvailabilityUpdater(updates); saveResultOnGraph.execute(graphWriterRunnable); } } - private class UpdateAvailabilities implements GraphWriterRunnable { + private class AvailabilityUpdater implements GraphWriterRunnable { private final List updates; private final Map parkingById; - private UpdateAvailabilities(List updates) { + private AvailabilityUpdater(List updates) { this.updates = List.copyOf(updates); this.parkingById = vehicleParkingService From 1837b79b2390d4a44f4c536c1aa19d11a048c5e8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 16:58:20 +0200 Subject: [PATCH 14/20] Apply review suggestions --- doc-templates/VehicleParking.md | 2 +- docs/sandbox/VehicleParking.md | 2 +- .../configure/UpdaterConfigurator.java | 4 --- ...VehicleParkingAvailabilityUpdaterTest.java | 36 ++++++++----------- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index a8a46d7875d..e3fcf84dc6e 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -50,7 +50,7 @@ All updaters have the following parameters in common: ## SIRI-FM -The SIRI-FM updaters works slighly differently from the others in that it only updates the availability +The SIRI-FM updater works slightly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 9ca4dcc4ee3..926a12b88a5 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -375,7 +375,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ## SIRI-FM -The SIRI-FM updaters works slighly differently from the others in that it only updates the availability +The SIRI-FM updater works slightly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 6662bae362b..83e0bd0fe85 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -32,8 +32,6 @@ import org.opentripplanner.updater.vehicle_position.PollingVehiclePositionUpdater; import org.opentripplanner.updater.vehicle_rental.VehicleRentalUpdater; import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDataSourceFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Sets up and starts all the graph updaters. @@ -44,8 +42,6 @@ */ public class UpdaterConfigurator { - private static final Logger LOG = LoggerFactory.getLogger(UpdaterConfigurator.class); - private final Graph graph; private final TransitModel transitModel; private final UpdatersParameters updatersParameters; diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 0af58378f90..01fdb5425ee 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -2,10 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import com.google.common.util.concurrent.Futures; -import java.time.Duration; import java.util.List; import java.util.concurrent.Future; import org.junit.jupiter.api.Test; @@ -15,6 +15,7 @@ import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehicleParkingUpdaterConfig; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.GraphUpdaterManager; @@ -24,27 +25,20 @@ class VehicleParkingAvailabilityUpdaterTest { - private static final VehicleParkingUpdaterParameters PARAMETERS = new VehicleParkingUpdaterParameters() { - @Override - public VehicleParkingSourceType sourceType() { - return VehicleParkingSourceType.SIRI_FM; - } - - @Override - public UpdateType updateType() { - return UpdateType.AVAILABILITY_ONLY; - } - - @Override - public Duration frequency() { - return Duration.ZERO; - } + private static final VehicleParkingUpdaterParameters PARAMETERS = VehicleParkingUpdaterConfig.create( + "ref", + newNodeAdapterForTest( + """ + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + """ + ) + ); - @Override - public String configRef() { - return null; - } - }; private static final FeedScopedId ID = id("parking1"); private static final AvailabiltyUpdate DEFAULT_UPDATE = new AvailabiltyUpdate(ID, 8); From f2ba2c8b951a25c7eddb0b1ed889a2fd772c23b9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 17:23:30 +0200 Subject: [PATCH 15/20] Lower frequency --- .../vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 01fdb5425ee..87222eeba8f 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -33,6 +33,7 @@ class VehicleParkingAvailabilityUpdaterTest { "type" : "vehicle-parking", "feedId" : "parking", "sourceType" : "siri-fm", + "frequency": "0s", "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } """ From e4199f1225f80fd399491318a4889cab7d4bed39 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 12:14:51 +0200 Subject: [PATCH 16/20] Apply review feedback --- .../routing/vehicle_parking/VehicleParking.java | 8 ++++++-- .../VehicleParkingAvailabilityUpdater.java | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index b50c583c79a..9cf198daa68 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -97,7 +97,7 @@ public class VehicleParking implements Serializable { /** * The currently available spaces at this vehicle parking. */ - private VehicleParkingSpaces availability; + private volatile VehicleParkingSpaces availability; /** * The vehicle parking group this parking belongs to. */ @@ -254,9 +254,13 @@ public boolean hasRealTimeDataForMode( /** * The only mutable method in this class: it allows to update the available parking spaces during * real-time updates. + * Since the entity is used both by writer threads (real-time updates) and reader threads + * (A* routing), the update process is synchronized. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { - this.availability = vehicleParkingSpaces; + synchronized (this) { + this.availability = vehicleParkingSpaces; + } } @Override diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 2a5de121893..31074aafe38 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -79,7 +79,7 @@ public void run(Graph graph, TransitModel ignored) { private void handleUpdate(AvailabiltyUpdate update) { if (!parkingById.containsKey(update.vehicleParkingId())) { - LOG.error( + LOG.warn( "Parking with id {} does not exist. Skipping availability update.", update.vehicleParkingId() ); From 2e71dd62db8d5d88578fd26e125610f481ebb702 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 12:50:20 +0200 Subject: [PATCH 17/20] Fix spelling --- doc-templates/VehicleParking.md | 2 +- docs/sandbox/VehicleParking.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index e3fcf84dc6e..721cbc2657a 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -54,7 +54,7 @@ The SIRI-FM updater works slightly differently from the others in that it only u of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile -requires SIRI 2.1. +which requires SIRI 2.1. diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 926a12b88a5..db057bd9dbd 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -379,7 +379,7 @@ The SIRI-FM updater works slightly differently from the others in that it only u of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile -requires SIRI 2.1. +which requires SIRI 2.1. From 81985cddc98f06abfdad7a72e08290570a9559b6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 15:34:24 +0200 Subject: [PATCH 18/20] Don't synchronize anymore --- .../routing/vehicle_parking/VehicleParking.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 9cf198daa68..7fc81f4d395 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -258,9 +258,7 @@ public boolean hasRealTimeDataForMode( * (A* routing), the update process is synchronized. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { - synchronized (this) { - this.availability = vehicleParkingSpaces; - } + this.availability = vehicleParkingSpaces; } @Override From 93205da889567b4207994507fa829b753880f24d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 15:46:59 +0200 Subject: [PATCH 19/20] Use volatile in parking service and document its use --- .../routing/vehicle_parking/VehicleParking.java | 2 ++ .../routing/vehicle_parking/VehicleParkingService.java | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 7fc81f4d395..e64dc92c890 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -96,6 +96,8 @@ public class VehicleParking implements Serializable { private final List entrances = new ArrayList<>(); /** * The currently available spaces at this vehicle parking. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ private volatile VehicleParkingSpaces availability; /** diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java index 639066871be..b0c08a2309b 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java @@ -21,13 +21,17 @@ public class VehicleParkingService implements Serializable { /** * To ensure that his is thread-safe, the set stored here should always be immutable. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private Set vehicleParkings = Set.of(); + private volatile Set vehicleParkings = Set.of(); /** * To ensure that his is thread-safe, {@link ImmutableListMultimap} is used. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); + private volatile ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); /** * Does atomic update of {@link VehicleParking} and index of {@link VehicleParkingGroup} in this From 1935ba0ff4e39a33010dc3788c13827d00897a9b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 17:24:10 +0200 Subject: [PATCH 20/20] Update docs --- .../opentripplanner/routing/vehicle_parking/VehicleParking.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index e64dc92c890..098af909296 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -257,7 +257,7 @@ public boolean hasRealTimeDataForMode( * The only mutable method in this class: it allows to update the available parking spaces during * real-time updates. * Since the entity is used both by writer threads (real-time updates) and reader threads - * (A* routing), the update process is synchronized. + * (A* routing), the variable holding the information is marked as volatile. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces;