From cb4a34e40c8111182793de386c1edb500a558d64 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 2 Feb 2024 18:16:23 +0200 Subject: [PATCH 001/108] Replace determine max car speed during graph build and use it for heuristics --- docs/RouteRequest.md | 2 - docs/RouterConfiguration.md | 1 - docs/examples/entur/router-config.json | 1 - .../model/DefaultRouteRequestType.java | 3 +- .../graph_builder/GraphBuilder.java | 3 + .../module/configure/GraphBuilderFactory.java | 5 ++ .../module/configure/GraphBuilderModules.java | 8 +- .../graph_builder/module/osm/OsmModule.java | 19 +++++ .../module/osm/OsmModuleBuilder.java | 8 ++ .../openstreetmap/OsmProvider.java | 14 +++- .../tagmapping/AtlantaMapper.java | 3 + .../tagmapping/ConstantSpeedMapper.java | 2 + .../tagmapping/DefaultMapper.java | 4 +- .../tagmapping/FinlandMapper.java | 8 +- .../tagmapping/GermanyMapper.java | 1 + .../tagmapping/HoustonMapper.java | 4 + .../tagmapping/NorwayMapper.java | 13 ++-- .../tagmapping/PortlandMapper.java | 4 + .../openstreetmap/tagmapping/UKMapper.java | 2 + .../wayproperty/WayPropertySet.java | 76 ++++++++++++++----- .../router/street/DirectStreetRouter.java | 17 +++-- .../request/preference/CarPreferences.java | 22 ------ .../preference/RoutingPreferences.java | 1 - .../routing/graph/SerializedGraphObject.java | 6 +- .../routing/impl/GraphPathFinder.java | 10 ++- .../opentripplanner/standalone/OTPMain.java | 3 +- .../api/OtpServerRequestContext.java | 3 + .../routerequest/RouteRequestConfig.java | 7 -- .../configure/ConstructApplication.java | 10 ++- .../ConstructApplicationFactory.java | 6 ++ .../configure/ConstructApplicationModule.java | 3 + .../standalone/configure/LoadApplication.java | 13 +++- .../configure/LoadApplicationFactory.java | 4 + .../server/DefaultServerRequestContext.java | 11 +++ .../model/StreetLimitationParameters.java | 35 +++++++++ .../EuclideanRemainingWeightHeuristic.java | 13 +++- .../apis/transmodel/schema.graphql | 2 +- .../opentripplanner/ConstantsForTests.java | 7 +- .../opentripplanner/TestServerContext.java | 2 + .../mapping/TripRequestMapperTest.java | 2 + .../module/osm/OsmModuleTest.java | 7 +- .../module/osm/TriangleInequalityTest.java | 2 +- .../tagmapping/DefaultMapperTest.java | 2 +- .../routing/algorithm/TurnCostTest.java | 2 +- .../preference/CarPreferencesTest.java | 13 +--- .../routing/graph/GraphSerializationTest.java | 6 +- .../street/model/TurnRestrictionTest.java | 5 +- .../street/model/edge/StreetEdgeTest.java | 2 +- .../transit/speed_test/SpeedTest.java | 2 + .../standalone/config/router-config.json | 1 - 50 files changed, 289 insertions(+), 111 deletions(-) create mode 100644 src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 199eda8a332..19d7e49124c 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -90,7 +90,6 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    pickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | |    pickupTime | `duration` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `"PT1M"` | 2.1 | |    reluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -|    speed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | |    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | |       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | |       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | @@ -1079,7 +1078,6 @@ include stairs as a last result. } }, "car" : { - "speed" : 40, "reluctance" : 10, "decelerationSpeed" : 2.9, "accelerationSpeed" : 2.9, diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 62fb5d9617a..71c34c1b31a 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -482,7 +482,6 @@ Used to group requests when monitoring OTP. } }, "car" : { - "speed" : 40, "reluctance" : 10, "decelerationSpeed" : 2.9, "accelerationSpeed" : 2.9, diff --git a/docs/examples/entur/router-config.json b/docs/examples/entur/router-config.json index ffdec9ded79..a4091a8918e 100644 --- a/docs/examples/entur/router-config.json +++ b/docs/examples/entur/router-config.json @@ -26,7 +26,6 @@ } }, "car": { - "speed": 40, "reluctance": 4.0, "decelerationSpeed": 2.9, "accelerationSpeed": 2.9, diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java index 80b6418c314..ea1600864db 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/DefaultRouteRequestType.java @@ -44,9 +44,10 @@ private GraphQLObjectType createGraphQLType() { GraphQLFieldDefinition .newFieldDefinition() .name("carSpeed") + .deprecate("This parameter is no longer configurable.") .description("Max car speed along streets, in meters per second") .type(Scalars.GraphQLFloat) - .dataFetcher(env -> preferences.car().speed()) + .dataFetcher(env -> 0) .build() ) .field( diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index 8b2e55ffc2f..c036c113e26 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -23,6 +23,7 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +66,7 @@ public static GraphBuilder create( WorldEnvelopeRepository worldEnvelopeRepository, @Nullable EmissionsDataModel emissionsDataModel, @Nullable StopConsolidationRepository stopConsolidationRepository, + StreetLimitationParameters streetLimitationParameters, boolean loadStreetGraph, boolean saveStreetGraph ) { @@ -82,6 +84,7 @@ public static GraphBuilder create( .transitModel(transitModel) .worldEnvelopeRepository(worldEnvelopeRepository) .stopConsolidationRepository(stopConsolidationRepository) + .streetLimitationParameters(streetLimitationParameters) .dataSources(dataSources) .timeZoneId(transitModel.getTimeZone()); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java index 9fd99d35ea0..9c778481d4d 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java @@ -31,6 +31,7 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitModel; @Singleton @@ -55,6 +56,7 @@ public interface GraphBuilderFactory { EdgeUpdaterModule dataOverlayFactory(); DataImportIssueReporter dataImportIssueReporter(); CalculateWorldEnvelopeModule calculateWorldEnvelopeModule(); + StreetLimitationParameters streetLimitationParameters(); @Nullable StopConsolidationModule stopConsolidationModule(); @@ -81,6 +83,9 @@ Builder stopConsolidationRepository( @Nullable StopConsolidationRepository stopConsolidationRepository ); + @BindsInstance + Builder streetLimitationParameters(StreetLimitationParameters streetLimitationParameters); + @BindsInstance Builder dataSources(GraphBuilderDataSources graphBuilderDataSources); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 444adb5b727..6f458bd8ca1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -43,6 +43,7 @@ import org.opentripplanner.routing.api.request.preference.WalkPreferences; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.standalone.config.BuildConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitModel; /** @@ -57,7 +58,8 @@ static OsmModule provideOpenStreetMapModule( GraphBuilderDataSources dataSources, BuildConfig config, Graph graph, - DataImportIssueStore issueStore + DataImportIssueStore issueStore, + StreetLimitationParameters streetLimitationParameters ) { List providers = new ArrayList<>(); for (ConfiguredDataSource osmConfiguredDataSource : dataSources.getOsmConfiguredDatasource()) { @@ -66,7 +68,8 @@ static OsmModule provideOpenStreetMapModule( osmConfiguredDataSource.dataSource(), osmConfiguredDataSource.config().osmTagMapper(), osmConfiguredDataSource.config().timeZone(), - config.osmCacheDataInMem + config.osmCacheDataInMem, + issueStore ) ); } @@ -81,6 +84,7 @@ static OsmModule provideOpenStreetMapModule( .withMaxAreaNodes(config.maxAreaNodes) .withBoardingAreaRefTags(config.boardingLocationTags) .withIssueStore(issueStore) + .withStreetLimitationParameters(streetLimitationParameters) .build(); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 9cc15d191fc..44133624ac1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -27,6 +28,7 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.util.ElevationUtils; import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.edge.StreetEdgeBuilder; @@ -55,11 +57,13 @@ public class OsmModule implements GraphBuilderModule { private final SafetyValueNormalizer normalizer; private final VertexGenerator vertexGenerator; private final OsmDatabase osmdb; + private final StreetLimitationParameters streetLimitationParameters; OsmModule( Collection providers, Graph graph, DataImportIssueStore issueStore, + @Nullable StreetLimitationParameters streetLimitationParameters, OsmProcessingParameters params ) { this.providers = List.copyOf(providers); @@ -69,6 +73,7 @@ public class OsmModule implements GraphBuilderModule { this.osmdb = new OsmDatabase(issueStore); this.vertexGenerator = new VertexGenerator(osmdb, graph, params.boardingAreaRefTags()); this.normalizer = new SafetyValueNormalizer(graph, issueStore); + this.streetLimitationParameters = streetLimitationParameters; } public static OsmModuleBuilder of(Collection providers, Graph graph) { @@ -94,6 +99,9 @@ public void buildGraph() { LOG.info("Building street graph from OSM"); build(); graph.hasStreets = true; + if (streetLimitationParameters != null) { + streetLimitationParameters.initMaxCarSpeed(getMaxCarSpeed()); + } } @Override @@ -551,4 +559,15 @@ private StreetEdge getEdgeForStreet( return street; } + + private float getMaxCarSpeed() { + float maxSpeed = 0f; + for (OsmProvider provider : providers) { + var wps = provider.getWayPropertySet(); + if (wps.maxUsedCarSpeed > maxSpeed) { + maxSpeed = wps.maxUsedCarSpeed; + } + } + return maxSpeed; + } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index a99752ca2b9..144399d81f6 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -8,6 +8,7 @@ import org.opentripplanner.graph_builder.services.osm.EdgeNamer; import org.opentripplanner.openstreetmap.OsmProvider; import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.street.model.StreetLimitationParameters; /** * Builder for the {@link OsmModule} @@ -24,6 +25,7 @@ public class OsmModuleBuilder { private boolean staticParkAndRide = false; private boolean staticBikeParkAndRide = false; private int maxAreaNodes; + private StreetLimitationParameters streetLimitationParameters; OsmModuleBuilder(Collection providers, Graph graph) { this.providers = providers; @@ -70,11 +72,17 @@ public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) { return this; } + public OsmModuleBuilder withStreetLimitationParameters(StreetLimitationParameters parameters) { + this.streetLimitationParameters = parameters; + return this; + } + public OsmModule build() { return new OsmModule( providers, graph, issueStore, + streetLimitationParameters, new OsmProcessingParameters( boardingAreaRefTags, edgeNamer, diff --git a/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java b/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java index cfe6713dd74..e35a846cbbd 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java +++ b/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java @@ -12,6 +12,7 @@ import org.opentripplanner.framework.application.OtpFileNames; import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.OsmDatabase; import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapper; import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapperSource; @@ -41,19 +42,26 @@ public class OsmProvider { /** For tests */ public OsmProvider(File file, boolean cacheDataInMem) { - this(new FileDataSource(file, FileType.OSM), OsmTagMapperSource.DEFAULT, null, cacheDataInMem); + this( + new FileDataSource(file, FileType.OSM), + OsmTagMapperSource.DEFAULT, + null, + cacheDataInMem, + DataImportIssueStore.NOOP + ); } public OsmProvider( DataSource dataSource, OsmTagMapperSource tagMapperSource, ZoneId zoneId, - boolean cacheDataInMem + boolean cacheDataInMem, + DataImportIssueStore issueStore ) { this.source = dataSource; this.zoneId = zoneId; this.osmTagMapper = tagMapperSource.getInstance(); - this.wayPropertySet = new WayPropertySet(); + this.wayPropertySet = new WayPropertySet(issueStore); osmTagMapper.populateProperties(wayPropertySet); this.cacheDataInMem = cacheDataInMem; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/AtlantaMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/AtlantaMapper.java index b7e60e0f008..f9539188904 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/AtlantaMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/AtlantaMapper.java @@ -24,6 +24,9 @@ public void populateProperties(WayPropertySet props) { props.setProperties("highway=trunk_link", withModes(ALL).bicycleSafety(2.5)); props.setProperties("highway=trunk", withModes(ALL).bicycleSafety(2.5)); + // Max speed limit in Georgia is 70 mph ~= 113kmh ~= 31.3m/s + props.maxPossibleCarSpeed = 31.4f; + // Read the rest from the default set new DefaultMapper().populateProperties(props); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java index 58cff2e9a3d..48a3c2ac9bf 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java @@ -31,6 +31,8 @@ public ConstantSpeedFinlandMapper(float speed) { @Override public void populateProperties(WayPropertySet props) { props.setCarSpeed("highway=*", speed); + props.maxPossibleCarSpeed = 22.22f; + props.maxUsedCarSpeed = 22.22f; // Read the rest from the default set new FinlandMapper().populateProperties(props); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java index 01cf213b03b..1463313203d 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java @@ -547,7 +547,9 @@ public void populateProperties(WayPropertySet props) { props.setCarSpeed("highway=road", 11.2f); // ~= 25 mph // default ~= 25 mph - props.defaultSpeed = 11.2f; + props.defaultCarSpeed = 11.2f; + // 38 m/s ~= 85 mph ~= 137 kph + props.maxPossibleCarSpeed = 38f; /* special situations */ diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java index 253bb385aa3..9bafe133f2a 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java @@ -163,9 +163,11 @@ else if (speedLimit <= 16.65f) { props.setProperties("highway=service;tunnel=yes;access=destination", withModes(NONE)); props.setProperties("highway=service;access=destination", withModes(ALL).bicycleSafety(1.1)); - /* - * Automobile speeds in Finland. General speed limit is 80kph unless signs says otherwise. - */ + // Automobile speeds in Finland. + // General speed limit is 80kph unless signs says otherwise. + props.defaultCarSpeed = 22.22f; + // 120kph is the max speed limit in Finland + props.maxPossibleCarSpeed = 33.34f; // = 100kph. Varies between 80 - 120 kph depending on road and season. props.setCarSpeed("highway=motorway", 27.77f); // = 54kph diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapper.java index c3d4c7fec8a..908d9838f53 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapper.java @@ -26,6 +26,7 @@ public void populateProperties(WayPropertySet props) { // Automobile speeds in Germany. General speed limit is 50kph in settlements, 100kph outside settlements. // For motorways, there (currently still) is no limit. Nevertheless 120kph is assumed to reflect varying // traffic conditions. + props.maxPossibleCarSpeed = 33.34f; props.setCarSpeed("highway=motorway", 33.33f); // = 120kph. Varies between 80 - 120 kph depending on road and season. props.setCarSpeed("highway=motorway_link", 15); // = 54kph props.setCarSpeed("highway=trunk", 27.27f); // 100kph diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HoustonMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HoustonMapper.java index e1e7aaf83df..7fdde133465 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HoustonMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HoustonMapper.java @@ -26,6 +26,10 @@ public void populateProperties(WayPropertySet props) { new ExactMatchSpecifier("highway=footway;layer=-1;tunnel=yes;indoor=yes"), withModes(NONE) ); + + // Max speed limit in Texas is 38 m/s ~= 85 mph ~= 137 kph + props.maxPossibleCarSpeed = 38f; + // Read the rest from the default set new DefaultMapper().populateProperties(props); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java index d2cd2f3cf96..9de824a145b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java @@ -608,17 +608,18 @@ else if (speedLimit >= 11.1f) { 13.89f // 50 km/t ); - props.setCarSpeed("highway=residential", 13.89f); // 50 km/t - props.setCarSpeed("highway=service", 13.89f); // 50 km/t + props.setCarSpeed("highway=residential", 13.89f); // 50 km/h + props.setCarSpeed("highway=service", 13.89f); // 50 km/h - props.setCarSpeed("highway=service;service=driveway", 8.33f); // 30 km/t + props.setCarSpeed("highway=service;service=driveway", 8.33f); // 30 km/h props.setCarSpeed("highway=service;service=parking_aisle", 8.33f); props.setCarSpeed("highway=track", 8.33f); - props.setCarSpeed("highway=living_street", 1.94f); // 7 km/t - props.setCarSpeed("highway=pedestrian", 1.94f); // 7 km/t + props.setCarSpeed("highway=living_street", 1.94f); // 7 km/h + props.setCarSpeed("highway=pedestrian", 1.94f); // 7 km/h - props.defaultSpeed = 22.22f; // 80 km/t + props.defaultCarSpeed = 22.22f; // 80 km/h + props.maxPossibleCarSpeed = 30.56f; // 110 km/h new DefaultMapper().populateNotesAndNames(props); diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java index 29b17921540..a532d279ba4 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapper.java @@ -40,6 +40,10 @@ public void populateProperties(WayPropertySet props) { props.setMixinProperties(exact("sidewalk=no;maxspeed=40 mph"), ofWalkSafety(3)); props.setMixinProperties(exact("sidewalk=no;maxspeed=35 mph"), ofWalkSafety(2)); props.setMixinProperties(exact("sidewalk=no;maxspeed=30 mph"), ofWalkSafety(1.5)); + + // Max speed limit in Oregon is 70 mph ~= 113kmh ~= 31.3m/s + props.maxPossibleCarSpeed = 31.4f; + // Read the rest from the default set new DefaultMapper().populateProperties(props); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/UKMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/UKMapper.java index cd809197559..7639c6594b2 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/UKMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/UKMapper.java @@ -61,6 +61,8 @@ public void populateProperties(WayPropertySet props) { * my (marcusyoung) personal experience in obtaining realistic routes. * */ + // Max speed limit is 70 mph ~113kmh ~31.3m/s + props.maxPossibleCarSpeed = 31.4f; props.setCarSpeed("highway=motorway", 30.4f); // ~=68mph props.setCarSpeed("highway=motorway_link", 22.4f); // ~= 50mph props.setCarSpeed("highway=trunk", 22.4f); // ~=50mph diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index 376614b1093..e867654a104 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -14,6 +14,7 @@ import java.util.regex.Pattern; import org.opentripplanner.framework.functional.FunctionUtils.TriFunction; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.BestMatchSpecifier; import org.opentripplanner.openstreetmap.wayproperty.specifier.OsmSpecifier; @@ -51,13 +52,21 @@ public class WayPropertySet { private final List notes; private final Pattern maxSpeedPattern; /** The automobile speed for street segments that do not match any SpeedPicker. */ - public Float defaultSpeed; + public Float defaultCarSpeed; + /** The maximum automobile speed that is possible. */ + public Float maxPossibleCarSpeed; + /** + * The maximum automobile speed that has been used. This can be used in heuristics later on to + * determine the minimum travel time. + */ + public float maxUsedCarSpeed = 0f; /** Resolves walk safety value for each {@link StreetTraversalPermission}. */ private TriFunction defaultWalkSafetyForPermission; /** Resolves bicycle safety value for each {@link StreetTraversalPermission}. */ private TriFunction defaultBicycleSafetyForPermission; /** The WayProperties applied to all ways that do not match any WayPropertyPicker. */ private final WayProperties defaultProperties; + private final DataImportIssueStore issueStore; public List getMixins() { return mixins; @@ -66,8 +75,15 @@ public List getMixins() { private final List mixins = new ArrayList<>(); public WayPropertySet() { + this(DataImportIssueStore.NOOP); + } + + public WayPropertySet(DataImportIssueStore issueStore) { /* sensible defaults */ - defaultSpeed = 11.2f; // 11.2 m/s ~= 25 mph ~= 40 kph, standard speed limit in the US + // 11.2 m/s ~= 25 mph ~= 40 kph, standard speed limit in the US + defaultCarSpeed = 11.2f; + // 38 m/s ~= 85 mph ~= 137 kph, max speed limit in the US + maxPossibleCarSpeed = 38f; defaultProperties = withModes(ALL).build(); wayProperties = new ArrayList<>(); creativeNamers = new ArrayList<>(); @@ -79,6 +95,7 @@ public WayPropertySet() { maxSpeedPattern = Pattern.compile("^([0-9][.0-9]*)\\s*(kmh|km/h|kmph|kph|mph|knots)?$"); defaultWalkSafetyForPermission = DEFAULT_SAFETY_RESOLVER; defaultBicycleSafetyForPermission = DEFAULT_SAFETY_RESOLVER; + this.issueStore = issueStore; } /** @@ -193,37 +210,53 @@ public float getCarSpeedForWay(OSMWithTags way, boolean backward) { Float speed = null; Float currentSpeed; - if (way.hasTag("maxspeed:motorcar")) speed = - getMetersSecondFromSpeed(way.getTag("maxspeed:motorcar")); + if (way.hasTag("maxspeed:motorcar")) { + speed = getMetersSecondFromSpeed(way.getTag("maxspeed:motorcar")); + } - if (speed == null && !backward && way.hasTag("maxspeed:forward")) speed = - getMetersSecondFromSpeed(way.getTag("maxspeed:forward")); + if (speed == null && !backward && way.hasTag("maxspeed:forward")) { + speed = getMetersSecondFromSpeed(way.getTag("maxspeed:forward")); + } - if (speed == null && backward && way.hasTag("maxspeed:backward")) speed = - getMetersSecondFromSpeed(way.getTag("maxspeed:backward")); + if (speed == null && backward && way.hasTag("maxspeed:backward")) { + speed = getMetersSecondFromSpeed(way.getTag("maxspeed:backward")); + } if (speed == null && way.hasTag("maxspeed:lanes")) { for (String lane : way.getTag("maxspeed:lanes").split("\\|")) { currentSpeed = getMetersSecondFromSpeed(lane); // Pick the largest speed from the tag // currentSpeed might be null if it was invalid, for instance 10|fast|20 - if (currentSpeed != null && (speed == null || currentSpeed > speed)) speed = currentSpeed; + if (currentSpeed != null && (speed == null || currentSpeed > speed)) { + speed = currentSpeed; + } } } if (way.hasTag("maxspeed") && speed == null) speed = getMetersSecondFromSpeed(way.getTag("maxspeed")); - // this would be bad, as the segment could never be traversed by an automobile - // The small epsilon is to account for possible rounding errors - if (speed != null && speed < 0.0001) LOG.warn( - "Zero or negative automobile speed detected at {} based on OSM " + - "maxspeed tags; ignoring these tags", - this - ); - - // if there was a defined speed and it's not 0, we're done - if (speed != null && speed > 0.0001) return speed; + if (speed != null) { + // Too low or too high speed limit indicates an error in the data, we use default speed + // limits for the way type in that case. + // The small epsilon is to account for possible rounding errors. + if (speed < 0.0001 || speed > maxPossibleCarSpeed + 0.0001) { + var id = way.getId(); + var link = way.url(); + issueStore.add( + "InvalidCarSpeedLimit", + "OSM object with id '%s' (%s) has an invalid maxspeed value (%f), that speed will be ignored", + id, + link, + speed + ); + } else { + if (speed > maxUsedCarSpeed) { + maxUsedCarSpeed = speed; + } + return speed; + } + } // otherwise, we use the speedPickers @@ -243,9 +276,12 @@ public float getCarSpeedForWay(OSMWithTags way, boolean backward) { } if (bestSpeed != null) { + if (bestSpeed > maxUsedCarSpeed) { + maxUsedCarSpeed = bestSpeed; + } return bestSpeed; } else { - return this.defaultSpeed; + return this.defaultCarSpeed; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java index 2c6b09c2fb2..e2ce8c113c1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java @@ -35,14 +35,18 @@ public static List route(OtpServerRequestContext serverContext, Route request.journey().direct().mode() ) ) { - if (!straightLineDistanceIsWithinLimit(directRequest, temporaryVertices)) { + var maxCarSpeed = serverContext.streetLimitationParameters().maxCarSpeed() != null + ? serverContext.streetLimitationParameters().maxCarSpeed() + : 40f; + if (!straightLineDistanceIsWithinLimit(directRequest, temporaryVertices, maxCarSpeed)) { return Collections.emptyList(); } // we could also get a persistent router-scoped GraphPathFinder but there's no setup cost here GraphPathFinder gpFinder = new GraphPathFinder( serverContext.traverseVisitor(), - serverContext.dataOverlayContext(request) + serverContext.dataOverlayContext(request), + maxCarSpeed ); List> paths = gpFinder.graphPathFinderEntryPoint( directRequest, @@ -69,7 +73,8 @@ public static List route(OtpServerRequestContext serverContext, Route private static boolean straightLineDistanceIsWithinLimit( RouteRequest request, - TemporaryVerticesContainer vertexContainer + TemporaryVerticesContainer vertexContainer, + float maxCarSpeed ) { // TODO This currently only calculates the distances between the first fromVertex // and the first toVertex @@ -77,7 +82,7 @@ private static boolean straightLineDistanceIsWithinLimit( vertexContainer.getFromVertices().iterator().next().getCoordinate(), vertexContainer.getToVertices().iterator().next().getCoordinate() ); - return distance < calculateDistanceMaxLimit(request); + return distance < calculateDistanceMaxLimit(request, maxCarSpeed); } /** @@ -85,7 +90,7 @@ private static boolean straightLineDistanceIsWithinLimit( * fastest mode available. This assumes that it is not possible to exceed the speed defined in the * RouteRequest. */ - private static double calculateDistanceMaxLimit(RouteRequest request) { + private static double calculateDistanceMaxLimit(RouteRequest request, float maxCarSpeed) { var preferences = request.preferences(); double distanceLimit; StreetMode mode = request.journey().direct().mode(); @@ -93,7 +98,7 @@ private static double calculateDistanceMaxLimit(RouteRequest request) { double durationLimit = preferences.street().maxDirectDuration().valueOf(mode).toSeconds(); if (mode.includesDriving()) { - distanceLimit = durationLimit * preferences.car().speed(); + distanceLimit = durationLimit * maxCarSpeed; } else if (mode.includesBiking()) { distanceLimit = durationLimit * preferences.bike().speed(); } else if (mode.includesWalking()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java index b2f226cf307..cc9b7ba62d8 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/CarPreferences.java @@ -22,7 +22,6 @@ public final class CarPreferences implements Serializable { public static final CarPreferences DEFAULT = new CarPreferences(); - private final double speed; private final double reluctance; private final VehicleParkingPreferences parking; private final VehicleRentalPreferences rental; @@ -33,7 +32,6 @@ public final class CarPreferences implements Serializable { /** Create a new instance with default values. */ private CarPreferences() { - this.speed = 40.0; this.reluctance = 2.0; this.parking = VehicleParkingPreferences.DEFAULT; this.rental = VehicleRentalPreferences.DEFAULT; @@ -44,7 +42,6 @@ private CarPreferences() { } private CarPreferences(Builder builder) { - this.speed = Units.speed(builder.speed); this.reluctance = Units.reluctance(builder.reluctance); this.parking = builder.parking; this.rental = builder.rental; @@ -62,15 +59,6 @@ public CarPreferences.Builder copyOf() { return new Builder(this); } - /** - * Max car speed along streets, in meters per second. - *

- * Default: 40 m/s, 144 km/h, above the maximum (finite) driving speed limit worldwide. - */ - public double speed() { - return speed; - } - public double reluctance() { return reluctance; } @@ -117,7 +105,6 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; CarPreferences that = (CarPreferences) o; return ( - DoubleUtils.doubleEquals(that.speed, speed) && DoubleUtils.doubleEquals(that.reluctance, reluctance) && parking.equals(that.parking) && rental.equals(that.rental) && @@ -131,7 +118,6 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash( - speed, reluctance, parking, rental, @@ -146,7 +132,6 @@ public int hashCode() { public String toString() { return ToStringBuilder .of(CarPreferences.class) - .addNum("speed", speed, DEFAULT.speed) .addNum("reluctance", reluctance, DEFAULT.reluctance) .addObj("parking", parking, DEFAULT.parking) .addObj("rental", rental, DEFAULT.rental) @@ -161,7 +146,6 @@ public String toString() { public static class Builder { private final CarPreferences original; - private double speed; private double reluctance; private VehicleParkingPreferences parking; private VehicleRentalPreferences rental; @@ -172,7 +156,6 @@ public static class Builder { public Builder(CarPreferences original) { this.original = original; - this.speed = original.speed; this.reluctance = original.reluctance; this.parking = original.parking; this.rental = original.rental; @@ -186,11 +169,6 @@ public CarPreferences original() { return original; } - public Builder withSpeed(double speed) { - this.speed = speed; - return this; - } - public Builder withReluctance(double reluctance) { this.reluctance = reluctance; return this; diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java index d3d22160935..b2ff61efcff 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java @@ -131,7 +131,6 @@ public double getSpeed(TraverseMode mode, boolean walkingBike) { return switch (mode) { case WALK -> walkingBike ? bike.walking().speed() : walk.speed(); case BICYCLE -> bike.speed(); - case CAR -> car.speed(); default -> throw new IllegalArgumentException("getSpeed(): Invalid mode " + mode); }; } diff --git a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java index 690c4c7e821..b2404adc5d1 100644 --- a/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java +++ b/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java @@ -30,6 +30,7 @@ import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.transit.model.basic.SubMode; @@ -77,6 +78,7 @@ public class SerializedGraphObject implements Serializable { public final StopConsolidationRepository stopConsolidationRepository; private final int routingTripPatternCounter; public final EmissionsDataModel emissionsDataModel; + public final StreetLimitationParameters streetLimitationParameters; public SerializedGraphObject( Graph graph, @@ -86,7 +88,8 @@ public SerializedGraphObject( RouterConfig routerConfig, DataImportIssueSummary issueSummary, EmissionsDataModel emissionsDataModel, - StopConsolidationRepository stopConsolidationRepository + StopConsolidationRepository stopConsolidationRepository, + StreetLimitationParameters streetLimitationParameters ) { this.graph = graph; this.edges = graph.getEdges(); @@ -99,6 +102,7 @@ public SerializedGraphObject( this.allTransitSubModes = SubMode.listAllCachedSubModes(); this.routingTripPatternCounter = RoutingTripPattern.indexCounter(); this.stopConsolidationRepository = stopConsolidationRepository; + this.streetLimitationParameters = streetLimitationParameters; } public static void verifyTheOutputGraphIsWritableIfDataSourceExist(DataSource graphOutput) { diff --git a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index 0e47901dcef..74d34a8d505 100644 --- a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -56,16 +56,20 @@ public class GraphPathFinder { private final DataOverlayContext dataOverlayContext; + private final Float maxCarSpeed; + public GraphPathFinder(@Nullable TraverseVisitor traverseVisitor) { - this(traverseVisitor, null); + this(traverseVisitor, null, null); } public GraphPathFinder( @Nullable TraverseVisitor traverseVisitor, - @Nullable DataOverlayContext dataOverlayContext + @Nullable DataOverlayContext dataOverlayContext, + @Nullable Float maxCarSpeed ) { this.traverseVisitor = traverseVisitor; this.dataOverlayContext = dataOverlayContext; + this.maxCarSpeed = maxCarSpeed; } /** @@ -81,7 +85,7 @@ public List> getPaths( StreetSearchBuilder aStar = StreetSearchBuilder .of() - .setHeuristic(new EuclideanRemainingWeightHeuristic()) + .setHeuristic(new EuclideanRemainingWeightHeuristic(maxCarSpeed)) .setSkipEdgeStrategy( new DurationSkipEdgeStrategy( preferences.maxDirectDuration().valueOf(request.journey().direct().mode()) diff --git a/src/main/java/org/opentripplanner/standalone/OTPMain.java b/src/main/java/org/opentripplanner/standalone/OTPMain.java index 365e1fb49c8..51172d5d90a 100644 --- a/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -153,7 +153,8 @@ private static void startOTPServer(CommandLineParameters cli) { config.routerConfig(), DataImportIssueSummary.combine(graphBuilder.issueSummary(), app.dataImportIssueSummary()), app.emissionsDataModel(), - app.stopConsolidationRepository() + app.stopConsolidationRepository(), + app.streetLimitationParameters() ) .save(app.graphOutputDataSource()); // Log size info for the deduplicator diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index fa3a7069e2d..3bc847d7833 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -24,6 +24,7 @@ import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.service.TransitService; @@ -98,6 +99,8 @@ public interface OtpServerRequestContext { @Nullable StopConsolidationService stopConsolidationService(); + StreetLimitationParameters streetLimitationParameters(); + MeterRegistry meterRegistry(); @Nullable diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 4538f6de84d..1f12e63ee39 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -573,13 +573,6 @@ private static void mapCarPreferences(NodeAdapter root, CarPreferences.Builder b var dft = builder.original(); NodeAdapter c = root.of("car").since(V2_5).summary("Car preferences.").asObject(); builder - .withSpeed( - c - .of("speed") - .since(V2_0) - .summary("Max car speed along streets, in meters per second") - .asDouble(dft.speed()) - ) .withReluctance( c .of("reluctance") diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 487e8489128..72a46254b58 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -31,6 +31,7 @@ import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.GrizzlyServer; import org.opentripplanner.standalone.server.OTPWebApplication; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.elevation.ElevationUtils; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.configure.UpdaterConfigurator; @@ -74,7 +75,8 @@ public class ConstructApplication { GraphBuilderDataSources graphBuilderDataSources, DataImportIssueSummary issueSummary, EmissionsDataModel emissionsDataModel, - @Nullable StopConsolidationRepository stopConsolidationRepository + @Nullable StopConsolidationRepository stopConsolidationRepository, + StreetLimitationParameters streetLimitationParameters ) { this.cli = cli; this.graphBuilderDataSources = graphBuilderDataSources; @@ -94,6 +96,7 @@ public class ConstructApplication { .emissionsDataModel(emissionsDataModel) .dataImportIssueSummary(issueSummary) .stopConsolidationRepository(stopConsolidationRepository) + .streetLimitationParameters(streetLimitationParameters) .build(); } @@ -126,6 +129,7 @@ public GraphBuilder createGraphBuilder() { factory.worldEnvelopeRepository(), factory.emissionsDataModel(), factory.stopConsolidationRepository(), + factory.streetLimitationParameters(), cli.doLoadStreetGraph(), cli.doSaveStreetGraph() ); @@ -304,4 +308,8 @@ private void createMetricsLogging() { public EmissionsDataModel emissionsDataModel() { return factory.emissionsDataModel(); } + + public StreetLimitationParameters streetLimitationParameters() { + return factory.streetLimitationParameters(); + } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index a4f0ee49652..719cf37ec0e 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -29,6 +29,7 @@ import org.opentripplanner.standalone.config.ConfigModel; import org.opentripplanner.standalone.config.configure.ConfigModule; import org.opentripplanner.standalone.server.MetricsLogging; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.configure.TransitModule; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; @@ -81,6 +82,8 @@ public interface ConstructApplicationFactory { @Nullable StopConsolidationRepository stopConsolidationRepository(); + StreetLimitationParameters streetLimitationParameters(); + @Component.Builder interface Builder { @BindsInstance @@ -109,6 +112,9 @@ Builder stopConsolidationRepository( @BindsInstance Builder emissionsDataModel(EmissionsDataModel emissionsDataModel); + @BindsInstance + Builder streetLimitationParameters(StreetLimitationParameters streetLimitationParameters); + ConstructApplicationFactory build(); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index 5d8efcd3a5b..88cce81423f 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -19,6 +19,7 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.visualizer.GraphVisualizer; @@ -36,6 +37,7 @@ OtpServerRequestContext providesServerContext( VehicleRentalService vehicleRentalService, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, + StreetLimitationParameters streetLimitationParameters, @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, LauncherRequestDecorator launcherRequestDecorator @@ -57,6 +59,7 @@ OtpServerRequestContext providesServerContext( routerConfig.flexConfig(), rideHailingServices, stopConsolidationService, + streetLimitationParameters, traverseVisitor ); } diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java index f22c5ab80ec..19415e489b4 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java @@ -11,6 +11,7 @@ import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.config.CommandLineParameters; import org.opentripplanner.standalone.config.ConfigModel; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitModel; /** @@ -57,7 +58,8 @@ public ConstructApplication appConstruction(SerializedGraphObject obj) { obj.worldEnvelopeRepository, obj.issueSummary, obj.emissionsDataModel, - obj.stopConsolidationRepository + obj.stopConsolidationRepository, + obj.streetLimitationParameters ); } @@ -69,7 +71,8 @@ public ConstructApplication appConstruction() { factory.emptyWorldEnvelopeRepository(), DataImportIssueSummary.empty(), factory.emptyEmissionsDataModel(), - factory.emptyStopConsolidationRepository() + factory.emptyStopConsolidationRepository(), + factory.emptyStreetLimitationParameters() ); } @@ -90,7 +93,8 @@ private ConstructApplication createAppConstruction( WorldEnvelopeRepository worldEnvelopeRepository, DataImportIssueSummary issueSummary, @Nullable EmissionsDataModel emissionsDataModel, - @Nullable StopConsolidationRepository stopConsolidationRepository + @Nullable StopConsolidationRepository stopConsolidationRepository, + StreetLimitationParameters streetLimitationParameters ) { return new ConstructApplication( cli, @@ -101,7 +105,8 @@ private ConstructApplication createAppConstruction( graphBuilderDataSources(), issueSummary, emissionsDataModel, - stopConsolidationRepository + stopConsolidationRepository, + streetLimitationParameters ); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java index ca90c613db5..aacb42c4336 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java @@ -16,6 +16,7 @@ import org.opentripplanner.standalone.config.CommandLineParameters; import org.opentripplanner.standalone.config.ConfigModel; import org.opentripplanner.standalone.config.configure.LoadConfigModule; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitModel; /** @@ -54,6 +55,9 @@ public interface LoadApplicationFactory { @Singleton StopConsolidationRepository emptyStopConsolidationRepository(); + @Singleton + StreetLimitationParameters emptyStreetLimitationParameters(); + @Component.Builder interface Builder { @BindsInstance diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 9a586219ba1..7cb5392ce8b 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -25,6 +25,7 @@ import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.TransitService; @HttpRequestScoped @@ -47,6 +48,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final VehicleRentalService vehicleRentalService; private final EmissionsService emissionsService; private final StopConsolidationService stopConsolidationService; + private final StreetLimitationParameters streetLimitationParameters; /** * Make sure all mutable components are copied/cloned before calling this constructor. @@ -66,6 +68,7 @@ private DefaultServerRequestContext( EmissionsService emissionsService, List rideHailingServices, StopConsolidationService stopConsolidationService, + StreetLimitationParameters streetLimitationParameters, FlexConfig flexConfig, TraverseVisitor traverseVisitor ) { @@ -85,6 +88,7 @@ private DefaultServerRequestContext( this.rideHailingServices = rideHailingServices; this.emissionsService = emissionsService; this.stopConsolidationService = stopConsolidationService; + this.streetLimitationParameters = streetLimitationParameters; } /** @@ -105,6 +109,7 @@ public static DefaultServerRequestContext create( FlexConfig flexConfig, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, + StreetLimitationParameters streetLimitationParameters, @Nullable TraverseVisitor traverseVisitor ) { return new DefaultServerRequestContext( @@ -122,6 +127,7 @@ public static DefaultServerRequestContext create( emissionsService, rideHailingServices, stopConsolidationService, + streetLimitationParameters, flexConfig, traverseVisitor ); @@ -199,6 +205,11 @@ public StopConsolidationService stopConsolidationService() { return stopConsolidationService; } + @Override + public StreetLimitationParameters streetLimitationParameters() { + return streetLimitationParameters; + } + @Override public MeterRegistry meterRegistry() { return meterRegistry; diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java new file mode 100644 index 00000000000..a10fe77144c --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java @@ -0,0 +1,35 @@ +package org.opentripplanner.street.model; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.io.Serializable; +import javax.annotation.Nullable; + +/** + * Holds limits of the street graph. + *

+ * TODO this can be expanded to include some fields from the {@link org.opentripplanner.routing.graph.Graph}. + */ +@Singleton +public class StreetLimitationParameters implements Serializable { + + private Float maxCarSpeed = null; + + @Inject + public StreetLimitationParameters() {} + + /** + * Initiliaze the maximum speed limit in m/s. + */ + public void initMaxCarSpeed(float maxCarSpeed) { + this.maxCarSpeed = maxCarSpeed; + } + + /** + * If this graph contains car routable streets, this value is the maximum speed limit in m/s. + */ + @Nullable + public Float maxCarSpeed() { + return maxCarSpeed; + } +} diff --git a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java index e43278901d4..5891f60bed1 100644 --- a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java +++ b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java @@ -16,11 +16,22 @@ */ public class EuclideanRemainingWeightHeuristic implements RemainingWeightHeuristic { + private static final Float DEFAULT_MAX_CAR_SPEED = 40f; + private double lat; private double lon; private double maxStreetSpeed; private double walkingSpeed; private boolean arriveBy; + private float maxCarSpeed; + + public EuclideanRemainingWeightHeuristic() { + this(DEFAULT_MAX_CAR_SPEED); + } + + public EuclideanRemainingWeightHeuristic(Float maxCarSpeed) { + this.maxCarSpeed = maxCarSpeed != null ? maxCarSpeed : DEFAULT_MAX_CAR_SPEED; + } // TODO This currently only uses the first toVertex. If there are multiple toVertices, it will // not work correctly. @@ -50,7 +61,7 @@ public void initialize( private double getStreetSpeedUpperBound(RoutingPreferences preferences, StreetMode streetMode) { // Assume carSpeed > bikeSpeed > walkSpeed if (streetMode.includesDriving()) { - return preferences.car().speed(); + return maxCarSpeed; } if (streetMode.includesBiking()) { return preferences.bike().speed(); diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 25cf95ca2f9..c759ce25be9 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -997,7 +997,7 @@ type RoutingParameters { "Time to park a car in a park and ride, w/o taking into account driving and walking cost." carDropOffTime: Int "Max car speed along streets, in meters per second" - carSpeed: Float + carSpeed: Float @deprecated(reason : "This parameter is no longer configurable.") compactLegsByReversedSearch: Boolean @deprecated(reason : "NOT IN USE IN OTP2.") debugItineraryFilter: Boolean @deprecated(reason : "Use `itineraryFilter.debug` instead.") "Option to disable the default filtering of GTFS-RT alerts by time." diff --git a/src/test/java/org/opentripplanner/ConstantsForTests.java b/src/test/java/org/opentripplanner/ConstantsForTests.java index e74c66e527b..53b2bf9b396 100644 --- a/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -38,6 +38,7 @@ import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.standalone.config.OtpConfigLoader; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.TraverseModeSet; import org.opentripplanner.test.support.ResourceLoader; @@ -132,6 +133,7 @@ public static TestOtpModel buildNewPortlandGraph(boolean withElevation) { .of(osmProvider, graph) .withStaticParkAndRide(true) .withStaticBikeParkAndRide(true) + .withStreetLimitationParameters(new StreetLimitationParameters()) .build(); osmModule.buildGraph(); } @@ -172,7 +174,10 @@ public static TestOtpModel buildOsmGraph(File osmFile) { var transitModel = new TransitModel(stopModel, deduplicator); // Add street data from OSM OsmProvider osmProvider = new OsmProvider(osmFile, true); - OsmModule osmModule = OsmModule.of(osmProvider, graph).build(); + OsmModule osmModule = OsmModule + .of(osmProvider, graph) + .withStreetLimitationParameters(new StreetLimitationParameters()) + .build(); osmModule.buildGraph(); return new TestOtpModel(graph, transitModel); } catch (Exception e) { diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 5d74dbba240..f80795c2155 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -20,6 +20,7 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; @@ -51,6 +52,7 @@ public static OtpServerRequestContext createServerContext( routerConfig.flexConfig(), List.of(), null, + new StreetLimitationParameters(), null ); creatTransitLayerForRaptor(transitModel, routerConfig.transitTuningConfig()); diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index 127fe66f0ea..f4d4f5b005f 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -48,6 +48,7 @@ import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeService; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -132,6 +133,7 @@ public class TripRequestMapperTest implements PlanTestConstants { RouterConfig.DEFAULT.flexConfig(), List.of(), null, + new StreetLimitationParameters(), null ), null, diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java index 091652a2dbf..99ac1554134 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java @@ -33,6 +33,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.impl.GraphPathFinder; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.vertex.BarrierVertex; @@ -356,7 +357,11 @@ private void testBuildingAreas(boolean skipVisibility) { File file = RESOURCE_LOADER.file("usf_area.osm.pbf"); OsmProvider provider = new OsmProvider(file, false); - OsmModule loader = OsmModule.of(provider, graph).withAreaVisibility(!skipVisibility).build(); + OsmModule loader = OsmModule + .of(provider, graph) + .withAreaVisibility(!skipVisibility) + .withStreetLimitationParameters(new StreetLimitationParameters()) + .build(); loader.buildGraph(); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java index 4cdada95e4b..7d83ec9c87c 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java @@ -180,7 +180,7 @@ private void checkTriangleInequality(RequestModes modes, List fil preferences .withWalk(walk -> walk.withStairsReluctance(1.0).withSpeed(1.0).withReluctance(1.0)) .withStreet(street -> street.withTurnReluctance(1.0)) - .withCar(car -> car.withSpeed(1.0).withReluctance(1.0)) + .withCar(car -> car.withReluctance(1.0)) .withBike(bike -> bike.withSpeed(1.0).withReluctance(1.0)) ); diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java index cbaccb87f84..5f95436833d 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java @@ -69,7 +69,7 @@ public void testCarSpeeds() { wps.addSpeedPicker(getSpeedPicker("highway=motorway", kmhAsMs(100))); wps.addSpeedPicker(getSpeedPicker("highway=*", kmhAsMs(35))); wps.addSpeedPicker(getSpeedPicker("surface=gravel", kmhAsMs(10))); - wps.defaultSpeed = kmhAsMs(25); + wps.defaultCarSpeed = kmhAsMs(25); way = new OSMWithTags(); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java b/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java index 53e340dcc63..f3febc38cbf 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java @@ -92,7 +92,7 @@ public void before() { proto = new RouteRequest(); proto.withPreferences(preferences -> preferences - .withCar(it -> it.withSpeed(1.0).withReluctance(1.0)) + .withCar(it -> it.withReluctance(1.0)) .withBike(bike -> bike.withSpeed(1.0).withReluctance(1.0)) .withWalk(walk -> walk.withSpeed(1.0).withStairsReluctance(1.0).withReluctance(1.0)) .withStreet(it -> it.withTurnReluctance(1.0)) diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java index 4359ed9b33d..6b03873b80d 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/CarPreferencesTest.java @@ -10,8 +10,6 @@ class CarPreferencesTest { - private static final double SPEED = 20.111; - private static final double EXPECTED_SPEED = 20.0; private static final double RELUCTANCE = 5.111; private static final double EXPECTED_RELUCTANCE = 5.1; private static final int PICKUP_TIME = 600; @@ -23,7 +21,6 @@ class CarPreferencesTest { private final CarPreferences subject = CarPreferences .of() - .withSpeed(SPEED) .withReluctance(RELUCTANCE) .withPickupTime(Duration.ofSeconds(PICKUP_TIME)) .withPickupCost(PICKUP_COST) @@ -33,11 +30,6 @@ class CarPreferencesTest { .withParking(parking -> parking.withCost(PARK_COST).build()) .build(); - @Test - void speed() { - assertEquals(EXPECTED_SPEED, subject.speed()); - } - @Test void reluctance() { assertEquals(EXPECTED_RELUCTANCE, subject.reluctance()); @@ -82,8 +74,8 @@ void testCopyOfEqualsAndHashCode() { assertSame(subject, subject.copyOf().build()); // Create a copy, make a change and set it back again to force creating a new object - var other = subject.copyOf().withSpeed(0.0).build(); - var same = other.copyOf().withSpeed(SPEED).build(); + var other = subject.copyOf().withReluctance(0.0).build(); + var same = other.copyOf().withReluctance(RELUCTANCE).build(); assertEqualsAndHashCode(subject, other, same); } @@ -92,7 +84,6 @@ void testToString() { assertEquals("CarPreferences{}", CarPreferences.DEFAULT.toString()); assertEquals( "CarPreferences{" + - "speed: 20.0, " + "reluctance: 5.1, " + "parking: VehicleParkingPreferences{cost: $30}, " + "rental: VehicleRentalPreferences{pickupTime: 30s}, " + diff --git a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index 818efb67d41..f1f9ff8bfe6 100644 --- a/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -27,6 +27,7 @@ import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeRepository; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.service.TransitModel; @@ -180,6 +181,8 @@ private void testRoundTrip( ) throws Exception { // Now round-trip the graph through serialization. File tempFile = TempFile.createTempFile("graph", "pdx"); + var streetLimitationParameters = new StreetLimitationParameters(); + streetLimitationParameters.initMaxCarSpeed(40); SerializedGraphObject serializedObj = new SerializedGraphObject( originalGraph, originalTransitModel, @@ -188,7 +191,8 @@ private void testRoundTrip( RouterConfig.DEFAULT, DataImportIssueSummary.empty(), emissionsDataModel, - null + null, + streetLimitationParameters ); serializedObj.save(new FileDataSource(tempFile, FileType.GRAPH)); SerializedGraphObject deserializedGraph = SerializedGraphObject.load(tempFile); diff --git a/src/test/java/org/opentripplanner/street/model/TurnRestrictionTest.java b/src/test/java/org/opentripplanner/street/model/TurnRestrictionTest.java index d40930f8bdc..f5d660e5d88 100644 --- a/src/test/java/org/opentripplanner/street/model/TurnRestrictionTest.java +++ b/src/test/java/org/opentripplanner/street/model/TurnRestrictionTest.java @@ -94,9 +94,7 @@ public void testHasExplicitTurnRestrictions() { public void testForwardDefault() { var request = new RouteRequest(); - request.withPreferences(preferences -> - preferences.withCar(it -> it.withSpeed(1.0)).withWalk(w -> w.withSpeed(1.0)) - ); + request.withPreferences(preferences -> preferences.withWalk(w -> w.withSpeed(1.0))); ShortestPathTree tree = StreetSearchBuilder .of() @@ -156,7 +154,6 @@ public void testForwardAsPedestrian() { @Test public void testForwardAsCar() { var request = new RouteRequest(); - request.withPreferences(p -> p.withCar(it -> it.withSpeed(1.0))); ShortestPathTree tree = StreetSearchBuilder .of() diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java index 42d841b9fa3..eb60517735a 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java @@ -58,7 +58,7 @@ public void before() { .withBike(it -> it.withSpeed(5.0f).withReluctance(1.0).withWalking(w -> w.withSpeed(0.8)) ) - .withCar(c -> c.withSpeed(15.0f).withReluctance(1.0)) + .withCar(c -> c.withReluctance(1.0)) ) .build(); } diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 7a11c35bace..e66e828856a 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -31,6 +31,7 @@ import org.opentripplanner.standalone.config.OtpConfigLoader; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.speed_test.model.SpeedTestProfile; @@ -120,6 +121,7 @@ public SpeedTest( config.flexConfig, List.of(), null, + new StreetLimitationParameters(), null ); // Creating transitLayerForRaptor should be integrated into the TransitModel, but for now diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 9293cfad8f4..b4c76b7261c 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -43,7 +43,6 @@ } }, "car": { - "speed": 40, "reluctance": 10, "decelerationSpeed": 2.9, "accelerationSpeed": 2.9, From 8640e08095402b16204dba8b410bc3dacf6da195 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 8 Feb 2024 14:24:09 +0200 Subject: [PATCH 002/108] Add service for street limitation parameters --- .../router/street/DirectStreetRouter.java | 4 +--- .../routing/impl/GraphPathFinder.java | 6 +++--- .../api/OtpServerRequestContext.java | 4 ++-- .../ConstructApplicationFactory.java | 2 ++ .../configure/ConstructApplicationModule.java | 6 +++--- .../server/DefaultServerRequestContext.java | 16 +++++++------- ...aultStreetLimitationParametersService.java | 20 ++++++++++++++++++ .../model/StreetLimitationParameters.java | 7 +++---- .../StreetLimitationParametersService.java | 13 ++++++++++++ ...reetLimitationParametersServiceModule.java | 21 +++++++++++++++++++ .../opentripplanner/TestServerContext.java | 8 ++++++- .../mapping/TripRequestMapperTest.java | 3 ++- .../transit/speed_test/SpeedTest.java | 3 +-- 13 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java create mode 100644 src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java create mode 100644 src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java index e2ce8c113c1..f895425f1c7 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java @@ -35,9 +35,7 @@ public static List route(OtpServerRequestContext serverContext, Route request.journey().direct().mode() ) ) { - var maxCarSpeed = serverContext.streetLimitationParameters().maxCarSpeed() != null - ? serverContext.streetLimitationParameters().maxCarSpeed() - : 40f; + var maxCarSpeed = serverContext.streetLimitationParametersService().getMaxCarSpeed(); if (!straightLineDistanceIsWithinLimit(directRequest, temporaryVertices, maxCarSpeed)) { return Collections.emptyList(); } diff --git a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index 74d34a8d505..0145eacd5ac 100644 --- a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -56,16 +56,16 @@ public class GraphPathFinder { private final DataOverlayContext dataOverlayContext; - private final Float maxCarSpeed; + private final float maxCarSpeed; public GraphPathFinder(@Nullable TraverseVisitor traverseVisitor) { - this(traverseVisitor, null, null); + this(traverseVisitor, null, 40f); } public GraphPathFinder( @Nullable TraverseVisitor traverseVisitor, @Nullable DataOverlayContext dataOverlayContext, - @Nullable Float maxCarSpeed + float maxCarSpeed ) { this.traverseVisitor = traverseVisitor; this.dataOverlayContext = dataOverlayContext; diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 3bc847d7833..99f6c5af3f1 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -24,7 +24,7 @@ import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.model.StreetLimitationParametersService; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.service.TransitService; @@ -99,7 +99,7 @@ public interface OtpServerRequestContext { @Nullable StopConsolidationService stopConsolidationService(); - StreetLimitationParameters streetLimitationParameters(); + StreetLimitationParametersService streetLimitationParametersService(); MeterRegistry meterRegistry(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 719cf37ec0e..10ad5ad8baa 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -30,6 +30,7 @@ import org.opentripplanner.standalone.config.configure.ConfigModule; import org.opentripplanner.standalone.server.MetricsLogging; import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.model.StreetLimitationParametersServiceModule; import org.opentripplanner.transit.configure.TransitModule; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; @@ -53,6 +54,7 @@ EmissionsServiceModule.class, StopConsolidationServiceModule.class, InteractiveLauncherModule.class, + StreetLimitationParametersServiceModule.class, } ) public interface ConstructApplicationFactory { diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index 88cce81423f..aa55ef3e2ad 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -19,7 +19,7 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; -import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.model.StreetLimitationParametersService; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.visualizer.GraphVisualizer; @@ -37,7 +37,7 @@ OtpServerRequestContext providesServerContext( VehicleRentalService vehicleRentalService, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, - StreetLimitationParameters streetLimitationParameters, + StreetLimitationParametersService streetLimitationParametersService, @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, LauncherRequestDecorator launcherRequestDecorator @@ -59,7 +59,7 @@ OtpServerRequestContext providesServerContext( routerConfig.flexConfig(), rideHailingServices, stopConsolidationService, - streetLimitationParameters, + streetLimitationParametersService, traverseVisitor ); } diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 7cb5392ce8b..42c9538fe08 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -25,7 +25,7 @@ import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.model.StreetLimitationParametersService; import org.opentripplanner.transit.service.TransitService; @HttpRequestScoped @@ -48,7 +48,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final VehicleRentalService vehicleRentalService; private final EmissionsService emissionsService; private final StopConsolidationService stopConsolidationService; - private final StreetLimitationParameters streetLimitationParameters; + private final StreetLimitationParametersService streetLimitationParametersService; /** * Make sure all mutable components are copied/cloned before calling this constructor. @@ -68,7 +68,7 @@ private DefaultServerRequestContext( EmissionsService emissionsService, List rideHailingServices, StopConsolidationService stopConsolidationService, - StreetLimitationParameters streetLimitationParameters, + StreetLimitationParametersService streetLimitationParametersService, FlexConfig flexConfig, TraverseVisitor traverseVisitor ) { @@ -88,7 +88,7 @@ private DefaultServerRequestContext( this.rideHailingServices = rideHailingServices; this.emissionsService = emissionsService; this.stopConsolidationService = stopConsolidationService; - this.streetLimitationParameters = streetLimitationParameters; + this.streetLimitationParametersService = streetLimitationParametersService; } /** @@ -109,7 +109,7 @@ public static DefaultServerRequestContext create( FlexConfig flexConfig, List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, - StreetLimitationParameters streetLimitationParameters, + StreetLimitationParametersService streetLimitationParametersService, @Nullable TraverseVisitor traverseVisitor ) { return new DefaultServerRequestContext( @@ -127,7 +127,7 @@ public static DefaultServerRequestContext create( emissionsService, rideHailingServices, stopConsolidationService, - streetLimitationParameters, + streetLimitationParametersService, flexConfig, traverseVisitor ); @@ -206,8 +206,8 @@ public StopConsolidationService stopConsolidationService() { } @Override - public StreetLimitationParameters streetLimitationParameters() { - return streetLimitationParameters; + public StreetLimitationParametersService streetLimitationParametersService() { + return streetLimitationParametersService; } @Override diff --git a/src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java b/src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java new file mode 100644 index 00000000000..d6cafa320e2 --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java @@ -0,0 +1,20 @@ +package org.opentripplanner.street.model; + +import jakarta.inject.Inject; + +public class DefaultStreetLimitationParametersService implements StreetLimitationParametersService { + + private final StreetLimitationParameters streetLimitationParameters; + + @Inject + public DefaultStreetLimitationParametersService( + StreetLimitationParameters streetLimitationParameters + ) { + this.streetLimitationParameters = streetLimitationParameters; + } + + @Override + public float getMaxCarSpeed() { + return streetLimitationParameters.maxCarSpeed(); + } +} diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java index a10fe77144c..18937abad9b 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java @@ -3,7 +3,6 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.io.Serializable; -import javax.annotation.Nullable; /** * Holds limits of the street graph. @@ -13,7 +12,7 @@ @Singleton public class StreetLimitationParameters implements Serializable { - private Float maxCarSpeed = null; + private float maxCarSpeed = 40f; @Inject public StreetLimitationParameters() {} @@ -27,9 +26,9 @@ public void initMaxCarSpeed(float maxCarSpeed) { /** * If this graph contains car routable streets, this value is the maximum speed limit in m/s. + * Defaults to 40 m/s == 144 km/h. */ - @Nullable - public Float maxCarSpeed() { + public float maxCarSpeed() { return maxCarSpeed; } } diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java new file mode 100644 index 00000000000..9065924b49b --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java @@ -0,0 +1,13 @@ +package org.opentripplanner.street.model; + +/** + * A service for fetching limitation parameters of the street graph. + */ +public interface StreetLimitationParametersService { + /** + * Get the graph wide maximum car speed. + * + * @return Maximum car speed in meters per second. + */ + float getMaxCarSpeed(); +} diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java new file mode 100644 index 00000000000..f034b48f313 --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java @@ -0,0 +1,21 @@ +package org.opentripplanner.street.model; + +import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; + +/** + * The service is used during application serve phase, not loading, so we need to provide + * a module for the service without the repository, which is injected from the loading phase. + */ +@Module +public class StreetLimitationParametersServiceModule { + + @Provides + @Singleton + public StreetLimitationParametersService provideEmissionsService( + StreetLimitationParameters streetLimitationParameters + ) { + return new DefaultStreetLimitationParametersService(streetLimitationParameters) {}; + } +} diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index f80795c2155..4ed451b3db3 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -20,7 +20,9 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.DefaultStreetLimitationParametersService; import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.model.StreetLimitationParametersService; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; @@ -52,7 +54,7 @@ public static OtpServerRequestContext createServerContext( routerConfig.flexConfig(), List.of(), null, - new StreetLimitationParameters(), + createStreetLimitationParametersService(), null ); creatTransitLayerForRaptor(transitModel, routerConfig.transitTuningConfig()); @@ -82,4 +84,8 @@ public static VehicleRentalService createVehicleRentalService() { public static EmissionsService createEmissionsService() { return new DefaultEmissionsService(new EmissionsDataModel()); } + + public static StreetLimitationParametersService createStreetLimitationParametersService() { + return new DefaultStreetLimitationParametersService(new StreetLimitationParameters()); + } } diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index f4d4f5b005f..4ccb1627e7f 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -48,6 +48,7 @@ import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeService; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; +import org.opentripplanner.street.model.DefaultStreetLimitationParametersService; import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; @@ -133,7 +134,7 @@ public class TripRequestMapperTest implements PlanTestConstants { RouterConfig.DEFAULT.flexConfig(), List.of(), null, - new StreetLimitationParameters(), + new DefaultStreetLimitationParametersService(new StreetLimitationParameters()), null ), null, diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index e66e828856a..e045f49c01d 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -31,7 +31,6 @@ import org.opentripplanner.standalone.config.OtpConfigLoader; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; -import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.speed_test.model.SpeedTestProfile; @@ -121,7 +120,7 @@ public SpeedTest( config.flexConfig, List.of(), null, - new StreetLimitationParameters(), + TestServerContext.createStreetLimitationParametersService(), null ); // Creating transitLayerForRaptor should be integrated into the TransitModel, but for now From 479aa20578f8bc83ff8c5e09a8d27f6031f9c927 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 8 Feb 2024 15:06:22 +0200 Subject: [PATCH 003/108] Add tests for WayPropertySet --- .../wayproperty/WayPropertySetTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java index c5ae8b86f7d..e8f5b6e51d3 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java @@ -31,6 +31,44 @@ public void carTunnel() { assertEquals(CAR, wps.getDataForWay(tunnel).getPermission()); } + @Test + public void carMaxSpeed() { + var delta = 0.001f; + var motorWaySpeed = 35f; + WayPropertySet wps = wps(); + wps.setCarSpeed("highway=motorway", motorWaySpeed); + + // Test that there are default values + assertEquals(38f, wps.maxPossibleCarSpeed, delta); + assertEquals(0f, wps.maxUsedCarSpeed, delta); + + // Speed limit that is within limits should be used as the max used car speed + OSMWithTags streetWithSpeedLimit = new OSMWithTags(); + streetWithSpeedLimit.addTag("highway", "motorway"); + streetWithSpeedLimit.addTag("maxspeed", "120"); + var waySpeed = wps.getCarSpeedForWay(streetWithSpeedLimit, false); + assertEquals(33.33336, waySpeed, delta); + assertEquals(33.33336, wps.maxUsedCarSpeed, delta); + + // Speed limit that is higher than maxPossibleCarSpeed should be ignored and regular motorway + // speed limit should be used instead + OSMWithTags streetWithTooHighSpeedLimit = new OSMWithTags(); + streetWithTooHighSpeedLimit.addTag("highway", "motorway"); + streetWithTooHighSpeedLimit.addTag("maxspeed", "200"); + waySpeed = wps.getCarSpeedForWay(streetWithTooHighSpeedLimit, false); + assertEquals(motorWaySpeed, waySpeed, delta); + assertEquals(motorWaySpeed, wps.maxUsedCarSpeed, delta); + + // Speed limit that is too low should be ignored and regular motorway speed limit should + // be used instead + OSMWithTags streetWithTooLowSpeedLimit = new OSMWithTags(); + streetWithTooLowSpeedLimit.addTag("highway", "motorway"); + streetWithTooLowSpeedLimit.addTag("maxspeed", "0"); + waySpeed = wps.getCarSpeedForWay(streetWithTooLowSpeedLimit, false); + assertEquals(motorWaySpeed, waySpeed, delta); + assertEquals(motorWaySpeed, wps.maxUsedCarSpeed, delta); + } + @Test void pedestrianTunnelSpecificity() { var tunnel = WayTestData.pedestrianTunnel(); From a7733a2eefb4af6dd7461a04f53c1258c38d818d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 8 Feb 2024 15:21:40 +0200 Subject: [PATCH 004/108] Create own package for services --- .../standalone/api/OtpServerRequestContext.java | 2 +- .../standalone/configure/ConstructApplicationFactory.java | 2 +- .../standalone/configure/ConstructApplicationModule.java | 2 +- .../standalone/server/DefaultServerRequestContext.java | 2 +- .../DefaultStreetLimitationParametersService.java | 3 ++- .../{model => service}/StreetLimitationParametersService.java | 2 +- .../StreetLimitationParametersServiceModule.java | 3 ++- src/test/java/org/opentripplanner/TestServerContext.java | 4 ++-- .../apis/transmodel/mapping/TripRequestMapperTest.java | 2 +- 9 files changed, 12 insertions(+), 10 deletions(-) rename src/main/java/org/opentripplanner/street/{model => service}/DefaultStreetLimitationParametersService.java (81%) rename src/main/java/org/opentripplanner/street/{model => service}/StreetLimitationParametersService.java (86%) rename src/main/java/org/opentripplanner/street/{model => service}/StreetLimitationParametersServiceModule.java (84%) diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 99f6c5af3f1..082ae4515c8 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -24,9 +24,9 @@ import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.street.model.StreetLimitationParametersService; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.search.state.State; +import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.service.TransitService; /** diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 10ad5ad8baa..4e9f50607ac 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -30,7 +30,7 @@ import org.opentripplanner.standalone.config.configure.ConfigModule; import org.opentripplanner.standalone.server.MetricsLogging; import org.opentripplanner.street.model.StreetLimitationParameters; -import org.opentripplanner.street.model.StreetLimitationParametersServiceModule; +import org.opentripplanner.street.service.StreetLimitationParametersServiceModule; import org.opentripplanner.transit.configure.TransitModule; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index aa55ef3e2ad..93158f87cff 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -19,7 +19,7 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; -import org.opentripplanner.street.model.StreetLimitationParametersService; +import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.visualizer.GraphVisualizer; diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 42c9538fe08..019b7267015 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -25,7 +25,7 @@ import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.street.model.StreetLimitationParametersService; +import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.service.TransitService; @HttpRequestScoped diff --git a/src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java b/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java similarity index 81% rename from src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java rename to src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java index d6cafa320e2..6b42cef4b58 100644 --- a/src/main/java/org/opentripplanner/street/model/DefaultStreetLimitationParametersService.java +++ b/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java @@ -1,6 +1,7 @@ -package org.opentripplanner.street.model; +package org.opentripplanner.street.service; import jakarta.inject.Inject; +import org.opentripplanner.street.model.StreetLimitationParameters; public class DefaultStreetLimitationParametersService implements StreetLimitationParametersService { diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersService.java similarity index 86% rename from src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java rename to src/main/java/org/opentripplanner/street/service/StreetLimitationParametersService.java index 9065924b49b..dcbc53cf406 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersService.java +++ b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersService.java @@ -1,4 +1,4 @@ -package org.opentripplanner.street.model; +package org.opentripplanner.street.service; /** * A service for fetching limitation parameters of the street graph. diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java similarity index 84% rename from src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java rename to src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java index f034b48f313..48552ed3ea3 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetLimitationParametersServiceModule.java +++ b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java @@ -1,8 +1,9 @@ -package org.opentripplanner.street.model; +package org.opentripplanner.street.service; import dagger.Module; import dagger.Provides; import jakarta.inject.Singleton; +import org.opentripplanner.street.model.StreetLimitationParameters; /** * The service is used during application serve phase, not loading, so we need to provide diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 4ed451b3db3..bf6a8d2bbd3 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -20,9 +20,9 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; -import org.opentripplanner.street.model.DefaultStreetLimitationParametersService; import org.opentripplanner.street.model.StreetLimitationParameters; -import org.opentripplanner.street.model.StreetLimitationParametersService; +import org.opentripplanner.street.service.DefaultStreetLimitationParametersService; +import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index 4ccb1627e7f..0fe594543c0 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -48,8 +48,8 @@ import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeService; import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; -import org.opentripplanner.street.model.DefaultStreetLimitationParametersService; import org.opentripplanner.street.model.StreetLimitationParameters; +import org.opentripplanner.street.service.DefaultStreetLimitationParametersService; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; From 4e92d8343459e5c67d32c0c7c6c7d684058c3d2d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 8 Feb 2024 15:21:57 +0200 Subject: [PATCH 005/108] Add tests for service --- ...StreetLimitationParametersServiceTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java diff --git a/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java b/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java new file mode 100644 index 00000000000..27cf531a4fc --- /dev/null +++ b/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java @@ -0,0 +1,25 @@ +package org.opentripplanner.street.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.street.model.StreetLimitationParameters; + +public class DefaultStreetLimitationParametersServiceTest { + + @Test + public void getMaxCarSpeed() { + var maxSpeed = 25f; + var model = new StreetLimitationParameters(); + model.initMaxCarSpeed(maxSpeed); + var service = new DefaultStreetLimitationParametersService(model); + assertEquals(maxSpeed, service.getMaxCarSpeed()); + } + + @Test + public void getDefaultMaxCarSpeed() { + var model = new StreetLimitationParameters(); + var service = new DefaultStreetLimitationParametersService(model); + assertEquals(40f, service.getMaxCarSpeed()); + } +} From 567ddaaf3c3769612ed1c9aa48bc3f18713be026 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 8 Feb 2024 15:25:47 +0200 Subject: [PATCH 006/108] Clarify comment --- .../openstreetmap/wayproperty/WayPropertySet.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index e867654a104..f3be186f917 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -53,7 +53,10 @@ public class WayPropertySet { private final Pattern maxSpeedPattern; /** The automobile speed for street segments that do not match any SpeedPicker. */ public Float defaultCarSpeed; - /** The maximum automobile speed that is possible. */ + /** + * The maximum automobile speed that can be defined through OSM speed limit tagging. Car speed + * defaults for different way types can be higher than this. + */ public Float maxPossibleCarSpeed; /** * The maximum automobile speed that has been used. This can be used in heuristics later on to From 65ee548c4f38bb490f027e95c7caef30f58c5302 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 1 Feb 2024 14:13:50 +0100 Subject: [PATCH 007/108] Use proper lookup for consolidation data source --- .../org/opentripplanner/datastore/OtpDataStore.java | 7 +++++++ .../datastore/api/OtpDataStoreConfig.java | 7 +++++++ .../graph_builder/GraphBuilderDataSources.java | 10 ++-------- .../module/configure/GraphBuilderModules.java | 2 +- .../standalone/config/BuildConfig.java | 13 ++++++++++--- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java index 0bfec084b2c..fd63eb7f8dd 100644 --- a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java +++ b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -59,6 +60,7 @@ public class OtpDataStore { /* Named resources available for both reading and writing. */ private DataSource streetGraph; private DataSource graph; + private DataSource stopConsolidation; private CompositeDataSource buildReportDir; private boolean opened = false; @@ -102,6 +104,7 @@ public void open() { streetGraph = findSingleSource(config.streetGraph(), STREET_GRAPH_FILENAME, GRAPH); graph = findSingleSource(config.graph(), GRAPH_FILENAME, GRAPH); + stopConsolidation = findSingleSource(config.stopConsolidation(), "", CONFIG); buildReportDir = findCompositeSource(config.reportDirectory(), BUILD_REPORT_DIR, REPORT); addAll(Arrays.asList(streetGraph, graph, buildReportDir)); @@ -152,6 +155,10 @@ public CompositeDataSource getBuildReportDir() { return buildReportDir; } + public Optional stopConsolidation() { + return Optional.ofNullable(stopConsolidation); + } + /* private methods */ private void add(DataSource source) { if (source != null) { diff --git a/src/main/java/org/opentripplanner/datastore/api/OtpDataStoreConfig.java b/src/main/java/org/opentripplanner/datastore/api/OtpDataStoreConfig.java index 7441b754103..984d66de2e9 100644 --- a/src/main/java/org/opentripplanner/datastore/api/OtpDataStoreConfig.java +++ b/src/main/java/org/opentripplanner/datastore/api/OtpDataStoreConfig.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.regex.Pattern; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.datastore.OtpDataStore; /** @@ -68,6 +69,12 @@ public interface OtpDataStoreConfig { */ URI streetGraph(); + /** + * The URI to the stop consolidation data source. + */ + @Nullable + URI stopConsolidation(); + /** * * A pattern to lookup local GTFS files diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilderDataSources.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilderDataSources.java index eaa61fea139..3ed675c0da8 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilderDataSources.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilderDataSources.java @@ -19,7 +19,6 @@ import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.api.OtpBaseDirectory; -import org.opentripplanner.datastore.file.FileDataSource; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.graph_builder.module.ned.parameter.DemExtractParameters; import org.opentripplanner.graph_builder.module.ned.parameter.DemExtractParametersBuilder; @@ -186,13 +185,8 @@ public NetexFeedParameters getNetexConfig(DataSource dataSource) { /** * Returns the optional data source for the stop consolidation configuration. */ - public Optional stopConsolidationDataSource() { - return Optional - .ofNullable(buildConfig.stopConsolidationFile) - .map(fileName -> { - var f = baseDirectory.toPath().resolve(fileName).toFile(); - return new FileDataSource(f, FileType.CONFIG); - }); + public Optional stopConsolidation() { + return store.stopConsolidation(); } /** diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 444adb5b727..857edca2b8e 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -293,7 +293,7 @@ static StopConsolidationModule providesStopConsolidationModule( GraphBuilderDataSources dataSources ) { return dataSources - .stopConsolidationDataSource() + .stopConsolidation() .map(ds -> StopConsolidationModule.of(transitModel, repo, ds)) .orElse(null); } diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index d62aa1bf41f..a542c330afe 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.regex.Pattern; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.datastore.api.OtpDataStoreConfig; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayConfig; import org.opentripplanner.ext.emissions.EmissionsConfig; @@ -182,7 +183,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final LocalDate transitServiceEnd; public final ZoneId transitModelTimeZone; - public final String stopConsolidationFile; + public final URI stopConsolidation; /** * Set all parameters from the given Jackson JSON tree, applying defaults. Supplying @@ -602,14 +603,14 @@ that we support remote input files (cloud storage or arbitrary URLs) not all dat ) .asUri(null); - stopConsolidationFile = + stopConsolidation = root .of("stopConsolidationFile") .since(V2_5) .summary( "Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation." ) - .asString(null); + .asUri(null); osmDefaults = OsmConfig.mapOsmDefaults(root, "osmDefaults"); osm = OsmConfig.mapOsmConfig(root, "osm", osmDefaults); @@ -676,6 +677,12 @@ public URI streetGraph() { return streetGraph; } + @Override + @Nullable + public URI stopConsolidation() { + return stopConsolidation; + } + @Override public Pattern gtfsLocalFilePattern() { return gtfsLocalFilePattern; From 4386def509693893edeff707a4c54bf008d95fb7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 9 Feb 2024 12:48:36 +0100 Subject: [PATCH 008/108] Update docs --- docs/BuildConfiguration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 05da4cf4d0a..4340195b28c 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -40,7 +40,7 @@ Sections follow that describe particular settings in more depth. | [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | | staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | | staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | -| stopConsolidationFile | `string` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | +| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | | [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | | [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | | [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | From e3b97a8bdedeec8cb4ce6da7ba7206d39999b9cd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 9 Feb 2024 16:46:01 +0200 Subject: [PATCH 009/108] Fix copypaste naming mistake --- .../street/service/StreetLimitationParametersServiceModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java index 48552ed3ea3..b9cd8aa6abe 100644 --- a/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java +++ b/src/main/java/org/opentripplanner/street/service/StreetLimitationParametersServiceModule.java @@ -14,7 +14,7 @@ public class StreetLimitationParametersServiceModule { @Provides @Singleton - public StreetLimitationParametersService provideEmissionsService( + public StreetLimitationParametersService provideStreetLimitationParametersService( StreetLimitationParameters streetLimitationParameters ) { return new DefaultStreetLimitationParametersService(streetLimitationParameters) {}; From 198a1966b7195138cc2b6f735fcb1768ae802160 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 9 Feb 2024 16:59:25 +0200 Subject: [PATCH 010/108] Make streetLimitationParameter nonnull in OsmModule --- .../graph_builder/module/osm/OsmModule.java | 11 +++++------ .../graph_builder/module/osm/OsmModuleBuilder.java | 2 +- .../java/org/opentripplanner/ConstantsForTests.java | 7 +------ .../graph_builder/module/osm/OsmModuleTest.java | 7 +------ 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 44133624ac1..0685b739c7e 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -7,7 +7,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import java.util.Objects; +import javax.annotation.Nonnull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -63,7 +64,7 @@ public class OsmModule implements GraphBuilderModule { Collection providers, Graph graph, DataImportIssueStore issueStore, - @Nullable StreetLimitationParameters streetLimitationParameters, + @Nonnull StreetLimitationParameters streetLimitationParameters, OsmProcessingParameters params ) { this.providers = List.copyOf(providers); @@ -73,7 +74,7 @@ public class OsmModule implements GraphBuilderModule { this.osmdb = new OsmDatabase(issueStore); this.vertexGenerator = new VertexGenerator(osmdb, graph, params.boardingAreaRefTags()); this.normalizer = new SafetyValueNormalizer(graph, issueStore); - this.streetLimitationParameters = streetLimitationParameters; + this.streetLimitationParameters = Objects.requireNonNull(streetLimitationParameters); } public static OsmModuleBuilder of(Collection providers, Graph graph) { @@ -99,9 +100,7 @@ public void buildGraph() { LOG.info("Building street graph from OSM"); build(); graph.hasStreets = true; - if (streetLimitationParameters != null) { - streetLimitationParameters.initMaxCarSpeed(getMaxCarSpeed()); - } + streetLimitationParameters.initMaxCarSpeed(getMaxCarSpeed()); } @Override diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index 144399d81f6..f0a40fa678f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -25,7 +25,7 @@ public class OsmModuleBuilder { private boolean staticParkAndRide = false; private boolean staticBikeParkAndRide = false; private int maxAreaNodes; - private StreetLimitationParameters streetLimitationParameters; + private StreetLimitationParameters streetLimitationParameters = new StreetLimitationParameters(); OsmModuleBuilder(Collection providers, Graph graph) { this.providers = providers; diff --git a/src/test/java/org/opentripplanner/ConstantsForTests.java b/src/test/java/org/opentripplanner/ConstantsForTests.java index 53b2bf9b396..e74c66e527b 100644 --- a/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -38,7 +38,6 @@ import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.standalone.config.OtpConfigLoader; -import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.TraverseModeSet; import org.opentripplanner.test.support.ResourceLoader; @@ -133,7 +132,6 @@ public static TestOtpModel buildNewPortlandGraph(boolean withElevation) { .of(osmProvider, graph) .withStaticParkAndRide(true) .withStaticBikeParkAndRide(true) - .withStreetLimitationParameters(new StreetLimitationParameters()) .build(); osmModule.buildGraph(); } @@ -174,10 +172,7 @@ public static TestOtpModel buildOsmGraph(File osmFile) { var transitModel = new TransitModel(stopModel, deduplicator); // Add street data from OSM OsmProvider osmProvider = new OsmProvider(osmFile, true); - OsmModule osmModule = OsmModule - .of(osmProvider, graph) - .withStreetLimitationParameters(new StreetLimitationParameters()) - .build(); + OsmModule osmModule = OsmModule.of(osmProvider, graph).build(); osmModule.buildGraph(); return new TestOtpModel(graph, transitModel); } catch (Exception e) { diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java index 99ac1554134..091652a2dbf 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java @@ -33,7 +33,6 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.impl.GraphPathFinder; -import org.opentripplanner.street.model.StreetLimitationParameters; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.vertex.BarrierVertex; @@ -357,11 +356,7 @@ private void testBuildingAreas(boolean skipVisibility) { File file = RESOURCE_LOADER.file("usf_area.osm.pbf"); OsmProvider provider = new OsmProvider(file, false); - OsmModule loader = OsmModule - .of(provider, graph) - .withAreaVisibility(!skipVisibility) - .withStreetLimitationParameters(new StreetLimitationParameters()) - .build(); + OsmModule loader = OsmModule.of(provider, graph).withAreaVisibility(!skipVisibility).build(); loader.buildGraph(); From d2f1a794c11f0c9c5d80c048cc06593f56baacac Mon Sep 17 00:00:00 2001 From: Andrew Byrd Date: Sat, 10 Feb 2024 19:58:12 +0800 Subject: [PATCH 011/108] add documentation page about OTP frontends --- docs/Frontends.md | 76 ++++++++++++++++++++++++++++++++++++++++++++ docs/README-DOCS.txt | 2 ++ mkdocs.yml | 1 + 3 files changed, 79 insertions(+) create mode 100644 docs/Frontends.md diff --git a/docs/Frontends.md b/docs/Frontends.md new file mode 100644 index 00000000000..7ad318b599c --- /dev/null +++ b/docs/Frontends.md @@ -0,0 +1,76 @@ +# Frontends (User Interfaces) for OpenTripPlanner + +## Introduction + +OpenTripPlanner is a client-server system. A backend service written in Java is accessed over an API by some client software, which is generally embedded in a web page or a mobile app. When OpenTripPlanner developers refer to OpenTripPlanner or OTP, they often specifically mean the Java backend service. But for this service to be useful to end users who want to plan their journeys, you will typically also want to deploy a user interface as well as a few other components providing mapping capabilities and address lookup functionality. + +For the purposes of this document and in many OpenTripPlanner development discussions, the terms frontend, UI, and client will be used interchangeably. In reality many kinds of clients are possible, not all of them being user interfaces (UIs). The OpenTripPlanner API may be called by various scripts and microservices, potentially all within what would traditionally be called a "backend" with no user interface at all. For example, updates to trip plans could be requested, summarized and sent as text messages over a mobile network. But in this documentation page, we are exclusively talking about graphical user interfaces, usually written in Javascript for execution and display in web browsers. + +## Two Kinds of Frontends + +Broadly speaking there are two kinds of frontends for OTP: debug frontends and production frontends. + +**Debug frontends** are included in the main OpenTripPlanner repository, and are intended to work "out of the box" with little to no configuration. They are served by OTP itself or a simple local web server, or set up such that a single deployment is usable by anyone via a content delivery network (CDN). The primary purpose of debug frontends is to allow OTP developers (as well as people evaluating OpenTripPlanner for their organization) to observe, test, and debug an OTP instance they're working on. Debug frontends will expose some internal details of OTP operation such as graph nodes and edges, traversal permissions, or transit data entity identifiers. Their primary role is as a development and deployment tool. + +On the other hand, **production frontends** are intended to be a component of larger public-facing deployments of OTP, with a more polished appearance and user experience. They generally do not work "out of the box", and require a significant amount of configuration and coordination with external components such as map tile servers and geocoders. They are designed to be components of a large professionally maintained and managed OTP deployment, and present a simpler view of OpenTripPlanner options and results to a non-technical audience of end users. + +## Debug Frontends + +The main OpenTripPlanner repository currently contains two debug web frontends: the original one in [`/src/client`](https://github.com/opentripplanner/OpenTripPlanner/tree/dev-2.x/src/client) and a newer one currently under development at [`/client-next`](https://github.com/opentripplanner/OpenTripPlanner/tree/dev-2.x/client-next). + +The **original debug client** is a jQuery and Backbone based UI whose history can be traced back over a decade to the first days of the OTP project. It connects to the OTP Java backend via a REST API using the GTFS vocabulary. Historically this was the default OTP interface, and it continues to be available by default on any running OTP instance at the root URL. + +The **new debug client** is a React/TypeScript Single Page App (SPA) that can be served locally or accessed over a content delivery network (CDN). Unlike the original debug client, it connects to the OTP Java backend via the GraphQL API using the Transmodel vocabulary. It is currently under development, but expected to replace the original debug client once it reaches effective feature parity. + +There is a third piece of software that might qualify as an OTP client: a Java Swing application making use of the Processing visualization library, located in the [GraphVisualizer class](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java). While it would not be accurate to call this a "native" desktop application (as it's cross-platform Java) it is not a web app. This very developer-centric UI is also over a decade old and has been very sparsely maintained, but continues to exist because it can visualize the progress of searches through the street network, providing some insight into the internals of the routing algorithms that are not otherwise visible. + +## Working with Debug Frontends + +While the "classic" (i.e. old) debug frontend is enabled by default as of this writing, it may not be in the future, or you may wish to disable it if you've chosen to use a different frontend. Also, to get full use of the existing debug frontends you may want to enable OTP's built-in simple testing geocoder which performs fuzzy searches for transit stops by name, supplying their coordinates to the routing engine. Without it, you will be limited to origins and destinations selected on a map or specified in terms of latitude and longitude coordinates. The debug frontend and the geocoder can be toggled in `otp-config.json`: + +```json5 +// otp-config.json +{ + "otpFeatures": { + "DebugClient": true, + "SandboxAPIGeocoder": true + } +} +``` + +## Production Frontends + +Many different production OTP frontends exist. Any number of agencies and consultancies may have built new frontends, whether in Javascript or as native mobile apps, without the OpenTripPlanner development team even being aware of them. + +That said, there are two main Javascript-based web user interfaces that are generally recommended by members of the OTP development team who also work on professional, public-facing OpenTripPlanner deployments: +- The [Digitransit UI](https://github.com/HSLdevcom/digitransit-ui), part of the Finnish Digitransit project. +- The [OpenTripPlanner React UI](https://github.com/opentripplanner/otp-react-redux), developed and maintained by [Arcadis IBI](https://www.ibigroup.com)'s [transit routing team](https://www.ibigroup.com/ibi-products/transit-routing/). + +**Digitransit** is an open-source public transportation project, originally created in Finland to replace existing nationwide and regional journey planning solutions in 2014. Digitransit has since been used around the world in other projects, for example in Germany. It is a joint project of Helsinki Transit Authority (HSL), Fintraffic, and Waltti Solutions. + +**Arcadis IBI** has for several years actively developed, deployed, and maintained instances of the React-based OTP UI, systematically contributing their improvements back to the original repositories under the OpenTripPlanner organization: +- https://github.com/opentripplanner/otp-ui +- https://github.com/opentripplanner/otp-react-redux + +Both major frontend projects mentioned above support internationalization and have several translations already available. + +## Deploying a Production Frontend + +Deploying a full OpenTripPlanner-based system including a web UI can be quite complex, as there are many "moving parts". Such a system needs to remain responsive and function smoothly under load that varies wildly from one time of day to another, constantly refresh input data, and meet customer expectations in recognizing addresses and names of landmarks etc. Because deployment and management of such a system demands long term commitment by a professional team with sufficient experience and knowledge about the component parts, the major production frontend projects do not really aim to work "out of the box". There's a working assumption that they'll be actively customized for any large regional deployment. + +This understandably creates a bit of a barrier for organizations evaluating such components. Please don't hesitate to join the OpenTripPlanner Gitter chat and ask if you need any information on this subject. Several organizations that manage multiple large OTP deployments are active there and will be happy to engage with you. + +While you are welcome to stand up an instance of one of these UIs for testing or evaluation, and there has been some effort to make this more straightforward especially on the Arcadis IBI projects, it is still strongly recommended to collaborate with a specialist. The process of tailoring a deployment to a specific region and deployment environment is estimated to take at least several full days even for an experienced professional, and while it may start off looking deceptively simple, can rapidly grow into a seemingly never-ending project for those who are not aware of hidden pitfalls. The goal here is not by any means to discourage you from using or learning about any of these projects, but to set expectations that the process will be much smoother if you work with someone who's done it before. + +If you do decide to take on this task, be aware that both of these systems rely on multiple additional backend services other than the OTP Java backend. To get either one working, you will need to configure a source of map tiles and a geocoder that translates geographic names to latitude and longitude coordinates, among other components. + +## Historical Background + +The history of the more widely used OpenTripPlanner interfaces is roughly as follows: + +- From the beginning, OpenTripPlanner included a Javascript UI module. This jQuery and Backbone based UI was deployed as the main interface in several locations in the early to mid 2010s. +- As of early 2024, this UI is still present in the main OpenTripPlanner repository under `src/client`, but is not intended for use by the general public. It has long been regarded as a "debug" UI, serving as a built-in, readily available and familiar interface for OTP developers to test and debug the behavior of the backend. It uses the older OTP REST API which is slated for deprecation. +- In the late 2010s people started developing a new React-based UI as a more modular, modern interface for public consumption. This project is located at https://github.com/opentripplanner/otp-react-redux under the OpenTripPlanner Github organization, and is developed and maintainted by Arcadis IBI. +- Some React components were factored out of that UI project, allowing them to be integrated in different ways with different OTP deployments. This component library is in a separate repository at https://github.com/opentripplanner/otp-ui. Likewise, it is developed and maintained by Arcadis IBI. +- Meanwhile, starting in 2014, HSL (the Helsinki transit authority) and Finntrafic (the Finnish national transportation authority) began the Digitransit project, a set of open-source microservices to replace their existing national and regional scale trip planners. This includes a Javascript web UI module. In addition to Finland, the Digitransit system has been deployed in various places around the world including Germany. +- As of 2024, a completely new debug UI (again, intended for developer use rather than public consumption) is being developed in the main OpenTripPlanner repository under `src/debug-client-preview`. This new UI follows a more conventional contemporary Javascript development style, and uses the most recent OpenTripPlanner GraphQL API which is expected to fully replace the older REST API. diff --git a/docs/README-DOCS.txt b/docs/README-DOCS.txt index 3450a616124..52f7aa1a62d 100644 --- a/docs/README-DOCS.txt +++ b/docs/README-DOCS.txt @@ -8,3 +8,5 @@ In short, to preview the documentation as you work on it: $ pip install -r docs/requirements.txt $ mkdocs serve + +If you create any new documentation pages, be sure to update the `nav:` section of `mkdocs.yml` in the root of the repository to ensure that your page is included in the table of contents and documentation navigation tree. diff --git a/mkdocs.yml b/mkdocs.yml index e4341b296fc..82646fafb1c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - Netex and SIRI: 'Netex-Norway.md' - Troubleshooting: 'Troubleshooting-Routing.md' - Comparing OTP2 to OTP1: 'Version-Comparison.md' + - Frontends: 'Frontends.md' - APIs: - Introduction: 'apis/Apis.md' - GraphQL Tutorial: 'apis/GraphQL-Tutorial.md' From 0159b8d6851b3e26b9edd0c9e1476bcdc9a697f3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 12 Feb 2024 10:36:38 +0200 Subject: [PATCH 012/108] Remove duplicate issue store entry and adjust minimum speed limit --- .../issues/StreetCarSpeedZero.java | 19 ------------------- .../graph_builder/module/osm/OsmModule.java | 5 ----- .../wayproperty/WayPropertySet.java | 6 +++--- 3 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java b/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java deleted file mode 100644 index 1107486af97..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.opentripplanner.graph_builder.issues; - -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.openstreetmap.model.OSMWithTags; - -public record StreetCarSpeedZero(OSMWithTags entity) implements DataImportIssue { - private static final String FMT = "Way %s has car speed zero"; - private static final String HTMLFMT = "Way '%s' has car speed zero"; - - @Override - public String getMessage() { - return String.format(FMT, entity.getId()); - } - - @Override - public String getHTMLMessage() { - return String.format(HTMLFMT, entity.url(), entity.getId()); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 0685b739c7e..a365d2dc3eb 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -548,11 +548,6 @@ private StreetEdge getEdgeForStreet( seb.withBogusName(true); } - // < 0.04: account for - if (carSpeed < 0.04) { - issueStore.add(new StreetCarSpeedZero(way)); - } - StreetEdge street = seb.buildAndConnect(); params.edgeNamer().recordEdge(way, street); diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index f3be186f917..004bb5d5b0f 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -240,10 +240,10 @@ public float getCarSpeedForWay(OSMWithTags way, boolean backward) { getMetersSecondFromSpeed(way.getTag("maxspeed")); if (speed != null) { - // Too low or too high speed limit indicates an error in the data, we use default speed - // limits for the way type in that case. + // Too low (less than 5 km/h or too high speed limit indicates an error in the data, + // we use default speed limits for the way type in that case. // The small epsilon is to account for possible rounding errors. - if (speed < 0.0001 || speed > maxPossibleCarSpeed + 0.0001) { + if (speed < 1.387 || speed > maxPossibleCarSpeed + 0.0001) { var id = way.getId(); var link = way.url(); issueStore.add( From e5d58062ccc7d60d3e232b293b1910a5a28f6d08 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 12 Feb 2024 10:39:11 +0200 Subject: [PATCH 013/108] Fix comment --- .../openstreetmap/wayproperty/WayPropertySet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index 004bb5d5b0f..9a414a56080 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -240,7 +240,7 @@ public float getCarSpeedForWay(OSMWithTags way, boolean backward) { getMetersSecondFromSpeed(way.getTag("maxspeed")); if (speed != null) { - // Too low (less than 5 km/h or too high speed limit indicates an error in the data, + // Too low (less than 5 km/h) or too high speed limit indicates an error in the data, // we use default speed limits for the way type in that case. // The small epsilon is to account for possible rounding errors. if (speed < 1.387 || speed > maxPossibleCarSpeed + 0.0001) { From f4ffb4fabc321b61711d96669ec79fd48a8db8dc Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 12 Feb 2024 10:47:38 +0200 Subject: [PATCH 014/108] Remove import --- .../org/opentripplanner/graph_builder/module/osm/OsmModule.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index a365d2dc3eb..b279a4088f0 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -17,7 +17,6 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.graph_builder.issues.StreetCarSpeedZero; import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.graph_builder.module.osm.parameters.OsmProcessingParameters; import org.opentripplanner.openstreetmap.OsmProvider; From 6c7ab863d437447f3166ee9896f6f01aa4b27e34 Mon Sep 17 00:00:00 2001 From: Andrew Byrd Date: Mon, 12 Feb 2024 22:23:45 +0800 Subject: [PATCH 015/108] add brief OTP-RR setup hints --- docs/Frontends.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/Frontends.md b/docs/Frontends.md index 7ad318b599c..f1a2499511d 100644 --- a/docs/Frontends.md +++ b/docs/Frontends.md @@ -58,12 +58,20 @@ Both major frontend projects mentioned above support internationalization and ha Deploying a full OpenTripPlanner-based system including a web UI can be quite complex, as there are many "moving parts". Such a system needs to remain responsive and function smoothly under load that varies wildly from one time of day to another, constantly refresh input data, and meet customer expectations in recognizing addresses and names of landmarks etc. Because deployment and management of such a system demands long term commitment by a professional team with sufficient experience and knowledge about the component parts, the major production frontend projects do not really aim to work "out of the box". There's a working assumption that they'll be actively customized for any large regional deployment. -This understandably creates a bit of a barrier for organizations evaluating such components. Please don't hesitate to join the OpenTripPlanner Gitter chat and ask if you need any information on this subject. Several organizations that manage multiple large OTP deployments are active there and will be happy to engage with you. +Unfortunately this creates a bit of a barrier for organizations evaluating such components. Please don't hesitate to join the OpenTripPlanner Gitter chat and ask if you need any information on this subject. Several organizations that manage multiple large OTP deployments are active there and will be happy to engage with you. -While you are welcome to stand up an instance of one of these UIs for testing or evaluation, and there has been some effort to make this more straightforward especially on the Arcadis IBI projects, it is still strongly recommended to collaborate with a specialist. The process of tailoring a deployment to a specific region and deployment environment is estimated to take at least several full days even for an experienced professional, and while it may start off looking deceptively simple, can rapidly grow into a seemingly never-ending project for those who are not aware of hidden pitfalls. The goal here is not by any means to discourage you from using or learning about any of these projects, but to set expectations that the process will be much smoother if you work with someone who's done it before. +While you are welcome to stand up an instance of one of these UIs for testing or evaluation, and there has been some effort to make this more straightforward especially on the Arcadis IBI projects, it is still strongly recommended to collaborate with a specialist. The process of tailoring a deployment to a specific region and deployment environment is estimated to take at least several full days even for an experienced professional, and while it may start off looking deceptively simple, can rapidly grow into a seemingly never-ending project for those who are not aware of the pitfalls. The goal here is not by any means to discourage you from using or learning about any of these projects, but to set expectations that the process will be much smoother if you work with someone who's done it before. If you do decide to take on this task, be aware that both of these systems rely on multiple additional backend services other than the OTP Java backend. To get either one working, you will need to configure a source of map tiles and a geocoder that translates geographic names to latitude and longitude coordinates, among other components. +### Evaluating OTP-React-Redux + +At the time of this writing (early 2024) it is possible to try out the OTP-React-Redux production frontend maintained by Arcadis IBI with only a few adjustments to the example configuration file, so long as you are working on a **non-production test deployment**. To do so, you can use the map tile sets pre-configured in the map.baseLayers section of its `example-config.yml`, along with a test account from the maintainers of the Pelias geocoder at https://geocode.earth. They provide a free temporary geocoding account lasting about two weeks, which should be sufficient for testing out the system. You'll need to substitute their Pelias server URL and your temporary API key into the OTP-React-Redux `example-config.yml` in the geocoder.apiKey and geocoder.baseUrl sections. You'll also want to update the geocoder boundary rectangle and focusPoint. + +Finally, but perhaps most importantly, you need to update the `host` and `port` items at the beginning of that configuration file to point to host `http://localhost` and port 8080, and also uncomment the `v2: true` item, ensuring that it remains indented under the `api` section. OTP-React-Redux no longer supports the v1 API and will fail with a (somewhat invisible) Javascript console error if this v2 item is not enabled. + +Once configured and launched with `yarn install ; yarn start`, you should be able to access the UI in your browser. It supports several different languages out of the box, and will first check the `lang` key in `window.localstorage` then the `navigator.language` for ISO language codes such as `fr` or `es` before falling back on the `localization.defaultLocale` item defined in `example-config.yml`. + ## Historical Background The history of the more widely used OpenTripPlanner interfaces is roughly as follows: From 0631e8e4d89f8aa44154d1628a665e6a3604c308 Mon Sep 17 00:00:00 2001 From: Andrew Byrd Date: Tue, 13 Feb 2024 18:55:53 +0800 Subject: [PATCH 016/108] Update HSL to "Regional Transport Authority" in docs Co-authored-by: Joel Lappalainen --- docs/Frontends.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Frontends.md b/docs/Frontends.md index f1a2499511d..ee42f0d25bb 100644 --- a/docs/Frontends.md +++ b/docs/Frontends.md @@ -80,5 +80,5 @@ The history of the more widely used OpenTripPlanner interfaces is roughly as fol - As of early 2024, this UI is still present in the main OpenTripPlanner repository under `src/client`, but is not intended for use by the general public. It has long been regarded as a "debug" UI, serving as a built-in, readily available and familiar interface for OTP developers to test and debug the behavior of the backend. It uses the older OTP REST API which is slated for deprecation. - In the late 2010s people started developing a new React-based UI as a more modular, modern interface for public consumption. This project is located at https://github.com/opentripplanner/otp-react-redux under the OpenTripPlanner Github organization, and is developed and maintainted by Arcadis IBI. - Some React components were factored out of that UI project, allowing them to be integrated in different ways with different OTP deployments. This component library is in a separate repository at https://github.com/opentripplanner/otp-ui. Likewise, it is developed and maintained by Arcadis IBI. -- Meanwhile, starting in 2014, HSL (the Helsinki transit authority) and Finntrafic (the Finnish national transportation authority) began the Digitransit project, a set of open-source microservices to replace their existing national and regional scale trip planners. This includes a Javascript web UI module. In addition to Finland, the Digitransit system has been deployed in various places around the world including Germany. +- Meanwhile, starting in 2014, HSL (the Helsinki Regional Transport Authority) and Finntrafic (the Finnish national transportation authority) began the Digitransit project, a set of open-source microservices to replace their existing national and regional scale trip planners. This includes a Javascript web UI module. In addition to Finland, the Digitransit system has been deployed in various places around the world including Germany. - As of 2024, a completely new debug UI (again, intended for developer use rather than public consumption) is being developed in the main OpenTripPlanner repository under `src/debug-client-preview`. This new UI follows a more conventional contemporary Javascript development style, and uses the most recent OpenTripPlanner GraphQL API which is expected to fully replace the older REST API. From 15287e5dd8b6cb924dd1f0c78957bf2502e2f056 Mon Sep 17 00:00:00 2001 From: Andrew Byrd Date: Tue, 13 Feb 2024 18:56:03 +0800 Subject: [PATCH 017/108] Update HSL to "Regional Transport Authority" in docs Co-authored-by: Joel Lappalainen --- docs/Frontends.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Frontends.md b/docs/Frontends.md index ee42f0d25bb..3e671af6157 100644 --- a/docs/Frontends.md +++ b/docs/Frontends.md @@ -46,7 +46,7 @@ That said, there are two main Javascript-based web user interfaces that are gene - The [Digitransit UI](https://github.com/HSLdevcom/digitransit-ui), part of the Finnish Digitransit project. - The [OpenTripPlanner React UI](https://github.com/opentripplanner/otp-react-redux), developed and maintained by [Arcadis IBI](https://www.ibigroup.com)'s [transit routing team](https://www.ibigroup.com/ibi-products/transit-routing/). -**Digitransit** is an open-source public transportation project, originally created in Finland to replace existing nationwide and regional journey planning solutions in 2014. Digitransit has since been used around the world in other projects, for example in Germany. It is a joint project of Helsinki Transit Authority (HSL), Fintraffic, and Waltti Solutions. +**Digitransit** is an open-source public transportation project, originally created in Finland to replace existing nationwide and regional journey planning solutions in 2014. Digitransit has since been used around the world in other projects, for example in Germany. It is a joint project of Helsinki Regional Transport Authority (HSL), Fintraffic, and Waltti Solutions. **Arcadis IBI** has for several years actively developed, deployed, and maintained instances of the React-based OTP UI, systematically contributing their improvements back to the original repositories under the OpenTripPlanner organization: - https://github.com/opentripplanner/otp-ui From 593ae40eb66dbfdf35a0d2b32454b12a2b37bbd4 Mon Sep 17 00:00:00 2001 From: Andrew Byrd Date: Wed, 14 Feb 2024 12:57:05 +0800 Subject: [PATCH 018/108] remove OTP-RR specific configuration details --- docs/Frontends.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/Frontends.md b/docs/Frontends.md index 3e671af6157..e8b299a52ac 100644 --- a/docs/Frontends.md +++ b/docs/Frontends.md @@ -60,17 +60,9 @@ Deploying a full OpenTripPlanner-based system including a web UI can be quite co Unfortunately this creates a bit of a barrier for organizations evaluating such components. Please don't hesitate to join the OpenTripPlanner Gitter chat and ask if you need any information on this subject. Several organizations that manage multiple large OTP deployments are active there and will be happy to engage with you. -While you are welcome to stand up an instance of one of these UIs for testing or evaluation, and there has been some effort to make this more straightforward especially on the Arcadis IBI projects, it is still strongly recommended to collaborate with a specialist. The process of tailoring a deployment to a specific region and deployment environment is estimated to take at least several full days even for an experienced professional, and while it may start off looking deceptively simple, can rapidly grow into a seemingly never-ending project for those who are not aware of the pitfalls. The goal here is not by any means to discourage you from using or learning about any of these projects, but to set expectations that the process will be much smoother if you work with someone who's done it before. +While you are welcome to stand up an instance of one of these UIs for testing or evaluation, and there has been some effort to make this more straightforward especially on the Arcadis IBI projects, it is still strongly recommended to collaborate with a specialist. The process of tailoring a deployment to a specific region and deployment environment is estimated to take at least several full days even for an experienced professional, and while it may start off looking deceptively simple, can rapidly grow into a seemingly never-ending project for those who are not aware of the pitfalls. The goal here is not by any means to discourage you from using or learning about any of these projects, but to set expectations that the process will be much smoother if you work with someone who's done it before. Be aware that both of these systems rely on multiple additional backend services other than the OTP Java backend. To get either one working, you will need to configure a source of map tiles and a geocoder that translates geographic names to latitude and longitude coordinates, among other components. -If you do decide to take on this task, be aware that both of these systems rely on multiple additional backend services other than the OTP Java backend. To get either one working, you will need to configure a source of map tiles and a geocoder that translates geographic names to latitude and longitude coordinates, among other components. - -### Evaluating OTP-React-Redux - -At the time of this writing (early 2024) it is possible to try out the OTP-React-Redux production frontend maintained by Arcadis IBI with only a few adjustments to the example configuration file, so long as you are working on a **non-production test deployment**. To do so, you can use the map tile sets pre-configured in the map.baseLayers section of its `example-config.yml`, along with a test account from the maintainers of the Pelias geocoder at https://geocode.earth. They provide a free temporary geocoding account lasting about two weeks, which should be sufficient for testing out the system. You'll need to substitute their Pelias server URL and your temporary API key into the OTP-React-Redux `example-config.yml` in the geocoder.apiKey and geocoder.baseUrl sections. You'll also want to update the geocoder boundary rectangle and focusPoint. - -Finally, but perhaps most importantly, you need to update the `host` and `port` items at the beginning of that configuration file to point to host `http://localhost` and port 8080, and also uncomment the `v2: true` item, ensuring that it remains indented under the `api` section. OTP-React-Redux no longer supports the v1 API and will fail with a (somewhat invisible) Javascript console error if this v2 item is not enabled. - -Once configured and launched with `yarn install ; yarn start`, you should be able to access the UI in your browser. It supports several different languages out of the box, and will first check the `lang` key in `window.localstorage` then the `navigator.language` for ISO language codes such as `fr` or `es` before falling back on the `localization.defaultLocale` item defined in `example-config.yml`. +That said, as of this writing (early 2024) it may now be possible to get a minimal, non-production test deployment of OTP-React-Redux working with only a few adjustments to the example configuration file. See the README and `example-config.yml` in [the OTP-RR repo](https://github.com/opentripplanner/otp-react-redux). ## Historical Background From 95dcfd70f988d36a05100b4fa3f038d9f2907cba Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 15 Feb 2024 18:33:30 +0100 Subject: [PATCH 019/108] Add some defensive code --- .../ext/stopconsolidation/DecorateConsolidatedStopNames.java | 2 +- .../ext/stopconsolidation/StopConsolidationService.java | 3 ++- .../internal/DefaultStopConsolidationService.java | 4 ++-- .../ext/stopconsolidation/model/ConsolidatedStopLeg.java | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java index a287e6a7d66..1806b3e9e32 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java @@ -41,7 +41,7 @@ private void replaceConsolidatedStops(Itinerary i) { if (leg instanceof ScheduledTransitLeg stl && needsToRenameStops(stl)) { var agency = leg.getAgency(); // to show the name on the stop signage we use the primary stop's name - var from = service.primaryStop(stl.getFrom().stop.getId()); + var from = service.primaryStop(stl.getFrom().stop.getId()).orElse(stl.getFrom().stop); // to show the name that's on the display inside the vehicle we use the agency-specific name var to = service.agencySpecificStop(stl.getTo().stop, agency); return new ConsolidatedStopLeg(stl, from, to); diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java index a829a905e27..11ad4be69ff 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.stopconsolidation; import java.util.List; +import java.util.Optional; import org.opentripplanner.ext.stopconsolidation.model.StopReplacement; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.organization.Agency; @@ -41,5 +42,5 @@ public interface StopConsolidationService { /** * For a given stop id return the primary stop if it is part of a consolidated stop group. */ - StopLocation primaryStop(FeedScopedId id); + Optional primaryStop(FeedScopedId id); } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java index 51a57028121..216489512f5 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java @@ -94,7 +94,7 @@ private Optional findAgencySpecificStop(StopLocation stop, Agency } @Override - public StopLocation primaryStop(FeedScopedId id) { + public Optional primaryStop(FeedScopedId id) { var primaryId = repo .groups() .stream() @@ -102,6 +102,6 @@ public StopLocation primaryStop(FeedScopedId id) { .map(ConsolidatedStopGroup::primary) .findAny() .orElse(id); - return transitModel.getStopModel().getRegularStop(primaryId); + return Optional.ofNullable(transitModel.getStopModel().getRegularStop(primaryId)); } } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java index 39f06bd6347..a784bb5900e 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.stopconsolidation.model; +import java.util.Objects; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; @@ -12,8 +13,8 @@ public class ConsolidatedStopLeg extends ScheduledTransitLeg { public ConsolidatedStopLeg(ScheduledTransitLeg original, StopLocation from, StopLocation to) { super(new ScheduledTransitLegBuilder<>(original)); - this.from = from; - this.to = to; + this.from = Objects.requireNonNull(from); + this.to = Objects.requireNonNull(to); } @Override From 35629821f0ab29e04cfd20702d877a5eade79ad1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 16 Feb 2024 07:58:59 +0100 Subject: [PATCH 020/108] Fix finding of data source --- .../java/org/opentripplanner/datastore/OtpDataStore.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java index fd63eb7f8dd..937b4fb8203 100644 --- a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java +++ b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java @@ -104,9 +104,13 @@ public void open() { streetGraph = findSingleSource(config.streetGraph(), STREET_GRAPH_FILENAME, GRAPH); graph = findSingleSource(config.graph(), GRAPH_FILENAME, GRAPH); - stopConsolidation = findSingleSource(config.stopConsolidation(), "", CONFIG); buildReportDir = findCompositeSource(config.reportDirectory(), BUILD_REPORT_DIR, REPORT); + if (config.stopConsolidation() != null) { + stopConsolidation = + findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), CONFIG)); + } + addAll(Arrays.asList(streetGraph, graph, buildReportDir)); // Also read in unknown sources in case the data input source is miss-spelled, From a950d20ece9c87972872346d5de09022e4d72ff1 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 16 Feb 2024 12:05:02 +0200 Subject: [PATCH 021/108] Create class for holding street constants and use it for maxCarSpeed --- .../routing/impl/GraphPathFinder.java | 3 ++- .../street/model/StreetConstants.java | 14 ++++++++++++++ .../street/model/StreetLimitationParameters.java | 2 +- .../EuclideanRemainingWeightHeuristic.java | 3 ++- ...faultStreetLimitationParametersServiceTest.java | 3 ++- 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/opentripplanner/street/model/StreetConstants.java diff --git a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index 0145eacd5ac..d8de0332238 100644 --- a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -15,6 +15,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.StreetPreferences; import org.opentripplanner.routing.error.PathNotFoundException; +import org.opentripplanner.street.model.StreetConstants; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.StreetSearchBuilder; @@ -59,7 +60,7 @@ public class GraphPathFinder { private final float maxCarSpeed; public GraphPathFinder(@Nullable TraverseVisitor traverseVisitor) { - this(traverseVisitor, null, 40f); + this(traverseVisitor, null, StreetConstants.DEFAULT_MAX_CAR_SPEED); } public GraphPathFinder( diff --git a/src/main/java/org/opentripplanner/street/model/StreetConstants.java b/src/main/java/org/opentripplanner/street/model/StreetConstants.java new file mode 100644 index 00000000000..e3b01d84e7a --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/StreetConstants.java @@ -0,0 +1,14 @@ +package org.opentripplanner.street.model; + +/** + * This class holds constant values related to streets. If a value is only accessed from one place, + * it's better to store it there instead of here. + */ +public class StreetConstants { + + /** + * Default car speed that is used when max car speed has not been (yet) determined from the OSM + * data. Unit is m/s and value equals to 144 km/h. + */ + public static final float DEFAULT_MAX_CAR_SPEED = 40f; +} diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java index 18937abad9b..995cf4a30f6 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java @@ -12,7 +12,7 @@ @Singleton public class StreetLimitationParameters implements Serializable { - private float maxCarSpeed = 40f; + private float maxCarSpeed = StreetConstants.DEFAULT_MAX_CAR_SPEED; @Inject public StreetLimitationParameters() {} diff --git a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java index 5891f60bed1..dbc7d48f5da 100644 --- a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java +++ b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java @@ -5,6 +5,7 @@ import org.opentripplanner.framework.geometry.SphericalDistanceLibrary; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.street.model.StreetConstants; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.FreeEdge; import org.opentripplanner.street.model.vertex.Vertex; @@ -16,7 +17,7 @@ */ public class EuclideanRemainingWeightHeuristic implements RemainingWeightHeuristic { - private static final Float DEFAULT_MAX_CAR_SPEED = 40f; + private static final Float DEFAULT_MAX_CAR_SPEED = StreetConstants.DEFAULT_MAX_CAR_SPEED; private double lat; private double lon; diff --git a/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java b/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java index 27cf531a4fc..dd467d6a567 100644 --- a/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java +++ b/src/test/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersServiceTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; +import org.opentripplanner.street.model.StreetConstants; import org.opentripplanner.street.model.StreetLimitationParameters; public class DefaultStreetLimitationParametersServiceTest { @@ -20,6 +21,6 @@ public void getMaxCarSpeed() { public void getDefaultMaxCarSpeed() { var model = new StreetLimitationParameters(); var service = new DefaultStreetLimitationParametersService(model); - assertEquals(40f, service.getMaxCarSpeed()); + assertEquals(StreetConstants.DEFAULT_MAX_CAR_SPEED, service.getMaxCarSpeed()); } } From a8c455320b6ffbfc2f1d2bf67c0a2b91b0f280a2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 16 Feb 2024 12:05:40 +0200 Subject: [PATCH 022/108] Use correct injection annotations --- .../street/model/StreetLimitationParameters.java | 2 -- .../service/DefaultStreetLimitationParametersService.java | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java index 995cf4a30f6..bc346d9264a 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java +++ b/src/main/java/org/opentripplanner/street/model/StreetLimitationParameters.java @@ -1,7 +1,6 @@ package org.opentripplanner.street.model; import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.io.Serializable; /** @@ -9,7 +8,6 @@ *

* TODO this can be expanded to include some fields from the {@link org.opentripplanner.routing.graph.Graph}. */ -@Singleton public class StreetLimitationParameters implements Serializable { private float maxCarSpeed = StreetConstants.DEFAULT_MAX_CAR_SPEED; diff --git a/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java b/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java index 6b42cef4b58..4cfb52c48b8 100644 --- a/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java +++ b/src/main/java/org/opentripplanner/street/service/DefaultStreetLimitationParametersService.java @@ -1,13 +1,13 @@ package org.opentripplanner.street.service; -import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.opentripplanner.street.model.StreetLimitationParameters; +@Singleton public class DefaultStreetLimitationParametersService implements StreetLimitationParametersService { private final StreetLimitationParameters streetLimitationParameters; - @Inject public DefaultStreetLimitationParametersService( StreetLimitationParameters streetLimitationParameters ) { From c7c035413ae575e28efb51b81210e3704dbab6a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 00:16:12 +0000 Subject: [PATCH 023/108] Update dependency org.entur.gbfs:gbfs-java-model to v3.0.24 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 03d408a4b2e..e11a3fda96b 100644 --- a/pom.xml +++ b/pom.xml @@ -688,7 +688,7 @@ org.entur.gbfs gbfs-java-model - 3.0.20 + 3.0.24 From 29de909ed3119407060c8b30e83419e1b49b494c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 02:12:54 +0000 Subject: [PATCH 024/108] Update dependency ch.qos.logback:logback-classic to v1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e11a3fda96b..d9729261ddc 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.2 1.12.2 5.5.3 - 1.4.14 + 1.5.0 9.9.1 2.0.12 2.0.15 From 5da22683573f94b5d32f4711e29ff2252c28fcac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:26:36 +0000 Subject: [PATCH 025/108] Update dependency org.apache.commons:commons-compress to v1.26.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d9729261ddc..a621ebb5b00 100644 --- a/pom.xml +++ b/pom.xml @@ -929,7 +929,7 @@ org.apache.commons commons-compress - 1.25.0 + 1.26.0 test From 257dda2a4e2130f1c059641dbb773a98e1c9ed22 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 19 Feb 2024 15:54:41 +0100 Subject: [PATCH 026/108] feature: Remove current time-penalty implementation --- .../raptor/api/model/RaptorConstants.java | 5 + .../mapping/RaptorPathToItineraryMapper.java | 4 - .../transit/DefaultAccessEgress.java | 50 +-------- .../transit/FlexAccessEgressAdapter.java | 10 +- .../AccessEgressPenaltyDecoratorTest.java | 7 +- .../transit/DefaultAccessEgressTest.java | 102 +----------------- 6 files changed, 12 insertions(+), 166 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java index f08b39641ed..5257c49fa35 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java @@ -21,6 +21,11 @@ */ public class RaptorConstants { + /** + * Zero (0) constant used inside Raptor. + */ + public static final int ZERO = 0; + /** * This constant is used to indicate that a value is not set. This applies to parameters of type * {@code generalized-cost}, {@code link min-travel-time} and {@code duration} inside Raptor. diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 6e365b4cd49..2780cc8ba14 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -181,10 +181,6 @@ private List mapAccessLeg(AccessPathLeg accessPathLeg) { int fromTime = accessPathLeg.fromTime(); - if (accessPath.hasPenalty()) { - fromTime = accessPath.timeShiftDepartureTimeToActualTime(fromTime); - } - return subItinerary.withTimeShiftToStartAt(createZonedDateTime(fromTime)).getLegs(); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index e0ca070d76f..34b7c71cf17 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -1,10 +1,8 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit; import java.util.Objects; -import java.util.function.IntUnaryOperator; import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; -import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.street.search.state.State; @@ -36,7 +34,7 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) { throw new IllegalStateException("Can not add penalty twice..."); } this.stop = other.stop(); - this.durationInSeconds = other.durationInSeconds() + (int) penalty.time().toSeconds(); + this.durationInSeconds = other.durationInSeconds(); this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds(); this.penalty = penalty; this.lastState = other.getLastState(); @@ -97,10 +95,6 @@ public int latestArrivalTime(int requestedArrivalTime) { return requestedArrivalTime; } - public int timeShiftDepartureTimeToActualTime(int computedDepartureTimeIncludingPenalty) { - return computedDepartureTimeIncludingPenalty + penalty.timeInSeconds(); - } - @Override public String toString() { return asString(true, true, summary()); @@ -129,48 +123,6 @@ public int hashCode() { return Objects.hash(stop, durationInSeconds, generalizedCost, penalty); } - /** - * Allow a subclass to calculate the departureTime if it has opening hours. This method will - * adjust the times to apply the time penalty correct. - *

- * The penalty must be removed before calculating the departure with the opening hours. - * Then before returning, the penalty must be added back. If the departure is not possible, - * this "state" must be kept. - */ - protected int calculateEarliestDepartureTimeWithOpeningHours( - int requestedDepartureTime, - IntUnaryOperator calculateFirstPossibleDeparture - ) { - int dt = penalty().timeInSeconds(); - int actual = requestedDepartureTime + dt; - int adjusted = calculateFirstPossibleDeparture.applyAsInt(actual); - return ifNotSet(adjusted, v -> v - dt); - } - - /** - * Allow a subclass to calculate the arrivalTime if it has opening hours. This method will adjust - * the times to apply the time penalty correct. - *

- * The penalty must be removed before calculating the arrival with the opening hours. - * Then before returning, the penalty must be added back. If the arrival is not possible, - * this "state" must be kept. - */ - protected int calculateLatestArrivalTimeWithOpeningHours( - int requestedArrivalTime, - IntUnaryOperator calculateLatestPossibleArrival - ) { - int dt = penalty().timeInSeconds(); - int actual = requestedArrivalTime - dt; - int adjusted = calculateLatestPossibleArrival.applyAsInt(actual); - return ifNotSet(adjusted, v -> v + dt); - } - - protected int ifNotSet(int value, IntUnaryOperator body) { - return value == RaptorConstants.TIME_NOT_SET - ? RaptorConstants.TIME_NOT_SET - : body.applyAsInt(value); - } - /** * Include summary information in toString. We only include information relevant for using this * in routing (not latestState). diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java index af602b857ad..0cf95b544f4 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java @@ -27,18 +27,12 @@ private FlexAccessEgressAdapter(FlexAccessEgressAdapter other, TimeAndCost penal @Override public int earliestDepartureTime(int requestedDepartureTime) { - return calculateEarliestDepartureTimeWithOpeningHours( - requestedDepartureTime, - v -> mapToRaptorTime(flexAccessEgress.earliestDepartureTime(v)) - ); + return mapToRaptorTime(flexAccessEgress.earliestDepartureTime(requestedDepartureTime)); } @Override public int latestArrivalTime(int requestedArrivalTime) { - return calculateLatestArrivalTimeWithOpeningHours( - requestedArrivalTime, - v -> mapToRaptorTime(flexAccessEgress.latestArrivalTime(v)) - ); + return mapToRaptorTime(flexAccessEgress.latestArrivalTime(requestedArrivalTime)); } @Override diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java index e063ddfbb14..7d68c5bfeeb 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java @@ -42,12 +42,9 @@ class AccessEgressPenaltyDecoratorTest { @BeforeAll static void verifyTestSetup() { + assertEquals("Walk 2m15s C₁238_035 w/penalty(13m23s $1606) ~ 1", EXP_WALK_W_PENALTY.toString()); assertEquals( - "Walk 15m38s C₁238_035 w/penalty(13m23s $1606) ~ 1", - EXP_WALK_W_PENALTY.toString() - ); - assertEquals( - "Walk 11m53s C₁237_887 w/penalty(11m8s $1336) ~ 1", + "Walk 45s C₁237_887 w/penalty(11m8s $1336) ~ 1", EXP_CAR_RENTAL_W_PENALTY.toString() ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index a9e088a2ad3..494e25df62e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.framework.model.TimeAndCost; -import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; @@ -34,7 +33,7 @@ void durationInSeconds() { // TODO - The value is ? int expected = 118215; assertEquals(expected, subject.durationInSeconds()); - assertEquals(expected + TIME_PENALTY.toSeconds(), subjectWithPenalty.durationInSeconds()); + assertEquals(expected, subjectWithPenalty.durationInSeconds()); } @Test @@ -118,106 +117,9 @@ void latestArrivalTime() { assertEquals(89, subject.latestArrivalTime(89)); } - @Test - void timeShiftDepartureTimeToActualTime() { - assertEquals(89, subject.timeShiftDepartureTimeToActualTime(89)); - assertEquals( - 89 + PENALTY.timeInSeconds(), - subjectWithPenalty.timeShiftDepartureTimeToActualTime(89) - ); - } - @Test void testToString() { assertEquals("Walk 1d8h50m15s C₁236_429 ~ 5", subject.toString()); - assertEquals("Walk 1d8h50m16s C₁236_440 w/penalty(1s $11) ~ 5", subjectWithPenalty.toString()); - } - - @Test - void calculateEarliestDepartureTimeWithOpeningHours_NoPenalty() { - final int requestedTime = 100; - final int opensAtTime = 120; - assertEquals( - opensAtTime, - subject.calculateEarliestDepartureTimeWithOpeningHours( - requestedTime, - v -> { - assertEquals(requestedTime, v); - return opensAtTime; - } - ) - ); - } - - @Test - void calculateEarliestDepartureTimeWithOpeningHours_OpensAt() { - final int requestedTime = 100; - final int opensAtTime = 120; - - assertEquals( - opensAtTime - PENALTY.timeInSeconds(), - subjectWithPenalty.calculateEarliestDepartureTimeWithOpeningHours( - requestedTime, - v -> { - assertEquals(requestedTime + PENALTY.timeInSeconds(), v); - return opensAtTime; - } - ) - ); - } - - @Test - void calculateEarliestDepartureTimeWithOpeningHours_Closed() { - assertEquals( - RaptorConstants.TIME_NOT_SET, - subjectWithPenalty.calculateEarliestDepartureTimeWithOpeningHours( - 879789, - v -> RaptorConstants.TIME_NOT_SET - ) - ); - } - - @Test - void calculateLatestArrivalTimeWithOpeningHours_NoPenalty() { - final int requestedTime = 100; - final int closesAtTime = 80; - assertEquals( - closesAtTime, - subject.calculateLatestArrivalTimeWithOpeningHours( - requestedTime, - v -> { - assertEquals(requestedTime, v); - return closesAtTime; - } - ) - ); - } - - @Test - void calculateLatestArrivalTimeWithOpeningHours_ClosesAt() { - final int requestedTime = 100; - final int closesAtTime = 80; - - assertEquals( - closesAtTime + PENALTY.timeInSeconds(), - subjectWithPenalty.calculateLatestArrivalTimeWithOpeningHours( - requestedTime, - v -> { - assertEquals(requestedTime - PENALTY.timeInSeconds(), v); - return closesAtTime; - } - ) - ); - } - - @Test - void calculateLatestArrivalTimeWithOpeningHours_Closed() { - assertEquals( - RaptorConstants.TIME_NOT_SET, - subjectWithPenalty.calculateLatestArrivalTimeWithOpeningHours( - 879789, - v -> RaptorConstants.TIME_NOT_SET - ) - ); + assertEquals("Walk 1d8h50m15s C₁236_440 w/penalty(1s $11) ~ 5", subjectWithPenalty.toString()); } } From fdb1a7d736d544e5c3178741677005cb26b6e73b Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 19 Feb 2024 16:33:23 +0100 Subject: [PATCH 027/108] feature: Add time-penalty to RaptorAccessEgress and implement it in the OTP model --- .../raptor/api/model/RaptorAccessEgress.java | 29 +++++++++++++++++++ .../transit/DefaultAccessEgress.java | 5 ++++ .../transit/DefaultAccessEgressTest.java | 7 +++++ 3 files changed, 41 insertions(+) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index 16dff3b2e99..de1c91a0b4b 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -45,6 +45,35 @@ public interface RaptorAccessEgress { */ int durationInSeconds(); + /** + * Raptor can add an optional time-penalty to a access/egress to make it less favourable compared + * with other access/egress/transit options (paths). The penalty is a virtual extra duration of + * time added inside Raptor when comparing time. The penalty does not propagate the c1 or c2 cost + * values. This feature is useful when you want to limit the access/egress and the access/egress + * is FASTER than the preferred option. + *

+ * For example, for Park&Ride, driving all the way to the + * destination is very often the best option when looking at the time criteria. When an + * increasing time-penalty is applied to access/egress with driving then driving less become + * more favorable. This also improves perfomance, since we usually add a very high cost to + * driving - making all park&ride access legs optimal - forcing Raptor to compute a path for + * every option. The short drives are optimal on cost, and the long are optimal on time. In the + * case of park&ride the time-penalty enables Raptor to choose one of the shortest access/egress + * paths over the longer ones. + *

+ * Another example is FLEX, where we in many use-cases want regular transit to win if there is + * an offer. Only in the case where the FLEX is the only solution we want it to be presented. + * To achieve this, we must add an extra duration to the time of the FLEX access/egress - it does + * not help to just edd extra cost - witch makes both FLEX optimal on time and transit optimal on + * cost. Many optimal access paths have an inpact on performance as vell. + *

+ * + * The unit is seconds and default value is 0 seconds. + */ + default int timePenalty() { + return RaptorConstants.ZERO; + } + /* TIME-DEPENDENT ACCESS/TRANSFER/EGRESS */ // The methods below should be only overridden when an RaptorAccessEgress is only available at // specific times, such as flexible transit, TNC or shared vehicle schemes with limited opening diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 34b7c71cf17..000e56b300c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -54,6 +54,11 @@ public int durationInSeconds() { return durationInSeconds; } + @Override + public int timePenalty() { + return penalty.timeInSeconds(); + } + @Override public int stop() { return stop; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 494e25df62e..6037eae74d7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -36,6 +36,13 @@ void durationInSeconds() { assertEquals(expected, subjectWithPenalty.durationInSeconds()); } + @Test + void timePenalty() { + int expected = (int) TIME_PENALTY.toSeconds(); + assertEquals(expected, subject.timePenalty()); + assertEquals(expected, subjectWithPenalty.timePenalty()); + } + @Test void stop() { assertEquals(STOP, subject.stop()); From 3807451a4eae2815ffc6b88440d7c52fce6494e1 Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 20 Feb 2024 10:24:03 +0100 Subject: [PATCH 028/108] feat: add short name in agency, use short name if name not exists --- .../netex/mapping/AuthorityToAgencyMapper.java | 6 +++++- .../transit/model/organization/Agency.java | 18 ++++++++++++++++-- .../model/organization/AgencyBuilder.java | 9 +++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java index 5e9727f4462..60d77ac2b47 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java @@ -33,9 +33,13 @@ class AuthorityToAgencyMapper { * Map authority and time zone to OTP agency. */ Agency mapAuthorityToAgency(Authority source) { + String agencyName = source.getName() != null ? source.getName().getValue() : null; + String agencyShortName = source.getShortName() != null ? source.getShortName().getValue() : null; + AgencyBuilder target = Agency .of(idFactory.createId(source.getId())) - .withName(source.getName().getValue()) + .withName(agencyName) + .withShortName(agencyShortName) .withTimezone(timeZone); withOptional( diff --git a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java index 62de89e639b..8ed66a97ce1 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java @@ -17,6 +17,7 @@ public final class Agency extends AbstractTransitEntity implements LogInfo { private final String name; + private final String shortName; private final ZoneId timezone; private final String url; private final String lang; @@ -26,9 +27,17 @@ public final class Agency extends AbstractTransitEntity i Agency(AgencyBuilder builder) { super(builder.getId()); + // Fill agency name, if not exists take short name + String nameValue = (builder.getName() != null && !builder.getName().isBlank()) + ? builder.getName() + : builder.getShortName() != null && !builder.getShortName().isBlank() + ? builder.getShortName() + : null; + // Required fields - this.name = - assertHasValue(builder.getName(), "Missing mandatory name on Agency %s", builder.getId()); + this.name = assertHasValue(nameValue, "Missing mandatory name on Agency %s", builder.getId()); + this.shortName = builder.getShortName(); + this.timezone = ZoneId.of( assertHasValue( @@ -54,6 +63,10 @@ public static AgencyBuilder of(@Nonnull FeedScopedId id) { public String getName() { return logName(); } + @Nullable + public String getShortName() { + return shortName; + } @Nonnull public ZoneId getTimezone() { @@ -102,6 +115,7 @@ public boolean sameAs(@Nonnull Agency other) { return ( getId().equals(other.getId()) && Objects.equals(name, other.name) && + Objects.equals(shortName, other.shortName) && Objects.equals(timezone, other.timezone) && Objects.equals(url, other.url) && Objects.equals(lang, other.lang) && diff --git a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java index 8411797f888..18c2dd09eb2 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java @@ -7,6 +7,7 @@ public class AgencyBuilder extends AbstractEntityBuilder { private String name; + private String shortName; private String timezone; private String url; private String lang; @@ -21,6 +22,7 @@ public class AgencyBuilder extends AbstractEntityBuilder AgencyBuilder(@Nonnull Agency original) { super(original); this.name = original.getName(); + this.shortName = original.getShortName(); this.timezone = original.getTimezone().getId(); this.url = original.getUrl(); this.lang = original.getLang(); @@ -32,11 +34,18 @@ public class AgencyBuilder extends AbstractEntityBuilder public String getName() { return name; } + public String getShortName() { + return shortName; + } public AgencyBuilder withName(String name) { this.name = name; return this; } + public AgencyBuilder withShortName(String shortName) { + this.shortName = shortName; + return this; + } public String getTimezone() { return timezone; From 884d6cd201a04dad65b4a20d7ee0880374e2bc8a Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 20 Feb 2024 11:37:39 +0100 Subject: [PATCH 029/108] test: test agency short name --- .../netex/mapping/AuthorityToAgencyMapperTest.java | 10 +++++++--- .../transit/model/organization/AgencyTest.java | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java index 8ab84a4084a..3a47052b022 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java @@ -14,6 +14,7 @@ public class AuthorityToAgencyMapperTest { private static final String ID = "ID"; private static final String NAME = "Olsen"; + private static final String SHORT_NAME = "Short"; private static final String URL = "http://olsen.no/help"; private static final String PHONE = "+47 88882222"; private static final String TIME_ZONE = "CET"; @@ -24,7 +25,7 @@ public class AuthorityToAgencyMapperTest { @Test public void mapAgency() { // Given - Authority authority = authority(ID, NAME, URL, PHONE); + Authority authority = authority(ID, NAME, SHORT_NAME, URL, PHONE); // When mapped Agency a = mapper.mapAuthorityToAgency(authority); @@ -32,6 +33,7 @@ public void mapAgency() { // Then expect assertEquals(ID, a.getId().getId()); assertEquals(NAME, a.getName()); + assertEquals(SHORT_NAME, a.getShortName()); assertEquals(TIME_ZONE, a.getTimezone().getId()); assertEquals(URL, a.getUrl()); assertEquals(PHONE, a.getPhone()); @@ -40,7 +42,7 @@ public void mapAgency() { @Test public void mapAgencyWithoutOptionalElements() { // Given - Authority authority = authority(ID, NAME, null, null); + Authority authority = authority(ID, NAME, null, null, null); // When mapped Agency a = mapper.mapAuthorityToAgency(authority); @@ -48,6 +50,7 @@ public void mapAgencyWithoutOptionalElements() { // Then expect assertNull(a.getUrl()); assertNull(a.getPhone()); + assertNull(a.getShortName()); } @Test @@ -64,9 +67,10 @@ public void getDefaultAgency() { } @SuppressWarnings("SameParameterValue") - private static Authority authority(String id, String name, String url, String phone) { + private static Authority authority(String id, String name, String shortName, String url, String phone) { return new Authority() .withId(id) + .withShortName(new MultilingualString().withValue(shortName)) .withName(new MultilingualString().withValue(name)) .withContactDetails(new ContactStructure().withUrl(url).withPhone(phone)); } diff --git a/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java b/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java index b2cf1a57db3..ad5f0d9c289 100644 --- a/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java +++ b/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java @@ -14,6 +14,7 @@ class AgencyTest { private static final String ID = "1"; private static final String BRANDING_URL = "http://branding.aaa.com"; private static final String NAME = "name"; + private static final String SHORT_NAME = "shortname"; private static final String URL = "http://info.aaa.com"; private static final String TIMEZONE = "Europe/Oslo"; private static final String PHONE = "+47 95566333"; @@ -23,6 +24,7 @@ class AgencyTest { private static final Agency subject = Agency .of(TransitModelForTest.id(ID)) .withName(NAME) + .withShortName(SHORT_NAME) .withUrl(URL) .withTimezone(TIMEZONE) .withPhone(PHONE) @@ -48,6 +50,7 @@ void copy() { assertEquals(subject, copy); assertEquals(ID, copy.getId().getId()); + assertEquals(SHORT_NAME, copy.getShortName()); assertEquals("v2", copy.getName()); assertEquals(URL, copy.getUrl()); assertEquals(TIMEZONE, copy.getTimezone().getId()); @@ -62,6 +65,7 @@ void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); assertFalse(subject.sameAs(subject.copy().withId(TransitModelForTest.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withName("X").build())); + assertFalse(subject.sameAs(subject.copy().withShortName("X").build())); assertFalse(subject.sameAs(subject.copy().withUrl("X").build())); assertFalse(subject.sameAs(subject.copy().withTimezone("CET").build())); assertFalse(subject.sameAs(subject.copy().withPhone("X").build())); From d45a7a66eb7cec1d2017aea3949e7281ffd6ecf4 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 20 Feb 2024 12:13:13 +0100 Subject: [PATCH 030/108] feature: Remove searchWindowAccessSlackInSeconds (used with access time penalty) --- .../raptor/api/model/RaptorAccessEgress.java | 8 +- .../raptor/api/request/SearchParams.java | 36 +----- .../api/request/SearchParamsBuilder.java | 11 -- .../rangeraptor/DefaultRangeRaptorWorker.java | 8 +- .../ForwardRaptorTransitCalculator.java | 4 +- .../ReverseRaptorTransitCalculator.java | 4 +- .../raptoradapter/router/TransitRouter.java | 1 - .../router/street/AccessEgresses.java | 9 -- .../transit/mappers/RaptorRequestMapper.java | 11 -- .../J01_SearchWindowAccessSlack.java | 103 ------------------ .../router/street/AccessEgressesTest.java | 5 - .../transit/DefaultAccessEgressTest.java | 2 +- .../mappers/RaptorRequestMapperTest.java | 1 - 13 files changed, 16 insertions(+), 187 deletions(-) delete mode 100644 src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index de1c91a0b4b..894213315cf 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -68,7 +68,7 @@ public interface RaptorAccessEgress { * cost. Many optimal access paths have an inpact on performance as vell. *

* - * The unit is seconds and default value is 0 seconds. + * The unit is seconds and the default value is 0 seconds. */ default int timePenalty() { return RaptorConstants.ZERO; @@ -99,16 +99,16 @@ default int timePenalty() { int latestArrivalTime(int requestedArrivalTime); /** - * This method should return {@code true} if, and only if the instance have restricted + * This method should return {@code true} if, and only if the instance has restricted * opening-hours. */ boolean hasOpeningHours(); /** * Return the opening hours in a short human-readable way for the departure at the origin. Do - * not parse this, this should only be used for things like testing, debugging and logging. + * not parse this. This should only be used for things like testing, debugging and logging. *

- * This method return {@code null} if there is no opening hours, see {@link #hasOpeningHours()}. + * This method return {@code null} if there are no opening hours, see {@link #hasOpeningHours()}. */ @Nullable default String openingHoursToString() { diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java index 03911e3eb4c..7dabe8e2a1c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParams.java @@ -19,7 +19,6 @@ public class SearchParams { private final int earliestDepartureTime; private final int latestArrivalTime; private final int searchWindowInSeconds; - private final int searchWindowAccessSlackInSeconds; private final boolean preferLateArrival; private final int numberOfAdditionalTransfers; private final int maxNumberOfTransfers; @@ -30,13 +29,12 @@ public class SearchParams { private final boolean allowEmptyAccessEgressPaths; /** - * Default values is defined in the default constructor. + * Default values are defined in the default constructor. */ private SearchParams() { earliestDepartureTime = RaptorConstants.TIME_NOT_SET; latestArrivalTime = RaptorConstants.TIME_NOT_SET; searchWindowInSeconds = RaptorConstants.NOT_SET; - searchWindowAccessSlackInSeconds = 0; preferLateArrival = false; numberOfAdditionalTransfers = 5; maxNumberOfTransfers = RaptorConstants.NOT_SET; @@ -51,7 +49,6 @@ private SearchParams() { this.earliestDepartureTime = builder.earliestDepartureTime(); this.latestArrivalTime = builder.latestArrivalTime(); this.searchWindowInSeconds = builder.searchWindowInSeconds(); - this.searchWindowAccessSlackInSeconds = builder.searchWindowAccessSlackInSeconds(); this.preferLateArrival = builder.preferLateArrival(); this.numberOfAdditionalTransfers = builder.numberOfAdditionalTransfers(); this.maxNumberOfTransfers = builder.maxNumberOfTransfers(); @@ -73,13 +70,6 @@ public int earliestDepartureTime() { return earliestDepartureTime; } - /** - * The {@link #earliestDepartureTime()} including search-window-access-slack. - */ - public int routerEarliestDepartureTime() { - return earliestDepartureTime - searchWindowAccessSlackInSeconds; - } - public boolean isEarliestDepartureTimeSet() { return earliestDepartureTime != RaptorConstants.TIME_NOT_SET; } @@ -102,10 +92,10 @@ public boolean isLatestArrivalTimeSet() { /** * The time window used to search. The unit is seconds. *

- * For a *depart by search*, this is added to the 'earliestDepartureTime' to find the + * For a *depart-by-search*, this is added to the 'earliestDepartureTime' to find the * 'latestDepartureTime'. *

- * For a *arrive by search* this is used to calculate the 'earliestArrivalTime'. The algorithm + * For an *arrive-by-search* this is used to calculate the 'earliestArrivalTime'. The algorithm * will find all optimal travels within the given time window. *

* Set the search window to 0 (zero) to run 1 iteration. @@ -116,26 +106,6 @@ public int searchWindowInSeconds() { return searchWindowInSeconds; } - /** - * The {@link #searchWindowInSeconds()} plus search-window-access-slack. - */ - public int routerSearchWindowInSeconds() { - return searchWindowInSeconds == 0 - ? 0 - : searchWindowInSeconds() + searchWindowAccessSlackInSeconds; - } - - /** - * A slack to force Raptor to start the iterations before the earliest-departure-time. - * This will enable paths starting before the earliest-departure-time to be included in the - * result. This is useful if these paths are used to prune other paths(filtering) or if a - * path only can be time-shifted AFTER the search - this enables us to add a time-penalty - * to access. - */ - public int searchWindowAccessSlackInSeconds() { - return searchWindowAccessSlackInSeconds; - } - public boolean isSearchWindowSet() { return searchWindowInSeconds != RaptorConstants.NOT_SET; } diff --git a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java index 587b7b09bd5..517780eb69c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/SearchParamsBuilder.java @@ -24,7 +24,6 @@ public class SearchParamsBuilder { private int earliestDepartureTime; private int latestArrivalTime; private int searchWindowInSeconds; - private int searchWindowAccessSlackInSeconds; private boolean preferLateArrival; private int numberOfAdditionalTransfers; private int maxNumberOfTransfers; @@ -37,7 +36,6 @@ public SearchParamsBuilder(RaptorRequestBuilder parent, SearchParams defaults this.earliestDepartureTime = defaults.earliestDepartureTime(); this.latestArrivalTime = defaults.latestArrivalTime(); this.searchWindowInSeconds = defaults.searchWindowInSeconds(); - this.searchWindowAccessSlackInSeconds = defaults.searchWindowAccessSlackInSeconds(); this.preferLateArrival = defaults.preferLateArrival(); this.numberOfAdditionalTransfers = defaults.numberOfAdditionalTransfers(); this.maxNumberOfTransfers = defaults.maxNumberOfTransfers(); @@ -169,14 +167,6 @@ public SearchParamsBuilder allowEmptyAccessEgressPaths(boolean allowEmptyEgre return this; } - public int searchWindowAccessSlackInSeconds() { - return searchWindowAccessSlackInSeconds; - } - - public void searchWindowAccessSlack(Duration searchWindowAccessSlack) { - this.searchWindowAccessSlackInSeconds = (int) searchWindowAccessSlack.toSeconds(); - } - public boolean allowEmptyAccessEgressPaths() { return allowEmptyAccessEgressPaths; } @@ -197,7 +187,6 @@ public String toString() { .addServiceTime("earliestDepartureTime", earliestDepartureTime, RaptorConstants.TIME_NOT_SET) .addServiceTime("latestArrivalTime", latestArrivalTime, RaptorConstants.TIME_NOT_SET) .addDurationSec("searchWindow", searchWindowInSeconds) - .addDurationSec("searchWindowAccessSlack", searchWindowAccessSlackInSeconds, 0) .addBoolIfTrue("departAsLateAsPossible", preferLateArrival) .addNum("numberOfAdditionalTransfers", numberOfAdditionalTransfers) .addCollection("accessPaths", accessPaths, 5) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index f0cd4e9628e..1b820e6e654 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -55,11 +55,11 @@ public final class DefaultRangeRaptorWorker private final RoutingStrategy transitWorker; /** - * The RangeRaptor state - we delegate keeping track of state to the state object, this allows the - * worker implementation to focus on the algorithm, while the state keep track of the result. + * The RangeRaptor state - we delegate keeping track of state to the state object, this allows + * the worker implementation to focus on the algorithm, while the state keep track of the result. *

- * This also allow us to try out different strategies for storing the result in memory. For a long - * time we had a state which stored all data as int arrays in addition to the current + * This also allows us to try out different strategies for storing the result in memory. For a + * long time, we had a state which stored all data as int arrays in addition to the current * object-oriented approach. There were no performance differences(=> GC is not the bottleneck), * so we dropped the integer array implementation. */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java index 48ccd51e992..528c8ca276a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java @@ -39,8 +39,8 @@ public ForwardRaptorTransitCalculator( @Nullable IntPredicate acceptC2AtDestination ) { this( - s.routerEarliestDepartureTime(), - s.routerSearchWindowInSeconds(), + s.earliestDepartureTime(), + s.searchWindowInSeconds(), s.latestArrivalTime(), t.iterationDepartureStepInSeconds(), acceptC2AtDestination diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java index 9fc80298191..867e04ef680 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java @@ -32,8 +32,8 @@ public ReverseRaptorTransitCalculator(SearchParams s, RaptorTuningParameters t) // goes with destination and 'latestArrivalTime()' match origin. this( s.latestArrivalTime(), - s.routerSearchWindowInSeconds(), - s.routerEarliestDepartureTime(), + s.searchWindowInSeconds(), + s.earliestDepartureTime(), t.iterationDepartureStepInSeconds() ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index ab11d654ef7..b58af3c8b23 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -127,7 +127,6 @@ private TransitRouterResult route() { serverContext.raptorConfig().isMultiThreaded(), accessEgresses.getAccesses(), accessEgresses.getEgresses(), - accessEgresses.calculateMaxAccessTimePenalty(), serverContext.meterRegistry() ); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java index 09018dc3349..f2e8fe6c9f1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java @@ -1,6 +1,5 @@ package org.opentripplanner.routing.algorithm.raptoradapter.router.street; -import java.time.Duration; import java.util.Collection; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; @@ -24,12 +23,4 @@ public Collection getAccesses() { public Collection getEgresses() { return egresses; } - - public Duration calculateMaxAccessTimePenalty() { - return accesses - .stream() - .map(it -> it.penalty().time()) - .max(Duration::compareTo) - .orElse(Duration.ZERO); - } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 91547b1f62f..879536fdcd0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -3,7 +3,6 @@ import static org.opentripplanner.raptor.api.request.Optimization.PARALLEL; import io.micrometer.core.instrument.MeterRegistry; -import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; import java.util.Collection; @@ -21,7 +20,6 @@ import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.grouppriority.TransitGroupPriority32n; import org.opentripplanner.routing.api.request.DebugEventType; @@ -34,7 +32,6 @@ public class RaptorRequestMapper { private final RouteRequest request; private final Collection accessPaths; private final Collection egressPaths; - private final Duration searchWindowAccessSlack; private final long transitSearchTimeZeroEpocSecond; private final boolean isMultiThreadedEnbled; private final MeterRegistry meterRegistry; @@ -44,7 +41,6 @@ private RaptorRequestMapper( boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, - Duration searchWindowAccessSlack, long transitSearchTimeZeroEpocSecond, MeterRegistry meterRegistry ) { @@ -52,7 +48,6 @@ private RaptorRequestMapper( this.isMultiThreadedEnbled = isMultiThreaded; this.accessPaths = accessPaths; this.egressPaths = egressPaths; - this.searchWindowAccessSlack = searchWindowAccessSlack; this.transitSearchTimeZeroEpocSecond = transitSearchTimeZeroEpocSecond; this.meterRegistry = meterRegistry; } @@ -63,7 +58,6 @@ public static RaptorRequest mapRequest( boolean isMultiThreaded, Collection accessPaths, Collection egressPaths, - Duration searchWindowAccessSlack, MeterRegistry meterRegistry ) { return new RaptorRequestMapper( @@ -71,7 +65,6 @@ public static RaptorRequest mapRequest( isMultiThreaded, accessPaths, egressPaths, - searchWindowAccessSlack, transitSearchTimeZero.toEpochSecond(), meterRegistry ) @@ -172,10 +165,6 @@ private RaptorRequest doMap() { builder.searchParams().preferLateArrival(true); } - if (searchWindowAccessSlack.toSeconds() > 0) { - builder.searchParams().searchWindowAccessSlack(searchWindowAccessSlack); - } - // Add this last, it depends on generating an alias from the set values if (meterRegistry != null) { builder.performanceTimers( diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java deleted file mode 100644 index 8caa3a862ca..00000000000 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_SearchWindowAccessSlack.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.opentripplanner.raptor.moduletests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.raptor._data.transit.TestRoute.route; -import static org.opentripplanner.raptor._data.transit.TestTripPattern.pattern; -import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; -import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_MIN_DURATION; -import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_MIN_DURATION_REV; -import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.multiCriteria; -import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.standard; - -import java.time.Duration; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.framework.time.DurationUtils; -import org.opentripplanner.raptor.RaptorService; -import org.opentripplanner.raptor._data.RaptorTestConstants; -import org.opentripplanner.raptor._data.api.PathUtils; -import org.opentripplanner.raptor._data.transit.TestAccessEgress; -import org.opentripplanner.raptor._data.transit.TestTransitData; -import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; -import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.raptor.moduletests.support.ModuleTestDebugLogging; -import org.opentripplanner.raptor.moduletests.support.RaptorModuleTestCase; - -/** - * FEATURE UNDER TEST - *

- * Raptor should return a path starting before the search-window, if a search-window-access-slack - * is used. - */ -public class J01_SearchWindowAccessSlack implements RaptorTestConstants { - - private final TestTransitData data = new TestTransitData(); - private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>(); - private final RaptorService raptorService = new RaptorService<>( - RaptorConfig.defaultConfigForTest() - ); - - /** - * Schedule: - * Stop: A B - * R1: 00:10 - 00:20 - * - * Access (toStop & duration): - * A 1m - * - * Egress (fromStop & duration): - * B 30s - */ - @BeforeEach - void setup() { - data.withRoute(route(pattern("R1", STOP_A, STOP_B)).withTimetable(schedule("00:10 00:20"))); - requestBuilder - .searchParams() - .addAccessPaths(TestAccessEgress.walk(STOP_A, D1m)) - .addEgressPaths(TestAccessEgress.walk(STOP_B, D30s)) - .latestArrivalTime(T00_30) - .timetable(true); - - ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder); - } - - static List testCases() { - var path = "Walk 1m ~ A ~ BUS R1 0:10 0:20 ~ B ~ Walk 30s [0:09 0:20:30 11m30s Tₓ0 C₁1_380]"; - return RaptorModuleTestCase - .of() - .add(TC_MIN_DURATION, "[0:09 0:20:30 11m30s Tₓ0]") - .add(TC_MIN_DURATION_REV, "[0:18:30 0:30 11m30s Tₓ0]") - .add(standard(), PathUtils.withoutCost(path)) - .add(multiCriteria(), path) - .build(); - } - - @ParameterizedTest - @MethodSource("testCases") - void testRaptor1mSlack(RaptorModuleTestCase testCase) { - requestBuilder - .searchParams() - .earliestDepartureTime(T00_12) - .searchWindowAccessSlack(Duration.ofMinutes(3)); - - assertEquals(testCase.expected(), testCase.run(raptorService, data, requestBuilder)); - } - - @ParameterizedTest - @MethodSource("testCases") - void testRaptorNotEnoughSlack(RaptorModuleTestCase testCase) { - requestBuilder - .searchParams() - .earliestDepartureTime(T00_12) - .searchWindowAccessSlack(DurationUtils.duration("2m59s")); - - if (testCase.config() != TC_MIN_DURATION_REV) { - assertEquals("", testCase.run(raptorService, data, requestBuilder)); - } else { - assertEquals(testCase.expected(), testCase.run(raptorService, data, requestBuilder)); - } - } -} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java index dfd0a1231c8..1779155a69b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java @@ -50,9 +50,4 @@ void getAccesses() { void getEgresses() { assertEquals(EGRESSES, subject.getEgresses()); } - - @Test - void calculateMaxAccessTimePenalty() { - assertEquals(D7m, subject.calculateMaxAccessTimePenalty()); - } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 6037eae74d7..0c9f29bc8a4 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -39,7 +39,7 @@ void durationInSeconds() { @Test void timePenalty() { int expected = (int) TIME_PENALTY.toSeconds(); - assertEquals(expected, subject.timePenalty()); + assertEquals(0, subject.timePenalty()); assertEquals(expected, subjectWithPenalty.timePenalty()); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java index 89348be5c89..47542782884 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapperTest.java @@ -90,7 +90,6 @@ private static RaptorRequest map(RouteRequest request) { false, ACCESS, EGRESS, - D0s, null ); } From 8f0d04ebeff6ad8c3adcb69133aa2181fde3b918 Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 20 Feb 2024 13:55:06 +0100 Subject: [PATCH 031/108] chore: prettier --- .../netex/mapping/AuthorityToAgencyMapper.java | 4 +++- .../transit/model/organization/Agency.java | 5 +++-- .../transit/model/organization/AgencyBuilder.java | 2 ++ .../netex/mapping/AuthorityToAgencyMapperTest.java | 8 +++++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java index 60d77ac2b47..23edb034079 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java @@ -34,7 +34,9 @@ class AuthorityToAgencyMapper { */ Agency mapAuthorityToAgency(Authority source) { String agencyName = source.getName() != null ? source.getName().getValue() : null; - String agencyShortName = source.getShortName() != null ? source.getShortName().getValue() : null; + String agencyShortName = source.getShortName() != null + ? source.getShortName().getValue() + : null; AgencyBuilder target = Agency .of(idFactory.createId(source.getId())) diff --git a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java index 8ed66a97ce1..625078c06a7 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java @@ -31,8 +31,8 @@ public final class Agency extends AbstractTransitEntity i String nameValue = (builder.getName() != null && !builder.getName().isBlank()) ? builder.getName() : builder.getShortName() != null && !builder.getShortName().isBlank() - ? builder.getShortName() - : null; + ? builder.getShortName() + : null; // Required fields this.name = assertHasValue(nameValue, "Missing mandatory name on Agency %s", builder.getId()); @@ -63,6 +63,7 @@ public static AgencyBuilder of(@Nonnull FeedScopedId id) { public String getName() { return logName(); } + @Nullable public String getShortName() { return shortName; diff --git a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java index 18c2dd09eb2..d4dd94c71e4 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java @@ -34,6 +34,7 @@ public class AgencyBuilder extends AbstractEntityBuilder public String getName() { return name; } + public String getShortName() { return shortName; } @@ -42,6 +43,7 @@ public AgencyBuilder withName(String name) { this.name = name; return this; } + public AgencyBuilder withShortName(String shortName) { this.shortName = shortName; return this; diff --git a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java index 3a47052b022..6d809ae92f0 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java @@ -67,7 +67,13 @@ public void getDefaultAgency() { } @SuppressWarnings("SameParameterValue") - private static Authority authority(String id, String name, String shortName, String url, String phone) { + private static Authority authority( + String id, + String name, + String shortName, + String url, + String phone + ) { return new Authority() .withId(id) .withShortName(new MultilingualString().withValue(shortName)) From 652afe203539fdc52a645198c7643c4f9b4b0ec5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 20 Feb 2024 14:40:27 +0100 Subject: [PATCH 032/108] feature: Use decorators to apply time-penalty in Raptor --- .../model/AbstractAccessEgressDecorator.java | 115 ++++++++++++++++++ .../rangeraptor/DefaultRangeRaptorWorker.java | 2 +- .../rangeraptor/transit/AccessPaths.java | 18 ++- .../transit/AccessWithPenalty.java | 27 ++++ .../rangeraptor/transit/EgressPaths.java | 13 ++ .../transit/EgressWithPenalty.java | 27 ++++ .../transit/DefaultAccessEgress.java | 3 + 7 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java create mode 100644 src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java diff --git a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java new file mode 100644 index 00000000000..34c914e6155 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java @@ -0,0 +1,115 @@ +package org.opentripplanner.raptor.api.model; + +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * Using delegation to extend the {@link RaptorAccessEgress} functionality is common, so we provide + * a base delegation implementation here. This implementation delegates all operations to the + * delegate. + */ +public class AbstractAccessEgressDecorator implements RaptorAccessEgress { + + private final RaptorAccessEgress delegate; + + public AbstractAccessEgressDecorator(RaptorAccessEgress delegate) { + this.delegate = delegate; + } + + protected RaptorAccessEgress delegate() { + return delegate; + } + + @Override + public int stop() { + return delegate.stop(); + } + + @Override + public int c1() { + return delegate.c1(); + } + + @Override + public int durationInSeconds() { + return delegate.durationInSeconds(); + } + + @Override + public int timePenalty() { + return delegate.timePenalty(); + } + + @Override + public int earliestDepartureTime(int requestedDepartureTime) { + return delegate.earliestDepartureTime(requestedDepartureTime); + } + + @Override + public int latestArrivalTime(int requestedArrivalTime) { + return delegate.latestArrivalTime(requestedArrivalTime); + } + + @Override + public boolean hasOpeningHours() { + return delegate.hasOpeningHours(); + } + + @Nullable + @Override + public String openingHoursToString() { + return delegate.openingHoursToString(); + } + + @Override + public int numberOfRides() { + return delegate.numberOfRides(); + } + + @Override + public boolean hasRides() { + return delegate.hasRides(); + } + + @Override + public boolean stopReachedOnBoard() { + return delegate.stopReachedOnBoard(); + } + + @Override + public boolean stopReachedByWalking() { + return delegate.stopReachedByWalking(); + } + + @Override + public boolean isFree() { + return delegate.isFree(); + } + + @Override + public String defaultToString() { + return delegate.defaultToString(); + } + + @Override + public String asString(boolean includeStop, boolean includeCost, @Nullable String summary) { + return delegate.asString(includeStop, includeCost, summary); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractAccessEgressDecorator that = (AbstractAccessEgressDecorator) o; + return Objects.equals(delegate, that.delegate); + } + + @Override + public int hashCode() { + return Objects.hash(delegate); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 1b820e6e654..cc412ddc990 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -282,7 +282,7 @@ private void addAccessPaths(Collection accessPaths) { // Access must be available after the iteration departure time if (departureTime != RaptorConstants.TIME_NOT_SET) { - transitWorker.setAccessToStop(it, departureTime); + transitWorker.setAccessToStop(it, departureTime - it.timePenalty()); } } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 5c26a10db23..40386c37056 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -49,9 +49,9 @@ public int calculateMaxNumberOfRides() { /** * The multi-criteria state can handle multiple access/egress paths to a single stop, but the - * Standard and BestTime states do not. To get a deterministic behaviour we filter the paths and + * Standard and BestTime states do not. To get a deterministic behavior, we filter the paths and * return the paths with the shortest duration for non-multi-criteria search. If two paths have - * the same duration the first one is picked. Note! If the access/egress paths contains flex as + * the same duration, the first one is picked. Note! If the access/egress paths contains flex as * well, then we need to look at mode for arriving at tha stop as well. A Flex arrive-on-board can * be used with a transfer even if the time is worse compared with walking. *

@@ -63,12 +63,26 @@ public static AccessPaths create(Collection paths, RaptorPro } else { paths = removeNonOptimalPathsForStandardRaptor(paths); } + + paths = decorateWithTimePenaltyLogic(paths); + return new AccessPaths( groupByRound(paths, RaptorAccessEgress::stopReachedByWalking), groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard) ); } + /** + * Decorate access to implement time-penalty. This decoration will do the necessary + * adjustments to apply the penalty in the raptor algorithm. See the decorator class for more + * info. The original access object is returned if it does not have a time-penalty set. + */ + private static List decorateWithTimePenaltyLogic( + Collection paths + ) { + return paths.stream().map(it -> it.timePenalty() > 0 ? new AccessWithPenalty(it) : it).toList(); + } + /** Raptor uses this information to optimize boarding of the first trip */ public boolean hasTimeDependentAccess() { return ( diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java new file mode 100644 index 00000000000..e6b51f41de9 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java @@ -0,0 +1,27 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; + +/** + * This decorator will add the time penalty to the duration of the access and adjust the + * `requestedDepartureTime` when time-shifting the access according to opening-hours. + * + * TODO PEN - Write more + */ +public class AccessWithPenalty extends AbstractAccessEgressDecorator { + + public AccessWithPenalty(RaptorAccessEgress delegate) { + super(delegate); + } + + @Override + public int durationInSeconds() { + return delegate().durationInSeconds() + delegate().timePenalty(); + } + + @Override + public int earliestDepartureTime(int requestedDepartureTime) { + return delegate().earliestDepartureTime(requestedDepartureTime + delegate().timePenalty()); + } +} diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java index 374fc050782..19c77901af0 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -32,6 +32,8 @@ private EgressPaths(TIntObjectMap> pathsByStop) { * This method is static and package local to enable unit-testing. */ public static EgressPaths create(Collection paths, RaptorProfile profile) { + paths = decorateWithTimePenaltyLogic(paths); + if (MULTI_CRITERIA.is(profile)) { paths = removeNonOptimalPathsForMcRaptor(paths); } else { @@ -72,6 +74,17 @@ public int[] egressesWitchStartByARide() { return filterPathsAndGetStops(RaptorAccessEgress::stopReachedOnBoard); } + /** + * Decorate egress to implement time-penalty. This decoration will do the necessary + * adjustments to apply the penalty in the raptor algorithm. See the decorator class for more + * info. The original egress object is returned if it does not have a time-penalty set. + */ + private static List decorateWithTimePenaltyLogic( + Collection paths + ) { + return paths.stream().map(it -> it.timePenalty() > 0 ? new EgressWithPenalty(it) : it).toList(); + } + private int[] filterPathsAndGetStops(Predicate filter) { return pathsByStop .valueCollection() diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java new file mode 100644 index 00000000000..fb64fdcf487 --- /dev/null +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java @@ -0,0 +1,27 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; + +/** + * This decorator will add the time penalty to the duration of the egress and adjust the + * `requestedDepartureTime` when time-shifting the egress according to opening-hours. + * + * TODO PEN - Write more + */ +public class EgressWithPenalty extends AbstractAccessEgressDecorator { + + public EgressWithPenalty(RaptorAccessEgress delegate) { + super(delegate); + } + + @Override + public int durationInSeconds() { + return delegate().durationInSeconds() + delegate().timePenalty(); + } + + @Override + public int latestArrivalTime(int requestedArrivalTime) { + return delegate().latestArrivalTime(requestedArrivalTime - delegate().timePenalty()); + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 000e56b300c..67f56d0b4ad 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -35,6 +35,9 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) { } this.stop = other.stop(); this.durationInSeconds = other.durationInSeconds(); + // In the API we have a cost associated with the time-penalty. In Raptor, there is no + // association between the time-penalty and the cost. So, we add the time-penalty cost to + // the generalized cost here. In logic later on, we will remove it. this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds(); this.penalty = penalty; this.lastState = other.getLastState(); From ca49ca609e61c6b0e16ffd5c54a8513665c64948 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 20 Feb 2024 23:02:53 +0100 Subject: [PATCH 033/108] feature: Remove access/egress time-penalty decorators when returning out of Raptor --- .../api/model/AbstractAccessEgressDecorator.java | 15 +++++++++++++++ .../raptor/path/PathBuilderLeg.java | 11 ++++++++++- .../rangeraptor/transit/AccessWithPenalty.java | 9 +++++++++ .../rangeraptor/transit/EgressWithPenalty.java | 9 +++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java index 34c914e6155..d01c41dc0ca 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java @@ -112,4 +112,19 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(delegate); } + + /** + * Use this method to remove a decorator of the given type. + */ + public static RaptorAccessEgress removeRaptorDecoratorIfItExist( + RaptorAccessEgress path, + Class decoratorClazz + ) { + if (path == null) { + return null; + } + return path.getClass() == decoratorClazz + ? ((AbstractAccessEgressDecorator) path).delegate() + : path; + } } diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index 76d49ef4a0a..4c919ca9d7a 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -15,6 +15,8 @@ import org.opentripplanner.raptor.api.path.PathStringBuilder; import org.opentripplanner.raptor.api.path.TransferPathLeg; import org.opentripplanner.raptor.api.path.TransitPathLeg; +import org.opentripplanner.raptor.rangeraptor.transit.AccessWithPenalty; +import org.opentripplanner.raptor.rangeraptor.transit.EgressWithPenalty; import org.opentripplanner.raptor.rangeraptor.transit.ForwardTransitCalculator; import org.opentripplanner.raptor.rangeraptor.transit.TransitCalculator; import org.opentripplanner.raptor.spi.BoardAndAlightTime; @@ -351,6 +353,9 @@ AccessPathLeg createAccessPathLeg( PathLeg nextLeg = next.createPathLeg(costCalculator, slackProvider); var accessPath = asAccessLeg().streetPath; int cost = cost(costCalculator, accessPath); + + accessPath = AccessWithPenalty.removeDecoratorIfItExist(accessPath); + return new AccessPathLeg<>(accessPath, fromTime, toTime, cost, nextLeg); } @@ -447,7 +452,11 @@ private EgressPathLeg createEgressPathLeg( RaptorSlackProvider slackProvider ) { int cost = egressCost(costCalculator, slackProvider); - return new EgressPathLeg<>(asEgressLeg().streetPath, fromTime, toTime, cost); + var egressPath = asEgressLeg().streetPath; + + egressPath = EgressWithPenalty.removeDecoratorIfItExist(egressPath); + + return new EgressPathLeg<>(egressPath, fromTime, toTime, cost); } /** diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java index e6b51f41de9..1226d56c028 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java @@ -24,4 +24,13 @@ public int durationInSeconds() { public int earliestDepartureTime(int requestedDepartureTime) { return delegate().earliestDepartureTime(requestedDepartureTime + delegate().timePenalty()); } + + /** + * This class is used internally in Raptor to decorate an access path. This method removes the + * decorator and returns the original access path if decorated. If not, the given path is + * returned. + */ + public static RaptorAccessEgress removeDecoratorIfItExist(RaptorAccessEgress path) { + return removeRaptorDecoratorIfItExist(path, AccessWithPenalty.class); + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java index fb64fdcf487..d6a50f7c982 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java @@ -24,4 +24,13 @@ public int durationInSeconds() { public int latestArrivalTime(int requestedArrivalTime) { return delegate().latestArrivalTime(requestedArrivalTime - delegate().timePenalty()); } + + /** + * This class is used internally in Raptor to decorate an access path. This method removes the + * decorator and returns the original access path if decorated. If not, the given path is + * returned. + */ + public static RaptorAccessEgress removeDecoratorIfItExist(RaptorAccessEgress path) { + return removeRaptorDecoratorIfItExist(path, EgressWithPenalty.class); + } } From 567a6a5bf164f38f13b32de0430e79befba983f4 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 21 Feb 2024 00:37:14 +0100 Subject: [PATCH 034/108] refactor: Encapsulate access paths in AccessPaths --- .../rangeraptor/DefaultRangeRaptorWorker.java | 8 +-- .../multicriteria/McStopArrivals.java | 14 ++--- .../rangeraptor/transit/AccessPaths.java | 60 ++++++++++++------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index cc412ddc990..a05d6aa832f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -261,11 +261,11 @@ private void findTransfersForRound() { } private void findAccessOnStreetForRound() { - addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides().get(round())); + addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round())); } private void findAccessOnBoardForRound() { - addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides().get(round())); + addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round())); } /** @@ -273,10 +273,6 @@ private void findAccessOnBoardForRound() { * scheduled search at the next-earlier minute. */ private void addAccessPaths(Collection accessPaths) { - if (accessPaths == null) { - return; - } - for (RaptorAccessEgress it : accessPaths) { int departureTime = calculator.departureTime(it, iterationDepartureTime); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java index 98bb22a46c5..4090890cc82 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McStopArrivals.java @@ -2,13 +2,10 @@ import static org.opentripplanner.raptor.api.model.PathLegType.TRANSIT; -import gnu.trove.map.TIntObjectMap; import java.util.BitSet; import java.util.Collections; -import java.util.List; import java.util.function.Function; import java.util.stream.Stream; -import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.rangeraptor.debug.DebugHandlerFactory; import org.opentripplanner.raptor.rangeraptor.multicriteria.arrivals.ArrivalParetoSetComparatorFactory; @@ -54,7 +51,7 @@ public McStopArrivals( this.debugHandlerFactory = debugHandlerFactory; this.debugStats = new DebugStopArrivalsStatistics(debugHandlerFactory.debugLogger()); - initAccessArrivals(accessPaths.arrivedOnBoardByNumOfRides()); + initAccessArrivals(accessPaths); glueTogetherEgressStopWithDestinationArrivals(egressPaths, paths); } @@ -142,9 +139,10 @@ private StopArrivalParetoSet findOrCreateSet(final int stop) { return arrivals[stop]; } - private void initAccessArrivals(TIntObjectMap> accessOnBoardByRides) { - for (int round : accessOnBoardByRides.keys()) { - for (var access : accessOnBoardByRides.get(round)) { + private void initAccessArrivals(AccessPaths accessPaths) { + int maxNRides = accessPaths.calculateMaxNumberOfRides(); + for (int nRides = 0; nRides <= maxNRides; ++nRides) { + for (var access : accessPaths.arrivedOnBoardByNumOfRides(nRides)) { int stop = access.stop(); arrivals[stop] = StopArrivalParetoSet.createStopArrivalSet( @@ -157,7 +155,7 @@ private void initAccessArrivals(TIntObjectMap> accessOn /** * This method creates a ParetoSet for the given egress stop. When arrivals are added to the stop, - * the "glue" make sure new destination arrivals is added to the destination arrivals. + * the "glue" make sure new destination arrivals are added to the destination arrivals. */ private void glueTogetherEgressStopWithDestinationArrivals( EgressPaths egressPaths, diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 40386c37056..2e8bd01049a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -24,29 +24,6 @@ private AccessPaths( this.arrivedOnBoardByNumOfRides = arrivedOnBoardByNumOfRides; } - /** - * Return the transfer arriving at the stop on-street(walking) grouped by Raptor round. The Raptor - * round is calculated from the number of rides in the transfer. - */ - public TIntObjectMap> arrivedOnStreetByNumOfRides() { - return arrivedOnStreetByNumOfRides; - } - - /** - * Return the transfer arriving at the stop on-board a transit(flex) service grouped by Raptor - * round. The Raptor round is calculated from the number of rides in the transfer. - */ - public TIntObjectMap> arrivedOnBoardByNumOfRides() { - return arrivedOnBoardByNumOfRides; - } - - public int calculateMaxNumberOfRides() { - return Math.max( - Arrays.stream(arrivedOnStreetByNumOfRides.keys()).max().orElse(0), - Arrays.stream(arrivedOnBoardByNumOfRides.keys()).max().orElse(0) - ); - } - /** * The multi-criteria state can handle multiple access/egress paths to a single stop, but the * Standard and BestTime states do not. To get a deterministic behavior, we filter the paths and @@ -72,6 +49,33 @@ public static AccessPaths create(Collection paths, RaptorPro ); } + /** + * Return the transfer arriving at the stop on-street(walking) grouped by Raptor round. The Raptor + * round is calculated from the number of rides in the transfer. + *

+ * If no access exists for the given round, an empty list is returned. + */ + public List arrivedOnStreetByNumOfRides(int round) { + return emptyListIfNull(arrivedOnStreetByNumOfRides.get(round)); + } + + /** + * Return the transfer arriving at the stop on-board a transit(flex) service grouped by Raptor + * round. The Raptor round is calculated from the number of rides in the transfer. + *

+ * If no access exists for the given round, an empty list is returned. + */ + public List arrivedOnBoardByNumOfRides(int round) { + return emptyListIfNull(arrivedOnBoardByNumOfRides.get(round)); + } + + public int calculateMaxNumberOfRides() { + return Math.max( + Arrays.stream(arrivedOnStreetByNumOfRides.keys()).max().orElse(0), + Arrays.stream(arrivedOnBoardByNumOfRides.keys()).max().orElse(0) + ); + } + /** * Decorate access to implement time-penalty. This decoration will do the necessary * adjustments to apply the penalty in the raptor algorithm. See the decorator class for more @@ -99,4 +103,14 @@ private static boolean hasTimeDependentAccess(TIntObjectMap emptyListIfNull(List list) { + if (list == null) { + return List.of(); + } + return list; + } } From 99dd26f6a24b94f2b3eceeb1f0c58bda54ff7532 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:21:57 +0000 Subject: [PATCH 035/108] Update dependency org.apache.maven.plugins:maven-shade-plugin to v3.5.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a621ebb5b00..a298c1ec462 100644 --- a/pom.xml +++ b/pom.xml @@ -363,7 +363,7 @@ properly if some input files are missing a terminating newline) --> org.apache.maven.plugins maven-shade-plugin - 3.5.1 + 3.5.2 package From 8ba2fb8f782fa5df5caed3e0a8094d145984c9f9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 21 Feb 2024 21:18:31 +0100 Subject: [PATCH 036/108] Remove netty-bom [ci skip] --- renovate.json5 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index ac50333d01d..04e0fd14471 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -25,8 +25,7 @@ "com.microsoft.azure:azure-servicebus", "com.azure.resourcemanager:azure-resourcemanager-servicebus", "com.azure:azure-core", - "com.azure:azure-messaging-servicebus", - "io.netty:netty-bom" + "com.azure:azure-messaging-servicebus" ], "enabled": false }, From fa62c058b54c2e32d2ff90390a1bd8e13d2ef938 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 11:46:10 +0200 Subject: [PATCH 037/108] Use correct car speed resolver for areas --- .../graph_builder/module/osm/WalkableAreaBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index af17593d36a..5562f1df1a3 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -505,7 +505,7 @@ private Set createSegments( float carSpeed = areaEntity .getOsmProvider() - .getWayPropertySet() + .getOsmTagMapper() .getCarSpeedForWay(areaEntity, false); double length = SphericalDistanceLibrary.distance( From 65cbff9ca4f18884d859186f54d7ee7a23db282c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 11:53:48 +0200 Subject: [PATCH 038/108] Fix maxCarSpeed for ConstantSpeedMapper --- .../graph_builder/module/osm/OsmModule.java | 6 +++--- .../openstreetmap/tagmapping/ConstantSpeedMapper.java | 11 +++++++++-- .../openstreetmap/tagmapping/OsmTagMapper.java | 4 ++++ .../openstreetmap/tagmapping/DefaultMapperTest.java | 4 ++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index b279a4088f0..e831d2ada47 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -556,9 +556,9 @@ private StreetEdge getEdgeForStreet( private float getMaxCarSpeed() { float maxSpeed = 0f; for (OsmProvider provider : providers) { - var wps = provider.getWayPropertySet(); - if (wps.maxUsedCarSpeed > maxSpeed) { - maxSpeed = wps.maxUsedCarSpeed; + var carSpeed = provider.getOsmTagMapper().getMaxUsedCarSpeed(provider.getWayPropertySet()); + if (carSpeed > maxSpeed) { + maxSpeed = carSpeed; } } return maxSpeed; diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java index 48a3c2ac9bf..b233b3f549e 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/ConstantSpeedMapper.java @@ -31,10 +31,9 @@ public ConstantSpeedFinlandMapper(float speed) { @Override public void populateProperties(WayPropertySet props) { props.setCarSpeed("highway=*", speed); - props.maxPossibleCarSpeed = 22.22f; - props.maxUsedCarSpeed = 22.22f; // Read the rest from the default set new FinlandMapper().populateProperties(props); + props.maxPossibleCarSpeed = speed; } @Override @@ -44,4 +43,12 @@ public float getCarSpeedForWay(OSMWithTags way, boolean backward) { */ return speed; } + + @Override + public Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) { + // This is needed because the way property set uses normal speed limits from Finland mapper + // to set the walk safety limits which resets the maximum used car speed to be something else + // than what is used for the street edge car speeds. + return speed; + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapper.java index 389e3b90ca8..8bcfbfdf65a 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapper.java @@ -26,6 +26,10 @@ default float getCarSpeedForWay(OSMWithTags way, boolean backward) { return way.getOsmProvider().getWayPropertySet().getCarSpeedForWay(way, backward); } + default Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) { + return wayPropertySet.maxUsedCarSpeed; + } + default boolean isGeneralNoThroughTraffic(OSMWithTags way) { String access = way.getTag("access"); return doesTagValueDisallowThroughTraffic(access); diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java index 5f95436833d..9a16f6a8e2e 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java @@ -17,6 +17,7 @@ public class DefaultMapperTest { private WayPropertySet wps; + private OsmTagMapper mapper; float epsilon = 0.01f; @BeforeEach @@ -25,6 +26,7 @@ public void setup() { DefaultMapper source = new DefaultMapper(); source.populateProperties(wps); this.wps = wps; + this.mapper = source; } /** @@ -108,6 +110,8 @@ public void testCarSpeeds() { assertSpeed(4.305559158325195, "15.5 km/h"); assertSpeed(22.347200393676758, "50 mph"); assertSpeed(22.347200393676758, "50.0 mph"); + + assertEquals(wps.maxUsedCarSpeed, mapper.getMaxUsedCarSpeed(wps)); } @Test From 4a0b03b73bfeb8493e13faf2af997359a79c3b6a Mon Sep 17 00:00:00 2001 From: bartosz Date: Mon, 12 Feb 2024 15:39:22 +0100 Subject: [PATCH 039/108] Add configuration switch for Service Bus authentication with Federated Identity --- doc-templates/UpdaterConfig.md | 4 +- .../sandbox/siri}/SiriAzureUpdater.md | 27 ++- .../{ => sandbox/siri}/SiriUpdater.md | 0 docs/RouterConfiguration.md | 14 ++ docs/UpdaterConfig.md | 4 +- docs/examples/skanetrafiken/Readme.md | 3 + .../examples/skanetrafiken/router-config.json | 3 +- docs/sandbox/siri/SiriAzureUpdater.md | 197 ++++++++++++++++++ docs/sandbox/{ => siri}/SiriUpdater.md | 0 mkdocs.yml | 4 +- pom.xml | 11 +- .../azure/AbstractAzureSiriUpdater.java | 20 +- .../updater/azure/AuthenticationType.java | 6 + .../azure/SiriAzureUpdaterParameters.java | 18 ++ .../azure/SiriAzureETUpdaterConfig.java | 24 ++- .../azure/SiriAzureSXUpdaterConfig.java | 31 ++- .../azure/SiriAzureUpdaterConfig.java | 58 +++++- .../generate/doc/SiriAzureConfigDocTest.java | 98 +++++++++ .../generate/doc/SiriConfigDocTest.java | 4 +- .../generate/doc/UpdaterConfigDocTest.java | 1 + .../standalone/config/router-config.json | 15 ++ 21 files changed, 495 insertions(+), 47 deletions(-) rename {docs/sandbox => doc-templates/sandbox/siri}/SiriAzureUpdater.md (60%) rename doc-templates/{ => sandbox/siri}/SiriUpdater.md (100%) create mode 100644 docs/sandbox/siri/SiriAzureUpdater.md rename docs/sandbox/{ => siri}/SiriUpdater.md (100%) create mode 100644 src/ext/java/org/opentripplanner/ext/siri/updater/azure/AuthenticationType.java create mode 100644 src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java diff --git a/doc-templates/UpdaterConfig.md b/doc-templates/UpdaterConfig.md index 57152671ec7..b0444f038af 100644 --- a/doc-templates/UpdaterConfig.md +++ b/doc-templates/UpdaterConfig.md @@ -82,7 +82,7 @@ GBFS form factors: ## Other updaters in sandboxes - [Vehicle parking](sandbox/VehicleParking.md) -- [Siri over HTTP](sandbox/SiriUpdater.md) -- [Siri over Azure Message Bus](sandbox/SiriAzureUpdater.md) +- [Siri over HTTP](sandbox/siri/SiriUpdater.md) +- [Siri over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) diff --git a/docs/sandbox/SiriAzureUpdater.md b/doc-templates/sandbox/siri/SiriAzureUpdater.md similarity index 60% rename from docs/sandbox/SiriAzureUpdater.md rename to doc-templates/sandbox/siri/SiriAzureUpdater.md index 3af3b7ee854..eb092ddd08d 100644 --- a/docs/sandbox/SiriAzureUpdater.md +++ b/doc-templates/sandbox/siri/SiriAzureUpdater.md @@ -8,17 +8,26 @@ IT also OTP to download historical data from en HTTP endpoint on startup. Skånetrafiken, Sweden developer.otp@skanetrafiken.se +## Documentation + +Documentation available [here](../../examples/skanetrafiken/Readme.md). + +## Configuration + +To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. + +### Siri Azure ET Updater + + + +### Siri Azure SX Updater + + + ## Changelog -- Added configuration for turning off stop arrival time match feature. +- Added configuration for turning off stop arrival time match feature. - Initial version (April 2022) - Minor changes in logging (November 2022) - Retry fetch from history endpoint if it failed (February 2023) - Solve a bug in SiriAzureETUpdater and improve error logging (March 2023) - -## Documentation - -Documentation available [here](../examples/skanetrafiken/Readme.md). - -### Configuration - -See example configuration in `examples/skanetrafiken/router-config.json`. \ No newline at end of file +- Add support with federated identity authentication (February 2024) \ No newline at end of file diff --git a/doc-templates/SiriUpdater.md b/doc-templates/sandbox/siri/SiriUpdater.md similarity index 100% rename from doc-templates/SiriUpdater.md rename to doc-templates/sandbox/siri/SiriUpdater.md diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 73998f06278..34e9aa2c8d6 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -817,6 +817,20 @@ Used to group requests when monitoring OTP. "toDateTime" : "P1D", "timeout" : 300000 } + }, + { + "type" : "siri-azure-et-updater", + "topic" : "some_topic", + "authenticationType" : "SharedAccessKey", + "fullyQualifiedNamespace" : "fully_qualified_namespace", + "servicebus-url" : "service_bus_url", + "feedId" : "feed_id", + "customMidnight" : 4, + "history" : { + "url" : "endpoint_url", + "fromDateTime" : "-P1D", + "timeout" : 300000 + } } ], "rideHailingServices" : [ diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index a819a898240..973874be66a 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -414,7 +414,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ## Other updaters in sandboxes - [Vehicle parking](sandbox/VehicleParking.md) -- [Siri over HTTP](sandbox/SiriUpdater.md) -- [Siri over Azure Message Bus](sandbox/SiriAzureUpdater.md) +- [Siri over HTTP](sandbox/siri/SiriUpdater.md) +- [Siri over Azure Message Bus](sandbox/siri/SiriAzureUpdater.md) - [VehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) diff --git a/docs/examples/skanetrafiken/Readme.md b/docs/examples/skanetrafiken/Readme.md index fc342f4192b..a611c839140 100644 --- a/docs/examples/skanetrafiken/Readme.md +++ b/docs/examples/skanetrafiken/Readme.md @@ -93,6 +93,9 @@ id from the message. In case OTP was not able to find corresponding trip additio performed based on arrival-times/stop-patterns from the ET message. This feature turned off by default but can be activated by adding *fuzzyTripMatching* property to updater configuration. +### FederatedIdentity +It is also possible to connect to Service Bus through FederatedIdentity. Change **authenticationType** to +**FederatedIdentity** and provide **fullyQualifiedNamespace** in router-config. diff --git a/docs/examples/skanetrafiken/router-config.json b/docs/examples/skanetrafiken/router-config.json index d65604aaa00..74042d68e31 100644 --- a/docs/examples/skanetrafiken/router-config.json +++ b/docs/examples/skanetrafiken/router-config.json @@ -44,7 +44,8 @@ "type": "siri-azure-sx-updater", "topic": "", "feedId": "", - "servicebus-url": "", + "authenticationType": "FederatedIdentity", + "fullyQualifiedNamespace": "", "customMidnight": 4, "history": { "url": "", diff --git a/docs/sandbox/siri/SiriAzureUpdater.md b/docs/sandbox/siri/SiriAzureUpdater.md new file mode 100644 index 00000000000..75aa58897ec --- /dev/null +++ b/docs/sandbox/siri/SiriAzureUpdater.md @@ -0,0 +1,197 @@ +# Siri Azure Updater + +It is sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages through *Azure Service Bus*. +IT also OTP to download historical data from en HTTP endpoint on startup. + +## Contact Info + +Skånetrafiken, Sweden +developer.otp@skanetrafiken.se + +## Documentation + +Documentation available [here](../../examples/skanetrafiken/Readme.md). + +## Configuration + +To enable the SIRI updater you need to add it to the updaters section of the `router-config.json`. + +### Siri Azure ET Updater + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|------------------------------------------------------------|:---------:|----------------------------------------------------------------|:----------:|---------------------|:-----:| +| type = "siri-azure-et-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [authenticationType](#u__11__authenticationType) | `enum` | Which authentication type to use | *Optional* | `"sharedaccesskey"` | 2.5 | +| [customMidnight](#u__11__customMidnight) | `integer` | Time on which time breaks into new day. | *Optional* | `0` | 2.2 | +| feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | 2.2 | +| [fullyQualifiedNamespace](#u__11__fullyQualifiedNamespace) | `string` | Service Bus fully qualified namespace used for authentication. | *Optional* | | 2.5 | +| fuzzyTripMatching | `boolean` | Whether to apply fuzzyTripMatching on the updates | *Optional* | `false` | 2.2 | +| [servicebus-url](#u__11__servicebus_url) | `string` | Service Bus connection used for authentication. | *Optional* | | 2.2 | +| topic | `string` | Service Bus topic to connect to. | *Optional* | | 2.2 | +| history | `object` | Configuration for fetching historical data on startup | *Optional* | | 2.2 | +|    fromDateTime | `string` | Datetime boundary for historical data | *Optional* | `"-P1D"` | 2.2 | +|    timeout | `integer` | Timeout in milliseconds | *Optional* | `300000` | na | +|    url | `string` | Endpoint to fetch from | *Optional* | | na | + + +##### Parameter details + +

authenticationType

+ +**Since version:** `2.5` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"sharedaccesskey"` +**Path:** /updaters/[11] +**Enum values:** `sharedaccesskey` | `federatedidentity` + +Which authentication type to use + +

customMidnight

+ +**Since version:** `2.2` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `0` +**Path:** /updaters/[11] + +Time on which time breaks into new day. + +It is common that operating day date breaks a little bit later than midnight so that the switch happens when traffic is at the lowest point. Parameter uses 24-hour format. If the switch happens on 4 am then set this field to 4. + +

fullyQualifiedNamespace

+ +**Since version:** `2.5` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[11] + +Service Bus fully qualified namespace used for authentication. + +Has to be present for authenticationMethod FederatedIdentity. + +

servicebus-url

+ +**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[11] + +Service Bus connection used for authentication. + +Has to be present for authenticationMethod SharedAccessKey. This should be Primary/Secondary connection string from service bus. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "siri-azure-et-updater", + "topic" : "some_topic", + "authenticationType" : "SharedAccessKey", + "fullyQualifiedNamespace" : "fully_qualified_namespace", + "servicebus-url" : "service_bus_url", + "feedId" : "feed_id", + "customMidnight" : 4, + "history" : { + "url" : "endpoint_url", + "fromDateTime" : "-P1D", + "timeout" : 300000 + } + } + ] +} +``` + + + +### Siri Azure SX Updater + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|------------------------------------------------------------|:---------:|----------------------------------------------------------------|:----------:|---------------------|:-----:| +| type = "siri-azure-sx-updater" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [authenticationType](#u__10__authenticationType) | `enum` | Which authentication type to use | *Optional* | `"sharedaccesskey"` | 2.5 | +| [customMidnight](#u__10__customMidnight) | `integer` | Time on which time breaks into new day. | *Optional* | `0` | 2.2 | +| feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | 2.2 | +| [fullyQualifiedNamespace](#u__10__fullyQualifiedNamespace) | `string` | Service Bus fully qualified namespace used for authentication. | *Optional* | | 2.5 | +| fuzzyTripMatching | `boolean` | Whether to apply fuzzyTripMatching on the updates | *Optional* | `false` | 2.2 | +| [servicebus-url](#u__10__servicebus_url) | `string` | Service Bus connection used for authentication. | *Optional* | | 2.2 | +| topic | `string` | Service Bus topic to connect to. | *Optional* | | 2.2 | +| history | `object` | Configuration for fetching historical data on startup | *Optional* | | 2.2 | +|    fromDateTime | `string` | Datetime boundary for historical data. | *Optional* | `"-P1D"` | 2.2 | +|    timeout | `integer` | Timeout in milliseconds | *Optional* | `300000` | na | +|    toDateTime | `string` | Datetime boundary for historical data. | *Optional* | `"P1D"` | 2.2 | +|    url | `string` | Endpoint to fetch from | *Optional* | | na | + + +##### Parameter details + +

authenticationType

+ +**Since version:** `2.5` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"sharedaccesskey"` +**Path:** /updaters/[10] +**Enum values:** `sharedaccesskey` | `federatedidentity` + +Which authentication type to use + +

customMidnight

+ +**Since version:** `2.2` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `0` +**Path:** /updaters/[10] + +Time on which time breaks into new day. + +It is common that operating day date breaks a little bit later than midnight so that the switch happens when traffic is at the lowest point. Parameter uses 24-hour format. If the switch happens on 4 am then set this field to 4. + +

fullyQualifiedNamespace

+ +**Since version:** `2.5` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[10] + +Service Bus fully qualified namespace used for authentication. + +Has to be present for authenticationMethod FederatedIdentity. + +

servicebus-url

+ +**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[10] + +Service Bus connection used for authentication. + +Has to be present for authenticationMethod SharedAccessKey. This should be Primary/Secondary connection string from service bus. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "siri-azure-sx-updater", + "topic" : "some_topic", + "servicebus-url" : "service_bus_url", + "feedId" : "feed_id", + "customMidnight" : 4, + "history" : { + "url" : "endpoint_url", + "fromDateTime" : "-P1D", + "toDateTime" : "P1D", + "timeout" : 300000 + } + } + ] +} +``` + + + +## Changelog +- Added configuration for turning off stop arrival time match feature. +- Initial version (April 2022) +- Minor changes in logging (November 2022) +- Retry fetch from history endpoint if it failed (February 2023) +- Solve a bug in SiriAzureETUpdater and improve error logging (March 2023) +- Add support with federated identity authentication (February 2024) \ No newline at end of file diff --git a/docs/sandbox/SiriUpdater.md b/docs/sandbox/siri/SiriUpdater.md similarity index 100% rename from docs/sandbox/SiriUpdater.md rename to docs/sandbox/siri/SiriUpdater.md diff --git a/mkdocs.yml b/mkdocs.yml index c4717d4d2e0..dd24a6e2dd4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -95,8 +95,8 @@ nav: - Actuator API: 'sandbox/ActuatorAPI.md' - Direct Transfer Analyzer: 'sandbox/transferanalyzer.md' - Google Cloud Storage: 'sandbox/GoogleCloudStorage.md' - - SIRI Updaters: 'sandbox/SiriUpdater.md' - - SIRI Updater (Azure): 'sandbox/SiriAzureUpdater.md' + - SIRI Updaters: 'sandbox/siri/SiriUpdater.md' + - SIRI Updater (Azure): 'sandbox/siri/SiriAzureUpdater.md' - Vehicle Rental Service Directory API support: 'sandbox/VehicleRentalServiceDirectory.md' - Smoove Bike Rental Updator Support: 'sandbox/SmooveBikeRental.md' - Mapbox Vector Tiles API: 'sandbox/MapboxVectorTilesApi.md' diff --git a/pom.xml b/pom.xml index 03d408a4b2e..faee696b5cb 100644 --- a/pom.xml +++ b/pom.xml @@ -907,17 +907,18 @@ com.azure azure-core - 1.45.0 + 1.46.0 com.azure azure-messaging-servicebus - 7.14.5 + 7.15.0 - com.azure.resourcemanager - azure-resourcemanager-servicebus - 2.32.0 + com.azure + azure-identity + 1.11.2 + compile ch.poole diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java index 0d36233f22b..f96941c7370 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AbstractAzureSiriUpdater.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.siri.updater.azure; +import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.messaging.servicebus.ServiceBusClientBuilder; import com.azure.messaging.servicebus.ServiceBusErrorContext; import com.azure.messaging.servicebus.ServiceBusException; @@ -36,6 +37,8 @@ public abstract class AbstractAzureSiriUpdater implements GraphUpdater { private final Logger LOG = LoggerFactory.getLogger(getClass()); + private final AuthenticationType authenticationType; + private final String fullyQualifiedNamespace; private final String configRef; private final String serviceBusUrl; private final SiriFuzzyTripMatcher fuzzyTripMatcher; @@ -63,6 +66,8 @@ public abstract class AbstractAzureSiriUpdater implements GraphUpdater { public AbstractAzureSiriUpdater(SiriAzureUpdaterParameters config, TransitModel transitModel) { this.configRef = config.configRef(); + this.authenticationType = config.getAuthenticationType(); + this.fullyQualifiedNamespace = config.getFullyQualifiedNamespace(); this.serviceBusUrl = config.getServiceBusUrl(); this.topicName = config.getTopicName(); this.dataInitializationUrl = config.getDataInitializationUrl(); @@ -105,10 +110,17 @@ public void run() { } // Client with permissions to create subscription - serviceBusAdmin = - new ServiceBusAdministrationClientBuilder() - .connectionString(serviceBusUrl) - .buildAsyncClient(); + if (authenticationType == AuthenticationType.FederatedIdentity) { + serviceBusAdmin = + new ServiceBusAdministrationClientBuilder() + .credential(fullyQualifiedNamespace, new DefaultAzureCredentialBuilder().build()) + .buildAsyncClient(); + } else if (authenticationType == AuthenticationType.SharedAccessKey) { + serviceBusAdmin = + new ServiceBusAdministrationClientBuilder() + .connectionString(serviceBusUrl) + .buildAsyncClient(); + } // If Idle more then one day, then delete subscription so we don't have old obsolete subscriptions on Azure Service Bus var options = new CreateSubscriptionOptions(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AuthenticationType.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AuthenticationType.java new file mode 100644 index 00000000000..65cf8caac93 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/AuthenticationType.java @@ -0,0 +1,6 @@ +package org.opentripplanner.ext.siri.updater.azure; + +public enum AuthenticationType { + SharedAccessKey, + FederatedIdentity, +} diff --git a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureUpdaterParameters.java index 93e9a6bded8..0d207d27efe 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/siri/updater/azure/SiriAzureUpdaterParameters.java @@ -4,6 +4,8 @@ public abstract class SiriAzureUpdaterParameters { private String configRef; private String type; + private AuthenticationType authenticationType; + private String fullyQualifiedNamespace; private String serviceBusUrl; private String topicName; private String dataInitializationUrl; @@ -28,6 +30,22 @@ public String getType() { return type; } + public AuthenticationType getAuthenticationType() { + return authenticationType; + } + + public void setAuthenticationType(AuthenticationType authenticationType) { + this.authenticationType = authenticationType; + } + + public String getFullyQualifiedNamespace() { + return fullyQualifiedNamespace; + } + + public void setFullyQualifiedNamespace(String fullyQualifiedNamespace) { + this.fullyQualifiedNamespace = fullyQualifiedNamespace; + } + public String getServiceBusUrl() { return serviceBusUrl; } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureETUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureETUpdaterConfig.java index 80615acb9b2..791f9a9dadc 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureETUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureETUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters.azure; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import org.opentripplanner.ext.siri.updater.azure.SiriAzureETUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -14,13 +15,26 @@ public static SiriAzureETUpdaterParameters create(String configRef, NodeAdapter if (c.exist("history")) { NodeAdapter history = c .of("history") - .since(NA) - .summary("TODO") - .description(/*TODO DOC*/"TODO") + .since(V2_2) + .summary("Configuration for fetching historical data on startup") .asObject(); - String fromDateTime = history.of("fromDateTime").since(NA).summary("TODO").asString("-P1D"); - int customMidnight = c.of("customMidnight").since(NA).summary("TODO").asInt(0); + String fromDateTime = history + .of("fromDateTime") + .since(V2_2) + .summary("Datetime boundary for historical data") + .asString("-P1D"); + + int customMidnight = c + .of("customMidnight") + .since(V2_2) + .summary("Time on which time breaks into new day.") + .description( + "It is common that operating day date breaks a little bit later than midnight so " + + "that the switch happens when traffic is at the lowest point. Parameter uses 24-hour format. " + + "If the switch happens on 4 am then set this field to 4." + ) + .asInt(0); parameters.setFromDateTime(asDateOrRelativePeriod(fromDateTime, customMidnight)); } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureSXUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureSXUpdaterConfig.java index 7d63657caf8..cf5dacb5976 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureSXUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureSXUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters.azure; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import org.opentripplanner.ext.siri.updater.azure.SiriAzureSXUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -14,14 +15,32 @@ public static SiriAzureSXUpdaterParameters create(String configRef, NodeAdapter if (c.exist("history")) { NodeAdapter history = c .of("history") - .since(NA) - .summary("TODO") - .description(/*TODO DOC*/"TODO") + .since(V2_2) + .summary("Configuration for fetching historical data on startup.") .asObject(); - String fromDateTime = history.of("fromDateTime").since(NA).summary("TODO").asString("-P1D"); - String toDateTime = history.of("toDateTime").since(NA).summary("TODO").asString("P1D"); - int customMidnight = c.of("customMidnight").since(NA).summary("TODO").asInt(0); + String fromDateTime = history + .of("fromDateTime") + .since(V2_2) + .summary("Datetime boundary for historical data.") + .asString("-P1D"); + + String toDateTime = history + .of("toDateTime") + .since(V2_2) + .summary("Datetime boundary for historical data.") + .asString("P1D"); + + int customMidnight = c + .of("customMidnight") + .since(V2_2) + .summary("Time on which time breaks into new day.") + .description( + "It is common that operating day date breaks a little bit later than midnight so " + + "that the switch happens when traffic is at the lowest point. Parameter uses 24-hour format. " + + "If the switch happens on 4 am then set this field to 4." + ) + .asInt(0); parameters.setFromDateTime(asDateOrRelativePeriod(fromDateTime, customMidnight)); parameters.setToDateTime(asDateOrRelativePeriod(toDateTime, customMidnight)); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureUpdaterConfig.java index 319b2d45001..35da716337e 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/azure/SiriAzureUpdaterConfig.java @@ -1,12 +1,15 @@ package org.opentripplanner.standalone.config.routerconfig.updaters.azure; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.NA; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import java.time.LocalDate; import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; +import org.opentripplanner.ext.siri.updater.azure.AuthenticationType; import org.opentripplanner.ext.siri.updater.azure.SiriAzureUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -18,24 +21,61 @@ public static void populateConfig( NodeAdapter c ) { parameters.setConfigRef(configRef); - parameters.setServiceBusUrl(c.of("servicebus-url").since(NA).summary("TODO").asString(null)); - parameters.setTopicName(c.of("topic").since(NA).summary("TODO").asString(null)); - parameters.setFeedId(c.of("feedId").since(NA).summary("TODO").asString(null)); + parameters.setServiceBusUrl( + c + .of("servicebus-url") + .since(V2_2) + .summary("Service Bus connection used for authentication.") + .description( + "Has to be present for authenticationMethod SharedAccessKey. This should be Primary/Secondary connection string from service bus." + ) + .asString(null) + ); + parameters.setTopicName( + c.of("topic").since(V2_2).summary("Service Bus topic to connect to.").asString(null) + ); + parameters.setFeedId( + c + .of("feedId") + .since(V2_2) + .summary("The ID of the feed to apply the updates to.") + .asString(null) + ); parameters.setFuzzyTripMatching( - c.of("fuzzyTripMatching").since(NA).summary("TODO").asBoolean(false) + c + .of("fuzzyTripMatching") + .since(V2_2) + .summary("Whether to apply fuzzyTripMatching on the updates") + .asBoolean(false) + ); + parameters.setFullyQualifiedNamespace( + c + .of("fullyQualifiedNamespace") + .since(V2_5) + .summary("Service Bus fully qualified namespace used for authentication.") + .description("Has to be present for authenticationMethod FederatedIdentity.") + .asString(null) + ); + parameters.setAuthenticationType( + c + .of("authenticationType") + .since(V2_5) + .summary("Which authentication type to use") + .asEnum(AuthenticationType.SharedAccessKey) ); if (c.exist("history")) { NodeAdapter history = c .of("history") - .since(NA) - .summary("TODO") - .description(/*TODO DOC*/"TODO") + .since(V2_2) + .summary("Configuration for fetching historical data on startup") .asObject(); parameters.setDataInitializationUrl( - history.of("url").since(NA).summary("TODO").asString(null) + history.of("url").since(NA).summary("Endpoint to fetch from").asString(null) + ); + parameters.setTimeout( + history.of("timeout").since(NA).summary("Timeout in milliseconds").asInt(300000) ); - parameters.setTimeout(history.of("timeout").since(NA).summary("TODO").asInt(300000)); } } diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java new file mode 100644 index 00000000000..374ac2b4bbb --- /dev/null +++ b/src/test/java/org/opentripplanner/generate/doc/SiriAzureConfigDocTest.java @@ -0,0 +1,98 @@ +package org.opentripplanner.generate.doc; + +import static org.opentripplanner.framework.application.OtpFileNames.ROUTER_CONFIG_FILENAME; +import static org.opentripplanner.framework.io.FileUtils.assertFileEquals; +import static org.opentripplanner.framework.io.FileUtils.readFile; +import static org.opentripplanner.framework.io.FileUtils.writeFile; +import static org.opentripplanner.framework.text.MarkdownFormatter.HEADER_4; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.DOCS_ROOT; +import static org.opentripplanner.generate.doc.framework.DocsTestConstants.TEMPLATE_ROOT; +import static org.opentripplanner.generate.doc.framework.TemplateUtil.replaceSection; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeFromResource; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.generate.doc.framework.DocBuilder; +import org.opentripplanner.generate.doc.framework.GeneratesDocumentation; +import org.opentripplanner.generate.doc.framework.ParameterDetailsList; +import org.opentripplanner.generate.doc.framework.ParameterSummaryTable; +import org.opentripplanner.generate.doc.framework.SkipNodes; +import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +@GeneratesDocumentation +public class SiriAzureConfigDocTest { + + private static final File TEMPLATE = new File(TEMPLATE_ROOT, "sandbox/siri/SiriAzureUpdater.md"); + private static final File OUT_FILE = new File(DOCS_ROOT, "sandbox/siri/SiriAzureUpdater.md"); + + private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; + private static final Set INCLUDE_UPDATERS = Set.of( + "siri-azure-et-updater", + "siri-azure-sx-updater" + ); + private static final SkipNodes SKIP_NODES = SkipNodes.of().build(); + public static final ObjectMapper mapper = new ObjectMapper(); + + /** + * NOTE! This test updates the {@code docs/sandbox/SiriUpdater.md} document based on the latest + * version of the code. + */ + @Test + public void updateSiriDoc() { + NodeAdapter node = readUpdaterConfig(); + + // Read and close input file (same as output file) + String template = readFile(TEMPLATE); + String original = readFile(OUT_FILE); + + for (String childName : node.listChildrenByName()) { + var child = node.child(childName); + var type = child.typeQualifier(); + + if (INCLUDE_UPDATERS.contains(type)) { + template = replaceSection(template, type, updaterDoc(child)); + } + } + + writeFile(OUT_FILE, template); + assertFileEquals(original, OUT_FILE); + } + + private NodeAdapter readUpdaterConfig() { + var json = jsonNodeFromResource(ROUTER_CONFIG_PATH); + var conf = new RouterConfig(json, ROUTER_CONFIG_PATH, false); + return conf.asNodeAdapter().child("updaters"); + } + + private String updaterDoc(NodeAdapter node) { + DocBuilder buf = new DocBuilder(); + addParameterSummaryTable(buf, node); + addDetailsSection(buf, node); + addExample(buf, node); + return buf.toString(); + } + + private void addParameterSummaryTable(DocBuilder buf, NodeAdapter node) { + buf.addSection(new ParameterSummaryTable(SKIP_NODES).createTable(node).toMarkdownTable()); + } + + private void addDetailsSection(DocBuilder buf, NodeAdapter node) { + String details = getParameterDetailsTable(node); + + if (!details.isBlank()) { + buf.header(5, "Parameter details", null).addSection(details); + } + } + + private String getParameterDetailsTable(NodeAdapter node) { + return ParameterDetailsList.listParametersWithDetails(node, SKIP_NODES, HEADER_4); + } + + private void addExample(DocBuilder buf, NodeAdapter node) { + buf.addSection("##### Example configuration"); + buf.addUpdaterExample(ROUTER_CONFIG_FILENAME, node.rawNode()); + } +} diff --git a/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java index 1ceb0ab092c..2474f37c402 100644 --- a/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/SiriConfigDocTest.java @@ -25,8 +25,8 @@ @GeneratesDocumentation public class SiriConfigDocTest { - private static final File TEMPLATE = new File(TEMPLATE_ROOT, "SiriUpdater.md"); - private static final File OUT_FILE = new File(DOCS_ROOT, "sandbox/SiriUpdater.md"); + private static final File TEMPLATE = new File(TEMPLATE_ROOT, "sandbox/siri/SiriUpdater.md"); + private static final File OUT_FILE = new File(DOCS_ROOT, "sandbox/siri/SiriUpdater.md"); private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final Set INCLUDE_UPDATERS = Set.of("siri-et-updater", "siri-sx-updater"); diff --git a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java index fa5abca7814..4bdfe782615 100644 --- a/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java +++ b/src/test/java/org/opentripplanner/generate/doc/UpdaterConfigDocTest.java @@ -31,6 +31,7 @@ public class UpdaterConfigDocTest { private static final String ROUTER_CONFIG_PATH = "standalone/config/" + ROUTER_CONFIG_FILENAME; private static final Set SKIP_UPDATERS = Set.of( "siri-azure-sx-updater", + "siri-azure-et-updater", "vehicle-parking", "siri-et-updater", "siri-sx-updater" diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 2b2449a7415..1a270929947 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -392,6 +392,21 @@ "toDateTime": "P1D", "timeout": 300000 } + }, + // SIRI ET updater for Azure Service Bus + { + "type": "siri-azure-et-updater", + "topic": "some_topic", + "authenticationType": "SharedAccessKey", + "fullyQualifiedNamespace": "fully_qualified_namespace", + "servicebus-url": "service_bus_url", + "feedId": "feed_id", + "customMidnight": 4, + "history": { + "url": "endpoint_url", + "fromDateTime": "-P1D", + "timeout": 300000 + } } ], "rideHailingServices": [ From f8f6dab50530f47adff26a93e0c6a79bc9267b21 Mon Sep 17 00:00:00 2001 From: Bartosz-Kruba <98400292+Bartosz-Kruba@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:10:51 +0100 Subject: [PATCH 040/108] Update docs/sandbox/siri/SiriAzureUpdater.md Co-authored-by: Johan Torin --- docs/sandbox/siri/SiriAzureUpdater.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sandbox/siri/SiriAzureUpdater.md b/docs/sandbox/siri/SiriAzureUpdater.md index 75aa58897ec..fa9442054a4 100644 --- a/docs/sandbox/siri/SiriAzureUpdater.md +++ b/docs/sandbox/siri/SiriAzureUpdater.md @@ -1,7 +1,7 @@ # Siri Azure Updater -It is sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages through *Azure Service Bus*. -IT also OTP to download historical data from en HTTP endpoint on startup. +It is a sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages through *Azure Service Bus*. +It also allows for OTP to download historical real-time data from an HTTP endpoint on startup. ## Contact Info From 7be0113880b9b61c84ebd3e31fcdb6617164f77c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 22 Feb 2024 19:26:17 +0100 Subject: [PATCH 041/108] Fix broken document generation introduced in #5682 --- docs/sandbox/siri/SiriAzureUpdater.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sandbox/siri/SiriAzureUpdater.md b/docs/sandbox/siri/SiriAzureUpdater.md index fa9442054a4..75aa58897ec 100644 --- a/docs/sandbox/siri/SiriAzureUpdater.md +++ b/docs/sandbox/siri/SiriAzureUpdater.md @@ -1,7 +1,7 @@ # Siri Azure Updater -It is a sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages through *Azure Service Bus*. -It also allows for OTP to download historical real-time data from an HTTP endpoint on startup. +It is sandbox extension developed by Skånetrafiken that allows OTP to fetch Siri ET & SX messages through *Azure Service Bus*. +IT also OTP to download historical data from en HTTP endpoint on startup. ## Contact Info From e5b943a4ad5ad1735b88f99cfd80cddb5bc1d0b6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 23 Feb 2024 08:47:45 +0100 Subject: [PATCH 042/108] Disable Skanetrafiken speed test [ci skip] --- .github/workflows/performance-test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index 1b8201a01ae..3c2ffdba465 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -32,10 +32,11 @@ jobs: jfr-delay: "35s" profile: core - - location: skanetrafiken - iterations: 1 - jfr-delay: "50s" - profile: core + # disabled due to https://github.com/opentripplanner/OpenTripPlanner/issues/5702 + #- location: skanetrafiken + # iterations: 1 + # jfr-delay: "50s" + # profile: core # extended locations that are run only after merging to dev-2.x From 7b6f8a87d0688fb420b2258a4db4f12892d23565 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 23 Feb 2024 11:31:07 +0100 Subject: [PATCH 043/108] Add vector tile layer for area stops --- .../ext/vectortiles/VectorTilesResource.java | 3 ++ .../areastops/AreaStopsLayerBuilder.java | 53 +++++++++++++++++++ .../DigitransitAreaStopPropertyMapper.java | 45 ++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java create mode 100644 src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java index a1e9a83c85a..dddf0c2c035 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java @@ -18,6 +18,7 @@ import java.util.function.Predicate; import org.glassfish.grizzly.http.server.Request; import org.opentripplanner.apis.support.TileJson; +import org.opentripplanner.ext.vectortiles.layers.areastops.AreaStopsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.stations.StationsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.stops.StopsLayerBuilder; import org.opentripplanner.ext.vectortiles.layers.vehicleparkings.VehicleParkingGroupsLayerBuilder; @@ -123,6 +124,7 @@ private static LayerBuilder crateLayerBuilder( return switch (layerParameters.type()) { case Stop -> new StopsLayerBuilder(context.transitService(), layerParameters, locale); case Station -> new StationsLayerBuilder(context.transitService(), layerParameters, locale); + case AreaStop -> new AreaStopsLayerBuilder(context.transitService(), layerParameters, locale); case VehicleRental -> new VehicleRentalPlacesLayerBuilder( context.vehicleRentalService(), layerParameters, @@ -153,6 +155,7 @@ private static LayerBuilder crateLayerBuilder( public enum LayerType { Stop, Station, + AreaStop, VehicleRental, VehicleRentalVehicle, VehicleRentalStation, diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java new file mode 100644 index 00000000000..be03df4decf --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java @@ -0,0 +1,53 @@ +package org.opentripplanner.ext.vectortiles.layers.areastops; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.BiFunction; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.opentripplanner.apis.support.mapping.PropertyMapper; +import org.opentripplanner.ext.vectortiles.VectorTilesResource; +import org.opentripplanner.inspector.vector.LayerBuilder; +import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.service.TransitService; + +public class AreaStopsLayerBuilder extends LayerBuilder { + + static Map>> mappers = Map.of( + MapperType.Digitransit, + DigitransitAreaStopPropertyMapper::create + ); + private final TransitService transitService; + + public AreaStopsLayerBuilder( + TransitService transitService, + LayerParameters layerParameters, + Locale locale + ) { + super( + mappers.get(MapperType.valueOf(layerParameters.mapper())).apply(transitService, locale), + layerParameters.name(), + layerParameters.expansionFactor() + ); + this.transitService = transitService; + } + + protected List getGeometries(Envelope query) { + return transitService + .findAreaStops(query) + .stream() + .filter(g -> g.getGeometry() != null) + .map(stop -> { + Geometry point = stop.getGeometry().copy(); + point.setUserData(stop); + return point; + }) + .toList(); + } + + enum MapperType { + Digitransit, + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java new file mode 100644 index 00000000000..b826c30f38f --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java @@ -0,0 +1,45 @@ +package org.opentripplanner.ext.vectortiles.layers.areastops; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import org.opentripplanner.apis.support.mapping.PropertyMapper; +import org.opentripplanner.framework.i18n.I18NStringMapper; +import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.service.TransitService; + +public class DigitransitAreaStopPropertyMapper extends PropertyMapper { + + private final TransitService transitService; + private final I18NStringMapper i18NStringMapper; + + private DigitransitAreaStopPropertyMapper(TransitService transitService, Locale locale) { + this.transitService = transitService; + this.i18NStringMapper = new I18NStringMapper(locale); + } + + protected static DigitransitAreaStopPropertyMapper create( + TransitService transitService, + Locale locale + ) { + return new DigitransitAreaStopPropertyMapper(transitService, locale); + } + + @Override + protected Collection map(AreaStop stop) { + var routeColors = transitService + .getRoutesForStop(stop) + .stream() + .map(Route::getColor) + .distinct() + .toList(); + return List.of( + new KeyValue("gtfsId", stop.getId().toString()), + new KeyValue("name", i18NStringMapper.mapNonnullToApi(stop.getName())), + new KeyValue("code", stop.getCode()), + new KeyValue("routeColors", routeColors) + ); + } +} From 018806d2b6d24d3522b186b1f59b695785277035 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Thu, 22 Feb 2024 16:33:24 +0100 Subject: [PATCH 044/108] feat: Add Hamburg OSM mapper --- .../tagmapping/HamburgMapper.java | 32 +++++++++++++ .../tagmapping/OsmTagMapperSource.java | 2 + .../tagmapping/HamburgMapperTest.java | 48 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java create mode 100644 src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java new file mode 100644 index 00000000000..ae6ba260cd8 --- /dev/null +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java @@ -0,0 +1,32 @@ +package org.opentripplanner.openstreetmap.tagmapping; + +import org.opentripplanner.openstreetmap.model.OSMWithTags; + +/** + * Modified mapper to allow through traffic for combination access=customers and customers=HVV. + * + * @see GermanyMapper + * @see OsmTagMapper + * @see DefaultMapper + */ +public class HamburgMapper extends GermanyMapper { + + @Override + public boolean isGeneralNoThroughTraffic(OSMWithTags way) { + String access = way.getTag("access"); + boolean isNoThroughTraffic = doesTagValueDisallowThroughTraffic(access); + + if (isNoThroughTraffic && way.hasTag("customers")) { + String customers = way.getTag("customers"); + return !isAllowedThroughTrafficForHVV(access, customers); + } + + return isNoThroughTraffic; + } + + private boolean isAllowedThroughTrafficForHVV(String access, String customers) { + boolean isAccessCustomers = access.equals("customers"); + boolean isHVV = customers.equals("HVV"); + return isAccessCustomers && isHVV; + } +} diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperSource.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperSource.java index 6ed4a701c30..d430ad05f7d 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperSource.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperSource.java @@ -10,6 +10,7 @@ public enum OsmTagMapperSource { UK, FINLAND, GERMANY, + HAMBURG, ATLANTA, HOUSTON, PORTLAND, @@ -22,6 +23,7 @@ public OsmTagMapper getInstance() { case UK -> new UKMapper(); case FINLAND -> new FinlandMapper(); case GERMANY -> new GermanyMapper(); + case HAMBURG -> new HamburgMapper(); case ATLANTA -> new AtlantaMapper(); case HOUSTON -> new HoustonMapper(); case PORTLAND -> new PortlandMapper(); diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java new file mode 100644 index 00000000000..36096d9d698 --- /dev/null +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -0,0 +1,48 @@ +package org.opentripplanner.openstreetmap.tagmapping; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.opentripplanner.openstreetmap.model.OSMWithTags; + +public class HamburgMapperTest { + + private HamburgMapper mapper; + + @BeforeEach + void createMapper() { + mapper = new HamburgMapper(); + } + + @Test + public void shouldAllowThroughTraffic_WhenAccessCustomers_AndCustomersHVV() { + OSMWithTags way = new OSMWithTags(); + way.addTag("access", "customers"); + way.addTag("customers", "HVV"); + + boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); + + Assertions.assertFalse(generalNoThroughTraffic, + "access=customers and customers=hvv should not be considered through-traffic"); + } + + @ParameterizedTest + @CsvSource(value = { + "no", + "destination", + "private", + "customers", + "delivery" + }) + public void shouldDisallowThroughTraffic_WhenNoCustomersHVV(String access) { + OSMWithTags way = new OSMWithTags(); + way.addTag("access", access); + + boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); + + Assertions.assertTrue(generalNoThroughTraffic, + "access={no, destination, private, customers, delivery} should be blocked in general"); + } +} From 2ccf86f3c0e9fb36ca085ca25f67554c26e9e55d Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Thu, 22 Feb 2024 17:03:40 +0100 Subject: [PATCH 045/108] style: Apply style improvements --- .../tagmapping/HamburgMapperTest.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java index 36096d9d698..f49771c26a5 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -24,25 +24,23 @@ public void shouldAllowThroughTraffic_WhenAccessCustomers_AndCustomersHVV() { boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); - Assertions.assertFalse(generalNoThroughTraffic, - "access=customers and customers=hvv should not be considered through-traffic"); + Assertions.assertFalse( + generalNoThroughTraffic, + "access=customers and customers=hvv should not be considered through-traffic" + ); } @ParameterizedTest - @CsvSource(value = { - "no", - "destination", - "private", - "customers", - "delivery" - }) + @CsvSource(value = { "no", "destination", "private", "customers", "delivery" }) public void shouldDisallowThroughTraffic_WhenNoCustomersHVV(String access) { OSMWithTags way = new OSMWithTags(); way.addTag("access", access); boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); - Assertions.assertTrue(generalNoThroughTraffic, - "access={no, destination, private, customers, delivery} should be blocked in general"); + Assertions.assertTrue( + generalNoThroughTraffic, + "access={no, destination, private, customers, delivery} should be blocked in general" + ); } } From 6df6a007a24399153e92a5752be88c1ab81bee00 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Thu, 22 Feb 2024 17:03:52 +0100 Subject: [PATCH 046/108] docs: Add enum option --- docs/BuildConfiguration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index 05da4cf4d0a..b2ab185ef97 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -945,7 +945,7 @@ the local filesystem. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"default"` **Path:** /osm/[0] -**Enum values:** `default` | `norway` | `uk` | `finland` | `germany` | `atlanta` | `houston` | `portland` | `constant-speed-finland` +**Enum values:** `default` | `norway` | `uk` | `finland` | `germany` | `hamburg` | `atlanta` | `houston` | `portland` | `constant-speed-finland` The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. @@ -953,7 +953,7 @@ The named set of mapping rules applied when parsing OSM tags. Overrides the valu **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"default"` **Path:** /osmDefaults -**Enum values:** `default` | `norway` | `uk` | `finland` | `germany` | `atlanta` | `houston` | `portland` | `constant-speed-finland` +**Enum values:** `default` | `norway` | `uk` | `finland` | `germany` | `hamburg` | `atlanta` | `houston` | `portland` | `constant-speed-finland` The named set of mapping rules applied when parsing OSM tags. From c394161e5e24c5ec10e5f452f5723f86e1be1f0b Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Fri, 23 Feb 2024 14:06:40 +0100 Subject: [PATCH 047/108] refactor: Apply review suggestions --- .../openstreetmap/tagmapping/HamburgMapperTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java index f49771c26a5..0991decea46 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -1,10 +1,13 @@ package org.opentripplanner.openstreetmap.tagmapping; -import org.junit.jupiter.api.Assertions; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; public class HamburgMapperTest { @@ -24,21 +27,21 @@ public void shouldAllowThroughTraffic_WhenAccessCustomers_AndCustomersHVV() { boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); - Assertions.assertFalse( + assertFalse( generalNoThroughTraffic, "access=customers and customers=hvv should not be considered through-traffic" ); } @ParameterizedTest - @CsvSource(value = { "no", "destination", "private", "customers", "delivery" }) + @ValueSource(strings = { "no", "destination", "private", "customers", "delivery" }) public void shouldDisallowThroughTraffic_WhenNoCustomersHVV(String access) { OSMWithTags way = new OSMWithTags(); way.addTag("access", access); boolean generalNoThroughTraffic = mapper.isGeneralNoThroughTraffic(way); - Assertions.assertTrue( + assertTrue( generalNoThroughTraffic, "access={no, destination, private, customers, delivery} should be blocked in general" ); From cdcea7afcf98ffaf0ee7e92f84b9a366dc83e955 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Fri, 23 Feb 2024 14:27:37 +0100 Subject: [PATCH 048/108] refactor: Prevent NPE --- .../openstreetmap/tagmapping/HamburgMapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java index ae6ba260cd8..789828e975c 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java @@ -25,8 +25,8 @@ public boolean isGeneralNoThroughTraffic(OSMWithTags way) { } private boolean isAllowedThroughTrafficForHVV(String access, String customers) { - boolean isAccessCustomers = access.equals("customers"); - boolean isHVV = customers.equals("HVV"); + boolean isAccessCustomers = "customers".equals(access); + boolean isHVV = "HVV".equals(customers); return isAccessCustomers && isHVV; } } From f41cc65831ea6e702a20c595397ff0532c80dc2a Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Fri, 23 Feb 2024 14:28:45 +0100 Subject: [PATCH 049/108] refactor: Remove obsolete import --- .../openstreetmap/tagmapping/HamburgMapperTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java index 0991decea46..029c1b7c662 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; From f0285f372d66d74ac087795cb922bfe0077ef83b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 24 Feb 2024 22:28:12 +0100 Subject: [PATCH 050/108] Add test for tile layer --- .../areastops/AreaStopsLayerBuilderTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java new file mode 100644 index 00000000000..9c7a4530f8c --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java @@ -0,0 +1,54 @@ +package org.opentripplanner.ext.vectortiles.layers.areastops; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.geometry.Polygons; +import org.opentripplanner.ext.vectortiles.VectorTilesResource; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; +import org.opentripplanner.transit.service.StopModelBuilder; +import org.opentripplanner.transit.service.TransitModel; + +class AreaStopsLayerBuilderTest { + + private static final FeedScopedId ID = new FeedScopedId("FEED", "ID"); + private static final I18NString NAME = I18NString.of("Test stop"); + + private final StopModelBuilder stopModelBuilder = StopModel.of(); + + private final AreaStop AREA_STOP = stopModelBuilder + .areaStop(ID) + .withName(NAME) + .withGeometry(Polygons.BERLIN) + .build(); + + private final TransitModel transitModel = new TransitModel(stopModelBuilder.withAreaStop(AREA_STOP).build(), new Deduplicator()); + record Layer( + String name, + VectorTilesResource.LayerType type, + String mapper, + int maxZoom, + int minZoom, + int cacheMaxSeconds, + double expansionFactor + ) + implements LayerParameters {} + @Test + void getAreaStops() { + + transitModel.index(); + + var layer = new Layer("areaStops", VectorTilesResource.LayerType.AreaStop, "Digitransit", 20, 1, 10, .25); + var subject = new AreaStopsLayerBuilder(new DefaultTransitService(transitModel), layer, Locale.ENGLISH); + var geometries = subject.getGeometries(AREA_STOP.getGeometry().getEnvelopeInternal()); + assertEquals(List.of(Polygons.BERLIN), geometries); + } +} \ No newline at end of file From 40864c32db9a64859e8633237beff64bd675d7c0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 25 Feb 2024 08:47:15 +0100 Subject: [PATCH 051/108] Add tests --- .../areastops/AreaStopsLayerBuilderTest.java | 27 +++++++++--- ...DigitransitAreaStopPropertyMapperTest.java | 44 +++++++++++++++++++ .../DigitransitAreaStopPropertyMapper.java | 24 +++++++--- 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java index 9c7a4530f8c..b86336f867c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java @@ -30,7 +30,11 @@ class AreaStopsLayerBuilderTest { .withGeometry(Polygons.BERLIN) .build(); - private final TransitModel transitModel = new TransitModel(stopModelBuilder.withAreaStop(AREA_STOP).build(), new Deduplicator()); + private final TransitModel transitModel = new TransitModel( + stopModelBuilder.withAreaStop(AREA_STOP).build(), + new Deduplicator() + ); + record Layer( String name, VectorTilesResource.LayerType type, @@ -41,14 +45,27 @@ record Layer( double expansionFactor ) implements LayerParameters {} + @Test void getAreaStops() { - transitModel.index(); - var layer = new Layer("areaStops", VectorTilesResource.LayerType.AreaStop, "Digitransit", 20, 1, 10, .25); - var subject = new AreaStopsLayerBuilder(new DefaultTransitService(transitModel), layer, Locale.ENGLISH); + var layer = new Layer( + "areaStops", + VectorTilesResource.LayerType.AreaStop, + "Digitransit", + 20, + 1, + 10, + .25 + ); + + var subject = new AreaStopsLayerBuilder( + new DefaultTransitService(transitModel), + layer, + Locale.ENGLISH + ); var geometries = subject.getGeometries(AREA_STOP.getGeometry().getEnvelopeInternal()); assertEquals(List.of(Polygons.BERLIN), geometries); } -} \ No newline at end of file +} diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java new file mode 100644 index 00000000000..87e10c0ac22 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java @@ -0,0 +1,44 @@ +package org.opentripplanner.ext.vectortiles.layers.areastops; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.inspector.vector.KeyValue.kv; + +import java.util.List; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.geometry.Polygons; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.service.StopModel; + +class DigitransitAreaStopPropertyMapperTest { + + private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of()); + private static final AreaStop STOP = MODEL.areaStopForTest("123", Polygons.BERLIN); + private static final Route ROUTE_WITH_COLOR = TransitModelForTest + .route("123") + .withColor("ffffff") + .build(); + private static final Route ROUTE_WITHOUT_COLOR = TransitModelForTest.route("456").build(); + + @Test + void map() { + var mapper = new DigitransitAreaStopPropertyMapper( + ignored -> List.of(ROUTE_WITH_COLOR, ROUTE_WITHOUT_COLOR), + Locale.ENGLISH + ); + + var kv = mapper.map(STOP); + + assertEquals( + List.of( + kv("gtfsId", "F:123"), + kv("name", "123"), + kv("code", null), + kv("routeColors", "ffffff") + ), + kv + ); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java index b826c30f38f..fe60a2961cf 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java @@ -3,20 +3,27 @@ import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.framework.i18n.I18NStringMapper; import org.opentripplanner.inspector.vector.KeyValue; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.site.AreaStop; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; public class DigitransitAreaStopPropertyMapper extends PropertyMapper { - private final TransitService transitService; + private final Function> getRoutesForStop; private final I18NStringMapper i18NStringMapper; - private DigitransitAreaStopPropertyMapper(TransitService transitService, Locale locale) { - this.transitService = transitService; + protected DigitransitAreaStopPropertyMapper( + Function> getRoutesForStop, + Locale locale + ) { + this.getRoutesForStop = getRoutesForStop; this.i18NStringMapper = new I18NStringMapper(locale); } @@ -24,17 +31,20 @@ protected static DigitransitAreaStopPropertyMapper create( TransitService transitService, Locale locale ) { - return new DigitransitAreaStopPropertyMapper(transitService, locale); + return new DigitransitAreaStopPropertyMapper(transitService::getRoutesForStop, locale); } @Override protected Collection map(AreaStop stop) { - var routeColors = transitService - .getRoutesForStop(stop) + var routeColors = getRoutesForStop + .apply(stop) .stream() .map(Route::getColor) + .filter(Objects::nonNull) .distinct() - .toList(); + // the MVT spec explicitly doesn't cover how to encode arrays + // https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/ + .collect(Collectors.joining(",")); return List.of( new KeyValue("gtfsId", stop.getId().toString()), new KeyValue("name", i18NStringMapper.mapNonnullToApi(stop.getName())), From 672885a403654a9970a0cf51a0c547f92b061449 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 25 Feb 2024 21:45:36 +0100 Subject: [PATCH 052/108] Add documentation --- doc-templates/sandbox/MapboxVectorTilesApi.md | 9 +++++++++ docs/sandbox/MapboxVectorTilesApi.md | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/doc-templates/sandbox/MapboxVectorTilesApi.md b/doc-templates/sandbox/MapboxVectorTilesApi.md index dfec1ed085a..b8fe4924c48 100644 --- a/doc-templates/sandbox/MapboxVectorTilesApi.md +++ b/doc-templates/sandbox/MapboxVectorTilesApi.md @@ -49,6 +49,14 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, + { + "name": "areaStops", + "type": "AreaStop", + "mapper": "Digitransit", + "maxZoom": 20, + "minZoom": 14, + "cacheMaxSeconds": 600 + }, { "name": "stations", "type": "Station", @@ -136,6 +144,7 @@ For each layer, the configuration includes: - `name` which is used in the url to fetch tiles, and as the layer name in the vector tiles. - `type` which tells the type of the layer. Currently supported: - `Stop` + - `AreaStop` - `Station` - `VehicleRental`: all rental places: stations and free-floating vehicles - `VehicleRentalVehicle`: free-floating rental vehicles diff --git a/docs/sandbox/MapboxVectorTilesApi.md b/docs/sandbox/MapboxVectorTilesApi.md index 537f1b800dd..ddc3db90ac4 100644 --- a/docs/sandbox/MapboxVectorTilesApi.md +++ b/docs/sandbox/MapboxVectorTilesApi.md @@ -49,6 +49,14 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, + { + "name": "areaStops", + "type": "AreaStop", + "mapper": "Digitransit", + "maxZoom": 20, + "minZoom": 14, + "cacheMaxSeconds": 600 + }, { "name": "stations", "type": "Station", @@ -136,6 +144,7 @@ For each layer, the configuration includes: - `name` which is used in the url to fetch tiles, and as the layer name in the vector tiles. - `type` which tells the type of the layer. Currently supported: - `Stop` + - `AreaStop` - `Station` - `VehicleRental`: all rental places: stations and free-floating vehicles - `VehicleRentalVehicle`: free-floating rental vehicles From 9d31f8baaa549a80e79cd3ae71e13c7daf355720 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Mon, 26 Feb 2024 10:17:16 +0100 Subject: [PATCH 053/108] refactor: Simplified code --- .../openstreetmap/tagmapping/HamburgMapper.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java index 789828e975c..5bcd4d23380 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java @@ -16,17 +16,6 @@ public boolean isGeneralNoThroughTraffic(OSMWithTags way) { String access = way.getTag("access"); boolean isNoThroughTraffic = doesTagValueDisallowThroughTraffic(access); - if (isNoThroughTraffic && way.hasTag("customers")) { - String customers = way.getTag("customers"); - return !isAllowedThroughTrafficForHVV(access, customers); - } - - return isNoThroughTraffic; - } - - private boolean isAllowedThroughTrafficForHVV(String access, String customers) { - boolean isAccessCustomers = "customers".equals(access); - boolean isHVV = "HVV".equals(customers); - return isAccessCustomers && isHVV; + return isNoThroughTraffic && !way.isTag("customers", "HVV"); } } From fec949d71609c3a96855fb1ac3ae02d9bbb8f062 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 26 Feb 2024 15:00:30 +0100 Subject: [PATCH 054/108] Fix typo --- .../opentripplanner/ext/vectortiles/VectorTilesResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java index dddf0c2c035..29701ee2307 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java @@ -69,7 +69,7 @@ public Response tileGet( locale, Arrays.asList(requestedLayers.split(",")), serverContext.vectorTileConfig().layers(), - VectorTilesResource::crateLayerBuilder, + VectorTilesResource::createLayerBuilder, serverContext ); } @@ -116,7 +116,7 @@ private List getFeedInfos() { .toList(); } - private static LayerBuilder crateLayerBuilder( + private static LayerBuilder createLayerBuilder( LayerParameters layerParameters, Locale locale, OtpServerRequestContext context From dd135d114fdc9022c5ccaa6518db9c8dfd9b33ec Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 26 Feb 2024 16:19:53 +0100 Subject: [PATCH 055/108] bug: Apply real-time changes to StopPatterns on top of planned data, not earlier updates. --- .../ext/siri/ModifiedTripBuilder.java | 28 +++--- .../ext/siri/mapper/PickDropMapper.java | 23 +++-- .../StopConsolidationModule.java | 2 +- .../lang/MemEfficientArrayBuilder.java | 99 +++++++++++++++++++ .../transit/model/network/StopPattern.java | 67 ++++++++----- .../transit/model/network/TripPattern.java | 22 ++++- .../updater/trip/TimetableSnapshotSource.java | 3 +- .../lang/MemEfficientArrayBuilderTest.java | 91 +++++++++++++++++ .../model/TripPatternTest.java | 8 +- .../netex/mapping/ServiceLinkMapperTest.java | 2 +- .../model/_data/TransitModelForTest.java | 12 +-- 11 files changed, 291 insertions(+), 66 deletions(-) create mode 100644 src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java create mode 100644 src/test/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilderTest.java diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index df4509eb2d6..5c21be364a5 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -98,7 +98,7 @@ public ModifiedTripBuilder( public Result build() { RealTimeTripTimes newTimes = existingTripTimes.copyScheduledTimes(); - StopPattern stopPattern = createStopPattern(pattern, calls, entityResolver); + var stopPattern = createStopPattern(pattern, calls, entityResolver); if (cancellation || stopPattern.isAllStopsNonRoutable()) { LOG.debug("Trip is cancelled"); @@ -220,15 +220,12 @@ static StopPattern createStopPattern( EntityResolver entityResolver ) { int numberOfStops = pattern.numberOfStops(); - var builder = pattern.getStopPattern().mutate(); + var builder = pattern.copyPlannedStopPattern(); Set alreadyVisited = new HashSet<>(); // modify updated stop-times for (int i = 0; i < numberOfStops; i++) { - StopLocation stop = pattern.getStop(i); - builder.stops[i] = stop; - builder.dropoffs[i] = pattern.getAlightType(i); - builder.pickups[i] = pattern.getBoardType(i); + StopLocation stop = builder.stops.original(i); for (CallWrapper call : calls) { if (alreadyVisited.contains(call)) { @@ -241,22 +238,25 @@ static StopPattern createStopPattern( continue; } - int stopIndex = i; - builder.stops[stopIndex] = callStop; + // Used in lambda + final int stopIndex = i; + builder.stops.with(stopIndex, callStop); PickDropMapper - .mapPickUpType(call, builder.pickups[stopIndex]) - .ifPresent(value -> builder.pickups[stopIndex] = value); + .mapPickUpType(call, builder.pickups.original(stopIndex)) + .ifPresent(value -> builder.pickups.with(stopIndex, value)); PickDropMapper - .mapDropOffType(call, builder.dropoffs[stopIndex]) - .ifPresent(value -> builder.dropoffs[stopIndex] = value); + .mapDropOffType(call, builder.dropoffs.original(stopIndex)) + .ifPresent(value -> builder.dropoffs.with(stopIndex, value)); alreadyVisited.add(call); break; } } - - return builder.build(); + var newStopPattern = builder.build(); + return (pattern.isModified() && pattern.getStopPattern().equals(newStopPattern)) + ? pattern.getStopPattern() + : newStopPattern; } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/mapper/PickDropMapper.java b/src/ext/java/org/opentripplanner/ext/siri/mapper/PickDropMapper.java index 4feb4dbbd20..d35696ec35e 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/mapper/PickDropMapper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/mapper/PickDropMapper.java @@ -18,12 +18,12 @@ public class PickDropMapper { * The Siri ArrivalBoardingActivity includes less information than the pick drop type, therefore is it only * changed if routability has changed. * - * @param currentValue The current pick drop value on a stopTime + * @param plannedValue The current pick drop value on a stopTime * @param call The incoming call to be mapped * @return Mapped PickDrop type, empty if routability is not changed. */ - public static Optional mapDropOffType(CallWrapper call, PickDrop currentValue) { - if (shouldBeCancelled(currentValue, call.isCancellation(), call.getArrivalStatus())) { + public static Optional mapDropOffType(CallWrapper call, PickDrop plannedValue) { + if (shouldBeCancelled(plannedValue, call.isCancellation(), call.getArrivalStatus())) { return Optional.of(CANCELLED); } @@ -33,7 +33,7 @@ public static Optional mapDropOffType(CallWrapper call, PickDrop curre } return switch (arrivalBoardingActivityEnumeration) { - case ALIGHTING -> currentValue.isNotRoutable() ? Optional.of(SCHEDULED) : Optional.empty(); + case ALIGHTING -> plannedValue.isNotRoutable() ? Optional.of(SCHEDULED) : Optional.empty(); case NO_ALIGHTING -> Optional.of(NONE); case PASS_THRU -> Optional.of(CANCELLED); }; @@ -45,12 +45,12 @@ public static Optional mapDropOffType(CallWrapper call, PickDrop curre * The Siri DepartureBoardingActivity includes less information than the planned data, therefore is it only * changed if routability has changed. * - * @param currentValue The current pick drop value on a stopTime + * @param plannedValue The current pick drop value on a stopTime * @param call The incoming call to be mapped * @return Mapped PickDrop type, empty if routability is not changed. */ - public static Optional mapPickUpType(CallWrapper call, PickDrop currentValue) { - if (shouldBeCancelled(currentValue, call.isCancellation(), call.getDepartureStatus())) { + public static Optional mapPickUpType(CallWrapper call, PickDrop plannedValue) { + if (shouldBeCancelled(plannedValue, call.isCancellation(), call.getDepartureStatus())) { return Optional.of(CANCELLED); } @@ -60,7 +60,7 @@ public static Optional mapPickUpType(CallWrapper call, PickDrop curren } return switch (departureBoardingActivityEnumeration) { - case BOARDING -> currentValue.isNotRoutable() ? Optional.of(SCHEDULED) : Optional.empty(); + case BOARDING -> plannedValue.isNotRoutable() ? Optional.of(SCHEDULED) : Optional.empty(); case NO_BOARDING -> Optional.of(NONE); case PASS_THRU -> Optional.of(CANCELLED); }; @@ -71,17 +71,16 @@ public static Optional mapPickUpType(CallWrapper call, PickDrop curren * * If the existing PickDrop is non-routable, the value is not changed. * - * @param currentValue The current pick drop value on a stopTime + * @param plannedValue The planned pick drop value on a stopTime * @param isCallCancellation The incoming call cancellation-flag * @param callStatus The incoming call arrival/departure status - * @return */ private static boolean shouldBeCancelled( - PickDrop currentValue, + PickDrop plannedValue, Boolean isCallCancellation, CallStatusEnumeration callStatus ) { - if (currentValue.isNotRoutable()) { + if (plannedValue.isNotRoutable()) { return false; } return TRUE.equals(isCallCancellation) || callStatus == CallStatusEnumeration.CANCELLED; diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java index 91cbe5e1856..100941d88d4 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationModule.java @@ -67,7 +67,7 @@ private TripPattern modifyStopsInPattern( TripPattern pattern, List replacements ) { - var updatedStopPattern = pattern.getStopPattern().mutate(); + var updatedStopPattern = pattern.copyPlannedStopPattern(); replacements.forEach(r -> updatedStopPattern.replaceStop(r.secondary(), r.primary())); return pattern.copy().withStopPattern(updatedStopPattern.build()).build(); } diff --git a/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java b/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java new file mode 100644 index 00000000000..161ac5c5cf5 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java @@ -0,0 +1,99 @@ +package org.opentripplanner.framework.lang; + +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * This array builder is used to minimize creating new objects(arrays). It takes an array as base, + * the original array. A new array is created only if there are differences. + *

+ * A common case is that one original is updated several times. In this case, you can use the + * {@link #build(Object[])}, too also make sure the existing update is reused (deduplicated). + *

+ * Arrays are mutable, so be careful this class helps you reuse the original if it has the same + * values. It protects the original while in scope, but you should only use it if you do not + * modify the original or the result on the outside. This builder does not help protect the arrays. + */ +public final class MemEfficientArrayBuilder { + + private final T[] original; + private T[] array = null; + + private MemEfficientArrayBuilder(@Nonnull T[] original) { + this.original = Objects.requireNonNull(original); + } + + /** + * Create a new array with the same size and values as the original. + */ + public static MemEfficientArrayBuilder of(T[] original) { + return new MemEfficientArrayBuilder<>(original); + } + + /** + * The size of the original and new array under construction. + */ + public int size() { + return original.length; + } + + /** + * Set the value at the given index. + */ + public MemEfficientArrayBuilder with(int index, T value) { + if (isNotModified()) { + if (value == original[index]) { + return this; + } + array = Arrays.copyOf(original, original.length); + } else if (value == array[index]) { + return this; + } + array[index] = value; + return this; + } + + /** + * Return the value at the given index from the original array. + */ + public T original(int index) { + return original[index]; + } + + /** + * Return the new value or fallback to the original value at the given index. + */ + public T getOrOriginal(int index) { + return isNotModified() ? original[index] : array[index]; + } + + /** + * There are no changes compared to the original array so far. + */ + public boolean isNotModified() { + return array == null; + } + + /** + * Build a new array. + *

    + *
  1. If no modifications exist the original array is returned
  2. + *
  3. If the new array equals the candidate the candidate is returned
  4. + *
  5. If not, a new array is returned
  6. + *
+ */ + public T[] build(T[] candidate) { + if (isNotModified()) { + return original; + } + return Arrays.equals(candidate, array) ? candidate : array; + } + + /** + * Create a new array or return the original [if not modified] + */ + public T[] build() { + return isNotModified() ? original : array; + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/network/StopPattern.java b/src/main/java/org/opentripplanner/transit/model/network/StopPattern.java index fe1bb84cf3f..b5f11d387a9 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/StopPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/StopPattern.java @@ -9,6 +9,8 @@ import java.util.Optional; import java.util.function.Predicate; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.MemEfficientArrayBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -81,11 +83,19 @@ public StopPattern(Collection stopTimes) { * For creating StopTimes without StopTime, for example for unit testing. */ public static StopPatternBuilder create(int length) { - return new StopPatternBuilder(new StopPattern(length)); + return new StopPatternBuilder(new StopPattern(length), null); } - public StopPatternBuilder mutate() { - return new StopPatternBuilder(this); + /** + * This has package local access since a StopPattern is a part of a TripPattern. To change it + * use the {@link TripPattern#copyPlannedStopPattern()} method. + */ + StopPatternBuilder mutate() { + return new StopPatternBuilder(this, null); + } + + StopPatternBuilder mutate(StopPattern realTime) { + return new StopPatternBuilder(this, realTime); } public int hashCode() { @@ -300,16 +310,20 @@ boolean sameStations(@Nonnull StopPattern other, int index) { public static class StopPatternBuilder { - public final StopLocation[] stops; - public final PickDrop[] pickups; - public final PickDrop[] dropoffs; + public final MemEfficientArrayBuilder stops; + public final MemEfficientArrayBuilder pickups; + public final MemEfficientArrayBuilder dropoffs; private final StopPattern original; - public StopPatternBuilder(StopPattern original) { - stops = Arrays.copyOf(original.stops, original.stops.length); - pickups = Arrays.copyOf(original.pickups, original.pickups.length); - dropoffs = Arrays.copyOf(original.dropoffs, original.dropoffs.length); + @Nullable + private final StopPattern realTime; + + public StopPatternBuilder(StopPattern original, StopPattern realTime) { + stops = MemEfficientArrayBuilder.of(original.stops); + pickups = MemEfficientArrayBuilder.of(original.pickups); + dropoffs = MemEfficientArrayBuilder.of(original.dropoffs); this.original = original; + this.realTime = realTime; } /** @@ -319,8 +333,8 @@ public StopPatternBuilder(StopPattern original) { */ public StopPatternBuilder cancelStops(List cancelledStopIndices) { cancelledStopIndices.forEach(index -> { - pickups[index] = PickDrop.CANCELLED; - dropoffs[index] = PickDrop.CANCELLED; + pickups.with(index, PickDrop.CANCELLED); + dropoffs.with(index, PickDrop.CANCELLED); }); return this; } @@ -331,28 +345,33 @@ public StopPatternBuilder cancelStops(List cancelledStopIndices) { public StopPatternBuilder replaceStop(FeedScopedId old, StopLocation newStop) { Objects.requireNonNull(old); Objects.requireNonNull(newStop); - for (int i = 0; i < stops.length; i++) { - if (stops[i].getId().equals(old)) { - stops[i] = newStop; + for (int i = 0; i < stops.size(); i++) { + if (stops.getOrOriginal(i).getId().equals(old)) { + stops.with(i, newStop); } } return this; } + /** + * We want to deduplicate this as much as we can, since this is done + * millions of times during real-time updates. + */ public StopPattern build() { - boolean sameStops = Arrays.equals(stops, original.stops); - boolean sameDropoffs = Arrays.equals(dropoffs, original.dropoffs); - boolean samePickups = Arrays.equals(pickups, original.pickups); - - if (sameStops && samePickups && sameDropoffs) { + if (stops.isNotModified() && dropoffs.isNotModified() && pickups.isNotModified()) { return original; } - StopLocation[] newStops = sameStops ? original.stops : stops; - PickDrop[] newPickups = samePickups ? original.pickups : pickups; - PickDrop[] newDropoffs = sameDropoffs ? original.dropoffs : dropoffs; + if (realTime != null) { + var newStopPattern = new StopPattern( + stops.build(realTime.stops), + pickups.build(realTime.pickups), + dropoffs.build(realTime.dropoffs) + ); + return realTime.equals(newStopPattern) ? realTime : newStopPattern; + } - return new StopPattern(newStops, newPickups, newDropoffs); + return new StopPattern(stops.build(), pickups.build(), dropoffs.build()); } } } diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index 7057d9fd56e..a439c9fd4fe 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -159,6 +159,20 @@ public StopPattern getStopPattern() { return stopPattern; } + /** + * Return the "original"/planned stop pattern as a builder. This is used when a realtime-update + * contains a full set of stops/pickup/droppoff for a pattern. This will wipe out any changes + * to the stop-pattern from previous updates. + *

+ * Be aware, if the same update is applied twice, then the first instance will be reused to avoid + * unnecessary objects creation and gc. + */ + public StopPattern.StopPatternBuilder copyPlannedStopPattern() { + return isModified() + ? originalTripPattern.stopPattern.mutate(stopPattern) + : stopPattern.mutate(); + } + public LineString getGeometry() { if (hopGeometries == null || hopGeometries.length == 0) { return null; @@ -346,7 +360,7 @@ public void removeTrips(Predicate removeTrip) { */ public boolean isModifiedFromTripPatternWithEqualStops(TripPattern other) { return ( - originalTripPattern != null && + isModified() && originalTripPattern.equals(other) && getStopPattern().stopsEqual(other.getStopPattern()) ); @@ -394,6 +408,10 @@ public TripPattern getOriginalTripPattern() { return originalTripPattern; } + public boolean isModified() { + return originalTripPattern != null; + } + /** * Returns trip headsign from the scheduled timetables or from the original pattern's scheduled * timetables if this pattern is added by realtime and the stop sequence has not changed apart @@ -481,7 +499,7 @@ public TripPatternBuilder copy() { * is added through a realtime update. The pickup and dropoff values don't have to be the same. */ private boolean containsSameStopsAsOriginalPattern() { - return originalTripPattern != null && getStops().equals(originalTripPattern.getStops()); + return isModified() && getStops().equals(originalTripPattern.getStops()); } /** diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index 19db2c7e309..049d4933c51 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -467,8 +467,7 @@ private Result handleScheduledTrip( // If there are skipped stops, we need to change the pattern from the scheduled one if (skippedStopIndices.size() > 0) { StopPattern newStopPattern = pattern - .getStopPattern() - .mutate() + .copyPlannedStopPattern() .cancelStops(skippedStopIndices) .build(); diff --git a/src/test/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilderTest.java b/src/test/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilderTest.java new file mode 100644 index 00000000000..16468868fe7 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilderTest.java @@ -0,0 +1,91 @@ +package org.opentripplanner.framework.lang; + +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.DayOfWeek; +import org.junit.jupiter.api.Test; + +class MemEfficientArrayBuilderTest { + + private final DayOfWeek[] WEEKEND = { SATURDAY, SUNDAY }; + + @Test + void size() { + assertEquals(WEEKEND.length, MemEfficientArrayBuilder.of(WEEKEND).size()); + } + + @Test + void with() { + var smallWeekend = MemEfficientArrayBuilder + .of(WEEKEND) + .with(0, DayOfWeek.THURSDAY) + .with(1, DayOfWeek.FRIDAY) + .build(); + assertArrayEquals(new DayOfWeek[] { DayOfWeek.THURSDAY, DayOfWeek.FRIDAY }, smallWeekend); + } + + @Test + void withOutChange() { + var array = MemEfficientArrayBuilder.of(WEEKEND).build(); + assertSame(WEEKEND, array); + + array = MemEfficientArrayBuilder.of(WEEKEND).with(0, SATURDAY).build(); + assertSame(WEEKEND, array); + + array = MemEfficientArrayBuilder.of(WEEKEND).with(1, SUNDAY).with(0, SATURDAY).build(); + assertSame(WEEKEND, array); + } + + @Test + void getOrOriginal() { + var array = MemEfficientArrayBuilder.of(WEEKEND).with(1, MONDAY); + assertEquals(SATURDAY, array.getOrOriginal(0)); + assertEquals(MONDAY, array.getOrOriginal(1)); + } + + @Test + void original() { + // Verify that modifications do not change original + var array = MemEfficientArrayBuilder.of(WEEKEND).with(1, MONDAY); + assertEquals(SATURDAY, array.original(0)); + assertEquals(SUNDAY, array.original(1)); + } + + @Test + void isNotModified() { + var array = MemEfficientArrayBuilder.of(WEEKEND); + assertTrue(array.isNotModified()); + + array.with(0, SATURDAY).with(1, SUNDAY); + assertTrue(array.isNotModified()); + + array.with(0, MONDAY); + assertFalse(array.isNotModified()); + } + + @Test + void testBuildWithCandidate() { + DayOfWeek[] candidate = { TUESDAY, WEDNESDAY }; + var array = MemEfficientArrayBuilder.of(WEEKEND); + + // Without changes, we expect the original to be retuned + assertSame(WEEKEND, array.build(candidate)); + + // Second value set, but not first + array = MemEfficientArrayBuilder.of(WEEKEND).with(1, WEDNESDAY); + assertArrayEquals(new DayOfWeek[] { SATURDAY, WEDNESDAY }, array.build(candidate)); + + // Same as candidate build + array = MemEfficientArrayBuilder.of(WEEKEND).with(1, WEDNESDAY).with(0, TUESDAY); + assertArrayEquals(candidate, array.build(candidate)); + } +} diff --git a/src/test/java/org/opentripplanner/model/TripPatternTest.java b/src/test/java/org/opentripplanner/model/TripPatternTest.java index 0ea509be36b..bad159eb3cd 100644 --- a/src/test/java/org/opentripplanner/model/TripPatternTest.java +++ b/src/test/java/org/opentripplanner/model/TripPatternTest.java @@ -95,11 +95,11 @@ public TripPattern setupTripPattern( List geometry ) { var builder = StopPattern.create(2); - builder.stops[0] = origin; - builder.stops[1] = destination; + builder.stops.with(0, origin); + builder.stops.with(1, destination); for (int i = 0; i < 2; i++) { - builder.pickups[i] = PickDrop.SCHEDULED; - builder.dropoffs[i] = PickDrop.SCHEDULED; + builder.pickups.with(i, PickDrop.SCHEDULED); + builder.dropoffs.with(i, PickDrop.SCHEDULED); } var stopPattern = builder.build(); diff --git a/src/test/java/org/opentripplanner/netex/mapping/ServiceLinkMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/ServiceLinkMapperTest.java index afd0604a1e5..aa19a436112 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/ServiceLinkMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/ServiceLinkMapperTest.java @@ -124,7 +124,7 @@ void setUpTestData() { new NetexMainAndSubMode(TransitMode.BUS, "UNKNOWN"), Accessibility.NO_INFORMATION ); - stopPatternBuilder.stops[i] = stop; + stopPatternBuilder.stops.with(i, stop); stopsById.add(stop); } diff --git a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java index f177415d2fc..e01809d2b9f 100644 --- a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java +++ b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java @@ -221,9 +221,9 @@ public List stopTimesEvery5Minutes(int count, Trip trip, int startTime public StopPattern stopPattern(int numberOfStops) { var builder = StopPattern.create(numberOfStops); for (int i = 0; i < numberOfStops; i++) { - builder.stops[i] = stop("Stop_" + i).build(); - builder.pickups[i] = PickDrop.SCHEDULED; - builder.dropoffs[i] = PickDrop.SCHEDULED; + builder.stops.with(i, stop("Stop_" + i).build()); + builder.pickups.with(i, PickDrop.SCHEDULED); + builder.dropoffs.with(i, PickDrop.SCHEDULED); } return builder.build(); } @@ -231,9 +231,9 @@ public StopPattern stopPattern(int numberOfStops) { public static StopPattern stopPattern(RegularStop... stops) { var builder = StopPattern.create(stops.length); for (int i = 0; i < stops.length; i++) { - builder.stops[i] = stops[i]; - builder.pickups[i] = PickDrop.SCHEDULED; - builder.dropoffs[i] = PickDrop.SCHEDULED; + builder.stops.with(i, stops[i]); + builder.pickups.with(i, PickDrop.SCHEDULED); + builder.dropoffs.with(i, PickDrop.SCHEDULED); } return builder.build(); } From 51b24f9533ba1228aa190fc366fb960f24c68f3f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:07:11 +0000 Subject: [PATCH 056/108] Update typescript-eslint monorepo to v7 --- client-next/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-next/package.json b/client-next/package.json index f78e8ebb23d..d0461a508b2 100644 --- a/client-next/package.json +++ b/client-next/package.json @@ -35,8 +35,8 @@ "@testing-library/react": "14.1.2", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", - "@typescript-eslint/eslint-plugin": "6.5.0", - "@typescript-eslint/parser": "6.5.0", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", "@vitejs/plugin-react": "4.0.4", "@vitest/coverage-v8": "1.1.3", "eslint": "8.48.0", From ef8608a4f0fd694b14360d319e5426f761f5eb78 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 21 Feb 2024 17:13:15 +0100 Subject: [PATCH 057/108] refator: Add penalty to Raptor TestAccessEgress --- .../_data/transit/TestAccessEgress.java | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java index ed0702f39d5..caff88515ae 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java @@ -9,7 +9,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -26,26 +25,26 @@ public class TestAccessEgress implements RaptorAccessEgress { private final int stop; private final int durationInSeconds; - private final int cost; + private final int c1; private final int numberOfRides; private final boolean stopReachedOnBoard; private final boolean free; private final Integer opening; private final Integer closing; private final boolean closed; - private final TimeAndCost penalty; + private final int timePenalty; private TestAccessEgress(Builder builder) { this.stop = builder.stop; this.durationInSeconds = builder.durationInSeconds; - this.cost = builder.cost; this.numberOfRides = builder.numberOfRides; this.stopReachedOnBoard = builder.stopReachedOnBoard; this.free = builder.free; this.opening = builder.opening; this.closing = builder.closing; this.closed = builder.closed; - this.penalty = builder.penalty; + this.timePenalty = builder.timePenalty; + this.c1 = builder.c1; if (free) { assertEquals(0, durationInSeconds); @@ -64,7 +63,7 @@ public static TestAccessEgress free(int stop) { } /** - * @deprecated A stop can not be both free and have a cost - This is not a valid + * @deprecated A stop cannot be both free and have a cost - This is not a valid * access/egress. */ @Deprecated @@ -122,11 +121,12 @@ public static TestAccessEgress flexAndWalk(int stop, int durationInSeconds, int return flexAndWalk(stop, durationInSeconds, nRides, walkCost(durationInSeconds)); } - public static TestAccessEgress car(int stop, int durationInSeconds, TimeAndCost penalty) { + // TODO Fix: imeAndCost penalty is an otp model thing - nothing to do with Raptor + public static TestAccessEgress car(int stop, int durationInSeconds, Object penalty) { return new Builder(stop, durationInSeconds) .withFree() .withCost(durationInSeconds) - .withPenalty(penalty) + //.withPenalty(penalty) .build(); } @@ -162,7 +162,7 @@ public static int walkCost(int durationInSeconds, double reluctance) { *

* Opening and closing is specified as seconds since the start of "RAPTOR time" to limit the * time periods that the access is traversable, which is repeatead every 24 hours. This allows - * the access to only be traversable between for example 08:00 and 16:00 every day. + * access to only be traversable between given times like 08:00 and 16:00 every day. */ public TestAccessEgress openingHours(int opening, int closing) { return copyOf().withOpeningHours(opening, closing).build(); @@ -188,7 +188,7 @@ public int stop() { @Override public int c1() { - return cost; + return c1; } @Override @@ -196,6 +196,11 @@ public int durationInSeconds() { return durationInSeconds; } + @Override + public int timePenalty() { + return timePenalty; + } + @Override public int earliestDepartureTime(int requestedDepartureTime) { if (!hasOpeningHours()) { @@ -276,19 +281,20 @@ protected static class Builder { int stop; int durationInSeconds; - int cost; + int c1; int numberOfRides = DEFAULT_NUMBER_OF_RIDES; boolean stopReachedOnBoard = STOP_REACHED_ON_FOOT; Integer opening = null; Integer closing = null; private boolean free = false; private boolean closed = false; - private TimeAndCost penalty; + private int timePenalty; Builder(int stop, int durationInSeconds) { this.stop = stop; this.durationInSeconds = durationInSeconds; - this.cost = walkCost(durationInSeconds); + this.c1 = walkCost(durationInSeconds); + this.timePenalty = RaptorConstants.ZERO; } Builder(TestAccessEgress original) { @@ -296,12 +302,12 @@ protected static class Builder { this.stop = original.stop; this.durationInSeconds = original.durationInSeconds; this.stopReachedOnBoard = original.stopReachedOnBoard; - this.cost = original.cost; + this.c1 = original.c1; this.numberOfRides = original.numberOfRides; this.opening = original.opening; this.closing = original.closing; this.closed = original.closed; - this.penalty = original.penalty; + this.timePenalty = original.timePenalty; } Builder withFree() { @@ -311,7 +317,7 @@ Builder withFree() { } Builder withCost(int cost) { - this.cost = cost; + this.c1 = cost; return this; } @@ -325,8 +331,8 @@ Builder stopReachedOnBoard() { return this; } - Builder withPenalty(TimeAndCost penalty) { - this.penalty = penalty; + Builder withPenalty(int timePenalty) { + this.timePenalty = timePenalty; return this; } From 02e5c1e5121bf7470b6030a6750ee9c23d01504a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 21 Feb 2024 17:37:24 +0100 Subject: [PATCH 058/108] refactor: Remove the car method from Raptor TestAccessEgress Raptor does not care about mode, so there is no difference between walking and driving; Hence having factory methods for CAR are very confusing. We have methods for walking and flex because they serve as "knob" for what to expect and a name to use in conversation. WALK => (simple, time-shiftable, fixed cost and duration) FLEX => (complex, opening-hours, hasRides, may arrive onBoard, multiple transfers) --- .../raptor/_data/transit/TestAccessEgress.java | 9 --------- .../mapping/RaptorPathToItineraryMapperTest.java | 10 +++++++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java index caff88515ae..153fb38718f 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java @@ -121,15 +121,6 @@ public static TestAccessEgress flexAndWalk(int stop, int durationInSeconds, int return flexAndWalk(stop, durationInSeconds, nRides, walkCost(durationInSeconds)); } - // TODO Fix: imeAndCost penalty is an otp model thing - nothing to do with Raptor - public static TestAccessEgress car(int stop, int durationInSeconds, Object penalty) { - return new Builder(stop, durationInSeconds) - .withFree() - .withCost(durationInSeconds) - //.withPenalty(penalty) - .build(); - } - /** Create a flex access arriving at given stop by walking. */ public static TestAccessEgress flexAndWalk( int stop, diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java index 2b8f792a455..8c0710d7db6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -118,11 +118,15 @@ void penalty() { RaptorPathToItineraryMapper mapper = getRaptorPathToItineraryMapper(); var penalty = new TimeAndCost(Duration.ofMinutes(10), Cost.costOfMinutes(10)); - RaptorPath path = createTestTripSchedulePath(getTestTripSchedule()) - .egress(TestAccessEgress.car(2, RaptorCostConverter.toRaptorCost(1000), penalty)); + // TODO - The TestAccessEgress is an internal Raptor test dummy class and is not allowed + // to be used outside raptor and optimized transfers. Also, the Itinerary mapper + // expect the generic type DefaultTripSchedule and not TestTripSchedule - it is pure + // luck that it works.. + // RaptorPath path = createTestTripSchedulePath(getTestTripSchedule()) + // .egress(TestAccessEgress.car(2, RaptorCostConverter.toRaptorCost(1000), penalty)); // Act - var itinerary = mapper.createItinerary(path); + var itinerary = mapper.createItinerary(null); // Assert assertNotNull(itinerary); From 049ebc1e031b0007a3c7110d25e8c9d8813595ec Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 22 Feb 2024 13:09:10 +0100 Subject: [PATCH 059/108] refactor: Remove time-penalty for time-limit check at destination for both forward and reverse search. Minor cleanup included. --- .../rangeraptor/context/SearchContext.java | 18 ++- .../path/DestinationArrivalPaths.java | 54 +++++++-- .../path/configure/PathConfig.java | 3 +- .../ForwardRaptorTransitCalculator.java | 44 +------ .../transit/RaptorTransitCalculator.java | 11 +- .../ReverseRaptorTransitCalculator.java | 14 --- .../ForwardRaptorTransitCalculatorTest.java | 114 ++++-------------- .../ReverseRaptorTransitCalculatorTest.java | 9 +- 8 files changed, 95 insertions(+), 172 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index 1706c879a2c..5b4a6db52e2 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -68,6 +68,9 @@ public class SearchContext { private final AccessPaths accessPaths; private final LifeCycleSubscriptions lifeCycleSubscriptions = new LifeCycleSubscriptions(); + @Nullable + private final IntPredicate acceptC2AtDestination; + /** Lazy initialized */ private RaptorCostCalculator costCalculator = null; @@ -79,14 +82,14 @@ public SearchContext( RaptorRequest request, RaptorTuningParameters tuningParameters, RaptorTransitDataProvider transit, - IntPredicate acceptC2AtDestination + @Nullable IntPredicate acceptC2AtDestination ) { this.request = request; this.tuningParameters = tuningParameters; this.transit = transit; this.accessPaths = accessPaths(request); this.egressPaths = egressPaths(request); - this.calculator = createCalculator(request, tuningParameters, acceptC2AtDestination); + this.calculator = createCalculator(request, tuningParameters); this.roundTracker = new RoundTracker( nRounds(), @@ -94,6 +97,7 @@ public SearchContext( lifeCycle() ); this.debugFactory = new DebugHandlerFactory<>(debugRequest(request), lifeCycle()); + this.acceptC2AtDestination = acceptC2AtDestination; } public AccessPaths accessPaths() { @@ -171,6 +175,11 @@ public RaptorTimers performanceTimers() { return request.performanceTimers(); } + @Nullable + public IntPredicate acceptC2AtDestination() { + return acceptC2AtDestination; + } + /** Number of stops in transit graph. */ public int nStops() { return transit.numberOfStops(); @@ -268,14 +277,13 @@ static Collection accessOrEgressPaths( */ private static RaptorTransitCalculator createCalculator( RaptorRequest r, - RaptorTuningParameters t, - IntPredicate acceptC2AtDestination + RaptorTuningParameters t ) { var forward = r.searchDirection().isForward(); SearchParams s = r.searchParams(); if (forward) { - return new ForwardRaptorTransitCalculator<>(s, t, acceptC2AtDestination); + return new ForwardRaptorTransitCalculator<>(s, t); } else { return new ReverseRaptorTransitCalculator<>(s, t); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index 6ed88da4c89..54565679653 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -2,6 +2,8 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Optional; +import java.util.function.IntPredicate; import javax.annotation.Nullable; import org.opentripplanner.framework.lang.OtpNumberFormat; import org.opentripplanner.framework.logging.Throttle; @@ -48,6 +50,9 @@ public class DestinationArrivalPaths { @Nullable private final RaptorCostCalculator costCalculator; + @Nullable + private final IntPredicate acceptC2AtDestination; + private final SlackProvider slackProvider; private final PathMapper pathMapper; private final DebugHandler> debugPathHandler; @@ -59,6 +64,7 @@ public DestinationArrivalPaths( ParetoComparator> paretoComparator, RaptorTransitCalculator transitCalculator, @Nullable RaptorCostCalculator costCalculator, + @Nullable IntPredicate acceptC2AtDestination, SlackProvider slackProvider, PathMapper pathMapper, DebugHandlerFactory debugHandlerFactory, @@ -71,6 +77,7 @@ public DestinationArrivalPaths( this.costCalculator = costCalculator; this.slackProvider = slackProvider; this.pathMapper = pathMapper; + this.acceptC2AtDestination = acceptC2AtDestination; this.debugPathHandler = debugHandlerFactory.debugPathArrival(); this.stopNameResolver = stopNameResolver; lifeCycle.onPrepareForNextRound(round -> clearReachedCurrentRoundFlag()); @@ -84,18 +91,26 @@ public void add(ArrivalView stopArrival, RaptorAccessEgress egressPath) { return; } - var errors = transitCalculator.rejectDestinationArrival(destArrival); - if (!errors.isEmpty()) { - debugReject(destArrival, String.join(" ", errors)); + var errors = new ArrayList(); + + rejectArrivalIfItExceedsTimeLimit(destArrival).ifPresent(errors::add); + rejectArrivalIfC2CheckFails(destArrival).ifPresent(errors::add); + + if (errors.isEmpty()) { + addDestinationArrivalToPaths(destArrival); } else { - RaptorPath path = pathMapper.mapToPath(destArrival); + debugReject(destArrival, String.join(" ", errors)); + } + } - assertGeneralizedCostIsCalculatedCorrectByMapper(destArrival, path); + private void addDestinationArrivalToPaths(DestinationArrival destArrival) { + RaptorPath path = pathMapper.mapToPath(destArrival); - boolean added = paths.add(path); - if (added) { - reachedCurrentRound = true; - } + assertGeneralizedCostIsCalculatedCorrectByMapper(destArrival, path); + + boolean added = paths.add(path); + if (added) { + reachedCurrentRound = true; } } @@ -240,4 +255,25 @@ private String raptorCostsAsString(DestinationArrival destArrival) { // Remove decimals if zero return String.join(" ", arrivalCosts).replaceAll("\\.00", ""); } + + private Optional rejectArrivalIfItExceedsTimeLimit(ArrivalView destArrival) { + int arrivalTime = transitCalculator.minusDuration( + destArrival.arrivalTime(), + destArrival.egressPath().egress().timePenalty() + ); + if (transitCalculator.exceedsTimeLimit(arrivalTime)) { + return Optional.of(transitCalculator.exceedsTimeLimitReason()); + } + return Optional.empty(); + } + + /** + * Test if the c2 value is acceptable, or should be rejected. If ok return nothing, if rejected + * returns the reason for the debug event log. + */ + private Optional rejectArrivalIfC2CheckFails(ArrivalView destArrival) { + return acceptC2AtDestination == null || acceptC2AtDestination.test(destArrival.c2()) + ? Optional.empty() + : Optional.of("C2 value rejected: " + destArrival.c2() + "."); + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java index 89b2f447ca4..673c83e6b78 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/configure/PathConfig.java @@ -26,7 +26,7 @@ /** * This class is responsible for creating a a result collector - the set of paths. *

- * This class have REQUEST scope, so a new instance should be created for each new request/travel + * This class has REQUEST scope, so a new instance should be created for each new request/travel * search. * * @param The TripSchedule type defined by the user of the raptor API. @@ -57,6 +57,7 @@ public DestinationArrivalPaths createDestArrivalPaths( createPathParetoComparator(costConfig, c2Comp), ctx.calculator(), costConfig.includeC1() ? ctx.costCalculator() : null, + ctx.acceptC2AtDestination(), ctx.slackProvider(), createPathMapper(costConfig.includeC1()), ctx.debugFactory(), diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java index 528c8ca276a..5c50aa030f5 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculator.java @@ -1,11 +1,6 @@ package org.opentripplanner.raptor.rangeraptor.transit; -import java.util.ArrayList; -import java.util.Collection; import java.util.Iterator; -import java.util.Optional; -import java.util.function.IntPredicate; -import javax.annotation.Nullable; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; @@ -13,7 +8,6 @@ import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; -import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.spi.RaptorConstrainedBoardingSearch; import org.opentripplanner.raptor.spi.RaptorTimeTable; @@ -30,20 +24,12 @@ public final class ForwardRaptorTransitCalculator private final int latestAcceptableArrivalTime; private final int iterationStep; - @Nullable - private final IntPredicate acceptC2AtDestination; - - public ForwardRaptorTransitCalculator( - SearchParams s, - RaptorTuningParameters t, - @Nullable IntPredicate acceptC2AtDestination - ) { + public ForwardRaptorTransitCalculator(SearchParams s, RaptorTuningParameters t) { this( s.earliestDepartureTime(), s.searchWindowInSeconds(), s.latestArrivalTime(), - t.iterationDepartureStepInSeconds(), - acceptC2AtDestination + t.iterationDepartureStepInSeconds() ); } @@ -51,8 +37,7 @@ public ForwardRaptorTransitCalculator( int earliestDepartureTime, int searchWindowInSeconds, int latestAcceptableArrivalTime, - int iterationStep, - IntPredicate acceptC2AtDestination + int iterationStep ) { this.earliestDepartureTime = earliestDepartureTime; this.searchWindowInSeconds = searchWindowInSeconds; @@ -61,19 +46,6 @@ public ForwardRaptorTransitCalculator( ? unreachedTime() : latestAcceptableArrivalTime; this.iterationStep = iterationStep; - this.acceptC2AtDestination = acceptC2AtDestination; - } - - @Override - public Collection rejectDestinationArrival(ArrivalView destArrival) { - var errors = new ArrayList(); - - if (exceedsTimeLimit(destArrival.arrivalTime())) { - errors.add(exceedsTimeLimitReason()); - } - rejectC2AtDestination(destArrival).ifPresent(errors::add); - - return errors; } @Override @@ -141,14 +113,4 @@ public RaptorTripScheduleSearch createTripSearch(RaptorTimeTable timeTable public RaptorTripScheduleSearch createExactTripSearch(RaptorTimeTable pattern) { return new TripScheduleExactMatchSearch<>(createTripSearch(pattern), this, iterationStep); } - - /** - * Test if the c2 value is acceptable, or should be rejected. If ok return nothing, if rejected - * return the reason for the debug event log. - */ - private Optional rejectC2AtDestination(ArrivalView destArrival) { - return acceptC2AtDestination == null || acceptC2AtDestination.test(destArrival.c2()) - ? Optional.empty() - : Optional.of("C2 value rejected: " + destArrival.c2() + "."); - } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java index 3e8236d6cd8..2fce438b858 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java @@ -3,13 +3,11 @@ import static org.opentripplanner.framework.time.TimeUtils.hm2time; import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; -import java.util.Collection; import java.util.Iterator; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; -import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.spi.RaptorConstrainedBoardingSearch; import org.opentripplanner.raptor.spi.RaptorTimeTable; @@ -60,17 +58,10 @@ static RaptorTransitCalculator testDummyCalcul boolean forward ) { return forward - ? new ForwardRaptorTransitCalculator<>(hm2time(8, 0), 2 * 60 * 60, TIME_NOT_SET, 60, null) + ? new ForwardRaptorTransitCalculator<>(hm2time(8, 0), 2 * 60 * 60, TIME_NOT_SET, 60) : new ReverseRaptorTransitCalculator<>(hm2time(8, 0), 2 * 60 * 60, TIME_NOT_SET, 60); } - /** - * Check if the destination arrival is a valid/optimal result. if ok, return an empty list, if - * not return a list of reject reasons. The reject messages are used to produce reject events in - * the debug trace log. - */ - Collection rejectDestinationArrival(ArrivalView destArrival); - /** * Stop the search when the time exceeds the latest-acceptable-arrival-time. In a reverse search * this is the earliest acceptable departure time. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java index 867e04ef680..09ded9fb4b7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculator.java @@ -1,7 +1,5 @@ package org.opentripplanner.raptor.rangeraptor.transit; -import java.util.ArrayList; -import java.util.Collection; import java.util.Iterator; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.raptor.api.model.RaptorConstants; @@ -10,7 +8,6 @@ import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; -import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.spi.RaptorConstrainedBoardingSearch; import org.opentripplanner.raptor.spi.RaptorTimeTable; @@ -53,17 +50,6 @@ public ReverseRaptorTransitCalculator(SearchParams s, RaptorTuningParameters t) this.iterationStep = iterationStep; } - @Override - public Collection rejectDestinationArrival(ArrivalView destArrival) { - var errors = new ArrayList(); - - if (exceedsTimeLimit(destArrival.arrivalTime())) { - errors.add(exceedsTimeLimitReason()); - } - - return errors; - } - @Override public boolean exceedsTimeLimit(int time) { return isBefore(earliestAcceptableDepartureTime, time); diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculatorTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculatorTest.java index 0a992cc412d..ad7a269b856 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculatorTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ForwardRaptorTransitCalculatorTest.java @@ -8,77 +8,41 @@ import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_A; import static org.opentripplanner.raptor._data.RaptorTestConstants.STOP_B; -import javax.annotation.Nullable; import org.junit.jupiter.api.Test; -import org.opentripplanner.framework.lang.IntBox; +import org.opentripplanner.raptor._data.transit.TestAccessEgress; import org.opentripplanner.raptor._data.transit.TestTransfer; import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; -import org.opentripplanner.raptor.api.model.PathLegType; import org.opentripplanner.raptor.api.model.RaptorConstants; -import org.opentripplanner.raptor.api.view.ArrivalView; import org.opentripplanner.raptor.spi.IntIterator; public class ForwardRaptorTransitCalculatorTest { + private static final int BIG_TIME = hm2time(100, 0); private int earliestDepartureTime = hm2time(8, 0); private int searchWindowSizeInSeconds = 2 * 60 * 60; private int latestAcceptableArrivalTime = hm2time(16, 0); private int iterationStep = 60; - private int desiredC2 = 0; @Test public void exceedsTimeLimit() { latestAcceptableArrivalTime = 1200; var subject = create(); - assertTrue(subject.rejectDestinationArrival(new TestArrivalView(desiredC2, 0)).isEmpty()); - assertTrue( - subject - .rejectDestinationArrival(new TestArrivalView(desiredC2, latestAcceptableArrivalTime)) - .isEmpty() - ); - assertFalse( - subject - .rejectDestinationArrival(new TestArrivalView(desiredC2, latestAcceptableArrivalTime + 1)) - .isEmpty() - ); + assertFalse(subject.exceedsTimeLimit(latestAcceptableArrivalTime)); + assertTrue(subject.exceedsTimeLimit(latestAcceptableArrivalTime + 1)); latestAcceptableArrivalTime = hm2time(16, 0); - subject = create(); - var errors = subject.rejectDestinationArrival( - new TestArrivalView(desiredC2, latestAcceptableArrivalTime + 1) - ); - assertEquals(1, errors.size()); + assertEquals( "The arrival time exceeds the time limit, arrive to late: 16:00:00.", - errors.stream().findFirst().get() + create().exceedsTimeLimitReason() ); latestAcceptableArrivalTime = RaptorConstants.TIME_NOT_SET; subject = create(); - assertTrue(subject.rejectDestinationArrival(new TestArrivalView(desiredC2, 0)).isEmpty()); - assertTrue( - subject.rejectDestinationArrival(new TestArrivalView(desiredC2, 2_000_000_000)).isEmpty() - ); - } - - @Test - public void rejectC2AtDestination() { - desiredC2 = 1; - var subject = create(); - - var errors = subject.rejectDestinationArrival( - new TestArrivalView(desiredC2, latestAcceptableArrivalTime) - ); - assertTrue(errors.isEmpty()); - - errors = - subject.rejectDestinationArrival( - new TestArrivalView(desiredC2 + 1, latestAcceptableArrivalTime) - ); - assertEquals(1, errors.size()); - assertEquals("C2 value rejected: 2.", errors.stream().findFirst().get()); + assertFalse(subject.exceedsTimeLimit(-BIG_TIME)); + assertFalse(subject.exceedsTimeLimit(BIG_TIME)); } @Test @@ -122,13 +86,28 @@ public void getTransfers() { assertFalse(subject.getTransfers(transitData, STOP_B).hasNext()); } + @Test + void timeMinusPenalty() { + var subject = create(); + var walk200s = TestAccessEgress.walk(15, 200); + int time = 1000; + int penalty = 300; + int expectedTimeWithoutPenalty = time - penalty; + + assertEquals(time, subject.timeMinusPenalty(time, walk200s)); + assertEquals( + expectedTimeWithoutPenalty, + subject.timeMinusPenalty(time, walk200s.withTimePenalty(penalty)) + ); + } + private RaptorTransitCalculator create() { return new ForwardRaptorTransitCalculator<>( earliestDepartureTime, searchWindowSizeInSeconds, latestAcceptableArrivalTime, - iterationStep, - c2 -> c2 == desiredC2 + iterationStep + //c2 -> c2 == desiredC2 ); } @@ -139,47 +118,4 @@ private void assertIntIterator(IntIterator it, int... values) { } assertFalse(it.hasNext()); } - - public record TestArrivalView(int c2, int arrivalTime) implements ArrivalView { - @Override - public int stop() { - return c2; - } - - @Override - public int round() { - return 0; - } - - @Override - public int arrivalTime() { - return arrivalTime; - } - - @Override - public int c1() { - return 0; - } - - @Override - public int c2() { - return c2; - } - - @Nullable - @Override - public ArrivalView previous() { - return null; - } - - @Override - public PathLegType arrivedBy() { - return null; - } - - @Override - public boolean arrivedOnBoard() { - return false; - } - } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculatorTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculatorTest.java index 898a11fafa7..3afecaf3e81 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculatorTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/ReverseRaptorTransitCalculatorTest.java @@ -12,10 +12,13 @@ import org.opentripplanner.raptor._data.transit.TestTransfer; import org.opentripplanner.raptor._data.transit.TestTransitData; import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.spi.IntIterator; public class ReverseRaptorTransitCalculatorTest { + private static final int BIG_TIME = hm2time(100, 0); + private int latestArrivalTime = hm2time(8, 0); private int searchWindowSizeInSeconds = 2 * 60 * 60; private int earliestAcceptableDepartureTime = hm2time(16, 0); @@ -37,10 +40,10 @@ public void exceedsTimeLimit() { create().exceedsTimeLimitReason() ); - earliestAcceptableDepartureTime = -1; + earliestAcceptableDepartureTime = RaptorConstants.TIME_NOT_SET; subject = create(); - assertFalse(subject.exceedsTimeLimit(0)); - assertFalse(subject.exceedsTimeLimit(2_000_000_000)); + assertFalse(subject.exceedsTimeLimit(-BIG_TIME)); + assertFalse(subject.exceedsTimeLimit(BIG_TIME)); } @Test From 770bbc1f97f836939f6415e0406b7fba39b76b0e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 23 Feb 2024 19:13:12 +0100 Subject: [PATCH 060/108] refactor: Use TIME_NOT_SET and not ZERO when time-penalty does not exist. --- .../api/model/AbstractAccessEgressDecorator.java | 10 ++++++++++ .../raptor/api/model/RaptorAccessEgress.java | 8 ++++++-- .../raptor/rangeraptor/DefaultRangeRaptorWorker.java | 3 ++- .../rangeraptor/path/DestinationArrivalPaths.java | 6 ++---- .../raptor/rangeraptor/transit/AccessPaths.java | 8 +++++++- .../raptor/rangeraptor/transit/EgressPaths.java | 2 +- .../rangeraptor/transit/RaptorTransitCalculator.java | 8 ++++++++ .../raptoradapter/transit/DefaultAccessEgress.java | 8 +++++++- .../raptor/_data/transit/TestAccessEgress.java | 8 ++++++-- .../raptoradapter/transit/DefaultAccessEgressTest.java | 3 ++- 10 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java index d01c41dc0ca..35af442576c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java @@ -40,6 +40,11 @@ public int timePenalty() { return delegate.timePenalty(); } + @Override + public boolean hasTimePenalty() { + return delegate.hasTimePenalty(); + } + @Override public int earliestDepartureTime(int requestedDepartureTime) { return delegate.earliestDepartureTime(requestedDepartureTime); @@ -96,6 +101,11 @@ public String asString(boolean includeStop, boolean includeCost, @Nullable Strin return delegate.asString(includeStop, includeCost, summary); } + @Override + public String toString() { + return delegate.toString(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index 894213315cf..6edd6bfd89c 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -68,10 +68,14 @@ public interface RaptorAccessEgress { * cost. Many optimal access paths have an inpact on performance as vell. *

* - * The unit is seconds and the default value is 0 seconds. + * The unit is seconds and the default value is {@link RaptorConstants#TIME_NOT_SET}. */ default int timePenalty() { - return RaptorConstants.ZERO; + return RaptorConstants.TIME_NOT_SET; + } + + default boolean hasTimePenalty() { + return timePenalty() != RaptorConstants.TIME_NOT_SET; } /* TIME-DEPENDENT ACCESS/TRANSFER/EGRESS */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index a05d6aa832f..d15e1ca6ca8 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -278,7 +278,8 @@ private void addAccessPaths(Collection accessPaths) { // Access must be available after the iteration departure time if (departureTime != RaptorConstants.TIME_NOT_SET) { - transitWorker.setAccessToStop(it, departureTime - it.timePenalty()); + // TODO TP - Is this ok? + transitWorker.setAccessToStop(it, calculator.timeMinusPenalty(departureTime, it)); } } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index 54565679653..f5e67d593ca 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -257,10 +257,8 @@ private String raptorCostsAsString(DestinationArrival destArrival) { } private Optional rejectArrivalIfItExceedsTimeLimit(ArrivalView destArrival) { - int arrivalTime = transitCalculator.minusDuration( - destArrival.arrivalTime(), - destArrival.egressPath().egress().timePenalty() - ); + var egress = destArrival.egressPath().egress(); + int arrivalTime = transitCalculator.timeMinusPenalty(destArrival.arrivalTime(), egress); if (transitCalculator.exceedsTimeLimit(arrivalTime)) { return Optional.of(transitCalculator.exceedsTimeLimitReason()); } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 2e8bd01049a..0e616e60ed1 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -9,12 +9,14 @@ import java.util.Collection; import java.util.List; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.RaptorProfile; public class AccessPaths { private final TIntObjectMap> arrivedOnStreetByNumOfRides; private final TIntObjectMap> arrivedOnBoardByNumOfRides; + private int timePenaltyLimit = RaptorConstants.TIME_NOT_SET; private AccessPaths( TIntObjectMap> arrivedOnStreetByNumOfRides, @@ -84,7 +86,7 @@ public int calculateMaxNumberOfRides() { private static List decorateWithTimePenaltyLogic( Collection paths ) { - return paths.stream().map(it -> it.timePenalty() > 0 ? new AccessWithPenalty(it) : it).toList(); + return paths.stream().map(it -> it.hasTimePenalty() ? new AccessWithPenalty(it) : it).toList(); } /** Raptor uses this information to optimize boarding of the first trip */ @@ -95,6 +97,10 @@ public boolean hasTimeDependentAccess() { ); } + private boolean hasTimePenalty() { + return timePenaltyLimit != RaptorConstants.TIME_NOT_SET; + } + private static boolean hasTimeDependentAccess(TIntObjectMap> map) { for (List list : map.valueCollection()) { if (list.stream().anyMatch(RaptorAccessEgress::hasOpeningHours)) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java index 19c77901af0..8f1340552a8 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressPaths.java @@ -82,7 +82,7 @@ public int[] egressesWitchStartByARide() { private static List decorateWithTimePenaltyLogic( Collection paths ) { - return paths.stream().map(it -> it.timePenalty() > 0 ? new EgressWithPenalty(it) : it).toList(); + return paths.stream().map(it -> it.hasTimePenalty() ? new EgressWithPenalty(it) : it).toList(); } private int[] filterPathsAndGetStops(Predicate filter) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java index 2fce438b858..7a309217f0a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java @@ -147,4 +147,12 @@ Iterator getTransfers( RaptorTransitDataProvider transitDataProvider, int fromStop ); + + default int timePlusPenalty(int time, RaptorAccessEgress accessEgress) { + return accessEgress.hasTimePenalty() ? plusDuration(time, accessEgress.timePenalty()) : time; + } + + default int timeMinusPenalty(int time, RaptorAccessEgress accessEgress) { + return accessEgress.hasTimePenalty() ? minusDuration(time, accessEgress.timePenalty()) : time; + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 67f56d0b4ad..35987c0af7d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -3,6 +3,7 @@ import java.util.Objects; import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter; import org.opentripplanner.street.search.state.State; @@ -14,6 +15,9 @@ public class DefaultAccessEgress implements RaptorAccessEgress { private final int stop; private final int durationInSeconds; private final int generalizedCost; + private final int timePenalty; + + /** Keep this to be able to map back to itinerary */ private final TimeAndCost penalty; /** @@ -26,6 +30,7 @@ public DefaultAccessEgress(int stop, State lastState) { this.durationInSeconds = (int) lastState.getElapsedTimeSeconds(); this.generalizedCost = RaptorCostConverter.toRaptorCost(lastState.getWeight()); this.lastState = lastState; + this.timePenalty = RaptorConstants.TIME_NOT_SET; this.penalty = TimeAndCost.ZERO; } @@ -39,6 +44,7 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) { // association between the time-penalty and the cost. So, we add the time-penalty cost to // the generalized cost here. In logic later on, we will remove it. this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds(); + this.timePenalty = penalty.isZero() ? RaptorConstants.TIME_NOT_SET : penalty.timeInSeconds(); this.penalty = penalty; this.lastState = other.getLastState(); } @@ -59,7 +65,7 @@ public int durationInSeconds() { @Override public int timePenalty() { - return penalty.timeInSeconds(); + return timePenalty; } @Override diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java index 153fb38718f..f9357380b3d 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java @@ -168,6 +168,10 @@ public TestAccessEgress openingHoursClosed() { return copyOf().withClosed().build(); } + public TestAccessEgress withTimePenalty(int timePenalty) { + return this.copyOf().withTimePenalty(timePenalty).build(); + } + public Builder copyOf() { return new Builder(this); } @@ -285,7 +289,7 @@ protected static class Builder { this.stop = stop; this.durationInSeconds = durationInSeconds; this.c1 = walkCost(durationInSeconds); - this.timePenalty = RaptorConstants.ZERO; + this.timePenalty = RaptorConstants.TIME_NOT_SET; } Builder(TestAccessEgress original) { @@ -322,7 +326,7 @@ Builder stopReachedOnBoard() { return this; } - Builder withPenalty(int timePenalty) { + Builder withTimePenalty(int timePenalty) { this.timePenalty = timePenalty; return this; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 0c9f29bc8a4..92ed7aa4825 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.framework.model.TimeAndCost; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; @@ -39,7 +40,7 @@ void durationInSeconds() { @Test void timePenalty() { int expected = (int) TIME_PENALTY.toSeconds(); - assertEquals(0, subject.timePenalty()); + assertEquals(RaptorConstants.TIME_NOT_SET, subject.timePenalty()); assertEquals(expected, subjectWithPenalty.timePenalty()); } From 5367130d4ca171c7eea55befaf3de932c849520c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 26 Feb 2024 19:21:43 +0100 Subject: [PATCH 061/108] refactor: Add empty IntIterators --- .../java/org/opentripplanner/raptor/util/IntIterators.java | 7 +++++++ .../org/opentripplanner/raptor/util/IntIteratorsTest.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/org/opentripplanner/raptor/util/IntIterators.java b/src/main/java/org/opentripplanner/raptor/util/IntIterators.java index 3ab604669d7..cb8c6c38787 100644 --- a/src/main/java/org/opentripplanner/raptor/util/IntIterators.java +++ b/src/main/java/org/opentripplanner/raptor/util/IntIterators.java @@ -117,4 +117,11 @@ public boolean hasNext() { public static IntIterator singleValueIterator(final int value) { return intIncIterator(value, value + 1); } + + /** + * Return an empty iterator. All calls to {@link IntIterator#hasNext()} will return {@code false}. + */ + public static IntIterator empty() { + return intIncIterator(0, 0); + } } diff --git a/src/test/java/org/opentripplanner/raptor/util/IntIteratorsTest.java b/src/test/java/org/opentripplanner/raptor/util/IntIteratorsTest.java index ea9785dea26..598287164c2 100644 --- a/src/test/java/org/opentripplanner/raptor/util/IntIteratorsTest.java +++ b/src/test/java/org/opentripplanner/raptor/util/IntIteratorsTest.java @@ -84,6 +84,11 @@ public void testSingleValueIterator() { assertEquals("[3]", toString(singleValueIterator(3))); } + @Test + public void testEmptyIterator() { + assertEquals("[]", toString(IntIterators.empty())); + } + private static String toString(IntIterator it) { StringBuilder buf = new StringBuilder(); boolean empty = true; From fe4c0044773f8d750d2e4e069ff585ca088b70a4 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 26 Feb 2024 21:05:27 +0100 Subject: [PATCH 062/108] refactor: Move SECONDS_IN_A_DAY RaptorConstants to test scope --- .../raptor/api/model/RaptorAccessEgress.java | 4 ++-- .../raptor/api/model/RaptorConstants.java | 9 --------- .../raptor/_data/RaptorTestConstants.java | 7 +++++++ .../raptor/_data/transit/TestAccessEgress.java | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index 6edd6bfd89c..a1764d42374 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -1,8 +1,8 @@ package org.opentripplanner.raptor.api.model; -import static org.opentripplanner.raptor.api.model.RaptorConstants.SECONDS_IN_A_DAY; import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; +import java.time.temporal.ChronoUnit; import javax.annotation.Nullable; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; @@ -123,7 +123,7 @@ default String openingHoursToString() { // assumes the access/egress is a continuous period without gaps withing 24 hours from the // opening. We ignore the access/egress duration. This is ok for test, debugging and logging. int edt = earliestDepartureTime(0); - int lat = latestArrivalTime(edt + SECONDS_IN_A_DAY); + int lat = latestArrivalTime(edt + (int) ChronoUnit.DAYS.getDuration().toSeconds()); if (edt == TIME_NOT_SET || lat == TIME_NOT_SET) { return "closed"; diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java index 5257c49fa35..983e3c75155 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java @@ -1,7 +1,5 @@ package org.opentripplanner.raptor.api.model; -import static java.time.temporal.ChronoUnit.DAYS; - /** * Raptor relies on {@code int} operation to be fast, so in many cases we use a "magic number" to * represent state. In general "magic numbers" should be avoided, at least encapsulated - but in @@ -70,11 +68,4 @@ public class RaptorConstants { /** Alias for {@link #UNREACHED_HIGH} */ public static final int N_TRANSFERS_UNREACHED = UNREACHED_HIGH; - - /** - * There is 86400 seconds in a "normal" day(24 * 60 * 60). This is used for testing, logging - * and debugging, but do not base any important logic on this. A day with changes in - * daylight-saving-time does not have this amount of seconds. - */ - public static final int SECONDS_IN_A_DAY = (int) DAYS.getDuration().toSeconds(); } diff --git a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java index d293f7326e6..d1398ea05cd 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java +++ b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java @@ -27,6 +27,13 @@ public interface RaptorTestConstants { int D20m = durationInSeconds("20m"); int D24h = durationInSeconds("24h"); + /** + * There are 86400 seconds in a "normal" day(24 * 60 * 60). This is used for testing, logging + * and debugging, but does not base any important logic on this. A day with changes in + * daylight-saving-time does not have this number of seconds. + */ + int SECONDS_IN_A_DAY = (int) D24h; + // Time constants, all values are in seconds int T00_00 = hm2time(0, 0); int T00_01 = hm2time(0, 1); diff --git a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java index f9357380b3d..8047ec0d4cb 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java +++ b/src/test/java/org/opentripplanner/raptor/_data/transit/TestAccessEgress.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.raptor.api.model.RaptorConstants.SECONDS_IN_A_DAY; +import static org.opentripplanner.raptor._data.RaptorTestConstants.SECONDS_IN_A_DAY; import static org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter.toRaptorCost; import java.util.ArrayList; From a8d7810ab271ed4a26e51d7ea2704b59df481b98 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 26 Feb 2024 21:28:15 +0100 Subject: [PATCH 063/108] feature: Do proper time-shift in AccessWithPenalty and EgressWithPenalty This check for TIME_NOT_SET and add unit tests --- .../transit/AccessWithPenalty.java | 7 +- .../transit/EgressWithPenalty.java | 7 +- .../transit/AccessWithPenaltyTest.java | 79 ++++++++++++++++++ .../transit/EgressWithPenaltyTest.java | 82 +++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java index 1226d56c028..ef17f0b7b68 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java @@ -2,6 +2,7 @@ import org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; /** * This decorator will add the time penalty to the duration of the access and adjust the @@ -22,7 +23,11 @@ public int durationInSeconds() { @Override public int earliestDepartureTime(int requestedDepartureTime) { - return delegate().earliestDepartureTime(requestedDepartureTime + delegate().timePenalty()); + final int dt = delegate().timePenalty(); + int adjustedTime = delegate().earliestDepartureTime(requestedDepartureTime + dt); + return adjustedTime == RaptorConstants.TIME_NOT_SET + ? RaptorConstants.TIME_NOT_SET + : adjustedTime - dt; } /** diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java index d6a50f7c982..1647f6b7b52 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java @@ -2,6 +2,7 @@ import org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorConstants; /** * This decorator will add the time penalty to the duration of the egress and adjust the @@ -22,7 +23,11 @@ public int durationInSeconds() { @Override public int latestArrivalTime(int requestedArrivalTime) { - return delegate().latestArrivalTime(requestedArrivalTime - delegate().timePenalty()); + int dt = delegate().timePenalty(); + int adjustedTime = delegate().latestArrivalTime(requestedArrivalTime - dt); + return adjustedTime == RaptorConstants.TIME_NOT_SET + ? RaptorConstants.TIME_NOT_SET + : adjustedTime + dt; } /** diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java new file mode 100644 index 00000000000..cedc01e42a1 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java @@ -0,0 +1,79 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.opentripplanner.raptor._data.RaptorTestConstants.SECONDS_IN_A_DAY; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; + +class AccessWithPenaltyTest { + + private static final int STOP = 17; + private static final int DURATION = 400; + private static final int PENALTY = 100; + private static final int OPEN = 600; + private static final int CLOSE = 1800; + private static final int ANY_TIME = 500; + private static final int EXPECTED_OPENING = OPEN - PENALTY; + private static final int EXPECTED_CLOSING = CLOSE - PENALTY; + private static final int ONE_DAY = SECONDS_IN_A_DAY; + + @Test + void durationInSeconds() { + var subject = new AccessWithPenalty(walk(STOP, DURATION).withTimePenalty(PENALTY)); + assertEquals(DURATION + PENALTY, subject.durationInSeconds()); + } + + @Test + void earliestDepartureTimeIsBeforeOpeningHours() { + var subject = new AccessWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + assertEquals(EXPECTED_OPENING, subject.earliestDepartureTime(EXPECTED_OPENING - 200)); + assertEquals(EXPECTED_OPENING, subject.earliestDepartureTime(EXPECTED_OPENING - 1)); + } + + @Test + void earliestDepartureTimeIsInsideOpeningHours() { + var subject = new AccessWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + assertEquals(EXPECTED_OPENING, subject.earliestDepartureTime(EXPECTED_OPENING)); + assertEquals(EXPECTED_CLOSING, subject.earliestDepartureTime(EXPECTED_CLOSING)); + } + + @Test + void earliestDepartureTimeIsAfterClosing() { + var subject = new AccessWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + // If time is after closing, then wait until it opens next day. This is TestAccessEgress + // implementation specific. + assertEquals(EXPECTED_OPENING + ONE_DAY, subject.earliestDepartureTime(EXPECTED_CLOSING + 1)); + } + + @Test + void earliestDepartureTimeWhenServiceIsClosed() { + // Test closed + var subject = new AccessWithPenalty( + walk(STOP, DURATION).openingHoursClosed().withTimePenalty(PENALTY) + ); + assertEquals(TIME_NOT_SET, subject.earliestDepartureTime(ANY_TIME)); + } + + @Test + void removeDecoratorIfItExist() { + var original = walk(STOP, DURATION).withTimePenalty(PENALTY); + RaptorAccessEgress subject = new AccessWithPenalty(original); + + assertSame(original, AccessWithPenalty.removeDecoratorIfItExist(subject)); + assertSame(original, AccessWithPenalty.removeDecoratorIfItExist(original)); + assertSame(null, AccessWithPenalty.removeDecoratorIfItExist(null)); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java new file mode 100644 index 00000000000..acc7b642ddc --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java @@ -0,0 +1,82 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.opentripplanner.raptor._data.RaptorTestConstants.SECONDS_IN_A_DAY; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; + +class EgressWithPenaltyTest { + + private static final int STOP = 17; + private static final int DURATION = 400; + private static final int PENALTY = 100; + private static final int OPEN = 600; + private static final int CLOSE = 1800; + private static final int ANY_TIME = 500; + + // We are interested in the opening hours at the arrival, not in the beginning of the egress leg. + // Hence, we must add egress duration as well here. + private static final int EXPECTED_OPENING = OPEN + PENALTY + DURATION; + private static final int EXPECTED_CLOSING = CLOSE + PENALTY + DURATION; + private static final int ONE_DAY = SECONDS_IN_A_DAY; + + @Test + void durationInSeconds() { + var subject = new EgressWithPenalty(walk(STOP, DURATION).withTimePenalty(PENALTY)); + assertEquals(DURATION + PENALTY, subject.durationInSeconds()); + } + + @Test + void latestArrivalTimeIsBeforeOpeningHours() { + var subject = new EgressWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + // If time is before opening, then time-shift to the closing of the previous day. This is + // TestAccessEgress implementation specific. + assertEquals(EXPECTED_CLOSING - ONE_DAY, subject.latestArrivalTime(EXPECTED_OPENING - 1)); + } + + @Test + void latestArrivalTimeIsInsideOpeningHours() { + var subject = new EgressWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + assertEquals(EXPECTED_OPENING, subject.latestArrivalTime(EXPECTED_OPENING)); + assertEquals(EXPECTED_CLOSING, subject.latestArrivalTime(EXPECTED_CLOSING)); + } + + @Test + void latestArrivalTimeIsAfterClosing() { + var subject = new EgressWithPenalty( + walk(STOP, DURATION).openingHours(OPEN, CLOSE).withTimePenalty(PENALTY) + ); + + assertEquals(EXPECTED_CLOSING, subject.latestArrivalTime(EXPECTED_CLOSING + 1)); + assertEquals(EXPECTED_CLOSING, subject.latestArrivalTime(EXPECTED_CLOSING + 100)); + } + + @Test + void latestArrivalTimeWhenServiceIsClosed() { + // Test closed + var subject = new EgressWithPenalty( + walk(STOP, DURATION).openingHoursClosed().withTimePenalty(PENALTY) + ); + assertEquals(TIME_NOT_SET, subject.latestArrivalTime(ANY_TIME)); + } + + @Test + void removeDecoratorIfItExist() { + var original = walk(STOP, DURATION).withTimePenalty(PENALTY); + RaptorAccessEgress subject = new EgressWithPenalty(original); + + assertSame(original, EgressWithPenalty.removeDecoratorIfItExist(subject)); + assertSame(original, EgressWithPenalty.removeDecoratorIfItExist(original)); + assertSame(null, EgressWithPenalty.removeDecoratorIfItExist(null)); + } +} From 3ae31b78762c4d2a0ee6204fd77490aff9040848 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 21 Feb 2024 01:28:55 +0100 Subject: [PATCH 064/108] feature: Include iterations which have enough slack to include access time-penalty before earliest-departure-time --- .../raptor/api/model/RaptorConstants.java | 5 + .../rangeraptor/DefaultRangeRaptorWorker.java | 33 ++- .../rangeraptor/context/SearchContext.java | 6 +- .../rangeraptor/transit/AccessPaths.java | 69 +++++- .../transit/AccessWithPenalty.java | 9 +- .../transit/EgressWithPenalty.java | 4 +- .../transit/RaptorTransitCalculator.java | 12 +- .../rangeraptor/transit/AccessPathsTest.java | 201 ++++++++++++++++++ 8 files changed, 312 insertions(+), 27 deletions(-) create mode 100644 src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java index 983e3c75155..63ec7420db0 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorConstants.java @@ -24,6 +24,11 @@ public class RaptorConstants { */ public static final int ZERO = 0; + /** + * One minute is 60 seconds - iteration departure times are usually increased by one minute. + */ + public static final int ONE_MINUTE = 60; + /** * This constant is used to indicate that a value is not set. This applies to parameters of type * {@code generalized-cost}, {@code link min-travel-time} and {@code duration} inside Raptor. diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index d15e1ca6ca8..63cc4e2756c 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -135,10 +135,15 @@ public RaptorWorkerResult route() { // the arrival time given departure at minute t + 1. final IntIterator it = calculator.rangeRaptorMinutes(); while (it.hasNext()) { - OTPRequestTimeoutException.checkForTimeout(); - // Run the raptor search for this particular iteration departure time - iterationDepartureTime = it.next(); - lifeCycle.setupIteration(iterationDepartureTime); + setupIteration(it.next()); + runRaptorForMinute(); + } + + // Iterate over virtual departure times - this is needed to allow access with a time-penalty + // which falls outside the search-window due to the penalty to be added to the result. + final IntIterator as = accessPaths.iterateOverPathsWithPenalty(iterationDepartureTime); + while (as.hasNext()) { + setupIteration(as.next()); runRaptorForMinute(); } }); @@ -260,6 +265,10 @@ private void findTransfersForRound() { }); } + private int round() { + return roundTracker.round(); + } + private void findAccessOnStreetForRound() { addAccessPaths(accessPaths.arrivedOnStreetByNumOfRides(round())); } @@ -268,6 +277,15 @@ private void findAccessOnBoardForRound() { addAccessPaths(accessPaths.arrivedOnBoardByNumOfRides(round())); } + /** + * Run the raptor search for this particular iteration departure time + */ + private void setupIteration(int iterationDepartureTime) { + OTPRequestTimeoutException.checkForTimeout(); + this.iterationDepartureTime = iterationDepartureTime; + lifeCycle.setupIteration(this.iterationDepartureTime); + } + /** * Set the departure time in the scheduled search to the given departure time, and prepare for the * scheduled search at the next-earlier minute. @@ -278,13 +296,8 @@ private void addAccessPaths(Collection accessPaths) { // Access must be available after the iteration departure time if (departureTime != RaptorConstants.TIME_NOT_SET) { - // TODO TP - Is this ok? - transitWorker.setAccessToStop(it, calculator.timeMinusPenalty(departureTime, it)); + transitWorker.setAccessToStop(it, departureTime); } } } - - private int round() { - return roundTracker.round(); - } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index 5b4a6db52e2..65e0246290c 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -87,7 +87,7 @@ public SearchContext( this.request = request; this.tuningParameters = tuningParameters; this.transit = transit; - this.accessPaths = accessPaths(request); + this.accessPaths = accessPaths(tuningParameters.iterationDepartureStepInSeconds(), request); this.egressPaths = egressPaths(request); this.calculator = createCalculator(request, tuningParameters); this.roundTracker = @@ -314,11 +314,11 @@ private static ToIntFunction createBoardSlackProvider( : p -> slackProvider.alightSlack(p.slackIndex()); } - private static AccessPaths accessPaths(RaptorRequest request) { + private static AccessPaths accessPaths(int iterationStep, RaptorRequest request) { boolean forward = request.searchDirection().isForward(); var params = request.searchParams(); var paths = forward ? params.accessPaths() : params.egressPaths(); - return AccessPaths.create(paths, request.profile()); + return AccessPaths.create(iterationStep, paths, request.profile()); } private static EgressPaths egressPaths(RaptorRequest request) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 0e616e60ed1..434c2e3c611 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -11,19 +11,30 @@ import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.RaptorProfile; +import org.opentripplanner.raptor.spi.IntIterator; +import org.opentripplanner.raptor.util.IntIterators; public class AccessPaths { + private final int iterationStep; + private final int maxTimePenalty; private final TIntObjectMap> arrivedOnStreetByNumOfRides; private final TIntObjectMap> arrivedOnBoardByNumOfRides; - private int timePenaltyLimit = RaptorConstants.TIME_NOT_SET; + private int iterationTimePenaltyLimit = RaptorConstants.TIME_NOT_SET; private AccessPaths( + int iterationStep, TIntObjectMap> arrivedOnStreetByNumOfRides, TIntObjectMap> arrivedOnBoardByNumOfRides ) { + this.iterationStep = iterationStep; this.arrivedOnStreetByNumOfRides = arrivedOnStreetByNumOfRides; this.arrivedOnBoardByNumOfRides = arrivedOnBoardByNumOfRides; + this.maxTimePenalty = + Math.max( + maxTimePenalty(arrivedOnBoardByNumOfRides), + maxTimePenalty(arrivedOnStreetByNumOfRides) + ); } /** @@ -36,7 +47,11 @@ private AccessPaths( *

* This method is static and package local to enable unit-testing. */ - public static AccessPaths create(Collection paths, RaptorProfile profile) { + public static AccessPaths create( + int iterationStep, + Collection paths, + RaptorProfile profile + ) { if (profile.is(RaptorProfile.MULTI_CRITERIA)) { paths = removeNonOptimalPathsForMcRaptor(paths); } else { @@ -46,6 +61,7 @@ public static AccessPaths create(Collection paths, RaptorPro paths = decorateWithTimePenaltyLogic(paths); return new AccessPaths( + iterationStep, groupByRound(paths, RaptorAccessEgress::stopReachedByWalking), groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard) ); @@ -58,7 +74,7 @@ public static AccessPaths create(Collection paths, RaptorPro * If no access exists for the given round, an empty list is returned. */ public List arrivedOnStreetByNumOfRides(int round) { - return emptyListIfNull(arrivedOnStreetByNumOfRides.get(round)); + return filterOnTimePenaltyLimitIfExist(arrivedOnStreetByNumOfRides.get(round)); } /** @@ -68,7 +84,7 @@ public List arrivedOnStreetByNumOfRides(int round) { * If no access exists for the given round, an empty list is returned. */ public List arrivedOnBoardByNumOfRides(int round) { - return emptyListIfNull(arrivedOnBoardByNumOfRides.get(round)); + return filterOnTimePenaltyLimitIfExist(arrivedOnBoardByNumOfRides.get(round)); } public int calculateMaxNumberOfRides() { @@ -78,6 +94,39 @@ public int calculateMaxNumberOfRides() { ); } + public IntIterator iterateOverPathsWithPenalty(final int earliestDepartureTime) { + if (!hasTimePenalty()) { + return IntIterators.empty(); + } + // In the first iteration, we want the time-limit to be zero and the raptor-iteration-time + // to be one step before the earliest-departure-time in the search-window. This will include + // all access with a penalty in the first iteration. Then + this.iterationTimePenaltyLimit = -iterationStep; + final int raptorIterationStartTime = earliestDepartureTime - iterationStep; + + return new IntIterator() { + @Override + public boolean hasNext() { + return AccessPaths.this.iterationTimePenaltyLimit + iterationStep < maxTimePenalty; + } + + @Override + public int next() { + AccessPaths.this.iterationTimePenaltyLimit += iterationStep; + return raptorIterationStartTime - AccessPaths.this.iterationTimePenaltyLimit; + } + }; + } + + private int maxTimePenalty(TIntObjectMap> col) { + return col + .valueCollection() + .stream() + .flatMapToInt(it -> it.stream().mapToInt(RaptorAccessEgress::timePenalty)) + .max() + .orElse(RaptorConstants.TIME_NOT_SET); + } + /** * Decorate access to implement time-penalty. This decoration will do the necessary * adjustments to apply the penalty in the raptor algorithm. See the decorator class for more @@ -98,7 +147,7 @@ public boolean hasTimeDependentAccess() { } private boolean hasTimePenalty() { - return timePenaltyLimit != RaptorConstants.TIME_NOT_SET; + return maxTimePenalty != RaptorConstants.TIME_NOT_SET; } private static boolean hasTimeDependentAccess(TIntObjectMap> map) { @@ -111,12 +160,20 @@ private static boolean hasTimeDependentAccess(TIntObjectMap * This method returns an empty list if the given input list is {@code null}. */ - private List emptyListIfNull(List list) { + private List filterOnTimePenaltyLimitIfExist(List list) { if (list == null) { return List.of(); } + if (hasTimePenalty()) { + return list.stream().filter(e -> e.timePenalty() > iterationTimePenaltyLimit).toList(); + } return list; } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java index ef17f0b7b68..8c65270cbb4 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java @@ -7,8 +7,13 @@ /** * This decorator will add the time penalty to the duration of the access and adjust the * `requestedDepartureTime` when time-shifting the access according to opening-hours. - * - * TODO PEN - Write more + *

+ * The time-penalty should be invisible outside the Raptor algorithm and should not be part of the + * leg start and end times in the result. Inside Raptor the time-penalty is included in times used + * for comparing arrivals (comparing paths for optimality). In some cases, we need to exclude the + * time-penalty. Checking for limits like 'arrive-by'(in the forward search) and 'depart-after'(in + * the reverse search) requires that the time is without the time-penalty. This class does not + * do these checks. */ public class AccessWithPenalty extends AbstractAccessEgressDecorator { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java index 1647f6b7b52..6bb7e7bf2c5 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java @@ -7,8 +7,8 @@ /** * This decorator will add the time penalty to the duration of the egress and adjust the * `requestedDepartureTime` when time-shifting the egress according to opening-hours. - * - * TODO PEN - Write more + *

+ * @see AccessWithPenalty for more info on time-penalty. */ public class EgressWithPenalty extends AbstractAccessEgressDecorator { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java index 7a309217f0a..af9cff110d0 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/RaptorTransitCalculator.java @@ -148,10 +148,14 @@ Iterator getTransfers( int fromStop ); - default int timePlusPenalty(int time, RaptorAccessEgress accessEgress) { - return accessEgress.hasTimePenalty() ? plusDuration(time, accessEgress.timePenalty()) : time; - } - + /** + * This method removes the time-penalty from the given time if the provided accessEgress has + * a time-penalty, if not the given time is returned without any change. + *

+ * You may use this method to enforce time constraints like the arriveBy time passed into Raptor. + * This should not be applied to the time Raptor uses for the comparison, like in the ParetoSet. + * The arrival-times used in a pareto-set must include the time-penalty. + */ default int timeMinusPenalty(int time, RaptorAccessEgress accessEgress) { return accessEgress.hasTimePenalty() ? minusDuration(time, accessEgress.timePenalty()) : time; } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java new file mode 100644 index 00000000000..0371de45bac --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java @@ -0,0 +1,201 @@ +package org.opentripplanner.raptor.rangeraptor.transit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flex; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flexAndWalk; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA; +import static org.opentripplanner.raptor.api.request.RaptorProfile.STANDARD; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import org.junit.jupiter.api.Test; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestAccessEgress; +import org.opentripplanner.raptor.api.model.RaptorAccessEgress; +import org.opentripplanner.raptor.api.request.RaptorProfile; + +class AccessPathsTest implements RaptorTestConstants { + + // Walking paths + public static final TestAccessEgress WALK_FAST = walk(STOP_A, 29, 900); + public static final TestAccessEgress WALK_MEDIUM = walk(STOP_A, 30, 800); + public static final TestAccessEgress WALK_COST = walk(STOP_A, 31, 700); + public static final TestAccessEgress WALK_BAD = walk(STOP_A, 30, 900); + public static final TestAccessEgress WALK_B = walk(STOP_B, 60, 2000); + + // Flex with on board arrival + public static final TestAccessEgress FLEX_FAST = flex(STOP_A, 25, 3, 900); + public static final TestAccessEgress FLEX_COST = flex(STOP_A, 31, 3, 500); + public static final TestAccessEgress FLEX_TX2 = flex(STOP_A, 26, 2, 900); + public static final TestAccessEgress FLEX_BAD = flex(STOP_A, 26, 3, 900); + public static final TestAccessEgress FLEX_B = flex(STOP_B, 50, 3, 2000); + + // Flex with walking + public static final TestAccessEgress FLEX_WALK_FAST = flexAndWalk(STOP_A, 25, 2, 900); + public static final TestAccessEgress FLEX_WALK_COST = flexAndWalk(STOP_A, 31, 2, 400); + public static final TestAccessEgress FLEX_WALK_TX1 = flexAndWalk(STOP_A, 28, 1, 900); + public static final TestAccessEgress FLEX_WALK_BAD = flexAndWalk(STOP_A, 26, 2, 900); + public static final TestAccessEgress FLEX_WALK_B = flexAndWalk(STOP_B, 50, 2, 2000); + + @Test + void arrivedOnStreetByNumOfRides() { + var accessPaths = create(STANDARD); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), WALK_FAST, WALK_B); + expect(accessPaths.arrivedOnStreetByNumOfRides(1), FLEX_WALK_TX1); + expect(accessPaths.arrivedOnStreetByNumOfRides(2), FLEX_WALK_FAST, FLEX_WALK_B); + expect(accessPaths.arrivedOnStreetByNumOfRides(3)); + + accessPaths = create(MULTI_CRITERIA); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), WALK_FAST, WALK_MEDIUM, WALK_COST, WALK_B); + expect(accessPaths.arrivedOnStreetByNumOfRides(1), FLEX_WALK_TX1); + expect(accessPaths.arrivedOnStreetByNumOfRides(2), FLEX_WALK_FAST, FLEX_WALK_COST, FLEX_WALK_B); + expect(accessPaths.arrivedOnStreetByNumOfRides(3)); + } + + @Test + void arrivedOnBoardByNumOfRides() { + var accessPaths = create(STANDARD); + expect(accessPaths.arrivedOnBoardByNumOfRides(0)); + expect(accessPaths.arrivedOnBoardByNumOfRides(1)); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), FLEX_TX2); + expect(accessPaths.arrivedOnBoardByNumOfRides(3), FLEX_FAST, FLEX_B); + expect(accessPaths.arrivedOnBoardByNumOfRides(4)); + + accessPaths = create(MULTI_CRITERIA); + expect(accessPaths.arrivedOnBoardByNumOfRides(0)); + expect(accessPaths.arrivedOnBoardByNumOfRides(1)); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), FLEX_TX2); + expect(accessPaths.arrivedOnBoardByNumOfRides(3), FLEX_FAST, FLEX_COST, FLEX_B); + expect(accessPaths.arrivedOnBoardByNumOfRides(4)); + } + + @Test + void calculateMaxNumberOfRides() { + assertEquals(3, create(STANDARD).calculateMaxNumberOfRides()); + assertEquals(3, create(MULTI_CRITERIA).calculateMaxNumberOfRides()); + } + + @Test + void iterateOverPathsWithPenalty() { + // Expected at departure 540 + var flexFastWithPenalty = FLEX_FAST.withTimePenalty(60); + + // Expected at departure 540 and 480 + var flexTxWithPenalty = FLEX_TX2.withTimePenalty(61); + var flexCostWithPenalty = FLEX_COST.withTimePenalty(120); + + // Expected at departure 540, 480 and 420 + var walkFastWithPenalty = WALK_FAST.withTimePenalty(121); + var walkCostWithPenalty = WALK_COST.withTimePenalty(180); + + // Without time-penalty, the iterator should be empty + var accessPaths = AccessPaths.create( + 60, + List.of( + flexFastWithPenalty, + flexTxWithPenalty, + flexCostWithPenalty, + walkFastWithPenalty, + walkCostWithPenalty, + // Should be filtered away + WALK_B, + FLEX_B, + FLEX_WALK_B + ), + MULTI_CRITERIA + ); + + var iterator = accessPaths.iterateOverPathsWithPenalty(600); + + // First iteration + assertTrue(iterator.hasNext()); + assertEquals(540, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty, walkCostWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(1)); + expect(accessPaths.arrivedOnStreetByNumOfRides(1)); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), flexTxWithPenalty); + expect(accessPaths.arrivedOnStreetByNumOfRides(2)); + //expect(accessPaths.arrivedOnBoardByNumOfRides(3), flexFastWithPenalty, flexCostWithPenalty); + expect(accessPaths.arrivedOnStreetByNumOfRides(3)); + expect(accessPaths.arrivedOnBoardByNumOfRides(4)); + expect(accessPaths.arrivedOnStreetByNumOfRides(4)); + + // Second iteration + assertTrue(iterator.hasNext()); + assertEquals(480, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty, walkCostWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), flexTxWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(3), flexCostWithPenalty); + + // Third iteration + assertTrue(iterator.hasNext()); + assertEquals(420, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty, walkCostWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(2)); + expect(accessPaths.arrivedOnBoardByNumOfRides(3)); + + assertFalse(iterator.hasNext()); + } + + @Test + void hasTimeDependentAccess() { + var accessPaths = AccessPaths.create( + 60, + List.of(WALK_FAST, walk(STOP_A, 20).openingHours(1200, 2400)), + STANDARD + ); + assertTrue(accessPaths.hasTimeDependentAccess(), "Time dependent access is better."); + + accessPaths = + AccessPaths.create( + 60, + List.of(WALK_FAST, walk(STOP_A, 50).openingHours(1200, 2400)), + STANDARD + ); + assertFalse(accessPaths.hasTimeDependentAccess(), "Time dependent access is worse."); + } + + @Test + void hasNoTimeDependentAccess() { + var accessPaths = create(STANDARD); + assertFalse(accessPaths.hasTimeDependentAccess()); + + var it = accessPaths.iterateOverPathsWithPenalty(600); + assertFalse(it.hasNext()); + } + + private static void expect( + Collection result, + RaptorAccessEgress... expected + ) { + var r = result.stream().map(Objects::toString).sorted().toList(); + var e = Arrays.stream(expected).map(Objects::toString).sorted().toList(); + assertEquals(e, r); + } + + private static AccessPaths create(RaptorProfile profile) { + Collection accessPaths = List.of( + WALK_FAST, + WALK_MEDIUM, + WALK_COST, + WALK_BAD, + WALK_B, + FLEX_WALK_FAST, + FLEX_WALK_COST, + FLEX_WALK_TX1, + FLEX_WALK_BAD, + FLEX_WALK_B, + FLEX_FAST, + FLEX_COST, + FLEX_TX2, + FLEX_BAD, + FLEX_B + ); + return AccessPaths.create(60, accessPaths, profile); + } +} From 507f76405bf6a223e0d1d003c12639701882ad05 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Tue, 27 Feb 2024 09:45:33 +0100 Subject: [PATCH 065/108] style: Improve wording --- .../openstreetmap/tagmapping/HamburgMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java index 029c1b7c662..8e3dc9afc77 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -28,7 +28,7 @@ public void shouldAllowThroughTraffic_WhenAccessCustomers_AndCustomersHVV() { assertFalse( generalNoThroughTraffic, - "access=customers and customers=hvv should not be considered through-traffic" + "access=customers and customers=hvv should allow through-traffic" ); } From 528cb2739bea963aaa9c44d62aa1fca4c03770f5 Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 27 Feb 2024 12:34:18 +0100 Subject: [PATCH 066/108] feat: use agency short name as fallback in agency mapper --- .../netex/mapping/AuthorityToAgencyMapper.java | 10 +++++++--- .../transit/model/organization/Agency.java | 18 ++---------------- .../model/organization/AgencyBuilder.java | 11 ----------- .../mapping/AuthorityToAgencyMapperTest.java | 14 ++++++++++++-- .../transit/model/organization/AgencyTest.java | 4 ---- 5 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java index 23edb034079..cc1190bbc88 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java @@ -34,14 +34,18 @@ class AuthorityToAgencyMapper { */ Agency mapAuthorityToAgency(Authority source) { String agencyName = source.getName() != null ? source.getName().getValue() : null; - String agencyShortName = source.getShortName() != null + + String agencyShortName = source.getShortName() != null && + source.getShortName().getValue() != null && + !source.getShortName().getValue().isBlank() ? source.getShortName().getValue() : null; + String mainName = agencyName != null && !agencyName.isBlank() ? agencyName : agencyShortName; + AgencyBuilder target = Agency .of(idFactory.createId(source.getId())) - .withName(agencyName) - .withShortName(agencyShortName) + .withName(mainName) .withTimezone(timeZone); withOptional( diff --git a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java index 625078c06a7..d72aa6588f3 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/Agency.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/Agency.java @@ -17,7 +17,6 @@ public final class Agency extends AbstractTransitEntity implements LogInfo { private final String name; - private final String shortName; private final ZoneId timezone; private final String url; private final String lang; @@ -27,16 +26,9 @@ public final class Agency extends AbstractTransitEntity i Agency(AgencyBuilder builder) { super(builder.getId()); - // Fill agency name, if not exists take short name - String nameValue = (builder.getName() != null && !builder.getName().isBlank()) - ? builder.getName() - : builder.getShortName() != null && !builder.getShortName().isBlank() - ? builder.getShortName() - : null; - // Required fields - this.name = assertHasValue(nameValue, "Missing mandatory name on Agency %s", builder.getId()); - this.shortName = builder.getShortName(); + this.name = + assertHasValue(builder.getName(), "Missing mandatory name on Agency %s", builder.getId()); this.timezone = ZoneId.of( @@ -64,11 +56,6 @@ public String getName() { return logName(); } - @Nullable - public String getShortName() { - return shortName; - } - @Nonnull public ZoneId getTimezone() { return timezone; @@ -116,7 +103,6 @@ public boolean sameAs(@Nonnull Agency other) { return ( getId().equals(other.getId()) && Objects.equals(name, other.name) && - Objects.equals(shortName, other.shortName) && Objects.equals(timezone, other.timezone) && Objects.equals(url, other.url) && Objects.equals(lang, other.lang) && diff --git a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java index d4dd94c71e4..8411797f888 100644 --- a/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/organization/AgencyBuilder.java @@ -7,7 +7,6 @@ public class AgencyBuilder extends AbstractEntityBuilder { private String name; - private String shortName; private String timezone; private String url; private String lang; @@ -22,7 +21,6 @@ public class AgencyBuilder extends AbstractEntityBuilder AgencyBuilder(@Nonnull Agency original) { super(original); this.name = original.getName(); - this.shortName = original.getShortName(); this.timezone = original.getTimezone().getId(); this.url = original.getUrl(); this.lang = original.getLang(); @@ -35,20 +33,11 @@ public String getName() { return name; } - public String getShortName() { - return shortName; - } - public AgencyBuilder withName(String name) { this.name = name; return this; } - public AgencyBuilder withShortName(String shortName) { - this.shortName = shortName; - return this; - } - public String getTimezone() { return timezone; } diff --git a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java index 6d809ae92f0..5fa0846bec0 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapperTest.java @@ -33,7 +33,6 @@ public void mapAgency() { // Then expect assertEquals(ID, a.getId().getId()); assertEquals(NAME, a.getName()); - assertEquals(SHORT_NAME, a.getShortName()); assertEquals(TIME_ZONE, a.getTimezone().getId()); assertEquals(URL, a.getUrl()); assertEquals(PHONE, a.getPhone()); @@ -50,7 +49,18 @@ public void mapAgencyWithoutOptionalElements() { // Then expect assertNull(a.getUrl()); assertNull(a.getPhone()); - assertNull(a.getShortName()); + } + + @Test + public void mapAgencyWithShortName() { + // Given + Authority authority = authority(ID, null, SHORT_NAME, null, null); + + // When mapped + Agency a = mapper.mapAuthorityToAgency(authority); + + // Then expect + assertEquals(SHORT_NAME, a.getName()); } @Test diff --git a/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java b/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java index ad5f0d9c289..b2cf1a57db3 100644 --- a/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java +++ b/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java @@ -14,7 +14,6 @@ class AgencyTest { private static final String ID = "1"; private static final String BRANDING_URL = "http://branding.aaa.com"; private static final String NAME = "name"; - private static final String SHORT_NAME = "shortname"; private static final String URL = "http://info.aaa.com"; private static final String TIMEZONE = "Europe/Oslo"; private static final String PHONE = "+47 95566333"; @@ -24,7 +23,6 @@ class AgencyTest { private static final Agency subject = Agency .of(TransitModelForTest.id(ID)) .withName(NAME) - .withShortName(SHORT_NAME) .withUrl(URL) .withTimezone(TIMEZONE) .withPhone(PHONE) @@ -50,7 +48,6 @@ void copy() { assertEquals(subject, copy); assertEquals(ID, copy.getId().getId()); - assertEquals(SHORT_NAME, copy.getShortName()); assertEquals("v2", copy.getName()); assertEquals(URL, copy.getUrl()); assertEquals(TIMEZONE, copy.getTimezone().getId()); @@ -65,7 +62,6 @@ void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); assertFalse(subject.sameAs(subject.copy().withId(TransitModelForTest.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withName("X").build())); - assertFalse(subject.sameAs(subject.copy().withShortName("X").build())); assertFalse(subject.sameAs(subject.copy().withUrl("X").build())); assertFalse(subject.sameAs(subject.copy().withTimezone("CET").build())); assertFalse(subject.sameAs(subject.copy().withPhone("X").build())); From 9f031e3dde6015b901b6be0922e88205daf0b2fd Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 27 Feb 2024 12:48:02 +0100 Subject: [PATCH 067/108] chore: improve check empty strings --- .../netex/mapping/AuthorityToAgencyMapper.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java index cc1190bbc88..ea9b0cca52b 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java @@ -2,6 +2,7 @@ import static org.opentripplanner.netex.mapping.support.NetexObjectDecorator.withOptional; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.AgencyBuilder; @@ -33,15 +34,17 @@ class AuthorityToAgencyMapper { * Map authority and time zone to OTP agency. */ Agency mapAuthorityToAgency(Authority source) { - String agencyName = source.getName() != null ? source.getName().getValue() : null; + String agencyName = source.getName() != null && + StringUtils.hasValue(source.getName().getValue()) + ? source.getName().getValue() + : null; String agencyShortName = source.getShortName() != null && - source.getShortName().getValue() != null && - !source.getShortName().getValue().isBlank() + StringUtils.hasValue(source.getShortName().getValue()) ? source.getShortName().getValue() : null; - String mainName = agencyName != null && !agencyName.isBlank() ? agencyName : agencyShortName; + String mainName = agencyName != null ? agencyName : agencyShortName; AgencyBuilder target = Agency .of(idFactory.createId(source.getId())) From 693b3748ad23103eb9ebf17e2deef6677601f28a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 27 Feb 2024 12:44:41 +0100 Subject: [PATCH 068/108] Rename mapper to OTPRR, improve docs --- doc-templates/sandbox/MapboxVectorTilesApi.md | 8 +++++--- docs/RouterConfiguration.md | 8 ++++++++ docs/sandbox/MapboxVectorTilesApi.md | 8 +++++--- ...apperTest.java => AreaStopPropertyMapperTest.java} | 4 ++-- .../layers/areastops/AreaStopsLayerBuilderTest.java | 2 +- ...ropertyMapper.java => AreaStopPropertyMapper.java} | 11 ++++------- .../layers/areastops/AreaStopsLayerBuilder.java | 6 +++--- .../resources/standalone/config/router-config.json | 8 ++++++++ 8 files changed, 36 insertions(+), 19 deletions(-) rename src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/{DigitransitAreaStopPropertyMapperTest.java => AreaStopPropertyMapperTest.java} (92%) rename src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/{DigitransitAreaStopPropertyMapper.java => AreaStopPropertyMapper.java} (82%) diff --git a/doc-templates/sandbox/MapboxVectorTilesApi.md b/doc-templates/sandbox/MapboxVectorTilesApi.md index b8fe4924c48..35211eff00b 100644 --- a/doc-templates/sandbox/MapboxVectorTilesApi.md +++ b/doc-templates/sandbox/MapboxVectorTilesApi.md @@ -49,10 +49,11 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, + // flex zones { "name": "areaStops", "type": "AreaStop", - "mapper": "Digitransit", + "mapper": "OTPRR", "maxZoom": 20, "minZoom": 14, "cacheMaxSeconds": 600 @@ -144,7 +145,7 @@ For each layer, the configuration includes: - `name` which is used in the url to fetch tiles, and as the layer name in the vector tiles. - `type` which tells the type of the layer. Currently supported: - `Stop` - - `AreaStop` + - `AreaStop`: Flex zones - `Station` - `VehicleRental`: all rental places: stations and free-floating vehicles - `VehicleRentalVehicle`: free-floating rental vehicles @@ -200,4 +201,5 @@ key, and a function to create the mapper, with a `Graph` object as a parameter, * Added DigitransitRealtime for vehicle rental stations * Changed old vehicle parking mapper to be Stadtnavi * Added a new Digitransit vehicle parking mapper with no real-time information and less fields -- 2024-01-22: Make `basePath` configurable [#5627](https://github.com/opentripplanner/OpenTripPlanner/pull/5627) \ No newline at end of file +- 2024-01-22: Make `basePath` configurable [#5627](https://github.com/opentripplanner/OpenTripPlanner/pull/5627) +- 2024-02-27: Add layer for flex zones [#5704](https://github.com/opentripplanner/OpenTripPlanner/pull/5704) diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 34e9aa2c8d6..73d05a12eda 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -650,6 +650,14 @@ Used to group requests when monitoring OTP. "minZoom" : 14, "cacheMaxSeconds" : 600 }, + { + "name" : "areaStops", + "type" : "AreaStop", + "mapper" : "OTPRR", + "maxZoom" : 20, + "minZoom" : 14, + "cacheMaxSeconds" : 600 + }, { "name" : "stations", "type" : "Station", diff --git a/docs/sandbox/MapboxVectorTilesApi.md b/docs/sandbox/MapboxVectorTilesApi.md index ddc3db90ac4..45feec03d47 100644 --- a/docs/sandbox/MapboxVectorTilesApi.md +++ b/docs/sandbox/MapboxVectorTilesApi.md @@ -49,10 +49,11 @@ The feature must be configured in `router-config.json` as follows "minZoom": 14, "cacheMaxSeconds": 600 }, + // flex zones { "name": "areaStops", "type": "AreaStop", - "mapper": "Digitransit", + "mapper": "OTPRR", "maxZoom": 20, "minZoom": 14, "cacheMaxSeconds": 600 @@ -144,7 +145,7 @@ For each layer, the configuration includes: - `name` which is used in the url to fetch tiles, and as the layer name in the vector tiles. - `type` which tells the type of the layer. Currently supported: - `Stop` - - `AreaStop` + - `AreaStop`: Flex zones - `Station` - `VehicleRental`: all rental places: stations and free-floating vehicles - `VehicleRentalVehicle`: free-floating rental vehicles @@ -295,4 +296,5 @@ key, and a function to create the mapper, with a `Graph` object as a parameter, * Added DigitransitRealtime for vehicle rental stations * Changed old vehicle parking mapper to be Stadtnavi * Added a new Digitransit vehicle parking mapper with no real-time information and less fields -- 2024-01-22: Make `basePath` configurable [#5627](https://github.com/opentripplanner/OpenTripPlanner/pull/5627) \ No newline at end of file +- 2024-01-22: Make `basePath` configurable [#5627](https://github.com/opentripplanner/OpenTripPlanner/pull/5627) +- 2024-02-27: Add layer for flex zones [#5704](https://github.com/opentripplanner/OpenTripPlanner/pull/5704) diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java similarity index 92% rename from src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java rename to src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java index 87e10c0ac22..2cf0804b630 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java @@ -12,7 +12,7 @@ import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.service.StopModel; -class DigitransitAreaStopPropertyMapperTest { +class AreaStopPropertyMapperTest { private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of()); private static final AreaStop STOP = MODEL.areaStopForTest("123", Polygons.BERLIN); @@ -24,7 +24,7 @@ class DigitransitAreaStopPropertyMapperTest { @Test void map() { - var mapper = new DigitransitAreaStopPropertyMapper( + var mapper = new AreaStopPropertyMapper( ignored -> List.of(ROUTE_WITH_COLOR, ROUTE_WITHOUT_COLOR), Locale.ENGLISH ); diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java index b86336f867c..befaa6b886d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java @@ -53,7 +53,7 @@ void getAreaStops() { var layer = new Layer( "areaStops", VectorTilesResource.LayerType.AreaStop, - "Digitransit", + "OTPRR", 20, 1, 10, diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java similarity index 82% rename from src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java rename to src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java index fe60a2961cf..c91ea2cb6b6 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/DigitransitAreaStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java @@ -14,12 +14,12 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; -public class DigitransitAreaStopPropertyMapper extends PropertyMapper { +public class AreaStopPropertyMapper extends PropertyMapper { private final Function> getRoutesForStop; private final I18NStringMapper i18NStringMapper; - protected DigitransitAreaStopPropertyMapper( + protected AreaStopPropertyMapper( Function> getRoutesForStop, Locale locale ) { @@ -27,11 +27,8 @@ protected DigitransitAreaStopPropertyMapper( this.i18NStringMapper = new I18NStringMapper(locale); } - protected static DigitransitAreaStopPropertyMapper create( - TransitService transitService, - Locale locale - ) { - return new DigitransitAreaStopPropertyMapper(transitService::getRoutesForStop, locale); + protected static AreaStopPropertyMapper create(TransitService transitService, Locale locale) { + return new AreaStopPropertyMapper(transitService::getRoutesForStop, locale); } @Override diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java index be03df4decf..31952752dbc 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilder.java @@ -16,8 +16,8 @@ public class AreaStopsLayerBuilder extends LayerBuilder { static Map>> mappers = Map.of( - MapperType.Digitransit, - DigitransitAreaStopPropertyMapper::create + MapperType.OTPRR, + AreaStopPropertyMapper::create ); private final TransitService transitService; @@ -48,6 +48,6 @@ protected List getGeometries(Envelope query) { } enum MapperType { - Digitransit, + OTPRR, } } diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 1a270929947..6a74e5a9f4e 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -213,6 +213,14 @@ "minZoom": 14, "cacheMaxSeconds": 600 }, + { + "name": "areaStops", + "type": "AreaStop", + "mapper": "OTPRR", + "maxZoom": 20, + "minZoom": 14, + "cacheMaxSeconds": 600 + }, { "name": "stations", "type": "Station", From caf12a852df90a2d30c83cd51da8c35b8b18c3ec Mon Sep 17 00:00:00 2001 From: De Castri Andrea Date: Tue, 27 Feb 2024 14:45:02 +0100 Subject: [PATCH 069/108] chore: refactor using MultilingualStringMapper --- .../netex/mapping/AuthorityToAgencyMapper.java | 16 ++++------------ .../netex/mapping/MultilingualStringMapper.java | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java index ea9b0cca52b..ec466cc2e27 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/AuthorityToAgencyMapper.java @@ -34,21 +34,13 @@ class AuthorityToAgencyMapper { * Map authority and time zone to OTP agency. */ Agency mapAuthorityToAgency(Authority source) { - String agencyName = source.getName() != null && - StringUtils.hasValue(source.getName().getValue()) - ? source.getName().getValue() - : null; - - String agencyShortName = source.getShortName() != null && - StringUtils.hasValue(source.getShortName().getValue()) - ? source.getShortName().getValue() - : null; - - String mainName = agencyName != null ? agencyName : agencyShortName; + String name = MultilingualStringMapper.nullableValueOf(source.getName()); + String shortName = MultilingualStringMapper.nullableValueOf(source.getShortName()); + String agencyName = StringUtils.hasValue(name) ? name : shortName; AgencyBuilder target = Agency .of(idFactory.createId(source.getId())) - .withName(mainName) + .withName(agencyName) .withTimezone(timeZone); withOptional( diff --git a/src/main/java/org/opentripplanner/netex/mapping/MultilingualStringMapper.java b/src/main/java/org/opentripplanner/netex/mapping/MultilingualStringMapper.java index a51b628bcaa..0f0e2336bc7 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/MultilingualStringMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/MultilingualStringMapper.java @@ -1,12 +1,22 @@ package org.opentripplanner.netex.mapping; import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.StringUtils; import org.rutebanken.netex.model.MultilingualString; public class MultilingualStringMapper { @Nullable - public static String nullableValueOf(@Nullable MultilingualString string) { - return string == null ? null : string.getValue(); + public static String nullableValueOf(@Nullable MultilingualString multilingualString) { + if (multilingualString == null) { + return null; + } + + String value = multilingualString.getValue(); + if (StringUtils.hasNoValue(value)) { + return null; + } + + return value; } } From 456e628f9f16f3702d75a0d7d06fa1ad31176f53 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 27 Feb 2024 17:27:58 +0100 Subject: [PATCH 070/108] Adapt Bikely updater, reorganise tests --- .../bikely/BikelyUpdaterTest.java | 38 +- .../hslpark/HslParkUpdaterTest.java | 24 +- .../parkapi/ParkAPIUpdaterTest.java | 13 +- .../ext/vehicleparking/bikely/bikely.json | 324 ++ .../vehicleparking/hslpark/facilities.json | 0 .../ext}/vehicleparking/hslpark/hubs.json | 0 .../vehicleparking/hslpark/utilizations.json | 0 .../vehicleparking/parkapi/herrenberg.json | 0 .../parkapi/parkapi-reutlingen.json | 0 .../vehicleparking/bikely/bikely.json | 2809 ----------------- .../vehicleparking/bikely/BikelyUpdater.java | 19 +- .../bikely/BikelyUpdaterParameters.java | 3 +- .../updaters/VehicleParkingUpdaterConfig.java | 2 +- 13 files changed, 367 insertions(+), 2865 deletions(-) create mode 100644 src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikely/bikely.json rename src/ext-test/resources/{ => org/opentripplanner/ext}/vehicleparking/hslpark/facilities.json (100%) rename src/ext-test/resources/{ => org/opentripplanner/ext}/vehicleparking/hslpark/hubs.json (100%) rename src/ext-test/resources/{ => org/opentripplanner/ext}/vehicleparking/hslpark/utilizations.json (100%) rename src/ext-test/resources/{ => org/opentripplanner/ext}/vehicleparking/parkapi/herrenberg.json (100%) rename src/ext-test/resources/{ => org/opentripplanner/ext}/vehicleparking/parkapi/parkapi-reutlingen.json (100%) delete mode 100644 src/ext-test/resources/vehicleparking/bikely/bikely.json diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java index 771836c67f2..e56b87e6631 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java @@ -3,14 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Duration; import java.util.Locale; import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; +import org.opentripplanner.test.support.ResourceLoader; import org.opentripplanner.transit.model.basic.Locales; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.updater.spi.HttpHeaders; @@ -19,10 +18,10 @@ public class BikelyUpdaterTest { @Test void parseBikeBoxes() { - var url = "file:src/ext-test/resources/vehicleparking/bikely/bikely.json"; + var uri = ResourceLoader.of(this).uri("bikely.json"); var parameters = new BikelyUpdaterParameters( "", - url, + uri, "bikely", Duration.ofSeconds(30), HttpHeaders.empty() @@ -32,50 +31,37 @@ void parseBikeBoxes() { assertTrue(updater.update()); var parkingLots = updater.getUpdates(); - assertEquals(100, parkingLots.size()); + assertEquals(8, parkingLots.size()); - var first = parkingLots.get(0); - assertEquals(new FeedScopedId("bikely", "164"), first.getId()); - assertEquals("Husebybadet", first.getName().toString()); + var first = parkingLots.getFirst(); + assertEquals(new FeedScopedId("bikely", "7"), first.getId()); + assertEquals("Gjettum T-banestasjon", first.getName().toString()); assertFalse(first.hasAnyCarPlaces()); assertTrue(first.hasBicyclePlaces()); assertEquals( - "First 4 hour(s) is NOK0.00, afterwards NOK10.00 per 1 hour(s)", + "First 12 hour(s) is NOK0.00, afterwards NOK10.00 per 1 hour(s)", first.getNote().toString(Locale.ENGLISH) ); assertEquals( - "Første 4 time(r) er kr 0,00. Deretter kr 10,00 per 1 time(r)", + "Første 12 time(r) er kr 0,00. Deretter kr 10,00 per 1 time(r)", first.getNote().toString(Locales.NORWEGIAN_BOKMAL) ); assertEquals( - "Første 4 time(r) er kr 0,00. Deretter kr 10,00 per 1 time(r)", + "Første 12 time(r) er kr 0,00. Deretter kr 10,00 per 1 time(r)", first.getNote().toString(Locales.NORWAY) ); var availibility = first.getAvailability(); - assertEquals(4, availibility.getBicycleSpaces()); + assertEquals(8, availibility.getBicycleSpaces()); var capacity = first.getCapacity(); assertEquals(10, capacity.getBicycleSpaces()); var freeParkingLots = parkingLots.get(2); - assertEquals("Free of charge", freeParkingLots.getNote().toString(Locale.ENGLISH)); + assertEquals("First 12 hour(s) is NOK0.00, afterwards NOK10.00 per 1 hour(s)", freeParkingLots.getNote().toString(Locale.ENGLISH)); assertEquals(VehicleParkingState.OPERATIONAL, first.getState()); - var last = parkingLots.get(99); - assertEquals("Hamar Stasjon", last.getName().toString()); - assertNull(last.getOpeningHours()); - assertFalse(last.hasAnyCarPlaces()); - assertFalse(last.hasWheelchairAccessibleCarPlaces()); - - var closed = parkingLots - .stream() - .map(VehicleParking::getState) - .filter(x -> x == VehicleParkingState.TEMPORARILY_CLOSED) - .count(); - assertEquals(2, closed); - parkingLots.forEach(lot -> { assertNotNull(lot.getNote().toString()); }); diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterTest.java index de7f23922d6..2226a988d20 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterTest.java @@ -15,27 +15,30 @@ import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; import org.opentripplanner.model.calendar.openinghours.OsmOpeningHoursSupport; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; +import org.opentripplanner.test.support.ResourceLoader; import org.opentripplanner.transit.model.framework.Deduplicator; public class HslParkUpdaterTest { + private static final ResourceLoader LOADER = ResourceLoader.of(HslParkUpdaterTest.class); + private static final String UTILIZATIONS_URL = LOADER.url("utilizations.json").toString(); + private static final String HUBS_URL = LOADER.url("hubs.json").toString(); + private static final String FACILITIES_URL = LOADER.url("facilities.json").toString(); + @Test void parseParks() { - var facilitiesUrl = "file:src/ext-test/resources/vehicleparking/hslpark/facilities.json"; - var hubsUrl = "file:src/ext-test/resources/vehicleparking/hslpark/hubs.json"; - var utilizationsUrl = "file:src/ext-test/resources/vehicleparking/hslpark/utilizations.json"; var timeZone = ZoneIds.HELSINKI; var parameters = new HslParkUpdaterParameters( "", 3000, - facilitiesUrl, + FACILITIES_URL, "hslpark", null, 30, - utilizationsUrl, + UTILIZATIONS_URL, timeZone, - hubsUrl + HUBS_URL ); var openingHoursCalendarService = new OpeningHoursCalendarService( new Deduplicator(), @@ -149,21 +152,18 @@ void parseParks() { @Test void parseParksWithoutTimeZone() { - var facilitiesUrl = "file:src/ext-test/resources/vehicleparking/hslpark/facilities.json"; - var hubsUrl = "file:src/ext-test/resources/vehicleparking/hslpark/hubs.json"; - var utilizationsUrl = "file:src/ext-test/resources/vehicleparking/hslpark/utilizations.json"; ZoneId timeZone = null; var parameters = new HslParkUpdaterParameters( "", 3000, - facilitiesUrl, + FACILITIES_URL, "hslpark", null, 30, - utilizationsUrl, + UTILIZATIONS_URL, timeZone, - hubsUrl + HUBS_URL ); var openingHoursCalendarService = new OpeningHoursCalendarService( new Deduplicator(), diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterTest.java index 3fae4eb0f9d..d1e8bfe9c5f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterTest.java @@ -13,20 +13,23 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; import org.opentripplanner.model.calendar.openinghours.OsmOpeningHoursSupport; +import org.opentripplanner.test.support.ResourceLoader; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.updater.spi.HttpHeaders; public class ParkAPIUpdaterTest { private static final Duration FREQUENCY = Duration.ofSeconds(30); + private static final ResourceLoader LOADER = ResourceLoader.of(ParkAPIUpdaterTest.class); + private static final String REUTLINGEN_URL = LOADER.uri("parkapi-reutlingen.json").toString(); + private static final String HERRENBERG_URL = LOADER.uri("herrenberg.json").toString(); @Test void parseCars() { - var url = "file:src/ext-test/resources/vehicleparking/parkapi/parkapi-reutlingen.json"; var timeZone = ZoneIds.BERLIN; var parameters = new ParkAPIUpdaterParameters( "", - url, + REUTLINGEN_URL, "park-api", FREQUENCY, HttpHeaders.empty(), @@ -66,11 +69,10 @@ void parseCars() { @Test void parseCarsWithoutTimeZone() { - var url = "file:src/ext-test/resources/vehicleparking/parkapi/parkapi-reutlingen.json"; ZoneId timeZone = null; var parameters = new ParkAPIUpdaterParameters( "", - url, + REUTLINGEN_URL, "park-api", FREQUENCY, HttpHeaders.empty(), @@ -99,10 +101,9 @@ void parseCarsWithoutTimeZone() { @Test void parseHerrenbergOpeningHours() { - var url = "file:src/ext-test/resources/vehicleparking/parkapi/herrenberg.json"; var parameters = new ParkAPIUpdaterParameters( "", - url, + HERRENBERG_URL, "park-api", FREQUENCY, HttpHeaders.empty(), diff --git a/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikely/bikely.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikely/bikely.json new file mode 100644 index 00000000000..a3e1e9e2e57 --- /dev/null +++ b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikely/bikely.json @@ -0,0 +1,324 @@ +{ + "result": [ + { + "name": "Gjettum T-banestasjon", + "latitude": 59.908370304935886, + "longitude": 10.531885281680088, + "street": "Kolsåsstien", + "postCode": "1352", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": null, + "isPrivate": false, + "isGroupMember": false, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 8, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 1, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 1 + }, + "id": 7 + }, + { + "name": "Lørenskog sykkelhotell", + "latitude": 59.92782951468654, + "longitude": 10.955770147334647, + "street": "Bibliotekgata", + "postCode": "1470", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": "2021-09-02T12:10:25.957906Z", + "isPrivate": false, + "isGroupMember": true, + "totalStandardSpots": 1, + "hasStandardParking": true, + "availableStandardSpots": 73, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 5, + "startPriceDuration": 12, + "mainPriceAmount": 5, + "mainPriceDuration": 12, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 5, + "startPriceDuration": 12, + "mainPriceAmount": 5, + "mainPriceDuration": 12 + }, + "id": 9 + }, + { + "name": "Østerås T-Banestasjon", + "latitude": 59.939540539600415, + "longitude": 10.608225677856922, + "street": "104 Eiksveien", + "postCode": "1361", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": "2022-11-16T14:48:00Z", + "maintenanceStartTime": "2022-11-16T12:49:00.674743Z", + "isPrivate": false, + "isGroupMember": false, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 7, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 1, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 1 + }, + "id": 10 + }, + { + "name": "Slottet sykkelparkering", + "latitude": 58.147399578615044, + "longitude": 7.987953559998118, + "street": "Tordenskjolds gate 13", + "postCode": "4612", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": null, + "isPrivate": false, + "isGroupMember": false, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 2, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 0, + "mainPriceAmount": 15, + "mainPriceDuration": 12, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 0, + "mainPriceAmount": 15, + "mainPriceDuration": 12 + }, + "id": 20 + }, + { + "name": "AtB Steinkjer Stasjon", + "latitude": 64.01218345882226, + "longitude": 11.495342431116114, + "street": "Strandvegen", + "postCode": "7713", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": null, + "isPrivate": false, + "isGroupMember": false, + "totalStandardSpots": 4, + "hasStandardParking": true, + "availableStandardSpots": 4, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 4, + "mainPriceAmount": 20, + "mainPriceDuration": 24, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 4, + "mainPriceAmount": 20, + "mainPriceDuration": 24 + }, + "id": 21 + }, + { + "name": "Ørjaveita", + "latitude": 63.43390486150696, + "longitude": 10.396437736776587, + "street": "Ørjaveita", + "postCode": "7010", + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:55:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": "2022-09-30T07:23:33.102315Z", + "isPrivate": false, + "isGroupMember": true, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 8, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 4, + "mainPriceAmount": 10, + "mainPriceDuration": 1, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 4, + "mainPriceAmount": 10, + "mainPriceDuration": 1 + }, + "id": 24 + }, + { + "name": "Porsgrunn Stasjon", + "latitude": 59.1401227511141, + "longitude": 9.658452655675983, + "street": null, + "postCode": null, + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": null, + "isPrivate": false, + "isGroupMember": false, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 9, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 12, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 12, + "mainPriceAmount": 10, + "mainPriceDuration": 12 + }, + "id": 25 + }, + { + "name": "Faktry", + "latitude": 63.39686931503907, + "longitude": 10.401622501039789, + "street": null, + "postCode": null, + "municipality": null, + "openFrom": "2020-01-28T06:00:00Z", + "openTo": "2020-01-28T22:00:00Z", + "isAllDay": true, + "isInMaintenance": false, + "maintenanceEndingTime": null, + "maintenanceStartTime": null, + "isPrivate": false, + "isGroupMember": true, + "totalStandardSpots": 10, + "hasStandardParking": true, + "availableStandardSpots": 4, + "totalCargoSpots": 0, + "hasCargoParking": false, + "availableCargoSpots": 0, + "startPriceAmount": 0, + "startPriceDuration": 0, + "mainPriceAmount": 0, + "mainPriceDuration": 0, + "maxDailyAmount": 0, + "isCluster": false, + "zoomDelta": 0, + "subscriptionId": 0, + "subscribedDoorName": null, + "subscribedLockType": 0, + "locationPricing": { + "locationPaymentType": 1, + "startPriceAmount": 0, + "startPriceDuration": 0, + "mainPriceAmount": 0, + "mainPriceDuration": 0 + }, + "id": 26 + } + ] +} \ No newline at end of file diff --git a/src/ext-test/resources/vehicleparking/hslpark/facilities.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/facilities.json similarity index 100% rename from src/ext-test/resources/vehicleparking/hslpark/facilities.json rename to src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/facilities.json diff --git a/src/ext-test/resources/vehicleparking/hslpark/hubs.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/hubs.json similarity index 100% rename from src/ext-test/resources/vehicleparking/hslpark/hubs.json rename to src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/hubs.json diff --git a/src/ext-test/resources/vehicleparking/hslpark/utilizations.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/utilizations.json similarity index 100% rename from src/ext-test/resources/vehicleparking/hslpark/utilizations.json rename to src/ext-test/resources/org/opentripplanner/ext/vehicleparking/hslpark/utilizations.json diff --git a/src/ext-test/resources/vehicleparking/parkapi/herrenberg.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/parkapi/herrenberg.json similarity index 100% rename from src/ext-test/resources/vehicleparking/parkapi/herrenberg.json rename to src/ext-test/resources/org/opentripplanner/ext/vehicleparking/parkapi/herrenberg.json diff --git a/src/ext-test/resources/vehicleparking/parkapi/parkapi-reutlingen.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/parkapi/parkapi-reutlingen.json similarity index 100% rename from src/ext-test/resources/vehicleparking/parkapi/parkapi-reutlingen.json rename to src/ext-test/resources/org/opentripplanner/ext/vehicleparking/parkapi/parkapi-reutlingen.json diff --git a/src/ext-test/resources/vehicleparking/bikely/bikely.json b/src/ext-test/resources/vehicleparking/bikely/bikely.json deleted file mode 100644 index ce2b4d87aa6..00000000000 --- a/src/ext-test/resources/vehicleparking/bikely/bikely.json +++ /dev/null @@ -1,2809 +0,0 @@ -{ - "result": [ - { - "name": "Husebybadet", - "availableParkingSpots": 4, - "totalParkingSpots":10, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Blisterhaugvegen 13", - "postCode": "7078", - "postName": "Saupstad", - "municipality": null, - "longitude": 10.351535699801943, - "latitude": 63.365653571508794 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 164 - }, - { - "name": "Sentrum P-Hus", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 199.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Teatergata", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.741571022211218, - "latitude": 59.91600756265333 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 115 - }, - { - "name": "Skien Fritidspark", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Moflatvegen", - "postCode": null, - "postName": null, - "municipality": "Skien", - "longitude": 9.595596391124074, - "latitude": 59.18662360320687 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 112 - }, - { - "name": "Trondheim Spektrum", - "availableParkingSpots": 12, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Trondheim Spektrum", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.378531574243146, - "latitude": 63.42748116576243 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 103 - }, - { - "name": "Amfi Steinkjer", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 24 - }, - "address": { - "street": "Sjøfartsgata 2", - "postCode": "7714 ", - "postName": "Steinkjer", - "municipality": null, - "longitude": 11.493187779423364, - "latitude": 64.01155740883044 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 102 - }, - { - "name": "Tyholmen ", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 8 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 8.763968789298318, - "latitude": 58.45927282561365 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-02-16T07:47:14.369256Z", - "maintenanceEndingTime": "2022-02-16T21:00:52.169Z", - "isUnderMaintenance": true - }, - "id": 101 - }, - { - "name": "AMFI Ullevål Øst", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Kaj Munks vei", - "postCode": null, - "postName": "Oslo", - "municipality": null, - "longitude": 10.736078877999802, - "latitude": 59.94975795585445 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 100 - }, - { - "name": "VikelvfaretPOD", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Strandliveien", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.53595748700983, - "latitude": 63.42116945997451 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-08-11T06:48:54.409897Z", - "maintenanceEndingTime": "2022-08-12T06:48:00Z", - "isUnderMaintenance": false - }, - "id": 99 - }, - { - "name": "Tveita Senter", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Wilhelm Stenersens vei", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.841919396865917, - "latitude": 59.913573733717875 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-27T14:32:43.02938Z", - "maintenanceEndingTime": "2022-09-28T10:00:00Z", - "isUnderMaintenance": false - }, - "id": 98 - }, - { - "name": "Manglerud Senter", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Plogveien", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.812699321967543, - "latitude": 59.89730171094624 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-06-25T21:03:44.175534Z", - "maintenanceEndingTime": "2022-06-27T05:00:00Z", - "isUnderMaintenance": true - }, - "id": 97 - }, - { - "name": "Lambertseter Senter", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Cecilie Thoresens vei", - "postCode": null, - "postName": "Oslo", - "municipality": null, - "longitude": 10.811687688348668, - "latitude": 59.87397901539979 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 95 - }, - { - "name": "test sub", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 0.0, - "latitude": 0.0 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 94 - }, - { - "name": "Lagunen Storsenter", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 3, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Laguneveien 1", - "postCode": "5239", - "postName": "Rådal", - "municipality": null, - "longitude": 5.329471960391827, - "latitude": 60.29717349679797 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 93 - }, - { - "name": "IMEI Test Telenor", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Vikelvfaret 4", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.536120271331493, - "latitude": 63.421001992107435 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2021-09-08T09:50:48.224668Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 92 - }, - { - "name": "Kolsås Sykkelhotell", - "availableParkingSpots": 14, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 24, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Brynsveien", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.50149743216143, - "latitude": 59.91468587108326 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-10-17T03:00:00Z", - "maintenanceEndingTime": "2022-10-20T03:00:00Z", - "isUnderMaintenance": false - }, - "id": 91 - }, - { - "name": "Ranheimsveien", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Ranheimsveien", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.470519111242798, - "latitude": 63.43253182042508 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 89 - }, - { - "name": "Skien LastesykkelHotell", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Hesselbergs gate", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.608610388358091, - "latitude": 59.20960955258356 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 85 - }, - { - "name": "Sandefjord Torg", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Torget", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.22593781770841, - "latitude": 59.12977259494621 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 84 - }, - { - "name": "Kirkegata", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 15.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 15.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Kirkegata", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.661979503627865, - "latitude": 59.43538193911775 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 83 - }, - { - "name": "AMFI Ullevål Nord", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Ullevål", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.732811552248286, - "latitude": 59.94971992531088 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-03-02T11:53:34.699437Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 81 - }, - { - "name": "CC Vest", - "availableParkingSpots": 6, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 3, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Lilleakerveien 16", - "postCode": "0283", - "postName": "Oslo", - "municipality": null, - "longitude": 10.636677951126892, - "latitude": 59.91742604033082 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-05-13T22:56:29.291436Z", - "maintenanceEndingTime": "2022-05-20T22:56:00Z", - "isUnderMaintenance": false - }, - "id": 78 - }, - { - "name": "Jekta Storsenter", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Karlsøyvegen 12", - "postCode": "9015", - "postName": "Tromsø", - "municipality": null, - "longitude": 18.923834531786863, - "latitude": 69.67385798911647 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 77 - }, - { - "name": "Bike Fixx", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Filipstad", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.719912502614747, - "latitude": 59.91008276557721 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 118 - }, - { - "name": "Tromsø Havneterminal", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Strandtorget", - "postCode": "9008 ", - "postName": "Tromsø", - "municipality": null, - "longitude": 18.95831396328195, - "latitude": 69.64723042234459 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2021-12-01T13:00:46.591088Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 76 - }, - { - "name": "Punkt Fornebu", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Forneburingen 248", - "postCode": "1364", - "postName": "Fornebu", - "municipality": null, - "longitude": 10.622245451194132, - "latitude": 59.89786621862049 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 119 - }, - { - "name": "Moholt Allmenning Privat", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 199.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Moholt Allmenning", - "postCode": null, - "postName": null, - "municipality": "Trondheim", - "longitude": 10.433525875945952, - "latitude": 63.41153478175877 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 122 - }, - { - "name": "Rådhuskvartalet", - "availableParkingSpots": 10, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Peter Motzfeldts Gate 1a", - "postCode": "5017", - "postName": "Bergen", - "municipality": null, - "longitude": 5.328588666261149, - "latitude": 60.391802464650524 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 152 - }, - { - "name": "Majorstuen test", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 0.0, - "latitude": 0.0 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 151 - }, - { - "name": "Vikelvfaret ", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.536375440686458, - "latitude": 63.421421395112496 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 150 - }, - { - "name": "Gjøvik Skysstasjon", - "availableParkingSpots": 19, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 10, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 6 - }, - "address": { - "street": null, - "postCode": "2821", - "postName": " Gjøvik", - "municipality": null, - "longitude": 10.693611705915028, - "latitude": 60.7978828615738 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 147 - }, - { - "name": "Bikely HOME", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 0.0, - "latitude": 0.0 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 146 - }, - { - "name": "E-bike Pool", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.536298698980348, - "latitude": 63.421120823556926 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 145 - }, - { - "name": "CC Gjøvik Strandgata", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 3, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Jernbanesvingen 6", - "postCode": "2821", - "postName": "Gjøvik", - "municipality": "Innlandet", - "longitude": 10.692266848604755, - "latitude": 60.79934165258753 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 144 - }, - { - "name": "Tranberghallen", - "availableParkingSpots": 10, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 10, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Bjørnsvebakken 2", - "postCode": "2819", - "postName": "Gjøvik", - "municipality": null, - "longitude": 10.674990942984651, - "latitude": 60.81155519367008 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 143 - }, - { - "name": "CC Gjøvik Nord", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 3, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Jernbanesvingen 6", - "postCode": "2821", - "postName": "Gjøvik", - "municipality": null, - "longitude": 10.692767469501572, - "latitude": 60.80095906879147 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 142 - }, - { - "name": "AtB Melhus Skysstasjon", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 24 - }, - "address": { - "street": "Jernbanevegen 12, ", - "postCode": "7224 ", - "postName": "Melhus", - "municipality": null, - "longitude": 10.27720350042717, - "latitude": 63.28464073896281 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 140 - }, - { - "name": "Kvadrat Kjøpesenter NORD", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 2 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.721216223802292, - "latitude": 58.87746037465877 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-03-23T14:15:41.744292Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 139 - }, - { - "name": "Prestegaten", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Nedre Langgate 37", - "postCode": "3126", - "postName": "Tønsberg", - "municipality": null, - "longitude": 10.406923125009499, - "latitude": 59.26559407972196 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 48 - }, - { - "name": "Epsilon Cities PLC demo", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.600301917104393, - "latitude": 51.143920796723506 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 134 - }, - { - "name": "Prinsen Kino", - "availableParkingSpots": 12, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Prinsen Kino", - "postCode": null, - "postName": "Trondheim", - "municipality": "Trondheim", - "longitude": 10.392912861698846, - "latitude": 63.42655470553182 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2021-12-21T14:26:01.518642Z", - "maintenanceEndingTime": "2021-12-22T09:59:00Z", - "isUnderMaintenance": false - }, - "id": 131 - }, - { - "name": "P-Hus Aker Brygge - Felt C", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Sjøgata 4", - "postCode": "0250 ", - "postName": "Oslo", - "municipality": null, - "longitude": 10.723890489730298, - "latitude": 59.91039271039033 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 130 - }, - { - "name": "Kvadrat Kjøpesenter SØR-ØST", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 2 - }, - "address": { - "street": null, - "postCode": null, - "postName": "Sandnes", - "municipality": null, - "longitude": 5.721982699172825, - "latitude": 58.87578295212405 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-03-23T14:15:53.56905Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 129 - }, - { - "name": "Lillestrøm Parkering Storgata", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 20.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Storgata 7", - "postCode": "2000", - "postName": "Lillestrøm", - "municipality": null, - "longitude": 11.047494272702355, - "latitude": 59.9551079846462 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 128 - }, - { - "name": "Kvadrat Kjøpesenter SØR-VEST", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 2 - }, - "address": { - "street": null, - "postCode": null, - "postName": "Sandnes", - "municipality": null, - "longitude": 5.721655395609253, - "latitude": 58.87584612393457 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-03-23T14:16:08.835289Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 127 - }, - { - "name": "City Lade", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 3, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Håkon 7.gt", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.446035404436271, - "latitude": 63.44294160482934 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-01-04T14:25:22.808388Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 126 - }, - { - "name": "Onepark Sentrum P-hus 1B", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 20.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Pilestredet", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.742243366982436, - "latitude": 59.916147524186805 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 124 - }, - { - "name": "Lerkendal Studentby Privat", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 199.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Lerkendal", - "postCode": null, - "postName": null, - "municipality": "Trondheim", - "longitude": 10.400520415564788, - "latitude": 63.41156976316326 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 123 - }, - { - "name": "Nedre Berg Privat", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 199.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Nedre Berg", - "postCode": null, - "postName": null, - "municipality": "Trondheim", - "longitude": 10.412552683911223, - "latitude": 63.41457636233408 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 121 - }, - { - "name": "Bruveien parkering", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Bruveien", - "postCode": "3400", - "postName": "Lier", - "municipality": null, - "longitude": 10.243007215803273, - "latitude": 59.787372973019906 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 47 - }, - { - "name": "Slottet sykkelparkering", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 15.00, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Tordenskjolds gate 13", - "postCode": "4612", - "postName": "Kristiansand", - "municipality": null, - "longitude": 7.987953559998118, - "latitude": 58.147399578615044 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 20 - }, - { - "name": "Ibsenhuset", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Hesselbergs gate", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.605486702117382, - "latitude": 59.208581031898134 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 30 - }, - { - "name": "Gjettum T-banestasjon", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Kolsåsstien", - "postCode": "1352", - "postName": " Kolsås", - "municipality": null, - "longitude": 10.531885281680088, - "latitude": 59.908370304935886 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-21T08:49:44.620258Z", - "maintenanceEndingTime": "2022-09-26T10:00:00Z", - "isUnderMaintenance": false - }, - "id": 7 - }, - { - "name": "Lager 11 / Grip ", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Sluppenvegen 11", - "postCode": "7037", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.396483571230378, - "latitude": 63.39808834594847 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 64 - }, - { - "name": "Ringerike Rådhus", - "availableParkingSpots": 10, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Benterudgata 1", - "postCode": "3511", - "postName": "Hønefoss", - "municipality": null, - "longitude": 10.255685034595844, - "latitude": 60.16016458363586 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 56 - }, - { - "name": "Leutenhaven ", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Erling Skakkes gate", - "postCode": "7012 ", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.389317891553075, - "latitude": 63.42922275870133 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 40 - }, - { - "name": "Bergen Bystasjon", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.333323897536757, - "latitude": 60.38902516311156 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 33 - }, - { - "name": "KLP Teknobyen", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 2, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Abels Gate 5", - "postCode": null, - "postName": "Trondheim", - "municipality": null, - "longitude": 10.394627580519499, - "latitude": 63.41531182507022 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 59 - }, - { - "name": "Moholt Allmenning", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 15.0, - "startPriceDurationHours": 2, - "mainPriceAmount": 15.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.433145179115732, - "latitude": 63.411450813462544 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 70 - }, - { - "name": "Jar T-banestasjon", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.619360985274895, - "latitude": 59.92656137276969 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-21T08:49:18.151307Z", - "maintenanceEndingTime": "2022-09-26T10:00:00Z", - "isUnderMaintenance": false - }, - "id": 54 - }, - { - "name": "Bergen Marken", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.332616662863372, - "latitude": 60.390714704251785 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 34 - }, - { - "name": "Heggedal Idrettsanlegg", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Øvre Gjellum vei 4", - "postCode": "1389", - "postName": null, - "municipality": null, - "longitude": 10.449085122930573, - "latitude": 59.79219663780587 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 36 - }, - { - "name": "Majorstuen T-bane", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Majorstuen", - "postCode": null, - "postName": "Oslo", - "municipality": null, - "longitude": 10.714455875051554, - "latitude": 59.92966366431081 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-06-25T21:03:12.9946Z", - "maintenanceEndingTime": "2022-06-27T21:03:00Z", - "isUnderMaintenance": false - }, - "id": 75 - }, - { - "name": "Arnestad Idrettsanlegg", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Slemmestadveien 468", - "postCode": "1390", - "postName": null, - "municipality": null, - "longitude": 10.486714884883641, - "latitude": 59.802364746495144 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 35 - }, - { - "name": "Risenga Svømmehall", - "availableParkingSpots": 10, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Brages vei 8", - "postCode": "1387", - "postName": null, - "municipality": null, - "longitude": 10.442563632289863, - "latitude": 59.82506989908911 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 37 - }, - { - "name": "Farmannstorvet", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Farmannstorvet", - "postCode": "3126", - "postName": "Tønsberg", - "municipality": null, - "longitude": 10.409131331358115, - "latitude": 59.26949087588371 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 49 - }, - { - "name": "Thon Hotell", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Nedre Langgate 40", - "postCode": "3126", - "postName": "Tønsberg", - "municipality": null, - "longitude": 10.403879519443562, - "latitude": 59.26742751013589 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-08T13:07:32.593017Z", - "maintenanceEndingTime": "2022-09-09T05:30:00Z", - "isUnderMaintenance": false - }, - "id": 158 - }, - { - "name": "Re-torvet", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Kåpeveien 5", - "postCode": "3174", - "postName": "Tønsberg", - "municipality": null, - "longitude": 10.265188655718509, - "latitude": 59.37137931547057 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-08T13:07:11.735986Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 159 - }, - { - "name": "Bikely kontor", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.536038875579834, - "latitude": 63.42140508377716 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 149 - }, - { - "name": "Jernbanebommene", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Jernbanegaten 2", - "postCode": "3110 ", - "postName": "Tønsberg", - "municipality": null, - "longitude": 10.41125267456441, - "latitude": 59.271186192203146 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-08T13:07:48.684408Z", - "maintenanceEndingTime": "2022-09-09T05:30:00Z", - "isUnderMaintenance": false - }, - "id": 157 - }, - { - "name": "Pirbadet", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": " Havnegata 12", - "postCode": "7010", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.400604571667476, - "latitude": 63.440104921574346 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-08-23T14:10:21.24962Z", - "maintenanceEndingTime": "2022-08-31T12:00:00Z", - "isUnderMaintenance": false - }, - "id": 155 - }, - { - "name": "Oasen Terminal", - "availableParkingSpots": 16, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Oasen terminal, 5147 Fyllingsdalen", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.287504678584067, - "latitude": 60.34898300059523 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 156 - }, - { - "name": "Lerkendal Studentby", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 15.0, - "startPriceDurationHours": 2, - "mainPriceAmount": 15.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.40028702641794, - "latitude": 63.41166855841642 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 68 - }, - { - "name": "NAV Ringerike", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Hønefoss Bru 2", - "postCode": "3510", - "postName": "Hønefoss", - "municipality": null, - "longitude": 10.25959259066608, - "latitude": 60.1662774431612 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 57 - }, - { - "name": "Sletten Senter", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": " Vilhelm Bjerknes vei", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.358242600432832, - "latitude": 60.35640814540556 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 63 - }, - { - "name": "Nedre Berg", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 15.0, - "startPriceDurationHours": 2, - "mainPriceAmount": 15.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.41249315628851, - "latitude": 63.41457139184208 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 72 - }, - { - "name": "Holmen Ishall", - "availableParkingSpots": 11, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Vogellund 26 ", - "postCode": "1394", - "postName": null, - "municipality": null, - "longitude": 10.484556243805073, - "latitude": 59.853488879299015 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 38 - }, - { - "name": "Porsgrunn Stasjon", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.658452655675983, - "latitude": 59.1401227511141 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 25 - }, - { - "name": "Faktry", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.401622501039789, - "latitude": 63.39686931503907 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-04-13T08:12:16.611434Z", - "maintenanceEndingTime": "2022-04-19T07:00:00Z", - "isUnderMaintenance": false - }, - "id": 26 - }, - { - "name": "Skjelsvik Terminal", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.687602185911448, - "latitude": 59.09468648359441 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 29 - }, - { - "name": "Ørjaveita", - "availableParkingSpots": 9, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Ørjaveita", - "postCode": "7010", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.396437736776587, - "latitude": 63.43390486150696 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-30T07:23:33.102315Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 24 - }, - { - "name": "Botilrud", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Norderhovsveien", - "postCode": "3512", - "postName": "Hønefoss", - "municipality": null, - "longitude": 10.274488364176818, - "latitude": 60.12984784225455 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 43 - }, - { - "name": "Høvik Skole", - "availableParkingSpots": 6, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.239748734466318, - "latitude": 59.75941171762161 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 31 - }, - { - "name": "AtB Steinkjer Stasjon", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 24 - }, - "address": { - "street": "Strandvegen", - "postCode": "7713", - "postName": " Steinkjer", - "municipality": null, - "longitude": 11.495342431116114, - "latitude": 64.01218345882226 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-03-29T10:27:56.349993Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 21 - }, - { - "name": "Bragernes Torg", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 10, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Bragernes torg øvre", - "postCode": null, - "postName": "Drammen", - "municipality": null, - "longitude": 10.203655055754055, - "latitude": 59.74317281589255 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2021-10-24T06:46:15.535931Z", - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 52 - }, - { - "name": "PirSenteret Elsykkel-pool", - "availableParkingSpots": 5, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Havnegata 9", - "postCode": "7010", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.402578587946673, - "latitude": 63.44101099094018 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": "2021-04-29T09:00:00Z", - "isUnderMaintenance": false - }, - "id": 11 - }, - { - "name": "Sirkus Shopping", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 2, - "mainPriceAmount": 20.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Sirkus Shopping", - "postCode": "7044", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.454789422056505, - "latitude": 63.43590131147719 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 46 - }, - { - "name": "Stokke Stasjon", - "availableParkingSpots": 6, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Tassebekkveien 2, 3160 Stokke", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.300562831301017, - "latitude": 59.22144536511286 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 165 - }, - { - "name": "Lørenskog sykkelhotell", - "availableParkingSpots": 74, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 5.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 5.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Bibliotekgata", - "postCode": "1470", - "postName": "Lørenskog", - "municipality": null, - "longitude": 10.955770147334647, - "latitude": 59.92782951468654 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2021-09-02T12:10:25.957906Z", - "maintenanceEndingTime": "2021-09-06T21:55:00Z", - "isUnderMaintenance": false - }, - "id": 9 - }, - { - "name": "Nittedal Stasjon", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 15.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 10.864211782361556, - "latitude": 60.05816419277446 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-22T07:12:14.17422Z", - "maintenanceEndingTime": "2022-09-23T14:01:00Z", - "isUnderMaintenance": false - }, - "id": 50 - }, - { - "name": "Skien Stasjon", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.602695726983418, - "latitude": 59.218894232081524 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 27 - }, - { - "name": "Skien SykkelHotell", - "availableParkingSpots": 38, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": null, - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 9.608779312105321, - "latitude": 59.20951432347752 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 61 - }, - { - "name": "Hønefoss Bussteminal", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Hønefoss Bussteminal - Stangs gate 9", - "postCode": "3510", - "postName": "Hønefoss", - "municipality": null, - "longitude": 10.2580229269916, - "latitude": 60.1645916747253 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 44 - }, - { - "name": "Østerås T-Banestasjon", - "availableParkingSpots": 3, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "104 Eiksveien", - "postCode": "1361", - "postName": " Østerås", - "municipality": null, - "longitude": 10.608225677856922, - "latitude": 59.939540539600415 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-21T08:50:03.004692Z", - "maintenanceEndingTime": "2022-09-26T10:00:00Z", - "isUnderMaintenance": false - }, - "id": 10 - }, - { - "name": "Pirsenteret", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 1, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Havnegata 9", - "postCode": "7010", - "postName": "Trondheim", - "municipality": null, - "longitude": 10.403231564385752, - "latitude": 63.44068572623346 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 74 - }, - { - "name": "Vefsn VGS", - "availableParkingSpots": 2, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Kirkegata 9", - "postCode": "8656", - "postName": "Mosjøen", - "municipality": null, - "longitude": 13.192551834307421, - "latitude": 65.8375053559191 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 45 - }, - { - "name": "Hvervenkastet", - "availableParkingSpots": 4, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Arnegårdsveien", - "postCode": "3511", - "postName": "Hønefoss", - "municipality": null, - "longitude": 10.251621386917051, - "latitude": 60.148912920668295 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 42 - }, - { - "name": "Universitetsbiblioteket", - "availableParkingSpots": 8, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 4, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 1 - }, - "address": { - "street": "Haakon Sheteligs plass", - "postCode": null, - "postName": null, - "municipality": null, - "longitude": 5.318577002636498, - "latitude": 60.38765367051367 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 62 - }, - { - "name": "KLP EL-pool", - "availableParkingSpots": 1, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Abels Gate 5", - "postCode": null, - "postName": "Trondheim", - "municipality": "Trondheim", - "longitude": 10.394825378437904, - "latitude": 63.41533303540145 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 60 - }, - { - "name": "Lier Bussterminal", - "availableParkingSpots": 7, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 0.0, - "startPriceDurationHours": 0, - "mainPriceAmount": 0.0, - "mainPriceDurationHours": 0 - }, - "address": { - "street": "Lierbyen bussterminal", - "postCode": "3400", - "postName": " Lier", - "municipality": null, - "longitude": 10.242350688317096, - "latitude": 59.787017349097006 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": null, - "maintenanceEndingTime": null, - "isUnderMaintenance": false - }, - "id": 32 - }, - { - "name": "Hamar Stasjon", - "availableParkingSpots": 10, - "totalParkingSpots": 0, - "price": { - "startPriceAmount": 10.0, - "startPriceDurationHours": 12, - "mainPriceAmount": 10.0, - "mainPriceDurationHours": 12 - }, - "address": { - "street": "Stangevegen 34", - "postCode": "2317", - "postName": "Hamar", - "municipality": null, - "longitude": 11.082666445627662, - "latitude": 60.79116600147056 - }, - "workingHours": { - "openFrom": null, - "openTo": null, - "allDay": true, - "maintenanceStartTime": "2022-09-11T17:21:56.166899Z", - "maintenanceEndingTime": "2022-09-12T05:01:00Z", - "isUnderMaintenance": false - }, - "id": 80 - } - ], - "targetUrl": null, - "success": true, - "error": null, - "unAuthorizedRequest": false, - "__abp": true -} \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java index bfa180015e4..8c451a4fd5c 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java @@ -26,7 +26,7 @@ public class BikelyUpdater extends GenericJsonDataSource { private final String feedId; public BikelyUpdater(BikelyUpdaterParameters parameters) { - super(parameters.url(), JSON_PARSE_PATH, parameters.httpHeaders()); + super(parameters.url().toString(), JSON_PARSE_PATH, parameters.httpHeaders()); this.feedId = parameters.feedId(); } @@ -34,20 +34,19 @@ public BikelyUpdater(BikelyUpdaterParameters parameters) { protected VehicleParking parseElement(JsonNode jsonNode) { var vehicleParkId = new FeedScopedId(feedId, jsonNode.get("id").asText()); - var address = jsonNode.get("address"); var workingHours = jsonNode.get("workingHours"); - var lat = address.get("latitude").asDouble(); - var lng = address.get("longitude").asDouble(); + var lat = jsonNode.get("latitude").asDouble(); + var lng = jsonNode.get("longitude").asDouble(); var coord = new WgsCoordinate(lat, lng); var name = new NonLocalizedString(jsonNode.path("name").asText()); - var totalSpots = jsonNode.get("totalParkingSpots").asInt(); - var freeSpots = jsonNode.get("availableParkingSpots").asInt(); - var isUnderMaintenance = workingHours.get("isUnderMaintenance").asBoolean(); + var totalSpots = jsonNode.get("totalStandardSpots").asInt(); + var freeSpots = jsonNode.get("availableStandardSpots").asInt(); + var isUnderMaintenance = jsonNode.get("isInMaintenance").asBoolean(); - LocalizedString note = toNote(jsonNode.get("price")); + LocalizedString note = toNote(jsonNode); VehicleParking.VehicleParkingEntranceCreator entrance = builder -> builder @@ -75,8 +74,8 @@ private static LocalizedString toNote(JsonNode price) { var startPriceAmount = price.get("startPriceAmount").floatValue(); var mainPriceAmount = price.get("mainPriceAmount").floatValue(); - var startPriceDurationHours = price.get("startPriceDurationHours").asInt(); - var mainPriceDurationHours = price.get("mainPriceDurationHours").asInt(); + var startPriceDurationHours = price.get("startPriceDuration").asInt(); + var mainPriceDurationHours = price.get("mainPriceDuration").asInt(); if (startPriceAmount == 0 && mainPriceAmount == 0) { return new LocalizedString("price.free"); 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 e97444439fa..26e40f4ec4a 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,6 @@ package org.opentripplanner.ext.vehicleparking.bikely; +import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; @@ -11,7 +12,7 @@ */ public record BikelyUpdaterParameters( String configRef, - String url, + URI url, String feedId, Duration frequency, HttpHeaders httpHeaders 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 1ac2dbf72e0..e808ad6905c 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 @@ -66,7 +66,7 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap ); case BIKELY -> new BikelyUpdaterParameters( updaterRef, - c.of("url").since(V2_3).summary("URL of the locations endpoint.").asString(null), + c.of("url").since(V2_3).summary("URL of the locations endpoint.").asUri(null), feedId, c .of("frequency") From 16d1a8f59e7a60d23e572dfe710f8b261045a97c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 27 Feb 2024 17:46:31 +0100 Subject: [PATCH 071/108] Regenerate docs --- docs/sandbox/VehicleParking.md | 2 +- .../ext/vehicleparking/bikely/BikelyUpdaterTest.java | 5 ++++- .../ext/vehicleparking/bikely/BikelyUpdater.java | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index a9b32770b95..4bfc2f6bd36 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -196,7 +196,7 @@ Tags to add to the parking lots. | [feedId](#u__4__feedId) | `string` | The name of the data source. | *Required* | | 2.2 | | frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.3 | | [sourceType](#u__4__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | -| url | `string` | URL of the locations endpoint. | *Optional* | | 2.3 | +| url | `uri` | URL of the locations endpoint. | *Optional* | | 2.3 | | [headers](#u__4__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java index e56b87e6631..569db85f33b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterTest.java @@ -58,7 +58,10 @@ void parseBikeBoxes() { assertEquals(10, capacity.getBicycleSpaces()); var freeParkingLots = parkingLots.get(2); - assertEquals("First 12 hour(s) is NOK0.00, afterwards NOK10.00 per 1 hour(s)", freeParkingLots.getNote().toString(Locale.ENGLISH)); + assertEquals( + "First 12 hour(s) is NOK0.00, afterwards NOK10.00 per 1 hour(s)", + freeParkingLots.getNote().toString(Locale.ENGLISH) + ); assertEquals(VehicleParkingState.OPERATIONAL, first.getState()); diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java index 8c451a4fd5c..c788e2314dc 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java @@ -34,8 +34,6 @@ public BikelyUpdater(BikelyUpdaterParameters parameters) { protected VehicleParking parseElement(JsonNode jsonNode) { var vehicleParkId = new FeedScopedId(feedId, jsonNode.get("id").asText()); - var workingHours = jsonNode.get("workingHours"); - var lat = jsonNode.get("latitude").asDouble(); var lng = jsonNode.get("longitude").asDouble(); var coord = new WgsCoordinate(lat, lng); From f4d3478362131d10cec579d371f588eed7d8ea19 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:54:14 +0000 Subject: [PATCH 072/108] Update google.dagger.version to v2.51 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4bf56201d45..48e03115c17 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 146 30.2 - 2.50 + 2.51 2.16.1 3.1.5 5.10.2 From 0ee0df01cf6cb1b3a851f1957a079b6d7b6d8d4a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 28 Feb 2024 10:00:43 +0100 Subject: [PATCH 073/108] Wait when opening dagger PRs [ci skip] --- renovate.json5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/renovate.json5 b/renovate.json5 index 04e0fd14471..e2a94318313 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -121,6 +121,13 @@ ], "automerge": true, "schedule": "after 11pm and before 5am every weekday" + }, + { + "description": "give some projects time to publish a changelog before opening the PR", + "matchPackagePrefixes": [ + "com.google.dagger:" + ], + "minimumReleaseAge": "1 week" } ], "timezone": "Europe/Berlin" From dd93de0c83943001060249516b172d93ecaa0b44 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 28 Feb 2024 11:44:41 +0000 Subject: [PATCH 074/108] Add changelog entry for #5698 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index d577d0c469f..fec6d606a4b 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -94,6 +94,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add scooter preferences [#5632](https://github.com/opentripplanner/OpenTripPlanner/pull/5632) - Add GroupStop layer to new debug frontend [#5666](https://github.com/opentripplanner/OpenTripPlanner/pull/5666) - Update to newest version of GTFS Flex location groups [#5655](https://github.com/opentripplanner/OpenTripPlanner/pull/5655) +- Use NeTEx authority short name if name is not present [#5698](https://github.com/opentripplanner/OpenTripPlanner/pull/5698) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From eee8618696c6cd11e2a05e770a1ae26e91fd1963 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 28 Feb 2024 13:05:15 +0100 Subject: [PATCH 075/108] Send POST request instead of GET --- .../vehicleparking/bikely/BikelyUpdater.java | 125 +++++++++++++----- .../framework/io/OtpHttpClient.java | 58 +++++--- 2 files changed, 133 insertions(+), 50 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java index c788e2314dc..5758d5d99e1 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdater.java @@ -3,69 +3,128 @@ import static org.opentripplanner.routing.vehicle_parking.VehicleParkingState.OPERATIONAL; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.Duration; +import java.util.ArrayList; import java.util.Currency; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.LocalizedString; import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.transit.model.basic.LocalizedMoney; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.updater.spi.GenericJsonDataSource; +import org.opentripplanner.updater.spi.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Vehicle parking updater class for the Norwegian bike box provider Bikely: * https://www.safebikely.com/ */ -public class BikelyUpdater extends GenericJsonDataSource { +public class BikelyUpdater implements DataSource { + + private static final Logger LOG = LoggerFactory.getLogger(BikelyUpdater.class); private static final String JSON_PARSE_PATH = "result"; private static final Currency NOK = Currency.getInstance("NOK"); - private final String feedId; + private static final ObjectMapper OBJECT_MAPPER = ObjectMappers.ignoringExtraFields(); + private static final ObjectNode POST_PARAMS = OBJECT_MAPPER + .createObjectNode() + .put("groupPins", true) + .put("lonMin", 0) + .put("lonMax", 0) + .put("latMin", 0) + .put("latMax", 0); + private final OtpHttpClient httpClient = new OtpHttpClient(); + private final BikelyUpdaterParameters parameters; + private List lots; public BikelyUpdater(BikelyUpdaterParameters parameters) { - super(parameters.url().toString(), JSON_PARSE_PATH, parameters.httpHeaders()); - this.feedId = parameters.feedId(); + this.parameters = parameters; } @Override - protected VehicleParking parseElement(JsonNode jsonNode) { - var vehicleParkId = new FeedScopedId(feedId, jsonNode.get("id").asText()); + public boolean update() { + this.lots = + httpClient.postJsonAndMap( + parameters.url(), + POST_PARAMS, + Duration.ofSeconds(30), + parameters.httpHeaders().asMap(), + is -> { + try { + var lots = new ArrayList(); + OBJECT_MAPPER + .readTree(is) + .path(JSON_PARSE_PATH) + .forEach(node -> lots.add(parseElement(node))); + + return lots.stream().filter(Objects::nonNull).toList(); + } catch (Exception e) { + LOG.error("Could not get Bikely updates", e); + } + + return List.of(); + } + ); + + return true; + } - var lat = jsonNode.get("latitude").asDouble(); - var lng = jsonNode.get("longitude").asDouble(); - var coord = new WgsCoordinate(lat, lng); + @Override + public List getUpdates() { + return List.copyOf(lots); + } - var name = new NonLocalizedString(jsonNode.path("name").asText()); + @Nullable + private VehicleParking parseElement(JsonNode jsonNode) { + if (jsonNode.path("hasStandardParking").asBoolean()) { + var vehicleParkId = new FeedScopedId(parameters.feedId(), jsonNode.get("id").asText()); - var totalSpots = jsonNode.get("totalStandardSpots").asInt(); - var freeSpots = jsonNode.get("availableStandardSpots").asInt(); - var isUnderMaintenance = jsonNode.get("isInMaintenance").asBoolean(); + var lat = jsonNode.get("latitude").asDouble(); + var lng = jsonNode.get("longitude").asDouble(); + var coord = new WgsCoordinate(lat, lng); - LocalizedString note = toNote(jsonNode); + var name = new NonLocalizedString(jsonNode.path("name").asText()); - VehicleParking.VehicleParkingEntranceCreator entrance = builder -> - builder - .entranceId(new FeedScopedId(feedId, vehicleParkId.getId() + "/entrance")) + var totalSpots = jsonNode.get("totalStandardSpots").asInt(); + var freeSpots = jsonNode.get("availableStandardSpots").asInt(); + var isUnderMaintenance = jsonNode.get("isInMaintenance").asBoolean(); + + LocalizedString note = toNote(jsonNode); + + VehicleParking.VehicleParkingEntranceCreator entrance = builder -> + builder + .entranceId(new FeedScopedId(parameters.feedId(), vehicleParkId.getId() + "/entrance")) + .name(name) + .coordinate(coord) + .walkAccessible(true) + .carAccessible(false); + + return VehicleParking + .builder() + .id(vehicleParkId) .name(name) + .bicyclePlaces(true) + .capacity(VehicleParkingSpaces.builder().bicycleSpaces(totalSpots).build()) + .availability(VehicleParkingSpaces.builder().bicycleSpaces(freeSpots).build()) + .state(toState(isUnderMaintenance)) .coordinate(coord) - .walkAccessible(true) - .carAccessible(false); - - return VehicleParking - .builder() - .id(vehicleParkId) - .name(name) - .bicyclePlaces(true) - .capacity(VehicleParkingSpaces.builder().bicycleSpaces(totalSpots).build()) - .availability(VehicleParkingSpaces.builder().bicycleSpaces(freeSpots).build()) - .state(toState(isUnderMaintenance)) - .coordinate(coord) - .entrance(entrance) - .note(note) - .build(); + .entrance(entrance) + .note(note) + .build(); + } else { + return null; + } } private static LocalizedString toNote(JsonNode price) { diff --git a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java index 0afe431e2c9..d53a1058249 100644 --- a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java +++ b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java @@ -27,6 +27,7 @@ import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.SocketConfig; @@ -264,23 +265,20 @@ public T getAndMap( Map headers, ResponseMapper contentMapper ) { - URL downloadUrl; - try { - downloadUrl = uri.toURL(); - } catch (MalformedURLException e) { - throw new OtpHttpClientException(e); - } - String proto = downloadUrl.getProtocol(); - if (proto.equals("http") || proto.equals("https")) { - return executeAndMap(new HttpGet(uri), timeout, headers, contentMapper); - } else { - // Local file probably, try standard java - try (InputStream is = downloadUrl.openStream()) { - return contentMapper.apply(is); - } catch (Exception e) { - throw new OtpHttpClientException(e); - } - } + return sendAndMap(new HttpGet(uri), uri, timeout, headers, contentMapper); + } + + public T postJsonAndMap( + URI uri, + JsonNode jsonBody, + Duration timeout, + Map headers, + ResponseMapper contentMapper + ) { + var request = new HttpPost(uri); + request.setEntity(new StringEntity(jsonBody.toString())); + request.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON); + return sendAndMap(request, uri, timeout, headers, contentMapper); } /** @@ -400,6 +398,32 @@ protected T executeAndMapWithResponseHandler( } } + private T sendAndMap( + HttpUriRequestBase request, + URI uri, + Duration timeout, + Map headers, + ResponseMapper contentMapper + ) { + URL downloadUrl; + try { + downloadUrl = uri.toURL(); + } catch (MalformedURLException e) { + throw new OtpHttpClientException(e); + } + String proto = downloadUrl.getProtocol(); + if (proto.equals("http") || proto.equals("https")) { + return executeAndMap(request, timeout, headers, contentMapper); + } else { + // Local file probably, try standard java + try (InputStream is = downloadUrl.openStream()) { + return contentMapper.apply(is); + } catch (Exception e) { + throw new OtpHttpClientException(e); + } + } + } + /** * Configures the request with a custom timeout. */ From 78d916941229cc313452f5b77aba06602a50c2cb Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Wed, 28 Feb 2024 15:48:54 +0100 Subject: [PATCH 076/108] docs: Add info about maintainer --- .../openstreetmap/tagmapping/HamburgMapper.java | 2 ++ .../openstreetmap/tagmapping/HamburgMapperTest.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java index 5bcd4d23380..1c8b68d622d 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java @@ -8,6 +8,8 @@ * @see GermanyMapper * @see OsmTagMapper * @see DefaultMapper + * + * @author Maintained by HBT (geofox-team@hbt.de) */ public class HamburgMapper extends GermanyMapper { diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java index 8e3dc9afc77..1af3eefee18 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapperTest.java @@ -9,6 +9,9 @@ import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.openstreetmap.model.OSMWithTags; +/** + * @author Maintained by HBT (geofox-team@hbt.de) + */ public class HamburgMapperTest { private HamburgMapper mapper; From b38259f95dc67aaad141acfb03a5a223c4de1246 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Wed, 28 Feb 2024 15:52:17 +0100 Subject: [PATCH 077/108] style: Fixed formatting --- .../opentripplanner/openstreetmap/tagmapping/HamburgMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java index 1c8b68d622d..f893fbb5519 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/HamburgMapper.java @@ -8,7 +8,7 @@ * @see GermanyMapper * @see OsmTagMapper * @see DefaultMapper - * + * * @author Maintained by HBT (geofox-team@hbt.de) */ public class HamburgMapper extends GermanyMapper { From 6302dc42ebd18684680d2f8cbe9b6300fa0f8836 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 28 Feb 2024 15:09:16 +0000 Subject: [PATCH 078/108] Add changelog entry for #5701 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index fec6d606a4b..27e70712e4c 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -95,6 +95,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add GroupStop layer to new debug frontend [#5666](https://github.com/opentripplanner/OpenTripPlanner/pull/5666) - Update to newest version of GTFS Flex location groups [#5655](https://github.com/opentripplanner/OpenTripPlanner/pull/5655) - Use NeTEx authority short name if name is not present [#5698](https://github.com/opentripplanner/OpenTripPlanner/pull/5698) +- Add Hamburg OSM mapper [#5701](https://github.com/opentripplanner/OpenTripPlanner/pull/5701) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 4ead620f1146a91cc984c40b56b1ef323e535186 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 28 Feb 2024 16:55:55 +0100 Subject: [PATCH 079/108] Add Javadoc --- .../java/org/opentripplanner/framework/io/OtpHttpClient.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java index d53a1058249..72b67441a18 100644 --- a/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java +++ b/src/main/java/org/opentripplanner/framework/io/OtpHttpClient.java @@ -268,6 +268,10 @@ public T getAndMap( return sendAndMap(new HttpGet(uri), uri, timeout, headers, contentMapper); } + /** + * Send an HTTP POST request with Content-Type: application/json. The body of the request + * is defined by {@code jsonBody}. + */ public T postJsonAndMap( URI uri, JsonNode jsonBody, From 36429c103f78fe1c32ecf22c21f08424a923ca84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Erik=20St=C3=B8wer?= Date: Thu, 29 Feb 2024 10:37:20 +0100 Subject: [PATCH 080/108] Satisfy peer dependency --- client-next/package-lock.json | 156 ++++++++++++++++++++-------------- client-next/package.json | 2 +- 2 files changed, 95 insertions(+), 63 deletions(-) diff --git a/client-next/package-lock.json b/client-next/package-lock.json index 247161948d4..7f01598ad30 100644 --- a/client-next/package-lock.json +++ b/client-next/package-lock.json @@ -26,11 +26,11 @@ "@testing-library/react": "14.1.2", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", - "@typescript-eslint/eslint-plugin": "6.5.0", - "@typescript-eslint/parser": "6.5.0", + "@typescript-eslint/eslint-plugin": "7.1.0", + "@typescript-eslint/parser": "7.1.0", "@vitejs/plugin-react": "4.0.4", "@vitest/coverage-v8": "1.1.3", - "eslint": "8.48.0", + "eslint": "8.56.0", "eslint-config-prettier": "9.0.0", "eslint-plugin-import": "2.28.1", "eslint-plugin-jsx-a11y": "6.7.1", @@ -1656,9 +1656,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", - "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3584,9 +3584,9 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", - "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/supercluster": { @@ -3612,16 +3612,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", - "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", + "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/type-utils": "6.5.0", - "@typescript-eslint/utils": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/type-utils": "7.1.0", + "@typescript-eslint/utils": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3637,8 +3637,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3680,15 +3680,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", - "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", + "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/typescript-estree": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/typescript-estree": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4" }, "engines": { @@ -3699,7 +3699,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3708,13 +3708,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", - "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", + "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0" + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3725,13 +3725,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", - "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", + "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.5.0", - "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/typescript-estree": "7.1.0", + "@typescript-eslint/utils": "7.1.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3743,7 +3743,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3752,9 +3752,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", - "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", + "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3765,16 +3765,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", - "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", + "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -3791,6 +3792,15 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3803,6 +3813,21 @@ "node": ">=10" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -3825,17 +3850,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", + "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/typescript-estree": "7.1.0", "semver": "^7.5.4" }, "engines": { @@ -3846,7 +3871,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { @@ -3883,12 +3908,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", - "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", + "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/types": "7.1.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3899,6 +3924,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vitejs/plugin-react": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", @@ -5721,18 +5752,19 @@ } }, "node_modules/eslint": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", - "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.48.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", diff --git a/client-next/package.json b/client-next/package.json index d0461a508b2..ad3f1fa10a5 100644 --- a/client-next/package.json +++ b/client-next/package.json @@ -39,7 +39,7 @@ "@typescript-eslint/parser": "7.1.0", "@vitejs/plugin-react": "4.0.4", "@vitest/coverage-v8": "1.1.3", - "eslint": "8.48.0", + "eslint": "8.56.0", "eslint-config-prettier": "9.0.0", "eslint-plugin-import": "2.28.1", "eslint-plugin-jsx-a11y": "6.7.1", From 5f2323867b01c45c7d163a18ba7087a68d225d64 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Thu, 29 Feb 2024 09:50:54 +0000 Subject: [PATCH 081/108] Upgrade debug client to version 2024/02/2024-02-29T09:50 --- src/client/debug-client-preview/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/debug-client-preview/index.html b/src/client/debug-client-preview/index.html index cbc31d4b96d..a7b15bef13b 100644 --- a/src/client/debug-client-preview/index.html +++ b/src/client/debug-client-preview/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +

From 6a0298535c51a6d626b0e4c81d7fb6efa8510ed8 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 29 Feb 2024 13:23:09 +0000 Subject: [PATCH 082/108] Add changelog entry for #5657 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 27e70712e4c..0b6134e4939 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -96,6 +96,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Update to newest version of GTFS Flex location groups [#5655](https://github.com/opentripplanner/OpenTripPlanner/pull/5655) - Use NeTEx authority short name if name is not present [#5698](https://github.com/opentripplanner/OpenTripPlanner/pull/5698) - Add Hamburg OSM mapper [#5701](https://github.com/opentripplanner/OpenTripPlanner/pull/5701) +- Remove configurable car speed and determine it in graph build [#5657](https://github.com/opentripplanner/OpenTripPlanner/pull/5657) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 6472249a9f7d1aa799ecffab61eb252ba50168b5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 29 Feb 2024 16:21:51 +0100 Subject: [PATCH 083/108] Apply review feedback --- src/main/java/org/opentripplanner/datastore/OtpDataStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java index 937b4fb8203..0c2e9a6608c 100644 --- a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java +++ b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java @@ -108,7 +108,7 @@ public void open() { if (config.stopConsolidation() != null) { stopConsolidation = - findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), CONFIG)); + findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), GTFS)); } addAll(Arrays.asList(streetGraph, graph, buildReportDir)); From 0e781bfcee4a17ff8025f167b37fd81f87866d15 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 29 Feb 2024 17:15:31 +0100 Subject: [PATCH 084/108] fix: Make access/egress time-penalty decorators in Raptor work with reverse search --- .../model/AbstractAccessEgressDecorator.java | 15 ----- .../raptor/path/PathBuilderLeg.java | 18 ++++-- .../rangeraptor/DefaultRangeRaptorWorker.java | 12 ++-- .../rangeraptor/context/SearchContext.java | 2 +- .../rangeraptor/transit/AccessPaths.java | 42 ++++++++++-- .../transit/AccessWithPenalty.java | 4 +- .../transit/EgressWithPenalty.java | 4 +- .../rangeraptor/transit/AccessPathsTest.java | 64 +++++++++++++++++-- .../transit/AccessWithPenaltyTest.java | 8 +-- .../transit/EgressWithPenaltyTest.java | 8 +-- 10 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java index 35af442576c..ddb266e0884 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/AbstractAccessEgressDecorator.java @@ -122,19 +122,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(delegate); } - - /** - * Use this method to remove a decorator of the given type. - */ - public static RaptorAccessEgress removeRaptorDecoratorIfItExist( - RaptorAccessEgress path, - Class decoratorClazz - ) { - if (path == null) { - return null; - } - return path.getClass() == decoratorClazz - ? ((AbstractAccessEgressDecorator) path).delegate() - : path; - } } diff --git a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java index 4c919ca9d7a..335d38203b1 100644 --- a/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/raptor/path/PathBuilderLeg.java @@ -353,9 +353,6 @@ AccessPathLeg createAccessPathLeg( PathLeg nextLeg = next.createPathLeg(costCalculator, slackProvider); var accessPath = asAccessLeg().streetPath; int cost = cost(costCalculator, accessPath); - - accessPath = AccessWithPenalty.removeDecoratorIfItExist(accessPath); - return new AccessPathLeg<>(accessPath, fromTime, toTime, cost, nextLeg); } @@ -453,9 +450,6 @@ private EgressPathLeg createEgressPathLeg( ) { int cost = egressCost(costCalculator, slackProvider); var egressPath = asEgressLeg().streetPath; - - egressPath = EgressWithPenalty.removeDecoratorIfItExist(egressPath); - return new EgressPathLeg<>(egressPath, fromTime, toTime, cost); } @@ -703,13 +697,23 @@ private abstract static class MyStreetLeg extends AbstractMyLeg { final RaptorAccessEgress streetPath; MyStreetLeg(RaptorAccessEgress streetPath) { - this.streetPath = streetPath; + this.streetPath = removeTimePenaltyDecorator(streetPath); } @Override public boolean hasRides() { return streetPath.hasRides(); } + + private static RaptorAccessEgress removeTimePenaltyDecorator(RaptorAccessEgress path) { + if (path instanceof AccessWithPenalty awp) { + return awp.removeDecorator(); + } + if (path instanceof EgressWithPenalty awp) { + return awp.removeDecorator(); + } + return path; + } } private static class MyAccessLeg extends MyStreetLeg { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java index 63cc4e2756c..0f2ceb9d6e4 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/DefaultRangeRaptorWorker.java @@ -140,11 +140,13 @@ public RaptorWorkerResult route() { } // Iterate over virtual departure times - this is needed to allow access with a time-penalty - // which falls outside the search-window due to the penalty to be added to the result. - final IntIterator as = accessPaths.iterateOverPathsWithPenalty(iterationDepartureTime); - while (as.hasNext()) { - setupIteration(as.next()); - runRaptorForMinute(); + // which falls outside the search-window due to the added time-penalty. + if (!calculator.oneIterationOnly()) { + final IntIterator as = accessPaths.iterateOverPathsWithPenalty(iterationDepartureTime); + while (as.hasNext()) { + setupIteration(as.next()); + runRaptorForMinute(); + } } }); return state.results(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java index 65e0246290c..518d02ae3ad 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/context/SearchContext.java @@ -318,7 +318,7 @@ private static AccessPaths accessPaths(int iterationStep, RaptorRequest reque boolean forward = request.searchDirection().isForward(); var params = request.searchParams(); var paths = forward ? params.accessPaths() : params.egressPaths(); - return AccessPaths.create(iterationStep, paths, request.profile()); + return AccessPaths.create(iterationStep, paths, request.profile(), request.searchDirection()); } private static EgressPaths egressPaths(RaptorRequest request) { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 434c2e3c611..26fb49ec88a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -8,26 +8,40 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.IntUnaryOperator; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; +import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.request.RaptorProfile; import org.opentripplanner.raptor.spi.IntIterator; import org.opentripplanner.raptor.util.IntIterators; +/** + * This class is responsible for performing Raptor-specific functionality on access-paths. It + * groups paths based on number-of-trips(FLEX mainly) and stop-arrival "mode" (on-board or on-foot). + * This is used to insert the access into the Raptor rounds at the correct moment (round), so + * the number-of-transfers criteria become correct. + *

+ * This class also provides an iterator to iterate over iteration steps in the Raptor algorithm + * to cover extra minutes outside the search-window for access with a time-penalty. + */ public class AccessPaths { private final int iterationStep; private final int maxTimePenalty; + private final IntUnaryOperator iterationOp; private final TIntObjectMap> arrivedOnStreetByNumOfRides; private final TIntObjectMap> arrivedOnBoardByNumOfRides; private int iterationTimePenaltyLimit = RaptorConstants.TIME_NOT_SET; private AccessPaths( int iterationStep, + IntUnaryOperator iterationOp, TIntObjectMap> arrivedOnStreetByNumOfRides, TIntObjectMap> arrivedOnBoardByNumOfRides ) { this.iterationStep = iterationStep; + this.iterationOp = iterationOp; this.arrivedOnStreetByNumOfRides = arrivedOnStreetByNumOfRides; this.arrivedOnBoardByNumOfRides = arrivedOnBoardByNumOfRides; this.maxTimePenalty = @@ -50,7 +64,8 @@ private AccessPaths( public static AccessPaths create( int iterationStep, Collection paths, - RaptorProfile profile + RaptorProfile profile, + SearchDirection searchDirection ) { if (profile.is(RaptorProfile.MULTI_CRITERIA)) { paths = removeNonOptimalPathsForMcRaptor(paths); @@ -62,6 +77,7 @@ public static AccessPaths create( return new AccessPaths( iterationStep, + iterationOp(searchDirection), groupByRound(paths, RaptorAccessEgress::stopReachedByWalking), groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard) ); @@ -100,9 +116,9 @@ public IntIterator iterateOverPathsWithPenalty(final int earliestDepartureTime) } // In the first iteration, we want the time-limit to be zero and the raptor-iteration-time // to be one step before the earliest-departure-time in the search-window. This will include - // all access with a penalty in the first iteration. Then + // all access with a penalty in the first iteration. Then: this.iterationTimePenaltyLimit = -iterationStep; - final int raptorIterationStartTime = earliestDepartureTime - iterationStep; + final int raptorIterationStartTime = earliestDepartureTime - signedIterationStep(iterationStep); return new IntIterator() { @Override @@ -113,7 +129,9 @@ public boolean hasNext() { @Override public int next() { AccessPaths.this.iterationTimePenaltyLimit += iterationStep; - return raptorIterationStartTime - AccessPaths.this.iterationTimePenaltyLimit; + return ( + raptorIterationStartTime - signedIterationStep(AccessPaths.this.iterationTimePenaltyLimit) + ); } }; } @@ -176,4 +194,20 @@ private List filterOnTimePenaltyLimitIfExist(List step : (int step) -> -step; + } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java index 8c65270cbb4..e56b0b6b85e 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenalty.java @@ -40,7 +40,7 @@ public int earliestDepartureTime(int requestedDepartureTime) { * decorator and returns the original access path if decorated. If not, the given path is * returned. */ - public static RaptorAccessEgress removeDecoratorIfItExist(RaptorAccessEgress path) { - return removeRaptorDecoratorIfItExist(path, AccessWithPenalty.class); + public RaptorAccessEgress removeDecorator() { + return delegate(); } } diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java index 6bb7e7bf2c5..0f973d058d7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenalty.java @@ -35,7 +35,7 @@ public int latestArrivalTime(int requestedArrivalTime) { * decorator and returns the original access path if decorated. If not, the given path is * returned. */ - public static RaptorAccessEgress removeDecoratorIfItExist(RaptorAccessEgress path) { - return removeRaptorDecoratorIfItExist(path, EgressWithPenalty.class); + public RaptorAccessEgress removeDecorator() { + return delegate(); } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java index 0371de45bac..bf8e06a73d7 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPathsTest.java @@ -6,6 +6,8 @@ import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flex; import static org.opentripplanner.raptor._data.transit.TestAccessEgress.flexAndWalk; import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor.api.model.SearchDirection.FORWARD; +import static org.opentripplanner.raptor.api.model.SearchDirection.REVERSE; import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA; import static org.opentripplanner.raptor.api.request.RaptorProfile.STANDARD; @@ -107,7 +109,8 @@ void iterateOverPathsWithPenalty() { FLEX_B, FLEX_WALK_B ), - MULTI_CRITERIA + MULTI_CRITERIA, + FORWARD ); var iterator = accessPaths.iterateOverPathsWithPenalty(600); @@ -142,12 +145,64 @@ void iterateOverPathsWithPenalty() { assertFalse(iterator.hasNext()); } + @Test + void iterateOverPathsWithPenaltyInReversDirection() { + // Expected at departure 540 + var flexFastWithPenalty = FLEX_FAST.withTimePenalty(60); + + // Expected at departure 540 and 480 + var flexTxWithPenalty = FLEX_TX2.withTimePenalty(61); + + // Expected at departure 540, 480 and 420 + var walkFastWithPenalty = WALK_FAST.withTimePenalty(121); + + // Without time-penalty, the iterator should be empty + var accessPaths = AccessPaths.create( + 60, + List.of(flexFastWithPenalty, flexTxWithPenalty, walkFastWithPenalty, WALK_B, FLEX_B), + STANDARD, + REVERSE + ); + + var iterator = accessPaths.iterateOverPathsWithPenalty(600); + + // First iteration + assertTrue(iterator.hasNext()); + assertEquals(660, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(1)); + expect(accessPaths.arrivedOnStreetByNumOfRides(1)); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), flexTxWithPenalty); + expect(accessPaths.arrivedOnStreetByNumOfRides(2)); + expect(accessPaths.arrivedOnBoardByNumOfRides(3), flexFastWithPenalty); + expect(accessPaths.arrivedOnStreetByNumOfRides(3)); + expect(accessPaths.arrivedOnBoardByNumOfRides(4)); + expect(accessPaths.arrivedOnStreetByNumOfRides(4)); + + // Second iteration + assertTrue(iterator.hasNext()); + assertEquals(720, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(2), flexTxWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(3)); + + // Third iteration + assertTrue(iterator.hasNext()); + assertEquals(780, iterator.next()); + expect(accessPaths.arrivedOnStreetByNumOfRides(0), walkFastWithPenalty); + expect(accessPaths.arrivedOnBoardByNumOfRides(2)); + expect(accessPaths.arrivedOnBoardByNumOfRides(3)); + + assertFalse(iterator.hasNext()); + } + @Test void hasTimeDependentAccess() { var accessPaths = AccessPaths.create( 60, List.of(WALK_FAST, walk(STOP_A, 20).openingHours(1200, 2400)), - STANDARD + STANDARD, + FORWARD ); assertTrue(accessPaths.hasTimeDependentAccess(), "Time dependent access is better."); @@ -155,7 +210,8 @@ void hasTimeDependentAccess() { AccessPaths.create( 60, List.of(WALK_FAST, walk(STOP_A, 50).openingHours(1200, 2400)), - STANDARD + STANDARD, + REVERSE ); assertFalse(accessPaths.hasTimeDependentAccess(), "Time dependent access is worse."); } @@ -196,6 +252,6 @@ private static AccessPaths create(RaptorProfile profile) { FLEX_BAD, FLEX_B ); - return AccessPaths.create(60, accessPaths, profile); + return AccessPaths.create(60, accessPaths, profile, FORWARD); } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java index cedc01e42a1..bead5d607c6 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/AccessWithPenaltyTest.java @@ -7,7 +7,6 @@ import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; import org.junit.jupiter.api.Test; -import org.opentripplanner.raptor.api.model.RaptorAccessEgress; class AccessWithPenaltyTest { @@ -70,10 +69,7 @@ void earliestDepartureTimeWhenServiceIsClosed() { @Test void removeDecoratorIfItExist() { var original = walk(STOP, DURATION).withTimePenalty(PENALTY); - RaptorAccessEgress subject = new AccessWithPenalty(original); - - assertSame(original, AccessWithPenalty.removeDecoratorIfItExist(subject)); - assertSame(original, AccessWithPenalty.removeDecoratorIfItExist(original)); - assertSame(null, AccessWithPenalty.removeDecoratorIfItExist(null)); + var subject = new AccessWithPenalty(original); + assertSame(original, subject.removeDecorator()); } } diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java index acc7b642ddc..7277876dec6 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/transit/EgressWithPenaltyTest.java @@ -7,7 +7,6 @@ import static org.opentripplanner.raptor.api.model.RaptorConstants.TIME_NOT_SET; import org.junit.jupiter.api.Test; -import org.opentripplanner.raptor.api.model.RaptorAccessEgress; class EgressWithPenaltyTest { @@ -73,10 +72,7 @@ void latestArrivalTimeWhenServiceIsClosed() { @Test void removeDecoratorIfItExist() { var original = walk(STOP, DURATION).withTimePenalty(PENALTY); - RaptorAccessEgress subject = new EgressWithPenalty(original); - - assertSame(original, EgressWithPenalty.removeDecoratorIfItExist(subject)); - assertSame(original, EgressWithPenalty.removeDecoratorIfItExist(original)); - assertSame(null, EgressWithPenalty.removeDecoratorIfItExist(null)); + var subject = new EgressWithPenalty(original); + assertSame(original, subject.removeDecorator()); } } From 49096b1a82f5f7b0ec1394a4ddc6fd9b71705835 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 29 Feb 2024 17:44:45 +0100 Subject: [PATCH 085/108] fix: Include time-penalty in the startTime/endTime/duration in path criteria comparison --- .../raptor/api/path/RaptorPath.java | 43 +++++++++++++++---- .../org/opentripplanner/raptor/path/Path.java | 29 +++++++++++-- .../path/PathParetoSetComparators.java | 29 ++++++------- .../raptor/spi/UnknownPath.java | 10 +++++ .../raptor/_data/api/TestRaptorPath.java | 19 ++++++-- .../raptor/api/path/RaptorPathTest.java | 9 ++-- .../moduletests/support/ExpectedList.java | 16 +++---- 7 files changed, 112 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java index ca67599e262..b92d30643ec 100644 --- a/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java +++ b/src/main/java/org/opentripplanner/raptor/api/path/RaptorPath.java @@ -25,15 +25,37 @@ public interface RaptorPath extends Comparable - * {@code null} if the legs in the path is unknown. + * {@code null} if the legs in the path are unknown. */ @Nullable EgressPathLeg egressLeg(); @@ -89,7 +111,7 @@ default boolean isC2Set() { List listStops(); /** - * Aggregated wait-time in seconds. This method compute the total wait time for this path. + * Aggregated wait-time in seconds. This method computes the total wait time for this path. */ int waitTime(); @@ -125,11 +147,11 @@ default boolean isC2Set() { */ @Override default int compareTo(RaptorPath other) { - int c = endTime() - other.endTime(); + int c = endTimeInclusivePenalty() - other.endTimeInclusivePenalty(); if (c != 0) { return c; } - c = other.startTime() - startTime(); + c = other.startTimeInclusivePenalty() - startTimeInclusivePenalty(); if (c != 0) { return c; } @@ -151,14 +173,14 @@ static boolean compareArrivalTime( RaptorPath l, RaptorPath r ) { - return l.endTime() < r.endTime(); + return l.endTimeInclusivePenalty() < r.endTimeInclusivePenalty(); } static boolean compareDepartureTime( RaptorPath l, RaptorPath r ) { - return l.startTime() > r.startTime(); + return l.startTimeInclusivePenalty() > r.startTimeInclusivePenalty(); } static boolean compareIterationDepartureTime( @@ -168,8 +190,11 @@ static boolean compareIterationDepartureTime( return l.rangeRaptorIterationDepartureTime() > r.rangeRaptorIterationDepartureTime(); } - static boolean compareDuration(RaptorPath l, RaptorPath r) { - return l.durationInSeconds() < r.durationInSeconds(); + static boolean compareDurationInclusivePenalty( + RaptorPath l, + RaptorPath r + ) { + return l.durationInclusivePenaltyInSeconds() < r.durationInclusivePenaltyInSeconds(); } static boolean compareNumberOfTransfers( diff --git a/src/main/java/org/opentripplanner/raptor/path/Path.java b/src/main/java/org/opentripplanner/raptor/path/Path.java index a4d7770dd64..15fcf7b34e7 100644 --- a/src/main/java/org/opentripplanner/raptor/path/Path.java +++ b/src/main/java/org/opentripplanner/raptor/path/Path.java @@ -19,7 +19,15 @@ import org.opentripplanner.raptor.api.path.TransitPathLeg; /** - * The result path of a Raptor search describing the one possible journey. + * The result of a Raptor search is a path describing the one possible journey. The path is then + * main DTO part of the Raptor result, but it is also used internally in Raptor. Hence, it is a bit + * more complex, and it has more responsiblilites than it should. + *

+ * To improve the design, Raptor should not use the path internally. Instead, there should + * be a special destination arrival that could take over the Raptor responsibilities. The + * path would still need to be constructed at the time of arrival and then become a part of the + * destination arrival. The reason for this is that the data necessary to create a path is not + * kept in the Raptor state between rounds. * * @param The TripSchedule type defined by the user of the raptor API. */ @@ -27,7 +35,9 @@ public class Path implements RaptorPath { private final int iterationDepartureTime; private final int startTime; + private final int startTimeInclusivePenalty; private final int endTime; + private final int endTimeInclusivePenalty; private final int numberOfTransfers; private final int c1; private final int c2; @@ -44,7 +54,9 @@ private Path( ) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = startTime; + this.startTimeInclusivePenalty = startTime; this.endTime = endTime; + this.endTimeInclusivePenalty = endTime; this.numberOfTransfers = numberOfTransfers; this.c1 = c1; this.accessLeg = null; @@ -55,11 +67,17 @@ private Path( public Path(int iterationDepartureTime, AccessPathLeg accessLeg, int c1, int c2) { this.iterationDepartureTime = iterationDepartureTime; this.startTime = accessLeg.fromTime(); + var access = accessLeg.access(); + this.startTimeInclusivePenalty = + access.hasTimePenalty() ? startTime - access.timePenalty() : startTime; this.c1 = c1; this.accessLeg = accessLeg; this.egressLeg = findEgressLeg(accessLeg); this.numberOfTransfers = countNumberOfTransfers(accessLeg, egressLeg); this.endTime = egressLeg.toTime(); + var egress = egressLeg.egress(); + this.endTimeInclusivePenalty = + egress.hasTimePenalty() ? endTime + egress.timePenalty() : endTime; this.c2 = c2; } @@ -101,14 +119,19 @@ public final int startTime() { return startTime; } + @Override + public int startTimeInclusivePenalty() { + return startTimeInclusivePenalty; + } + @Override public final int endTime() { return endTime; } @Override - public final int durationInSeconds() { - return endTime - startTime; + public int endTimeInclusivePenalty() { + return endTimeInclusivePenalty; } @Override diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java index 00ea6d11fe5..192f957ae8f 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/path/PathParetoSetComparators.java @@ -3,7 +3,7 @@ import static org.opentripplanner.raptor.api.path.RaptorPath.compareArrivalTime; import static org.opentripplanner.raptor.api.path.RaptorPath.compareC1; import static org.opentripplanner.raptor.api.path.RaptorPath.compareDepartureTime; -import static org.opentripplanner.raptor.api.path.RaptorPath.compareDuration; +import static org.opentripplanner.raptor.api.path.RaptorPath.compareDurationInclusivePenalty; import static org.opentripplanner.raptor.api.path.RaptorPath.compareIterationDepartureTime; import static org.opentripplanner.raptor.api.path.RaptorPath.compareNumberOfTransfers; @@ -12,7 +12,6 @@ import org.opentripplanner.raptor.api.model.DominanceFunction; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.model.RelaxFunction; -import org.opentripplanner.raptor.api.model.SearchDirection; import org.opentripplanner.raptor.api.path.RaptorPath; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetCost; import org.opentripplanner.raptor.rangeraptor.internalapi.ParetoSetTime; @@ -37,7 +36,7 @@ *

  • Relax c1, if c2 is optimal
  • * * The {@code travelDuration} is added as a criteria to the pareto comparator in addition to the - * parameters used for each stop arrivals. The {@code travelDuration} is only needed at the + * parameters used for each stop-arrival. The {@code travelDuration} is only needed at the * destination, because Range Raptor works in iterations backwards in time. */ public class PathParetoSetComparators { @@ -123,7 +122,7 @@ > ParetoComparator> comparatorTimetableAndC1() { compareIterationDepartureTime(l, r) || compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r); } @@ -136,7 +135,7 @@ > ParetoComparator> comparatorTimetableAndRelaxedC1( compareIterationDepartureTime(l, r) || compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(relaxCost, l, r); } @@ -146,7 +145,7 @@ > ParetoComparator> comparatorArrivalTimeAndC1() { return (l, r) -> compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r); } @@ -156,7 +155,7 @@ > ParetoComparator> comparatorDepartureTimeAndC1() { return (l, r) -> compareDepartureTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r); } @@ -168,7 +167,7 @@ > ParetoComparator> comparatorArrivalTimeAndRelaxedC1( return (l, r) -> compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(relaxCost, l, r); } @@ -180,7 +179,7 @@ > ParetoComparator> comparatorDepartureTimeAndRelaxedC1( return (l, r) -> compareDepartureTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(relaxCost, l, r); } @@ -193,7 +192,7 @@ > ParetoComparator> comparatorTimetableAndC1AndC2( compareIterationDepartureTime(l, r) || compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r) || c2Comp.leftDominateRight(l.c2(), r.c2()); } @@ -208,7 +207,7 @@ > ParetoComparator> comparatorTimetableAndRelaxedC1IfC2IsOptimal( compareIterationDepartureTime(l, r) || compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp); } @@ -218,7 +217,7 @@ > ParetoComparator> comparatorWithC1AndC2(@Nonnull DominanceFuncti return (l, r) -> compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r) || c2Comp.leftDominateRight(l.c2(), r.c2()); } @@ -231,7 +230,7 @@ > ParetoComparator> comparatorDepartureTimeAndC1AndC2( return (l, r) -> compareDepartureTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1(l, r) || c2Comp.leftDominateRight(l.c2(), r.c2()); } @@ -245,7 +244,7 @@ > ParetoComparator> comparatorArrivalTimeAndRelaxedC1IfC2IsOptimal return (l, r) -> compareArrivalTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp); } @@ -258,7 +257,7 @@ > ParetoComparator> comparatorDepartureTimeAndRelaxedC1IfC2IsOptim return (l, r) -> compareDepartureTime(l, r) || compareNumberOfTransfers(l, r) || - compareDuration(l, r) || + compareDurationInclusivePenalty(l, r) || compareC1RelaxedIfC2IsOptimal(l, r, relaxCost, c2Comp); } diff --git a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java index de6a846f480..24eae14f997 100644 --- a/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java +++ b/src/main/java/org/opentripplanner/raptor/spi/UnknownPath.java @@ -46,11 +46,21 @@ public int startTime() { return departureTime; } + @Override + public int startTimeInclusivePenalty() { + return departureTime; + } + @Override public int endTime() { return arrivalTime; } + @Override + public int endTimeInclusivePenalty() { + return arrivalTime; + } + @Override public int durationInSeconds() { return arrivalTime - departureTime; diff --git a/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java b/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java index a0ec4523726..dab47a6d18b 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java +++ b/src/test/java/org/opentripplanner/raptor/_data/api/TestRaptorPath.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Stream; import javax.annotation.Nullable; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.model.RaptorTripSchedule; import org.opentripplanner.raptor.api.path.AccessPathLeg; import org.opentripplanner.raptor.api.path.EgressPathLeg; @@ -18,9 +19,9 @@ */ public record TestRaptorPath( int rangeRaptorIterationDepartureTime, - int startTime, - int endTime, - int durationInSeconds, + int startTimeInclusivePenalty, + int endTimeInclusivePenalty, + int durationInclusivePenaltyInSeconds, int numberOfTransfers, int c1, int c2 @@ -29,6 +30,18 @@ public record TestRaptorPath( private static final String NOT_IMPLEMENTED_MESSAGE = "Use the real Path implementation if you need legs..."; + @Override + public int startTime() { + // This should not be used in the pareto comparison. + return RaptorConstants.TIME_NOT_SET; + } + + @Override + public int endTime() { + // This should not be used in the pareto comparison. + return RaptorConstants.TIME_NOT_SET; + } + @Override public int numberOfTransfersExAccessEgress() { throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE); diff --git a/src/test/java/org/opentripplanner/raptor/api/path/RaptorPathTest.java b/src/test/java/org/opentripplanner/raptor/api/path/RaptorPathTest.java index edd45078086..5684a28c1c1 100644 --- a/src/test/java/org/opentripplanner/raptor/api/path/RaptorPathTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/path/RaptorPathTest.java @@ -1,6 +1,5 @@ package org.opentripplanner.raptor.api.path; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -112,10 +111,10 @@ void compareArrivalTime() { @Test void compareDuration() { - assertFalse(RaptorPath.compareDuration(subject, subject)); - assertFalse(RaptorPath.compareDuration(subject, same)); - assertFalse(RaptorPath.compareDuration(subject, smallDuration)); - assertTrue(RaptorPath.compareDuration(smallDuration, subject)); + assertFalse(RaptorPath.compareDurationInclusivePenalty(subject, subject)); + assertFalse(RaptorPath.compareDurationInclusivePenalty(subject, same)); + assertFalse(RaptorPath.compareDurationInclusivePenalty(subject, smallDuration)); + assertTrue(RaptorPath.compareDurationInclusivePenalty(smallDuration, subject)); } @Test diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/support/ExpectedList.java b/src/test/java/org/opentripplanner/raptor/moduletests/support/ExpectedList.java index ce4a68b1025..bca2c31ef0e 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/support/ExpectedList.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/support/ExpectedList.java @@ -22,14 +22,6 @@ public String[] first(int n) { return range(0, n); } - public String get(int index) { - return items[index]; - } - - public String[] get(int... indexes) { - return Arrays.stream(indexes).mapToObj(i -> items[i]).toList().toArray(new String[0]); - } - public String last() { return items[items.length - 1]; } @@ -38,6 +30,14 @@ public String[] last(int n) { return range(items.length - n, items.length); } + public String get(int index) { + return items[index]; + } + + public String[] get(int... indexes) { + return Arrays.stream(indexes).mapToObj(i -> items[i]).toList().toArray(new String[0]); + } + public String[] range(int startInclusive, int endExclusive) { return Arrays.stream(items, startInclusive, endExclusive).toList().toArray(new String[0]); } From 8f363243d31a5304c998ed3d5ac4a67d1eb550a5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 29 Feb 2024 17:45:59 +0100 Subject: [PATCH 086/108] test: Add module tests on access/egress with time-penalty --- .../L01_TimePenaltyAccessTest.java | 153 +++++++++++++++++ .../L01_TimePenaltyEgressTest.java | 158 ++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyAccessTest.java create mode 100644 src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyEgressTest.java diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyAccessTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyAccessTest.java new file mode 100644 index 00000000000..778dd36a227 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyAccessTest.java @@ -0,0 +1,153 @@ +package org.opentripplanner.raptor.moduletests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.raptor._data.api.PathUtils.withoutCost; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor._data.transit.TestRoute.route; +import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_ONE; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_REV; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_REV_ONE; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.multiCriteria; + +import java.time.Duration; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.raptor.RaptorService; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.transit.TestTransitData; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.moduletests.support.ExpectedList; +import org.opentripplanner.raptor.moduletests.support.ModuleTestDebugLogging; +import org.opentripplanner.raptor.moduletests.support.RaptorModuleTestCase; + +/* + * FEATURE UNDER TEST + * + * Raptor should take into account time-penalty for access. This test focuses on checking the + * logic for the time-penalty. The penalty should be included in the access when comparing for + * optimality, but should be excluded when checking for time constraints (arrive-by/depart-after). + * All paths in this test have the same penalty; Hence, we do not compare paths with/without a + * penalty. + */ +public class L01_TimePenaltyAccessTest implements RaptorTestConstants { + + private static final Duration D8m = Duration.ofMinutes(8); + + // There are 5 possible trips + + private final TestTransitData data = new TestTransitData(); + private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>(); + private final RaptorService raptorService = new RaptorService<>( + RaptorConfig.defaultConfigForTest() + ); + + @BeforeEach + public void setup() { + data.withRoute(route("R1", STOP_A, STOP_B).withTimetable(schedule("0:10 0:40").repeat(10, 60))); + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D2m).withTimePenalty(D1m)) + .addEgressPaths(walk(STOP_B, D1m)); + + requestBuilder.searchParams().timetable(true); + + ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder); + } + + private static List tripsAtTheEndOfTheSearchWindowTestCase() { + int edt = time("0:01"); + int lat = time("0:42"); + + var expected = new ExpectedList( + "Walk 2m 0:08 0:10 C₁240 ~ A 0s ~ BUS R1 0:10 .. [0:08 0:41 33m Tₓ0 C₁2_760]", + "Walk 2m 0:09 0:11 C₁240 ~ A 0s ~ BUS R1 0:11 .. [0:09 0:42 33m Tₓ0 C₁2_760]" + ); + + return RaptorModuleTestCase + .of() + .withRequest(r -> + r.searchParams().earliestDepartureTime(edt).latestArrivalTime(lat).searchWindow(D8m) + ) + .addMinDuration("34m", TX_0, edt, lat) + .add(TC_STANDARD, withoutCost(expected.all())) + .add(TC_STANDARD_ONE, withoutCost(expected.first())) + .add(TC_STANDARD_REV, withoutCost(expected.all())) + .add(TC_STANDARD_REV_ONE, withoutCost(expected.last())) + .add(multiCriteria(), expected.all()) + .build(); + } + + @ParameterizedTest + @MethodSource("tripsAtTheEndOfTheSearchWindowTestCase") + void tripsAtTheEndOfTheSearchWindowTest(RaptorModuleTestCase testCase) { + assertEquals( + testCase.expected(), + focusOnAccess(testCase.runDetailedResult(raptorService, data, requestBuilder)) + ); + } + + private static List tripsAtTheBeginningOfTheSearchWindowTestCases() { + int edt = time("0:16"); + // The last path arrive at the destination at 0:50, LAT=0:55 will iterate over the last 5 + // paths. + int lat = time("0:55"); + + // The latest buss is at 0:19, so with EDT=0:16 can only reach the last two buses, + // Running this test without the time-penalty confirm this result. + var expected = new ExpectedList( + "Walk 2m 0:16 0:18 C₁240 ~ A 0s ~ BUS R1 0:18 .. [0:16 0:49 33m Tₓ0 C₁2_760]", + "Walk 2m 0:17 0:19 C₁240 ~ A 0s ~ BUS R1 0:19 .. [0:17 0:50 33m Tₓ0 C₁2_760]" + ); + + return RaptorModuleTestCase + .of() + .withRequest(r -> + r.searchParams().earliestDepartureTime(edt).latestArrivalTime(lat).searchWindow(D8m) + ) + .addMinDuration("34m", TX_0, edt, lat) + .add(TC_STANDARD, withoutCost(expected.all())) + // We do not have special support for time-penalty for single iteration Raptor, so the + // first path is missed due to the penalty. + .add(TC_STANDARD_ONE, withoutCost(expected.last())) + // Note! this test that the time-penalty is removed from the "arrive-by" limit in the + // destination + .add(TC_STANDARD_REV, withoutCost(expected.all())) + .add(TC_STANDARD_REV_ONE, withoutCost(expected.last())) + .add(multiCriteria(), expected.all()) + .build(); + } + + @ParameterizedTest + @MethodSource("tripsAtTheBeginningOfTheSearchWindowTestCases") + void tripsAtTheBeginningOfTheSearchWindowTest(RaptorModuleTestCase testCase) { + assertEquals( + testCase.expected(), + focusOnAccess(testCase.runDetailedResult(raptorService, data, requestBuilder)) + ); + } + + public static String focusOnAccess(String path) { + // We are only interested in the access and the first boarding. We include the + // pareto vector as well. + var p = Pattern.compile("(.+BUS R1 \\d+:\\d+).+(\\[.+)"); + + String[] lines = path.split("\n"); + return Stream + .of(lines) + .map(s -> { + var m = p.matcher(s); + return m.find() ? m.group(1) + " .. " + m.group(2) : s; + }) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyEgressTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyEgressTest.java new file mode 100644 index 00000000000..1e6897475f4 --- /dev/null +++ b/src/test/java/org/opentripplanner/raptor/moduletests/L01_TimePenaltyEgressTest.java @@ -0,0 +1,158 @@ +package org.opentripplanner.raptor.moduletests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.raptor._data.api.PathUtils.withoutCost; +import static org.opentripplanner.raptor._data.transit.TestAccessEgress.walk; +import static org.opentripplanner.raptor._data.transit.TestRoute.route; +import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_ONE; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_REV; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.TC_STANDARD_REV_ONE; +import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.multiCriteria; + +import java.time.Duration; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.raptor.RaptorService; +import org.opentripplanner.raptor._data.RaptorTestConstants; +import org.opentripplanner.raptor._data.api.PathUtils; +import org.opentripplanner.raptor._data.transit.TestTransitData; +import org.opentripplanner.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.raptor.moduletests.support.ExpectedList; +import org.opentripplanner.raptor.moduletests.support.ModuleTestDebugLogging; +import org.opentripplanner.raptor.moduletests.support.RaptorModuleTestCase; + +/* + * FEATURE UNDER TEST + * + * Raptor should take into account time-penalty for egress. This test focuses on checking the + * logic for the time-penalty. The penalty should be included in the egress when comparing for + * optimality, but should be excluded when checking for time constraints (arrive-by/depart-after). + * All paths in this test have the same penalty; Hence, we do not compare paths with/without a + * penalty. + *

    + * Tip! Remove time-penalty from egress and the test should in most cases have the same result. + */ +public class L01_TimePenaltyEgressTest implements RaptorTestConstants { + + private static final Duration D8m = Duration.ofMinutes(8); + + // There are 5 possible trips + + private final TestTransitData data = new TestTransitData(); + private final RaptorRequestBuilder requestBuilder = new RaptorRequestBuilder<>(); + private final RaptorService raptorService = new RaptorService<>( + RaptorConfig.defaultConfigForTest() + ); + + @BeforeEach + public void setup() { + data.withRoute(route("R1", STOP_A, STOP_B).withTimetable(schedule("0:10 0:40").repeat(10, 60))); + requestBuilder + .searchParams() + .addAccessPaths(walk(STOP_A, D1m)) + .addEgressPaths(walk(STOP_B, D2m).withTimePenalty(D1m)); + + requestBuilder.searchParams().timetable(true); + + ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder); + } + + private static List firstTwoPathsArriveBeforeLAT() { + // EDT is set to allow 5 paths - not a limiting factor. + int edt = time("0:05"); + // We limit the search to the two first paths by setting the LAT to 0:43. + int lat = time("0:43"); + + var expected = new ExpectedList( + "BUS R1 0:10 0:40 30m ~ B 0s ~ Walk 2m 0:40 0:42 [0:09 0:42 33m Tₓ0]", + "BUS R1 0:11 0:41 30m ~ B 0s ~ Walk 2m 0:41 0:43 [0:10 0:43 33m Tₓ0]" + ); + + return RaptorModuleTestCase + .of() + .withRequest(r -> + r.searchParams().earliestDepartureTime(edt).latestArrivalTime(lat).searchWindow(D8m) + ) + .addMinDuration("34m", TX_0, edt, lat) + .add(TC_STANDARD, withoutCost(expected.all())) + .add(TC_STANDARD_ONE, withoutCost(expected.first())) + .add(TC_STANDARD_REV, withoutCost(expected.all())) + // The egress time-penalty will cause the first path to be missed (singe iteration) + .add(TC_STANDARD_REV_ONE, withoutCost(expected.first())) + .add(multiCriteria(), expected.all()) + .build(); + } + + @ParameterizedTest + @MethodSource("firstTwoPathsArriveBeforeLAT") + void firstTwoPathsArriveBeforeLAT(RaptorModuleTestCase testCase) { + assertEquals( + testCase.expected(), + focusOnEgress(testCase.runDetailedResult(raptorService, data, requestBuilder)) + ); + } + + private static List lastTwoPathsDepartsAfterEDT() { + // The latest buss is at 0:19, so with EDT=0:17 can only reach the last two buses. + int edt = time("0:17"); + int lat = time("0:51"); + + var expected = new ExpectedList( + "BUS R1 0:18 0:48 30m ~ B 0s ~ Walk 2m 0:48 0:50 [0:17 0:50 33m Tₓ0]", + "BUS R1 0:19 0:49 30m ~ B 0s ~ Walk 2m 0:49 0:51 [0:18 0:51 33m Tₓ0]" + ); + + return RaptorModuleTestCase + .of() + .withRequest(r -> + r.searchParams().earliestDepartureTime(edt).latestArrivalTime(lat).searchWindow(D8m) + ) + .addMinDuration("34m", TX_0, edt, lat) + // Note! this test that the time-penalty is removed from the "arrive-by" limit in the + // destination + .add(TC_STANDARD, withoutCost(expected.all())) + .add(TC_STANDARD_ONE, withoutCost(expected.first())) + .add(TC_STANDARD_REV, withoutCost(expected.all())) + // We do not have special support for time-penalty for single iteration Raptor, so the + // "last" path is missed due to the penalty for a reverse search. + .add(TC_STANDARD_REV_ONE, withoutCost(expected.first())) + .add(multiCriteria(), expected.all()) + .build(); + } + + @ParameterizedTest + @MethodSource("lastTwoPathsDepartsAfterEDT") + void lastTwoPathsDepartsAfterEDT(RaptorModuleTestCase testCase) { + assertEquals( + testCase.expected(), + focusOnEgress(testCase.runDetailedResult(raptorService, data, requestBuilder)) + ); + } + + public static String focusOnEgress(String path) { + // We are only interested in the access and the first boarding. We include the + // pareto vector as well. + var p = Pattern.compile("(BUS R1 .+)(\\[.+)"); + + // BUS R1 0:18 0:48 30m ~ B 0s ~ Walk 1m 0:48 0:49 .. [0:16 0:49 33m Tₓ0] + String[] lines = path.split("\n"); + return Stream + .of(lines) + .map(s -> { + int pos = s.indexOf("BUS"); + return pos > 0 ? s.substring(pos) : s; + }) + .map(PathUtils::withoutCost) + .collect(Collectors.joining("\n")); + } +} From bc56933bd01208569c623281f309f85d115ba440 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:19:19 +0000 Subject: [PATCH 087/108] chore(deps): update dependency com.google.cloud.tools:jib-maven-plugin to v3.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 48e03115c17..d1b9c8be0ba 100644 --- a/pom.xml +++ b/pom.xml @@ -444,7 +444,7 @@ com.google.cloud.tools jib-maven-plugin - 3.4.0 + 3.4.1 org.opentripplanner.standalone.OTPMain From b833b370e477e5c2bde29911aeea05c3d0dc33c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:19:25 +0000 Subject: [PATCH 088/108] fix(deps): update dependency ch.qos.logback:logback-classic to v1.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d1b9c8be0ba..9ed632e69d8 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.2 1.12.2 5.5.3 - 1.5.0 + 1.5.1 9.9.1 2.0.12 2.0.15 From 71415a9f2d7c2610614bf33a04fdf0d361b1065f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 1 Mar 2024 12:16:49 +0100 Subject: [PATCH 089/108] Apply suggestions from code review Co-authored-by: Leonard Ehrenfried --- .../opentripplanner/raptor/api/model/RaptorAccessEgress.java | 2 +- .../opentripplanner/raptor/rangeraptor/transit/AccessPaths.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index a1764d42374..aac389b7af4 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -55,7 +55,7 @@ public interface RaptorAccessEgress { * For example, for Park&Ride, driving all the way to the * destination is very often the best option when looking at the time criteria. When an * increasing time-penalty is applied to access/egress with driving then driving less become - * more favorable. This also improves perfomance, since we usually add a very high cost to + * more favorable. This also improves performance, since we usually add a very high cost to * driving - making all park&ride access legs optimal - forcing Raptor to compute a path for * every option. The short drives are optimal on cost, and the long are optimal on time. In the * case of park&ride the time-penalty enables Raptor to choose one of the shortest access/egress diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 26fb49ec88a..0e251192bc7 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -179,7 +179,7 @@ private static boolean hasTimeDependentAccess(TIntObjectMap From 2f17a31bd8125800fa2888a4089aac5a6f6a88f2 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 1 Mar 2024 12:23:41 +0100 Subject: [PATCH 090/108] doc: Add JavaDoc to the AccessPaths#iterateOverPathsWithPenalty method --- .../raptor/rangeraptor/transit/AccessPaths.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java index 0e251192bc7..d070a804f9c 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/transit/AccessPaths.java @@ -110,6 +110,11 @@ public int calculateMaxNumberOfRides() { ); } + /** + * This is used in the main "minutes" iteration to iterate over the extra minutes needed to + * include access with time-penalty. This method returns an iterator for the minutes in front of + * the normal search window starting at the given {@code earliestDepartureTime}. + */ public IntIterator iterateOverPathsWithPenalty(final int earliestDepartureTime) { if (!hasTimePenalty()) { return IntIterators.empty(); From 4758a77a6b38750bb8fb036b79672561680ff372 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 1 Mar 2024 12:40:40 +0100 Subject: [PATCH 091/108] refactor: Use a constant fixed implementation for IntIterator#empty() --- .../opentripplanner/raptor/util/IntIterators.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/raptor/util/IntIterators.java b/src/main/java/org/opentripplanner/raptor/util/IntIterators.java index cb8c6c38787..b0df1e56155 100644 --- a/src/main/java/org/opentripplanner/raptor/util/IntIterators.java +++ b/src/main/java/org/opentripplanner/raptor/util/IntIterators.java @@ -1,9 +1,22 @@ package org.opentripplanner.raptor.util; +import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.spi.IntIterator; public class IntIterators { + private static final IntIterator EMPTY = new IntIterator() { + @Override + public int next() { + return RaptorConstants.NOT_FOUND; + } + + @Override + public boolean hasNext() { + return false; + } + }; + /** This is private to forbid construction. */ private IntIterators() { /* NOOP*/ @@ -122,6 +135,6 @@ public static IntIterator singleValueIterator(final int value) { * Return an empty iterator. All calls to {@link IntIterator#hasNext()} will return {@code false}. */ public static IntIterator empty() { - return intIncIterator(0, 0); + return EMPTY; } } From 5bd379973e30d31d43aaba66661f07be4afd9e0c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 1 Mar 2024 12:51:36 +0100 Subject: [PATCH 092/108] refactor: Cleanup RaptorTestConstants --- .../opentripplanner/raptor/_data/RaptorTestConstants.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java index d1398ea05cd..0bca83be8bf 100644 --- a/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java +++ b/src/test/java/org/opentripplanner/raptor/_data/RaptorTestConstants.java @@ -28,18 +28,14 @@ public interface RaptorTestConstants { int D24h = durationInSeconds("24h"); /** - * There are 86400 seconds in a "normal" day(24 * 60 * 60). This is used for testing, logging - * and debugging, but does not base any important logic on this. A day with changes in - * daylight-saving-time does not have this number of seconds. + * There are 86400 seconds in a "normal" day(24 * 60 * 60). */ int SECONDS_IN_A_DAY = (int) D24h; // Time constants, all values are in seconds int T00_00 = hm2time(0, 0); - int T00_01 = hm2time(0, 1); int T00_02 = hm2time(0, 2); int T00_10 = hm2time(0, 10); - int T00_12 = hm2time(0, 12); int T00_30 = hm2time(0, 30); int T00_40 = hm2time(0, 40); int T01_00 = hm2time(1, 0); @@ -49,7 +45,7 @@ public interface RaptorTestConstants { int TX_2 = 2; // Stop indexes - Note! There is no stop defined for index 0(zero)! You must - // account for that in the test if you uses a stop index. + // account for that in the test if you use the stop index. int STOP_A = 1; int STOP_B = 2; int STOP_C = 3; From ed3d05aad648726ce5d3768ddba0167140335e24 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 1 Mar 2024 13:04:01 +0100 Subject: [PATCH 093/108] Apply suggestions from code review Co-authored-by: Johan Torin --- .../framework/lang/MemEfficientArrayBuilder.java | 6 +++--- .../opentripplanner/transit/model/network/TripPattern.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java b/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java index 161ac5c5cf5..8b3a6ba6cd0 100644 --- a/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java +++ b/src/main/java/org/opentripplanner/framework/lang/MemEfficientArrayBuilder.java @@ -5,13 +5,13 @@ import javax.annotation.Nonnull; /** - * This array builder is used to minimize creating new objects(arrays). It takes an array as base, + * This array builder is used to minimize the creation of new objects (arrays). It takes an array as base, * the original array. A new array is created only if there are differences. *

    * A common case is that one original is updated several times. In this case, you can use the - * {@link #build(Object[])}, too also make sure the existing update is reused (deduplicated). + * {@link #build(Object[])} method to also make sure that the existing update is reused (deduplicated). *

    - * Arrays are mutable, so be careful this class helps you reuse the original if it has the same + * Arrays are mutable, so be careful as this class helps you reuse the original if it has the same * values. It protects the original while in scope, but you should only use it if you do not * modify the original or the result on the outside. This builder does not help protect the arrays. */ diff --git a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java index a439c9fd4fe..7e40aa2d13b 100644 --- a/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java +++ b/src/main/java/org/opentripplanner/transit/model/network/TripPattern.java @@ -161,7 +161,7 @@ public StopPattern getStopPattern() { /** * Return the "original"/planned stop pattern as a builder. This is used when a realtime-update - * contains a full set of stops/pickup/droppoff for a pattern. This will wipe out any changes + * contains a full set of stops/pickup/dropoff for a pattern. This will wipe out any changes * to the stop-pattern from previous updates. *

    * Be aware, if the same update is applied twice, then the first instance will be reused to avoid From 7fd489235ca656dff6e5e011221068bb526656bd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Mar 2024 14:27:31 +0100 Subject: [PATCH 094/108] Run apt-get update before install [ci skip] --- .github/workflows/post-merge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index a2f29f0d1b0..86f3741aada 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -54,6 +54,7 @@ jobs: - name: Install xmllint run: | + sudo apt-get update sudo apt-get install -y libxml2-utils - name: Configure git user From 2b07ff88f13e6d82db97981fed17492ae35cade0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 1 Mar 2024 15:56:31 +0200 Subject: [PATCH 095/108] Bump serialization version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9ed632e69d8..d746c259a25 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 146 + 147 30.2 2.51 From 0136bbd59b3f5fd24cfaea9c90d448de6b4c8b12 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Mar 2024 16:03:42 +0100 Subject: [PATCH 096/108] Apply review feedback --- .../areastops/AreaStopsLayerBuilderTest.java | 47 ++++++++++--------- .../areastops/AreaStopPropertyMapper.java | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java index befaa6b886d..2e6c4e16c40 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopsLayerBuilderTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.areastops; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; import java.util.List; import java.util.Locale; @@ -9,6 +10,7 @@ import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.inspector.vector.LayerParameters; +import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.AreaStop; @@ -21,6 +23,28 @@ class AreaStopsLayerBuilderTest { private static final FeedScopedId ID = new FeedScopedId("FEED", "ID"); private static final I18NString NAME = I18NString.of("Test stop"); + private static final String CONFIG = + """ + { + "vectorTiles": { + "layers" : [ + { + "name": "areaStops", + "type": "AreaStop", + "mapper": "OTPRR", + "maxZoom": 20, + "minZoom": 14, + "cacheMaxSeconds": 60, + "expansionFactor": 0 + } + ] + } + } + """; + private static final LayerParameters LAYER_CONFIG = VectorTileConfig + .mapVectorTilesParameters(newNodeAdapterForTest(CONFIG), "vectorTiles") + .layers() + .getFirst(); private final StopModelBuilder stopModelBuilder = StopModel.of(); @@ -35,34 +59,13 @@ class AreaStopsLayerBuilderTest { new Deduplicator() ); - record Layer( - String name, - VectorTilesResource.LayerType type, - String mapper, - int maxZoom, - int minZoom, - int cacheMaxSeconds, - double expansionFactor - ) - implements LayerParameters {} - @Test void getAreaStops() { transitModel.index(); - var layer = new Layer( - "areaStops", - VectorTilesResource.LayerType.AreaStop, - "OTPRR", - 20, - 1, - 10, - .25 - ); - var subject = new AreaStopsLayerBuilder( new DefaultTransitService(transitModel), - layer, + LAYER_CONFIG, Locale.ENGLISH ); var geometries = subject.getGeometries(AREA_STOP.getGeometry().getEnvelopeInternal()); diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java index c91ea2cb6b6..ea6f9225e11 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapper.java @@ -40,7 +40,7 @@ protected Collection map(AreaStop stop) { .filter(Objects::nonNull) .distinct() // the MVT spec explicitly doesn't cover how to encode arrays - // https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/ + // https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/#what-the-spec-doesnt-cover .collect(Collectors.joining(",")); return List.of( new KeyValue("gtfsId", stop.getId().toString()), From 8ea6935f94957402bf3c8164bcc7ff94fff01211 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:06:48 +0000 Subject: [PATCH 097/108] fix(deps): update dependency ch.qos.logback:logback-classic to v1.5.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d746c259a25..7075f575344 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.2 1.12.2 5.5.3 - 1.5.1 + 1.5.2 9.9.1 2.0.12 2.0.15 From 28865d50f9ea4a14af08f9ff936c585313f41923 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:06:54 +0000 Subject: [PATCH 098/108] chore(deps): update dependency org.mockito:mockito-core to v5.11.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7075f575344..8764dc0578a 100644 --- a/pom.xml +++ b/pom.xml @@ -713,7 +713,7 @@ org.mockito mockito-core - 5.10.0 + 5.11.0 test From 7ac7cf220675ece42eeab19563c3f29f832e35cd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Mar 2024 12:25:02 +0100 Subject: [PATCH 099/108] Fix monorail test assertion --- .../java/org/opentripplanner/smoketest/SeattleSmokeTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/smoketest/SeattleSmokeTest.java b/src/test/java/org/opentripplanner/smoketest/SeattleSmokeTest.java index 186c7871ef7..69e693f2f7e 100644 --- a/src/test/java/org/opentripplanner/smoketest/SeattleSmokeTest.java +++ b/src/test/java/org/opentripplanner/smoketest/SeattleSmokeTest.java @@ -142,7 +142,8 @@ public void monorailRoute() throws IOException { .map(Route::mode) .map(Objects::toString) .collect(Collectors.toSet()); - assertEquals(Set.of("MONORAIL", "TRAM", "FERRY", "BUS", "RAIL"), modes); + // for some reason the monorail feed says its route is of type rail + assertEquals(Set.of("TRAM", "FERRY", "BUS", "RAIL"), modes); } @Test From d54db3b65dfffa4fbcf8c1b37c05f2a162795c21 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 4 Mar 2024 15:03:16 +0100 Subject: [PATCH 100/108] Apply suggestions from code review Co-authored-by: Vincent Paturet <46598384+vpaturet@users.noreply.github.com> --- .../raptor/api/model/RaptorAccessEgress.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java index aac389b7af4..3a7f48cf743 100644 --- a/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java +++ b/src/main/java/org/opentripplanner/raptor/api/model/RaptorAccessEgress.java @@ -54,8 +54,8 @@ public interface RaptorAccessEgress { *

    * For example, for Park&Ride, driving all the way to the * destination is very often the best option when looking at the time criteria. When an - * increasing time-penalty is applied to access/egress with driving then driving less become - * more favorable. This also improves performance, since we usually add a very high cost to + * increasing time-penalty is applied to a car access/egress, then driving become less + * favorable. This also improves performance, since we usually add a very high cost to * driving - making all park&ride access legs optimal - forcing Raptor to compute a path for * every option. The short drives are optimal on cost, and the long are optimal on time. In the * case of park&ride the time-penalty enables Raptor to choose one of the shortest access/egress @@ -64,8 +64,8 @@ public interface RaptorAccessEgress { * Another example is FLEX, where we in many use-cases want regular transit to win if there is * an offer. Only in the case where the FLEX is the only solution we want it to be presented. * To achieve this, we must add an extra duration to the time of the FLEX access/egress - it does - * not help to just edd extra cost - witch makes both FLEX optimal on time and transit optimal on - * cost. Many optimal access paths have an inpact on performance as vell. + * not help to just add extra cost - which makes both FLEX optimal on time and transit optimal on + * cost. Keeping a large number of optimal access paths has a negative impact on performance as well. *

    * * The unit is seconds and the default value is {@link RaptorConstants#TIME_NOT_SET}. From cacec850aa1f6d4fe0dbf5f1a7cd9786a9fc2750 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 4 Mar 2024 15:05:26 +0100 Subject: [PATCH 101/108] Apply suggestions from code review Co-authored-by: Vincent Paturet <46598384+vpaturet@users.noreply.github.com> --- src/main/java/org/opentripplanner/raptor/path/Path.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/raptor/path/Path.java b/src/main/java/org/opentripplanner/raptor/path/Path.java index 15fcf7b34e7..ebade8b2690 100644 --- a/src/main/java/org/opentripplanner/raptor/path/Path.java +++ b/src/main/java/org/opentripplanner/raptor/path/Path.java @@ -19,9 +19,9 @@ import org.opentripplanner.raptor.api.path.TransitPathLeg; /** - * The result of a Raptor search is a path describing the one possible journey. The path is then + * The result of a Raptor search is a path describing the one possible journey. The path is the * main DTO part of the Raptor result, but it is also used internally in Raptor. Hence, it is a bit - * more complex, and it has more responsiblilites than it should. + * more complex, and it has more responsiblilities than it should. *

    * To improve the design, Raptor should not use the path internally. Instead, there should * be a special destination arrival that could take over the Raptor responsibilities. The From cd76862ebe81ad33867cfdb2704836f61314d784 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 4 Mar 2024 14:06:38 +0000 Subject: [PATCH 102/108] Add changelog entry for #5705 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 0b6134e4939..1f86690f7ee 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -97,6 +97,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Use NeTEx authority short name if name is not present [#5698](https://github.com/opentripplanner/OpenTripPlanner/pull/5698) - Add Hamburg OSM mapper [#5701](https://github.com/opentripplanner/OpenTripPlanner/pull/5701) - Remove configurable car speed and determine it in graph build [#5657](https://github.com/opentripplanner/OpenTripPlanner/pull/5657) +- Avoid cumulative real-time updates [#5705](https://github.com/opentripplanner/OpenTripPlanner/pull/5705) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 573ae93cd62d2a57cf4be92c94dad84fd035f5ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:21:43 +0000 Subject: [PATCH 103/108] fix(deps): update dependency ch.qos.logback:logback-classic to v1.5.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8764dc0578a..a3d2763039c 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 5.10.2 1.12.2 5.5.3 - 1.5.2 + 1.5.3 9.9.1 2.0.12 2.0.15 From 1fc6f5c95078afef0b293f60b8e675e708f6a808 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:21:49 +0000 Subject: [PATCH 104/108] chore(deps): update dependency io.github.git-commit-id:git-commit-id-maven-plugin to v8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3d2763039c..ddc19f4528b 100644 --- a/pom.xml +++ b/pom.xml @@ -321,7 +321,7 @@ but we need the Maven project version as well, so we perform substitution. --> io.github.git-commit-id git-commit-id-maven-plugin - 7.0.0 + 8.0.0 From bfb7c37a49a122bfcd6e2cfc82cc8cfbdd46444a Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 5 Mar 2024 10:49:28 +0000 Subject: [PATCH 105/108] Add changelog entry for #5715 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1f86690f7ee..9e2e515c463 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -98,6 +98,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add Hamburg OSM mapper [#5701](https://github.com/opentripplanner/OpenTripPlanner/pull/5701) - Remove configurable car speed and determine it in graph build [#5657](https://github.com/opentripplanner/OpenTripPlanner/pull/5657) - Avoid cumulative real-time updates [#5705](https://github.com/opentripplanner/OpenTripPlanner/pull/5705) +- Fix time penalty [#5715](https://github.com/opentripplanner/OpenTripPlanner/pull/5715) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 6fc4f608a8f710d68d56c3febafe16f6932cf532 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Tue, 5 Mar 2024 10:49:51 +0000 Subject: [PATCH 106/108] Bump serialization version id for #5715 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ddc19f4528b..f809bae08c6 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 147 + 148 30.2 2.51 From ed2c083c48ad917b8ca3da1073e352881cb34a65 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 5 Mar 2024 12:41:42 +0100 Subject: [PATCH 107/108] Revert back to using CONFIG icon [ci skip] --- src/main/java/org/opentripplanner/datastore/OtpDataStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java index 0c2e9a6608c..937b4fb8203 100644 --- a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java +++ b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java @@ -108,7 +108,7 @@ public void open() { if (config.stopConsolidation() != null) { stopConsolidation = - findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), GTFS)); + findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), CONFIG)); } addAll(Arrays.asList(streetGraph, graph, buildReportDir)); From 0e9c32869dba575fe1e30aee51640eb200610d43 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 5 Mar 2024 13:02:10 +0100 Subject: [PATCH 108/108] Fix finding of stop consolidation file [ci skip] --- src/main/java/org/opentripplanner/datastore/OtpDataStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java index 937b4fb8203..397d3f64c70 100644 --- a/src/main/java/org/opentripplanner/datastore/OtpDataStore.java +++ b/src/main/java/org/opentripplanner/datastore/OtpDataStore.java @@ -108,7 +108,7 @@ public void open() { if (config.stopConsolidation() != null) { stopConsolidation = - findSourceUsingAllRepos(it -> it.findCompositeSource(config.stopConsolidation(), CONFIG)); + findSingleSource(config.stopConsolidation(), config.stopConsolidation().toString(), GTFS); } addAll(Arrays.asList(streetGraph, graph, buildReportDir));