diff --git a/docs/Changelog.md b/docs/Changelog.md index 37c8d26053e..df2ecd4b8fc 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -87,6 +87,8 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Introduce `generalizedCostPlusPenalty` to make cost comparsion fairer [#5483](https://github.com/opentripplanner/OpenTripPlanner/pull/5483) - Separate walk time from non-transit time [#5648](https://github.com/opentripplanner/OpenTripPlanner/pull/5648) - Remove "fare" [#5645](https://github.com/opentripplanner/OpenTripPlanner/pull/5645) +- Refactor GroupStopBuilder addLocation method [#5651](https://github.com/opentripplanner/OpenTripPlanner/pull/5651) +- Remove `VehicleToStopHeuristics` [#5381](https://github.com/opentripplanner/OpenTripPlanner/pull/5381) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) diff --git a/docs/Configuration.md b/docs/Configuration.md index 858edf0f9b4..93ca1fa6c1e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -250,7 +250,6 @@ Here is a list of all features which can be toggled on/off and their default val | `SandboxAPIParkAndRideApi` | Enable park-and-ride endpoint. | | ✓️ | | `SandboxAPITravelTime` | Enable the isochrone/travel time surface API. | | ✓️ | | `TransferAnalyzer` | Analyze transfers during graph build. | | ✓️ | -| `VehicleToStopHeuristics` | Enable improved heuristic for park-and-ride queries. | | ✓️ | diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 199eda8a332..4ef1e17a7d8 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -78,8 +78,8 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |       [safety](#rd_bicycle_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | |       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | |    walk | `object` | Preferences for walking a vehicle. | *Optional* | | 2.5 | -|       [hopCost](#rd_bicycle_walk_hopCost) | `integer` | The cost of hopping on or off a vehicle. | *Optional* | `0` | 2.0 | -|       [hopTime](#rd_bicycle_walk_hopTime) | `duration` | The time it takes the user to hop on or off a vehicle. | *Optional* | `"PT0S"` | 2.0 | +|       [mountDismountCost](#rd_bicycle_walk_mountDismountCost) | `integer` | The cost of hopping on or off a vehicle. | *Optional* | `0` | 2.0 | +|       [mountDismountTime](#rd_bicycle_walk_mountDismountTime) | `duration` | The time it takes the user to hop on or off a vehicle. | *Optional* | `"PT0S"` | 2.0 | |       reluctance | `double` | A multiplier for how bad walking with a vehicle is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | |       speed | `double` | The user's vehicle walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | |       stairsReluctance | `double` | How bad is it to walk the vehicle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | @@ -406,7 +406,12 @@ since the search-window is increased with the same amount as the maximum penalty the access legs used. In other cases where the access(CAR) is faster than transit the performance will be better. -The default is no penalty, if not configured. +The default values are + +- `car-to-park` = (timePenalty: 20m + 2.0 t, costFactor: 1.50) +- `car-rental` = (timePenalty: 20m + 2.0 t, costFactor: 1.50) +- `car-hailing` = (timePenalty: 20m + 2.0 t, costFactor: 1.50) +- `flexible` = (timePenalty: 20m + 2.0 t, costFactor: 1.50) Example: `"car-to-park" : { "timePenalty": "10m + 1.5t", "costFactor": 2.5 }` @@ -525,7 +530,7 @@ This factor can also include other concerns such as convenience and general cycl preferences by taking into account road surface etc. -

hopCost

+

mountDismountCost

**Since version:** `2.0` ∙ **Type:** `integer` ∙ **Cardinality:** `Optional` ∙ **Default value:** `0` **Path:** /routingDefaults/bicycle/walk @@ -536,7 +541,7 @@ There are different parameters for the cost of renting or parking a vehicle and not meant for controlling the cost of those events. -

hopTime

+

mountDismountTime

**Since version:** `2.0` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` **Path:** /routingDefaults/bicycle/walk diff --git a/docs/SandboxExtension.md b/docs/SandboxExtension.md index 4b978313ca6..914be238da4 100644 --- a/docs/SandboxExtension.md +++ b/docs/SandboxExtension.md @@ -23,7 +23,6 @@ nearby stops generated by routing via OSM data. - [Park and Ride API](sandbox/ParkAndRideApi.md) - Park and Ride API - [Data Overlay](sandbox/DataOverlay.md) - StreetEdge grid data populating affecting the route planning - [Vehicle Parking](sandbox/VehicleParking.md) - Vehicle Parking updaters -- [Vehicle-to-stop heuristics](sandbox/VehicleToStopHeuristics.md) - Speeding up Park+Ride, Bike+Ride and Bike+Transit searches - [Travel Time (Isochrone & Surface) API](sandbox/TravelTime.md) - Travel Time API - [IBI accessibility score](sandbox/IBIAccessibilityScore.md) - IBI accessibility score - [Fares](sandbox/Fares.md) - Fare calculation diff --git a/docs/apis/Apis.md b/docs/apis/Apis.md index dd9280a047f..ab6b41a25cd 100644 --- a/docs/apis/Apis.md +++ b/docs/apis/Apis.md @@ -18,7 +18,7 @@ entities on a vector map. The [Actuator API](../sandbox/ActuatorAPI.md) provides endpoints for checking the health status of the OTP instance and reading live application metrics. -The [Geocoder API](../sandbox/GeocoderAPI.md) allows you to geocode stop names. +The [Geocoder API](../sandbox/GeocoderAPI.md) allows you to geocode stop names and codes. ## Legacy APIs (to be removed) diff --git a/docs/examples/entur/otp-config.json b/docs/examples/entur/otp-config.json index 1746cd186d4..a072a3d4f30 100644 --- a/docs/examples/entur/otp-config.json +++ b/docs/examples/entur/otp-config.json @@ -6,7 +6,6 @@ "OptimizeTransfers": true, "TransferConstraints": true, "ParallelRouting": false, - "ReportApi" : true, - "VehicleToStopHeuristics": true + "ReportApi" : true } } \ No newline at end of file diff --git a/docs/sandbox/GeocoderAPI.md b/docs/sandbox/GeocoderAPI.md index 0405724fff6..f7a1be3f293 100644 --- a/docs/sandbox/GeocoderAPI.md +++ b/docs/sandbox/GeocoderAPI.md @@ -26,7 +26,7 @@ To enable this you need to add the feature to `otp-config.json`. The required geocode API for Stop and From/To searches in the debug client. -Path: `/otp/routers/{routerId}/geocode` +Path: `/otp/geocode` It supports the following URL parameters: @@ -40,12 +40,12 @@ It supports the following URL parameters: #### Stop clusters A stop cluster is a deduplicated groups of stops. This means that for any stop that has a parent -station only the parent is returned and for stops that have identical names and are very close +station only the parent is returned and for stops that have _identical_ names and are very close to each other, only one is returned. -This is useful for a general purpose fuzzy "stop" search. +This is useful for a general purpose fuzzy stop search. -Path: `/otp/routers/{routerId}/geocode/stopClusters` +Path: `/otp/geocode/stopClusters` It supports the following URL parameters: diff --git a/docs/sandbox/VehicleToStopHeuristics.md b/docs/sandbox/VehicleToStopHeuristics.md deleted file mode 100644 index 3197bbdca9d..00000000000 --- a/docs/sandbox/VehicleToStopHeuristics.md +++ /dev/null @@ -1,77 +0,0 @@ -# Vehicle-to-Stop heuristics - OTP Sandbox Extension - -## Contact Info - -- Leonard Ehrenfried ([mail@leonard.io](mailto:mail@leonard.io)) - -## Changelog - -- Create initial - implementation [#3906](https://github.com/opentripplanner/OpenTripPlanner/pull/3906) - -## Documentation - -This feature is meant to improve the performance and result quality for routing requests where a -vehicle (car, bike, scooter) is ridden to a stop where transit is boarded. - -Before this feature existed a search for nearby stops was executed finding all candidate stops for -boarding transit. For walking this yields a low number of stops but when driving a car this can -easily mean to an entire city of stops, since the default was a drive of 45 minutes. - -Having a very long driving time has several problems: - -- the access search itself is comparatively slow -- having many candidate stops slows down the transit search as many routes have to be checked -- often the quickest route would be to drive almost all the way to the destination and board transit - for a single stop - -We did not want to lower the maximum access time since in rural regions 45 minutes might be a useful -maximum, but we want it to scale with the density of well-connected stops. - -### Vehicle-to-stop heuristic - -In order to improve the Park+Ride and Bike+Ride results we reduced the number of candidate stops -with the following heuristic: - -1. When a stop is encountered check which routes depart from it -2. Each route adds to an "importance score" -3. Modes which are fast (RAIL, SUBWAY, FERRY) have a higher score than for example BUS -4. Once a maximum score is reached, the search is complete - -The code for this is located in `VehicleToStopSkipEdgeStrategy.java`. - -### Bicycle-on-transit heuristic - -This heuristic works slightly differently in that it doesn't assign a score but simply stops the -access search when a certain number of routes were encountered that allow you to take your bike onto -transit. - -The code for this is located in `BikeToStopSkipEdgeStrategy.java`. - -### Configuration - -Enable the feature by adding it to the ```otp-config.json```: - -```json -// otp-config.json -{ - "otpFeatures": { - "VehicleToStopHeuristics": true - } -} -``` - -### Collaborators wanted - -Since the current settings, scores and weights are hardcoded in the source code we are looking for -collaborators that can help to make it more adaptable for different environments. - -These are some the goals for the future: - -- make the scores that are assigned for routes of a certain mode configurable in JSON -- pre-calculate stop importance scores during the graph build - -If you want to help making this feature more flexible, please -contact [Leonard Ehrenfried](mailto:mail@leonard.io) -or use the regular channels of communication outlined -in [CONTRIBUTING.md](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CONTRIBUTING.md#primary-channels-of-communication) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index e4341b296fc..c4717d4d2e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,7 +106,6 @@ nav: - Park and Ride API: 'sandbox/ParkAndRideApi.md' - Data Overlay: 'sandbox/DataOverlay.md' - Vehicle Parking Updaters: 'sandbox/VehicleParking.md' - - Vehicle-to-stop Heuristics: 'sandbox/VehicleToStopHeuristics.md' - Geocoder API: 'sandbox/GeocoderAPI.md' - Travel Time Isochrones: 'sandbox/TravelTime.md' - IBI Accessibility Score: 'sandbox/IBIAccessibilityScore.md' diff --git a/pom.xml b/pom.xml index 6bdc75ccb30..406bcea932a 100644 --- a/pom.xml +++ b/pom.xml @@ -56,18 +56,18 @@ - 141 + 144 - 30.1 + 30.2 2.50 2.16.1 3.1.5 - 5.10.1 - 1.12.1 + 5.10.2 + 1.12.2 5.5.3 1.4.14 9.9.1 - 2.0.11 + 2.0.12 2.0.15 1.26 4.0.4 @@ -552,7 +552,7 @@ com.google.cloud libraries-bom - 26.27.0 + 26.31.0 pom import @@ -720,7 +720,7 @@ org.mockito mockito-core - 5.9.0 + 5.10.0 test @@ -746,7 +746,7 @@ com.google.guava guava - 32.1.3-jre + 33.0.0-jre @@ -890,7 +890,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.3 + 5.3.1 commons-cli diff --git a/smoke-tests/atlanta/router-config.json b/smoke-tests/atlanta/router-config.json index fe4c160bd12..b882746ab1f 100644 --- a/smoke-tests/atlanta/router-config.json +++ b/smoke-tests/atlanta/router-config.json @@ -16,6 +16,14 @@ "unknownCost": 600, "inaccessibleCost": 3600 } + }, + "accessEgress": { + "penalty": { + "FLEXIBLE" : { + "timePenalty" : "10m + 1.3t", + "costFactor" : 1.2 + } + } } } } diff --git a/smoke-tests/seattle/build-config.json b/smoke-tests/seattle/build-config.json index 2ab8a50787c..1f8d05db3cc 100644 --- a/smoke-tests/seattle/build-config.json +++ b/smoke-tests/seattle/build-config.json @@ -35,7 +35,7 @@ { "type" : "gtfs", "feedId" : "monorail", - "source" : "https://github.com/transitland/gtfs-archives-not-hosted-elsewhere/raw/master/seattlemonorail-wa-us.zip" + "source" : "https://gtfs.sound.obaweb.org/prod/96_gtfs.zip" } ], "transferRequests": [ diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java index a0585a9e6f2..f414572ecf2 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java @@ -32,6 +32,7 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty; import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.transit.service.TransitModel; @@ -224,6 +225,11 @@ private static List getItineraries( request.setTo(to); request.setNumItineraries(10); request.setSearchWindow(Duration.ofHours(2)); + request.withPreferences(p -> + p.withStreet(s -> + s.withAccessEgress(ae -> ae.withPenalty(Map.of(FLEXIBLE, TimeAndCostPenalty.ZERO))) + ) + ); var modes = request.journey().modes().copyOf(); modes.withEgressMode(FLEXIBLE); diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 371bcb249b6..b1fb33dfdd3 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; +import java.time.LocalDate; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -15,9 +16,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.opentripplanner.model.FeedInfo; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.site.StopLocation; @@ -28,57 +33,63 @@ class LuceneIndexTest { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + static final Agency BVG = Agency + .of(id("bvg")) + .withName("BVG") + .withTimezone("Europe/Berlin") + .build(); + // Berlin - static Station BERLIN_HAUPTBAHNHOF_STATION = TEST_MODEL + static final Station BERLIN_HAUPTBAHNHOF_STATION = TEST_MODEL .station("Hauptbahnhof") .withCoordinate(52.52495, 13.36952) .build(); - static Station ALEXANDERPLATZ_STATION = TEST_MODEL + static final Station ALEXANDERPLATZ_STATION = TEST_MODEL .station("Alexanderplatz") .withCoordinate(52.52277, 13.41046) .build(); - static RegularStop ALEXANDERPLATZ_BUS = TEST_MODEL + static final RegularStop ALEXANDERPLATZ_BUS = TEST_MODEL .stop("Alexanderplatz Bus") .withCoordinate(52.52277, 13.41046) .withVehicleType(BUS) .withParentStation(ALEXANDERPLATZ_STATION) .build(); - static RegularStop ALEXANDERPLATZ_RAIL = TEST_MODEL + static final RegularStop ALEXANDERPLATZ_RAIL = TEST_MODEL .stop("Alexanderplatz S-Bahn") .withCoordinate(52.52157, 13.41123) .withVehicleType(TransitMode.RAIL) .withParentStation(ALEXANDERPLATZ_STATION) .build(); - static RegularStop LICHTERFELDE_OST_1 = TEST_MODEL + static final RegularStop LICHTERFELDE_OST_1 = TEST_MODEL .stop("Lichterfelde Ost") .withId(id("lichterfelde-gleis-1")) .withCoordinate(52.42986, 13.32808) .build(); - static RegularStop LICHTERFELDE_OST_2 = TEST_MODEL + static final RegularStop LICHTERFELDE_OST_2 = TEST_MODEL .stop("Lichterfelde Ost") .withId(id("lichterfelde-gleis-2")) .withCoordinate(52.42985, 13.32807) .build(); - static RegularStop WESTHAFEN = TEST_MODEL + static final RegularStop WESTHAFEN = TEST_MODEL .stop("Westhafen") .withVehicleType(null) .withCoordinate(52.42985, 13.32807) .build(); // Atlanta - static Station FIVE_POINTS_STATION = TEST_MODEL + static final Station FIVE_POINTS_STATION = TEST_MODEL .station("Five Points") .withCoordinate(33.753899, -84.39156) .build(); - static RegularStop ARTS_CENTER = TEST_MODEL + static final RegularStop ARTS_CENTER = TEST_MODEL .stop("Arts Center") .withCode("4456") .withCoordinate(52.52277, 13.41046) .build(); - static RegularStop ARTHUR = TEST_MODEL + static final RegularStop ARTHUR = TEST_MODEL .stop("Arthur Langford Jr Pl SW at 220") .withCoordinate(52.52277, 13.41046) .build(); @@ -105,6 +116,7 @@ static void setup() { .of(ALEXANDERPLATZ_STATION, BERLIN_HAUPTBAHNHOF_STATION, FIVE_POINTS_STATION) .forEach(stopModel::withStation); var transitModel = new TransitModel(stopModel.build(), new Deduplicator()); + transitModel.index(); var transitService = new DefaultTransitService(transitModel) { private final Multimap modes = ImmutableMultimap .builder() @@ -119,6 +131,32 @@ public List getModesOfStopLocation(StopLocation stop) { return List.copyOf(modes.get(stop)); } } + + @Override + public Agency getAgencyForId(FeedScopedId id) { + if (id.equals(BVG.getId())) { + return BVG; + } + return null; + } + + @Override + public Set getRoutesForStop(StopLocation stop) { + return Set.of(TransitModelForTest.route("route1").withAgency(BVG).build()); + } + + @Override + public FeedInfo getFeedInfo(String feedId) { + return new FeedInfo( + "F", + "A Publisher", + "http://example.com", + "de", + LocalDate.MIN, + LocalDate.MIN, + "1" + ); + } }; index = new LuceneIndex(transitService); mapper = new StopClusterMapper(transitService); @@ -128,7 +166,7 @@ public List getModesOfStopLocation(StopLocation stop) { void stopLocations() { var result1 = index.queryStopLocations("lich", true).toList(); assertEquals(1, result1.size()); - assertEquals(LICHTERFELDE_OST_1.getName().toString(), result1.get(0).getName().toString()); + assertEquals(LICHTERFELDE_OST_1.getName().toString(), result1.getFirst().getName().toString()); var result2 = index.queryStopLocations("alexan", true).collect(Collectors.toSet()); assertEquals(Set.of(ALEXANDERPLATZ_BUS, ALEXANDERPLATZ_RAIL), result2); @@ -174,21 +212,22 @@ class StopClusters { } ) void stopClustersWithTypos(String searchTerm) { - var result1 = index.queryStopClusters(searchTerm).toList(); - assertEquals(List.of(mapper.map(ALEXANDERPLATZ_STATION)), result1); + var results = index.queryStopClusters(searchTerm).toList(); + var ids = results.stream().map(StopCluster::id).toList(); + assertEquals(List.of(ALEXANDERPLATZ_STATION.getId()), ids); } @Test void fuzzyStopClusters() { - var result1 = index.queryStopClusters("arts").toList(); - assertEquals(List.of(mapper.map(ARTS_CENTER).get()), result1); + var result1 = index.queryStopClusters("arts").map(StopCluster::id).toList(); + assertEquals(List.of(ARTS_CENTER.getId()), result1); } @Test void deduplicatedStopClusters() { var result = index.queryStopClusters("lich").toList(); assertEquals(1, result.size()); - assertEquals(LICHTERFELDE_OST_1.getName().toString(), result.get(0).name()); + assertEquals(LICHTERFELDE_OST_1.getName().toString(), result.getFirst().name()); } @ParameterizedTest @@ -220,8 +259,8 @@ void deduplicatedStopClusters() { } ) void stopClustersWithSpace(String query) { - var result = index.queryStopClusters(query).toList(); - assertEquals(List.of(mapper.map(FIVE_POINTS_STATION)), result); + var result = index.queryStopClusters(query).map(StopCluster::id).toList(); + assertEquals(List.of(FIVE_POINTS_STATION.getId()), result); } @ParameterizedTest @@ -229,16 +268,24 @@ void stopClustersWithSpace(String query) { void fuzzyStopCode(String query) { var result = index.queryStopClusters(query).toList(); assertEquals(1, result.size()); - assertEquals(ARTS_CENTER.getName().toString(), result.get(0).name()); + assertEquals(ARTS_CENTER.getName().toString(), result.getFirst().name()); } @Test void modes() { var result = index.queryStopClusters("westh").toList(); assertEquals(1, result.size()); - var stop = result.get(0); + var stop = result.getFirst(); assertEquals(WESTHAFEN.getName().toString(), stop.name()); assertEquals(List.of(FERRY.name(), BUS.name()), stop.modes()); } + + @Test + void agenciesAndFeedPublisher() { + var result = index.queryStopClusters("alexanderplatz").toList().getFirst(); + assertEquals(ALEXANDERPLATZ_STATION.getName().toString(), result.name()); + assertEquals(List.of(StopClusterMapper.toAgency(BVG)), result.agencies()); + assertEquals("A Publisher", result.feedPublisher().name()); + } } } diff --git a/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java b/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java index 38af8ea7187..d823a55cc4a 100644 --- a/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNamesTest.java @@ -37,8 +37,8 @@ void changeNames() { filter.decorate(itinerary); - var updatedLeg = itinerary.getLegs().get(0); - assertEquals(STOP_D.getName(), updatedLeg.getFrom().name); + var updatedLeg = itinerary.getLegs().getFirst(); + assertEquals(STOP_C.getName(), updatedLeg.getFrom().name); assertEquals(STOP_D.getName(), updatedLeg.getTo().name); } } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index 39ed6da297c..f5d1f950632 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -21,24 +21,30 @@ /** * OTP simple built-in geocoder used by the debug client. */ -@Path("/routers/{ignoreRouterId}/geocode") +@Path("/geocode") @Produces(MediaType.APPLICATION_JSON) public class GeocoderResource { private final OtpServerRequestContext serverContext; - /** - * @deprecated The support for multiple routers are removed from OTP2. See - * https://github.com/opentripplanner/OpenTripPlanner/issues/2760 - */ - @Deprecated - @PathParam("ignoreRouterId") - private String ignoreRouterId; - public GeocoderResource(@Context OtpServerRequestContext requestContext) { serverContext = requestContext; } + /** + * This class is only here for backwards-compatibility. It will be removed in the future. + */ + @Path("/routers/{ignoreRouterId}/geocode") + public static class GeocoderResourceOldPath extends GeocoderResource { + + public GeocoderResourceOldPath( + @Context OtpServerRequestContext serverContext, + @PathParam("ignoreRouterId") String ignore + ) { + super(serverContext); + } + } + /** * Geocode using data using the OTP graph for stops, clusters and street names * diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 71b0cf67b34..56769db0028 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -42,7 +42,6 @@ import org.apache.lucene.store.ByteBuffersDirectory; import org.opentripplanner.ext.geocoder.StopCluster.Coordinate; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; @@ -60,6 +59,7 @@ public class LuceneIndex implements Serializable { private static final String LAT = "latitude"; private static final String LON = "longitude"; private static final String MODE = "mode"; + private static final String AGENCY_IDS = "agency_ids"; private final TransitService transitService; private final Analyzer analyzer; @@ -67,6 +67,7 @@ public class LuceneIndex implements Serializable { public LuceneIndex(TransitService transitService) { this.transitService = transitService; + StopClusterMapper stopClusterMapper = new StopClusterMapper(transitService); this.analyzer = new PerFieldAnalyzerWrapper( @@ -80,7 +81,6 @@ public LuceneIndex(TransitService transitService) { var directory = new ByteBuffersDirectory(); - var stopClusterMapper = new StopClusterMapper(transitService); try { try ( var directoryWriter = new IndexWriter( @@ -99,6 +99,7 @@ public LuceneIndex(TransitService transitService) { stopLocation.getCode(), stopLocation.getCoordinate().latitude(), stopLocation.getCoordinate().longitude(), + Set.of(), Set.of() ) ); @@ -114,6 +115,7 @@ public LuceneIndex(TransitService transitService) { null, stopLocationsGroup.getCoordinate().latitude(), stopLocationsGroup.getCoordinate().longitude(), + Set.of(), Set.of() ) ); @@ -128,11 +130,12 @@ public LuceneIndex(TransitService transitService) { directoryWriter, StopCluster.class, stopCluster.id().toString(), - new NonLocalizedString(stopCluster.name()), + I18NString.of(stopCluster.name()), stopCluster.code(), stopCluster.coordinate().lat(), stopCluster.coordinate().lon(), - stopCluster.modes() + stopCluster.modes(), + stopCluster.agencyIds() ) ); } @@ -176,17 +179,34 @@ public Stream queryStopLocationGroups(String query, boolean * one of those is chosen at random and returned. */ public Stream queryStopClusters(String query) { - return matchingDocuments(StopCluster.class, query, false).map(LuceneIndex::toStopCluster); + return matchingDocuments(StopCluster.class, query, false).map(this::toStopCluster); } - private static StopCluster toStopCluster(Document document) { - var id = FeedScopedId.parse(document.get(ID)); + private StopCluster toStopCluster(Document document) { + var clusterId = FeedScopedId.parse(document.get(ID)); var name = document.get(NAME); var code = document.get(CODE); var lat = document.getField(LAT).numericValue().doubleValue(); var lon = document.getField(LON).numericValue().doubleValue(); var modes = Arrays.asList(document.getValues(MODE)); - return new StopCluster(id, code, name, new Coordinate(lat, lon), modes); + var agencies = Arrays + .stream(document.getValues(AGENCY_IDS)) + .map(id -> transitService.getAgencyForId(FeedScopedId.parse(id))) + .filter(Objects::nonNull) + .map(StopClusterMapper::toAgency) + .toList(); + var feedPublisher = StopClusterMapper.toFeedPublisher( + transitService.getFeedInfo(clusterId.getFeedId()) + ); + return new StopCluster( + clusterId, + code, + name, + new Coordinate(lat, lon), + modes, + agencies, + feedPublisher + ); } static IndexWriterConfig iwcWithSuggestField(Analyzer analyzer, final Set suggestFields) { @@ -214,7 +234,8 @@ private static void addToIndex( @Nullable String code, double latitude, double longitude, - Collection modes + Collection modes, + Collection agencyIds ) { String typeName = type.getSimpleName(); @@ -235,6 +256,9 @@ private static void addToIndex( for (var mode : modes) { document.add(new TextField(MODE, mode, Store.YES)); } + for (var ids : agencyIds) { + document.add(new TextField(AGENCY_IDS, ids, Store.YES)); + } try { writer.addDocument(document); diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneStopCluster.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneStopCluster.java new file mode 100644 index 00000000000..f58d7aa9af9 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneStopCluster.java @@ -0,0 +1,17 @@ +package org.opentripplanner.ext.geocoder; + +import java.util.Collection; +import javax.annotation.Nullable; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +/** + * A package-private helper type for transporting data before serializing. + */ +record LuceneStopCluster( + FeedScopedId id, + @Nullable String code, + String name, + StopCluster.Coordinate coordinate, + Collection modes, + Collection agencyIds +) {} diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java index afb60960ed4..8ffd44511fd 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopCluster.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.geocoder; import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -18,10 +19,22 @@ record StopCluster( @Nullable String code, String name, Coordinate coordinate, - Collection modes + Collection modes, + List agencies, + @Nullable FeedPublisher feedPublisher ) { /** * Easily serializable version of a coordinate */ public record Coordinate(double lat, double lon) {} + + /** + * Easily serializable version of an agency + */ + public record Agency(FeedScopedId id, String name) {} + + /** + * Easily serializable version of a feed publisher + */ + public record FeedPublisher(String name) {} } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index bc2f4f7022b..6f16d4a0cce 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -2,16 +2,20 @@ import com.google.common.collect.Iterables; import java.util.Collection; +import java.util.List; import java.util.Optional; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.FeedInfo; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; import org.opentripplanner.transit.service.TransitService; /** - * Mappers for generating {@link StopCluster} from the transit model. + * Mappers for generating {@link LuceneStopCluster} from the transit model. */ class StopClusterMapper { @@ -29,7 +33,7 @@ class StopClusterMapper { * - of "identical" stops which are very close to each other and have an identical name, only one * is chosen (at random) */ - Iterable generateStopClusters( + Iterable generateStopClusters( Collection stopLocations, Collection stopLocationsGroups ) { @@ -55,35 +59,67 @@ Iterable generateStopClusters( return Iterables.concat(deduplicatedStops, stations); } - StopCluster map(StopLocationsGroup g) { + LuceneStopCluster map(StopLocationsGroup g) { var modes = transitService.getModesOfStopLocationsGroup(g).stream().map(Enum::name).toList(); - return new StopCluster( + var agencies = agenciesForStopLocationsGroup(g) + .stream() + .map(s -> s.getId().toString()) + .toList(); + return new LuceneStopCluster( g.getId(), null, g.getName().toString(), toCoordinate(g.getCoordinate()), - modes + modes, + agencies ); } - Optional map(StopLocation sl) { + Optional map(StopLocation sl) { + var agencies = agenciesForStopLocation(sl).stream().map(a -> a.getId().toString()).toList(); return Optional .ofNullable(sl.getName()) .map(name -> { var modes = transitService.getModesOfStopLocation(sl).stream().map(Enum::name).toList(); - return new StopCluster( + return new LuceneStopCluster( sl.getId(), sl.getCode(), name.toString(), toCoordinate(sl.getCoordinate()), - modes + modes, + agencies ); }); } + private List agenciesForStopLocation(StopLocation stop) { + return transitService.getRoutesForStop(stop).stream().map(Route::getAgency).distinct().toList(); + } + + private List agenciesForStopLocationsGroup(StopLocationsGroup group) { + return group + .getChildStops() + .stream() + .flatMap(sl -> agenciesForStopLocation(sl).stream()) + .distinct() + .toList(); + } + private static StopCluster.Coordinate toCoordinate(WgsCoordinate c) { return new StopCluster.Coordinate(c.latitude(), c.longitude()); } + static StopCluster.Agency toAgency(Agency a) { + return new StopCluster.Agency(a.getId(), a.getName()); + } + + static StopCluster.FeedPublisher toFeedPublisher(FeedInfo fi) { + if (fi == null) { + return null; + } else { + return new StopCluster.FeedPublisher(fi.getPublisherName()); + } + } + private record DeduplicationKey(I18NString name, WgsCoordinate coordinate) {} } diff --git a/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java b/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java index 02dfc1bef5d..4a577433bfe 100644 --- a/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java +++ b/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java @@ -42,7 +42,7 @@ public ParkAndRideResource( // - serverContext.graphFinder(). This needs at least a comment! // - This can be replaced with a search done with the StopModel // - if we have a radius search there. - this.graphFinder = new DirectGraphFinder(serverContext.transitService()::findRegularStop); + this.graphFinder = new DirectGraphFinder(serverContext.transitService()::findRegularStops); } /** Envelopes are in latitude, longitude format */ diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java index 5bdda8a57a2..cab2be13ad0 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java @@ -200,7 +200,7 @@ public List getStopsInRadius( radius = Math.min(radius, MAX_STOP_SEARCH_RADIUS); - return new DirectGraphFinder(serverContext.transitService()::findRegularStop) + return new DirectGraphFinder(serverContext.transitService()::findRegularStops) .findClosestStops(new Coordinate(lon, lat), radius) .stream() .map(it -> StopMapper.mapToApiShort(it.stop, it.distance)) @@ -221,7 +221,7 @@ public List getStopsInRadius( new Coordinate(maxLon, maxLat) ); - var stops = transitService().findRegularStop(envelope); + var stops = transitService().findRegularStops(envelope); return stops .stream() .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java index bfac498ff8c..bf44da6be00 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java @@ -89,8 +89,8 @@ private void mapBike() { bike.withWalking(walk -> { setIfNotNull(req.bikeWalkingSpeed, walk::withSpeed); setIfNotNull(req.bikeWalkingReluctance, walk::withReluctance); - setIfNotNull(req.bikeSwitchTime, walk::withHopTime); - setIfNotNull(req.bikeSwitchCost, walk::withHopCost); + setIfNotNull(req.bikeSwitchTime, walk::withMountDismountTime); + setIfNotNull(req.bikeSwitchCost, walk::withMountDismountCost); }); }); } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java index 8fcba82a357..a287e6a7d66 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java @@ -7,9 +7,9 @@ import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator; /** - * A decorating filter that checks if a transit leg contains any primary stops and if it does, - * then replaces it with the secondary, agency-specific stop name. This is so that the in-vehicle - * display matches what OTP returns as a board/alight stop name. + * A decorating filter that checks if a transit leg contains any consolidated stops and if it does, + * then replaces it with the appropriate, agency-specific stop name. This is so that the physical + * signage and in-vehicle display matches what OTP returns as a board/alight stop name. */ public class DecorateConsolidatedStopNames implements ItineraryDecorator { @@ -21,31 +21,45 @@ public DecorateConsolidatedStopNames(StopConsolidationService service) { @Override public void decorate(Itinerary itinerary) { - replacePrimaryNamesWithSecondary(itinerary); + replaceConsolidatedStops(itinerary); } /** - * If the itinerary has a from/to that is the primary stop of a {@link org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup} - * then we replace its name with the secondary name of the agency that is - * operating the route, so that the name in the result matches the name in the in-vehicle - * display. + * If the itinerary has a "from" stop that is the secondary stop of a + * {@link org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup} + * then we replace its name with the primary name of the agency that is + * operating the route, so that the name in the result matches the physical signage on the stop. + *

+ * If the leg has a "to" stop that is a primary stop, then we don't want to show the stop that's on + * the signage but what is shown _inside_ the vehicle. That's why we use the agency-specific (aka + * secondary) stop. + *

+ * This follows the somewhat idiosyncratic logic of the consolidated stops feature. */ - private void replacePrimaryNamesWithSecondary(Itinerary i) { + private void replaceConsolidatedStops(Itinerary i) { i.transformTransitLegs(leg -> { if (leg instanceof ScheduledTransitLeg stl && needsToRenameStops(stl)) { var agency = leg.getAgency(); - return new ConsolidatedStopLeg( - stl, - service.agencySpecificName(stl.getFrom().stop, agency), - service.agencySpecificName(stl.getTo().stop, agency) - ); + // to show the name on the stop signage we use the primary stop's name + var from = service.primaryStop(stl.getFrom().stop.getId()); + // 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); } else { return leg; } }); } + /** + * Figures out if the from/to stops are part of a consolidated stop group and therefore + * some stops need to be replaced. + *

+ * Please consult the Javadoc of {@link DecorateConsolidatedStopNames#replaceConsolidatedStops(Itinerary)} + * for details of this idiosyncratic business logic and in particular why the logic is not the same + * for the from/to stops. + */ private boolean needsToRenameStops(ScheduledTransitLeg stl) { - return (service.isPrimaryStop(stl.getFrom().stop) || service.isPrimaryStop(stl.getTo().stop)); + return (service.isSecondaryStop(stl.getFrom().stop) || service.isPrimaryStop(stl.getTo().stop)); } } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java index 2418d8f5625..a829a905e27 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java @@ -2,7 +2,6 @@ import java.util.List; import org.opentripplanner.ext.stopconsolidation.model.StopReplacement; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.site.StopLocation; @@ -24,13 +23,23 @@ public interface StopConsolidationService { */ boolean isPrimaryStop(StopLocation stop); + /** + * Is the given stop a secondary stop as defined by the stop consolidation configuration? + */ + boolean isSecondaryStop(StopLocation stop); + /** * Are any stop consolidations defined? */ boolean isActive(); /** - * For a given primary stop look up the name as it was originally defined in the agency's feed. + * For a given primary stop look up secondary feed as it was originally defined in the agency's feed. + */ + StopLocation agencySpecificStop(StopLocation stop, Agency agency); + + /** + * For a given stop id return the primary stop if it is part of a consolidated stop group. */ - I18NString agencySpecificName(StopLocation stop, Agency agency); + StopLocation 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 54bd960d078..51a57028121 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java @@ -2,14 +2,15 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; +import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup; import org.opentripplanner.ext.stopconsolidation.model.StopReplacement; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.organization.Agency; -import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitModel; import org.slf4j.Logger; @@ -61,26 +62,46 @@ public boolean isPrimaryStop(StopLocation stop) { return repo.groups().stream().anyMatch(r -> r.primary().equals(stop.getId())); } + @Override + public boolean isSecondaryStop(StopLocation stop) { + return repo.groups().stream().anyMatch(r -> r.secondaries().contains(stop.getId())); + } + @Override public boolean isActive() { return !repo.groups().isEmpty(); } @Override - public I18NString agencySpecificName(StopLocation stop, Agency agency) { + public StopLocation agencySpecificStop(StopLocation stop, Agency agency) { if (agency.getId().getFeedId().equals(stop.getId().getFeedId())) { - return stop.getName(); + return stop; } else { - return repo - .groups() - .stream() - .filter(r -> r.primary().equals(stop.getId())) - .flatMap(g -> g.secondaries().stream()) - .filter(secondary -> secondary.getFeedId().equals(agency.getId().getFeedId())) - .findAny() - .map(id -> transitModel.getStopModel().getRegularStop(id)) - .map(RegularStop::getName) - .orElseGet(stop::getName); + return findAgencySpecificStop(stop, agency).orElse(stop); } } + + @Nonnull + private Optional findAgencySpecificStop(StopLocation stop, Agency agency) { + return repo + .groups() + .stream() + .filter(r -> r.primary().equals(stop.getId())) + .flatMap(g -> g.secondaries().stream()) + .filter(secondary -> secondary.getFeedId().equals(agency.getId().getFeedId())) + .findAny() + .map(id -> transitModel.getStopModel().getRegularStop(id)); + } + + @Override + public StopLocation primaryStop(FeedScopedId id) { + var primaryId = repo + .groups() + .stream() + .filter(g -> g.secondaries().contains(id)) + .map(ConsolidatedStopGroup::primary) + .findAny() + .orElse(id); + return 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 e201c7ed805..39f06bd6347 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java @@ -1,28 +1,28 @@ package org.opentripplanner.ext.stopconsolidation.model; -import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.model.plan.ScheduledTransitLegBuilder; +import org.opentripplanner.transit.model.site.StopLocation; public class ConsolidatedStopLeg extends ScheduledTransitLeg { - private final I18NString fromName; - private final I18NString toName; + private final StopLocation from; + private final StopLocation to; - public ConsolidatedStopLeg(ScheduledTransitLeg original, I18NString fromName, I18NString toName) { + public ConsolidatedStopLeg(ScheduledTransitLeg original, StopLocation from, StopLocation to) { super(new ScheduledTransitLegBuilder<>(original)); - this.fromName = fromName; - this.toName = toName; + this.from = from; + this.to = to; } @Override public Place getFrom() { - return Place.forStop(super.getFrom().stop, fromName); + return Place.forStop(from); } @Override public Place getTo() { - return Place.forStop(super.getTo().stop, toName); + return Place.forStop(to); } } diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java index 6d15816669e..2ad85587fe5 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java @@ -37,7 +37,7 @@ public StopsLayerBuilder( protected List getGeometries(Envelope query) { return transitService - .findRegularStop(query) + .findRegularStops(query) .stream() .map(stop -> { Geometry point = stop.getGeometry(); diff --git a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java b/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java deleted file mode 100644 index ad503b1212d..00000000000 --- a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.opentripplanner.ext.vehicletostopheuristics; - -import java.util.Collection; -import java.util.function.Function; -import org.opentripplanner.astar.spi.SkipEdgeStrategy; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.network.BikeAccess; -import org.opentripplanner.transit.model.site.RegularStop; -import org.opentripplanner.transit.model.timetable.Trip; - -/** - * When wanting to take a bike onto transit we want to improve the performance by limiting the - * number of accesses to those stops which actually have trips where you can take the bike on. Once - * we have reached enough of trips we skip all further edges. - */ -public class BikeToStopSkipEdgeStrategy implements SkipEdgeStrategy { - - private static final int LIMIT = 100; - private static final double MAX_FACTOR = 1.2; - - private final Function> getTripsForStop; - - int numberOfBikeableTripsReached = 0; - double distanceLimit = Double.MAX_VALUE; - - public BikeToStopSkipEdgeStrategy(Function> getTripsForStop) { - this.getTripsForStop = getTripsForStop; - } - - public static boolean bikeAccessForTrip(Trip trip) { - if (trip.getBikesAllowed() != BikeAccess.UNKNOWN) { - return trip.getBikesAllowed() == BikeAccess.ALLOWED; - } - - return trip.getRoute().getBikesAllowed() == BikeAccess.ALLOWED; - } - - @Override - public boolean shouldSkipEdge(State current, Edge edge) { - if ( - current.getVertex() instanceof TransitStopVertex stopVertex && - distanceLimit == Double.MAX_VALUE - ) { - numberOfBikeableTripsReached += - getTripsForStop - .apply(stopVertex.getStop()) - .stream() - .filter(BikeToStopSkipEdgeStrategy::bikeAccessForTrip) - .count(); - if (numberOfBikeableTripsReached >= LIMIT) { - distanceLimit = current.getWalkDistance() * MAX_FACTOR; - } - } - return current.getWalkDistance() > distanceLimit; - } -} diff --git a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java b/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java deleted file mode 100644 index 3eb466f97b9..00000000000 --- a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.opentripplanner.ext.vehicletostopheuristics; - -import static org.opentripplanner.routing.api.request.StreetMode.BIKE_RENTAL; -import static org.opentripplanner.routing.api.request.StreetMode.BIKE_TO_PARK; -import static org.opentripplanner.routing.api.request.StreetMode.CAR_HAILING; -import static org.opentripplanner.routing.api.request.StreetMode.CAR_PICKUP; -import static org.opentripplanner.routing.api.request.StreetMode.CAR_RENTAL; -import static org.opentripplanner.routing.api.request.StreetMode.CAR_TO_PARK; -import static org.opentripplanner.routing.api.request.StreetMode.SCOOTER_RENTAL; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Function; -import org.opentripplanner.astar.spi.SkipEdgeStrategy; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.request.filter.TransitFilter; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.basic.TransitMode; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.site.RegularStop; - -/** - * This strategy terminates when enough "important" stops are found. - *

- * The definition of important is a stop where many routes of mode RAIL, SUBWAY or FERRY depart. - *

- * This means that the search radius scales with density of "important" stops: - *

- *

  • in a city the radius is quite small - *
  • in more rural regions the radius is bigger and stops further away are considered - *

    - * The strategy is useful when you want to limit the number of accesses of Park+Ride, Bike+Ride and - * Bike+Transit: it improves both performance the quality of results. - *

    - * {@see https://github.com/opentripplanner/OpenTripPlanner/pull/3906} - */ -public class VehicleToStopSkipEdgeStrategy implements SkipEdgeStrategy { - - public static final Set applicableModes = Set.of( - BIKE_TO_PARK, - BIKE_RENTAL, - CAR_TO_PARK, - CAR_PICKUP, - CAR_HAILING, - CAR_RENTAL, - SCOOTER_RENTAL - ); - private final Function> getPatternsForStop; - private final int maxScore; - private final List filters; - private double sumOfScores; - - private final Set stopsCounted = new HashSet<>(); - - public VehicleToStopSkipEdgeStrategy( - Function> getPatternsForStop, - Collection filters - ) { - this.filters = new ArrayList<>(filters); - this.maxScore = 300; - this.getPatternsForStop = getPatternsForStop; - } - - @Override - public boolean shouldSkipEdge(State current, Edge edge) { - if (current.currentMode().isWalking()) { - if ( - current.getVertex() instanceof TransitStopVertex stopVertex && - !stopsCounted.contains(stopVertex.getStop().getId()) - ) { - // TODO: 2022-12-05 filters: check performance on that and verify that this is right. Previously we were filtering just on modes - var stop = stopVertex.getStop(); - - // Not using streams. Performance is important here - var patterns = getPatternsForStop.apply(stop); - var score = 0; - for (var pattern : patterns) { - for (var filter : filters) { - if (filter.matchTripPattern(pattern)) { - score += VehicleToStopSkipEdgeStrategy.score(pattern.getMode()); - break; - } - } - } - - stopsCounted.add(stop.getId()); - - sumOfScores = sumOfScores + score; - } - return false; - } else { - return sumOfScores >= maxScore; - } - } - - private static int score(TransitMode mode) { - return switch (mode) { - case RAIL, FERRY, SUBWAY -> 20; - case BUS -> 1; - default -> 2; - }; - } -} diff --git a/src/main/java/org/opentripplanner/apis/APIEndpoints.java b/src/main/java/org/opentripplanner/apis/APIEndpoints.java index 959815f1716..b6b70eb238e 100644 --- a/src/main/java/org/opentripplanner/apis/APIEndpoints.java +++ b/src/main/java/org/opentripplanner/apis/APIEndpoints.java @@ -63,6 +63,8 @@ private APIEndpoints() { addIfEnabled(SandboxAPIMapboxVectorTilesApi, VectorTilesResource.class); addIfEnabled(SandboxAPIParkAndRideApi, ParkAndRideResource.class); addIfEnabled(SandboxAPIGeocoder, GeocoderResource.class); + // scheduled to be removed and only here for backwards compatibility + addIfEnabled(SandboxAPIGeocoder, GeocoderResource.GeocoderResourceOldPath.class); addIfEnabled(SandboxAPITravelTime, TravelTimeResource.class); // scheduled to be removed diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index d749987384c..a4f5cb00e2f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; @@ -710,7 +709,7 @@ public DataFetcher> stopsByBbox() { ); Stream stopStream = getTransitService(environment) - .findRegularStop(envelope) + .findRegularStops(envelope) .stream() .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json index 6a2eb3343b9..db865eaa003 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json @@ -10,8 +10,8 @@ }, "license": "LGPL-3.0", "dependencies": { - "@graphql-codegen/add": "5.0.0", - "@graphql-codegen/cli": "5.0.0", + "@graphql-codegen/add": "5.0.2", + "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/java": "4.0.0", "@graphql-codegen/java-resolvers": "3.0.0", "graphql": "16.8.1" diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock index 7e3c815cfb1..fffe602db18 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock @@ -264,7 +264,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== -"@babel/helper-plugin-utils@^7.22.5": +"@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== @@ -699,24 +699,25 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@graphql-codegen/add@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.0.tgz#578ebaf4fa87c1e934c381cd679bcedcf79feaba" - integrity sha512-ynWDOsK2yxtFHwcJTB9shoSkUd7YXd6ZE57f0nk7W5cu/nAgxZZpEsnTPEpZB/Mjf14YRGe2uJHQ7AfElHjqUQ== +"@graphql-codegen/add@5.0.2", "@graphql-codegen/add@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.2.tgz#71b3ae0465a4537172dddb84531b6967ca5545f2" + integrity sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ== dependencies: - "@graphql-codegen/plugin-helpers" "^5.0.0" - tslib "~2.5.0" + "@graphql-codegen/plugin-helpers" "^5.0.3" + tslib "~2.6.0" -"@graphql-codegen/cli@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-5.0.0.tgz#761dcf08cfee88bbdd9cdf8097b2343445ec6f0a" - integrity sha512-A7J7+be/a6e+/ul2KI5sfJlpoqeqwX8EzktaKCeduyVKgOLA6W5t+NUGf6QumBDXU8PEOqXk3o3F+RAwCWOiqA== +"@graphql-codegen/cli@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-5.0.2.tgz#07ff691c16da4c3dcc0e1995d3231530379ab317" + integrity sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw== dependencies: "@babel/generator" "^7.18.13" "@babel/template" "^7.18.10" "@babel/types" "^7.18.13" - "@graphql-codegen/core" "^4.0.0" - "@graphql-codegen/plugin-helpers" "^5.0.1" + "@graphql-codegen/client-preset" "^4.2.2" + "@graphql-codegen/core" "^4.0.2" + "@graphql-codegen/plugin-helpers" "^5.0.3" "@graphql-tools/apollo-engine-loader" "^8.0.0" "@graphql-tools/code-file-loader" "^8.0.0" "@graphql-tools/git-loader" "^8.0.0" @@ -747,15 +748,45 @@ yaml "^2.3.1" yargs "^17.0.0" -"@graphql-codegen/core@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-4.0.0.tgz#b29c911746a532a675e33720acb4eb2119823e01" - integrity sha512-JAGRn49lEtSsZVxeIlFVIRxts2lWObR+OQo7V2LHDJ7ohYYw3ilv7nJ8pf8P4GTg/w6ptcYdSdVVdkI8kUHB/Q== +"@graphql-codegen/client-preset@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/client-preset/-/client-preset-4.2.2.tgz#545c62789a5687bee5df8b4738b4911e72ea8051" + integrity sha512-DF9pNWj3TEdA90E9FH5SsUIqiZfr872vqaQOspLVuVXGsaDx8F/JLLzaN+7ucmoo0ff/bLW8munVXYXTmgwwEA== dependencies: - "@graphql-codegen/plugin-helpers" "^5.0.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/template" "^7.20.7" + "@graphql-codegen/add" "^5.0.2" + "@graphql-codegen/gql-tag-operations" "4.0.4" + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-codegen/typed-document-node" "^5.0.4" + "@graphql-codegen/typescript" "^4.0.4" + "@graphql-codegen/typescript-operations" "^4.1.2" + "@graphql-codegen/visitor-plugin-common" "^4.1.2" + "@graphql-tools/documents" "^1.0.0" + "@graphql-tools/utils" "^10.0.0" + "@graphql-typed-document-node/core" "3.2.0" + tslib "~2.6.0" + +"@graphql-codegen/core@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-4.0.2.tgz#7e6ec266276f54bbf02f60599d9e518f4a59d85e" + integrity sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" "@graphql-tools/schema" "^10.0.0" "@graphql-tools/utils" "^10.0.0" - tslib "~2.5.0" + tslib "~2.6.0" + +"@graphql-codegen/gql-tag-operations@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.4.tgz#572be5db804af5efdc3ca24e4bcac815448730c5" + integrity sha512-dypul0iDLjb07yv+/cRb6qPbn42cFPcwlsJertVl9G6qkS4+3V4806WwSfUht4QVMWnvGfgDkJJqG0yUVKOHwA== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-codegen/visitor-plugin-common" "4.1.2" + "@graphql-tools/utils" "^10.0.0" + auto-bind "~4.0.0" + tslib "~2.6.0" "@graphql-codegen/java-common@^3.0.0": version "3.0.0" @@ -813,29 +844,59 @@ lodash "~4.17.0" tslib "~2.4.0" -"@graphql-codegen/plugin-helpers@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.0.tgz#40c18217454af5cf8317e5f46cf4d38e8cc78ae4" - integrity sha512-suL2ZMkBAU2a4YbBHaZvUPsV1z0q3cW6S96Z/eYYfkRIsJoe2vN+wNZ9Xdzmqx0JLmeeFCBSoBGC0imFyXlkDQ== +"@graphql-codegen/plugin-helpers@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.3.tgz#7027b9d911d7cb594663590fcf5d63e9cf7ec2ff" + integrity sha512-yZ1rpULIWKBZqCDlvGIJRSyj1B2utkEdGmXZTBT/GVayP4hyRYlkd36AJV/LfEsVD8dnsKL5rLz2VTYmRNlJ5Q== dependencies: "@graphql-tools/utils" "^10.0.0" change-case-all "1.0.15" common-tags "1.8.2" import-from "4.0.0" lodash "~4.17.0" - tslib "~2.5.0" + tslib "~2.6.0" -"@graphql-codegen/plugin-helpers@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.1.tgz#e2429fcfba3f078d5aa18aa062d46c922bbb0d55" - integrity sha512-6L5sb9D8wptZhnhLLBcheSPU7Tg//DGWgc5tQBWX46KYTOTQHGqDpv50FxAJJOyFVJrveN9otWk9UT9/yfY4ww== +"@graphql-codegen/schema-ast@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz#aeaa104e4555cca73a058f0a9350b4b0e290b377" + integrity sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q== dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" "@graphql-tools/utils" "^10.0.0" + tslib "~2.6.0" + +"@graphql-codegen/typed-document-node@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.4.tgz#06e286caacdd66c3566f98433dcb8f1a9c9a9f1d" + integrity sha512-t66Z6erQ4Dh1j6f9pRZmc8uYtHoUI3A49tLmJAlg9/3IV0kCmwrWKJut/G8SeOefDLG8cXBTVtI/YuZOe1Te+w== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-codegen/visitor-plugin-common" "4.1.2" + auto-bind "~4.0.0" change-case-all "1.0.15" - common-tags "1.8.2" - import-from "4.0.0" - lodash "~4.17.0" - tslib "~2.5.0" + tslib "~2.6.0" + +"@graphql-codegen/typescript-operations@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-4.1.2.tgz#a0f455ae19e16961e5870420ca7515bbe51b5568" + integrity sha512-CtCWK+gW7hS+Ely3lohr8CL1HVLswQzMcaUk3k1sxdWCWKTNq7abMsWa31rTVwRCJ+WNEkM/7S8sIBTpEG683A== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-codegen/typescript" "^4.0.4" + "@graphql-codegen/visitor-plugin-common" "4.1.2" + auto-bind "~4.0.0" + tslib "~2.6.0" + +"@graphql-codegen/typescript@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-4.0.4.tgz#e791c61f675ae454951ea077b0ae519ae352cc3e" + integrity sha512-x79CKLfP9UQCX+/I78qxQlMs2Mmq3pF1lKafZo7lAno0f/fvJ+qWUduzdgjRNz+YL+5blGeWcC0pWEDxniO7hw== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-codegen/schema-ast" "^4.0.2" + "@graphql-codegen/visitor-plugin-common" "4.1.2" + auto-bind "~4.0.0" + tslib "~2.6.0" "@graphql-codegen/visitor-plugin-common@2.13.1": version "2.13.1" @@ -853,6 +914,22 @@ parse-filepath "^1.0.2" tslib "~2.4.0" +"@graphql-codegen/visitor-plugin-common@4.1.2", "@graphql-codegen/visitor-plugin-common@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-4.1.2.tgz#674c5d5813f6c00dd65e1ee148a62536879e65e2" + integrity sha512-yk7iEAL1kYZ2Gi/pvVjdsZhul5WsYEM4Zcgh2Ev15VicMdJmPHsMhNUsZWyVJV0CaQCYpNOFlGD/11Ea3pn4GA== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + "@graphql-tools/optimize" "^2.0.0" + "@graphql-tools/relay-operation-optimizer" "^7.0.0" + "@graphql-tools/utils" "^10.0.0" + auto-bind "~4.0.0" + change-case-all "1.0.15" + dependency-graph "^0.11.0" + graphql-tag "^2.11.0" + parse-filepath "^1.0.2" + tslib "~2.6.0" + "@graphql-tools/apollo-engine-loader@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.0.tgz#ac1f351cbe41508411784f25757f5557b0f27489" @@ -897,6 +974,14 @@ tslib "^2.5.0" value-or-promise "^1.0.12" +"@graphql-tools/documents@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/documents/-/documents-1.0.0.tgz#e3ed97197cc22ec830ca227fd7d17e86d8424bdf" + integrity sha512-rHGjX1vg/nZ2DKqRGfDPNC55CWZBMldEVcH+91BThRa6JeT80NqXknffLLEZLRUxyikCfkwMsk6xR3UNMqG0Rg== + dependencies: + lodash.sortby "^4.7.0" + tslib "^2.4.0" + "@graphql-tools/executor-graphql-ws@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.0.2.tgz#29890f9370c5bebd4a2380e29904f8eaf9f013ca" @@ -1036,6 +1121,13 @@ dependencies: tslib "^2.4.0" +"@graphql-tools/optimize@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/optimize/-/optimize-2.0.0.tgz#7a9779d180824511248a50c5a241eff6e7a2d906" + integrity sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg== + dependencies: + tslib "^2.4.0" + "@graphql-tools/prisma-loader@^8.0.0": version "8.0.1" resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-8.0.1.tgz#0a013c69b04e0779b5be15757173d458cdf94e35" @@ -1069,6 +1161,15 @@ "@graphql-tools/utils" "9.1.1" tslib "^2.4.0" +"@graphql-tools/relay-operation-optimizer@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.0.tgz#24367666af87bc5a81748de5e8e9b3c523fd4207" + integrity sha512-UNlJi5y3JylhVWU4MBpL0Hun4Q7IoJwv9xYtmAz+CgRa066szzY7dcuPfxrA7cIGgG/Q6TVsKsYaiF4OHPs1Fw== + dependencies: + "@ardatan/relay-compiler" "12.0.0" + "@graphql-tools/utils" "^10.0.0" + tslib "^2.4.0" + "@graphql-tools/schema@^10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-10.0.0.tgz#7b5f6b6a59f51c927de8c9069bde4ebbfefc64b3" @@ -2374,6 +2475,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== + lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3049,7 +3155,7 @@ ts-log@^2.2.3: resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb" integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w== -tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.1, tslib@~2.5.0: +tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.1: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 212bfbcf380..5bc00cb7a85 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -340,8 +340,8 @@ private static void setVehicleWalkingPreferences( ) { callWith.argument("bikeWalkingReluctance", walking::withReluctance); callWith.argument("bikeWalkingSpeed", walking::withSpeed); - callWith.argument("bikeSwitchTime", time -> walking.withHopTime((int) time)); - callWith.argument("bikeSwitchCost", cost -> walking.withHopCost((int) cost)); + callWith.argument("bikeSwitchTime", time -> walking.withMountDismountTime((int) time)); + callWith.argument("bikeSwitchCost", cost -> walking.withMountDismountCost((int) cost)); } private static class CallerWithEnvironment { diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 00bf98c703f..638d7783e9a 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -687,7 +687,7 @@ private GraphQLSchema create() { ); return GqlUtil .getTransitService(environment) - .findRegularStop(envelope) + .findRegularStops(envelope) .stream() .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) .filter(stop -> diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java index accaf1e35fb..d178e5125b5 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java @@ -531,7 +531,7 @@ public static Collection fetchStopPlaces( ); Stream stations = transitService - .findRegularStop(envelope) + .findRegularStops(envelope) .stream() .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) .map(StopLocation::getParentStation) diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java index 03f4357e540..dc6ba627e0e 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java @@ -179,7 +179,7 @@ private static LayerBuilder createLayerBuilder( case RegularStop -> new StopLayerBuilder<>( layerParameters, locale, - e -> context.transitService().findRegularStop(e) + e -> context.transitService().findRegularStops(e) ); case AreaStop -> new StopLayerBuilder<>( layerParameters, diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 4847b204077..4ae5004cf6b 100644 --- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -105,8 +105,7 @@ public enum OTPFeature { SandboxAPIMapboxVectorTilesApi(false, true, "Enable Mapbox vector tiles API."), SandboxAPIParkAndRideApi(false, true, "Enable park-and-ride endpoint."), SandboxAPITravelTime(false, true, "Enable the isochrone/travel time surface API."), - TransferAnalyzer(false, true, "Analyze transfers during graph build."), - VehicleToStopHeuristics(false, true, "Enable improved heuristic for park-and-ride queries."); + TransferAnalyzer(false, true, "Analyze transfers during graph build."); private static final Object TEST_LOCK = new Object(); diff --git a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java index c726d03c66c..cbb32b9eaca 100644 --- a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java @@ -110,4 +110,13 @@ public static String padRight(String value, char ch, int width) { public static String quoteReplace(@Nonnull String text) { return text.replace('\'', '\"'); } + + /** + * Convert "HELLO_WORLD" or "HellO_WorlD" to "hello-world". + *

    + * https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case + */ + public static String kebabCase(String input) { + return input.toLowerCase().replace('_', '-'); + } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java index 360cdaee363..7386b60b452 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java @@ -18,8 +18,6 @@ import org.opentripplanner.astar.strategy.MaxCountSkipEdgeStrategy; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.ext.flex.trip.FlexTrip; -import org.opentripplanner.ext.vehicletostopheuristics.BikeToStopSkipEdgeStrategy; -import org.opentripplanner.ext.vehicletostopheuristics.VehicleToStopSkipEdgeStrategy; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.routing.api.request.RouteRequest; @@ -85,7 +83,7 @@ public NearbyStopFinder( // We need to accommodate straight line distance (in meters) but when streets are present we // use an earliest arrival search, which optimizes on time. Ideally we'd specify in meters, // but we don't have much of a choice here. Use the default walking speed to convert. - this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStop); + this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStops); } } @@ -205,7 +203,7 @@ public List findNearbyStopsViaStreets( ShortestPathTree spt = StreetSearchBuilder .of() - .setSkipEdgeStrategy(getSkipEdgeStrategy(reverseDirection, request)) + .setSkipEdgeStrategy(getSkipEdgeStrategy()) .setDominanceFunction(new DominanceFunctions.MinimumWeight()) .setRequest(request) .setArriveBy(reverseDirection) @@ -271,48 +269,14 @@ private List findNearbyStopsViaDirectTransfers(Vertex vertex) { return directGraphFinder.findClosestStops(c0, limitMeters); } - private SkipEdgeStrategy getSkipEdgeStrategy( - boolean reverseDirection, - RouteRequest routingRequest - ) { + private SkipEdgeStrategy getSkipEdgeStrategy() { var durationSkipEdgeStrategy = new DurationSkipEdgeStrategy(durationLimit); - // if we compute the accesses for Park+Ride, Bike+Ride and Bike+Transit we don't want to - // search the full durationLimit as this returns way too many stops. - // this is both slow and returns suboptimal results as it favours long drives with short - // transit legs. - // therefore, we use a heuristic based on the number of routes and their mode to determine - // what are "good" stops for those accesses. if we have reached a threshold of "good" stops - // we stop the access search. - if ( - !reverseDirection && - OTPFeature.VehicleToStopHeuristics.isOn() && - VehicleToStopSkipEdgeStrategy.applicableModes.contains( - routingRequest.journey().access().mode() - ) - ) { - var strategy = new VehicleToStopSkipEdgeStrategy( - transitService::getPatternsForStop, - routingRequest.journey().transit().filters() - ); - + if (maxStopCount > 0) { + var strategy = new MaxCountSkipEdgeStrategy<>(maxStopCount, NearbyStopFinder::hasReachedStop); return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy); - } else if ( - OTPFeature.VehicleToStopHeuristics.isOn() && - routingRequest.journey().access().mode() == StreetMode.BIKE - ) { - var strategy = new BikeToStopSkipEdgeStrategy(transitService::getTripsForStop); - return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy); - } else { - if (maxStopCount > 0) { - var strategy = new MaxCountSkipEdgeStrategy<>( - maxStopCount, - NearbyStopFinder::hasReachedStop - ); - return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy); - } - return durationSkipEdgeStrategy; } + return durationSkipEdgeStrategy; } private static List createDirectlyConnectedStops( diff --git a/src/main/java/org/opentripplanner/model/plan/Place.java b/src/main/java/org/opentripplanner/model/plan/Place.java index 71c6d9bc188..fe3a9dee420 100644 --- a/src/main/java/org/opentripplanner/model/plan/Place.java +++ b/src/main/java/org/opentripplanner/model/plan/Place.java @@ -90,11 +90,7 @@ public static Place normal(Vertex vertex, I18NString name) { } public static Place forStop(StopLocation stop) { - return forStop(stop, stop.getName()); - } - - public static Place forStop(StopLocation stop, I18NString nameOverride) { - return new Place(nameOverride, stop.getCoordinate(), VertexType.TRANSIT, stop, null, null); + return new Place(stop.getName(), stop.getCoordinate(), VertexType.TRANSIT, stop, null, null); } public static Place forFlexStop(StopLocation stop, Vertex vertex) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java b/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java index 60bd4d4c769..c45fc3e33a8 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java +++ b/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java @@ -81,6 +81,13 @@ private EnumMap copyValues() { return values.isEmpty() ? new EnumMap<>(type) : new EnumMap<>(values); } + /** + * Convert the values to an {@link EnumMap}. + */ + public EnumMap asEnumMap() { + return copyValues(); + } + private static > String toString( Class clazz, Map values diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java index db8845e7c41..ada495b19ba 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java @@ -7,12 +7,12 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; -import org.opentripplanner.framework.model.Units; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty; import org.opentripplanner.routing.api.request.framework.TimeAndCostPenaltyForEnum; +import org.opentripplanner.routing.api.request.framework.TimePenalty; /** * Preferences for access/egress routing on street network @@ -21,14 +21,27 @@ */ public final class AccessEgressPreferences implements Serializable { + private static final TimeAndCostPenalty DEFAULT_PENALTY = TimeAndCostPenalty.of( + TimePenalty.of(ofMinutes(20), 2f), + 1.5 + ); + private static final TimeAndCostPenaltyForEnum DEFAULT_TIME_AND_COST = TimeAndCostPenaltyForEnum + .of(StreetMode.class) + .with(StreetMode.CAR_TO_PARK, DEFAULT_PENALTY) + .with(StreetMode.CAR_HAILING, DEFAULT_PENALTY) + .with(StreetMode.CAR_RENTAL, DEFAULT_PENALTY) + .with(StreetMode.FLEXIBLE, DEFAULT_PENALTY) + .build(); + public static final AccessEgressPreferences DEFAULT = new AccessEgressPreferences(); + private final TimeAndCostPenaltyForEnum penalty; private final DurationForEnum maxDuration; private final int maxStopCount; private AccessEgressPreferences() { this.maxDuration = durationForStreetModeOf(ofMinutes(45)); - this.penalty = TimeAndCostPenaltyForEnum.ofDefault(StreetMode.class); + this.penalty = DEFAULT_TIME_AND_COST; this.maxStopCount = 500; } diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java index aa3631e1c6b..b7adc04df1c 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java @@ -19,15 +19,15 @@ public class VehicleWalkingPreferences implements Serializable { private final double speed; private final double reluctance; - private final Duration hopTime; - private final Cost hopCost; + private final Duration mountDismountTime; + private final Cost mountDismountCost; private final double stairsReluctance; private VehicleWalkingPreferences() { this.speed = 1.33; this.reluctance = 5.0; - this.hopTime = Duration.ZERO; - this.hopCost = Cost.ZERO; + this.mountDismountTime = Duration.ZERO; + this.mountDismountCost = Cost.ZERO; // very high reluctance to carry the bike up/down a flight of stairs this.stairsReluctance = 10; } @@ -39,8 +39,8 @@ private VehicleWalkingPreferences() { private VehicleWalkingPreferences(Builder builder) { this.speed = Units.speed(builder.speed); this.reluctance = Units.reluctance(builder.reluctance); - this.hopTime = Duration.ofSeconds(Units.duration(builder.hopTime)); - this.hopCost = Cost.costOfSeconds(builder.hopCost); + this.mountDismountTime = Duration.ofSeconds(Units.duration(builder.mountDismountTime)); + this.mountDismountCost = Cost.costOfSeconds(builder.mountDismountCost); this.stairsReluctance = Units.reluctance(builder.stairsReluctance); } @@ -73,13 +73,13 @@ public double reluctance() { } /** Time to get on and off your own vehicle. */ - public Duration hopTime() { - return hopTime; + public Duration mountDismountTime() { + return mountDismountTime; } /** Cost of getting on and off your own vehicle. */ - public Cost hopCost() { - return hopCost; + public Cost mountDismountCost() { + return mountDismountCost; } /** Reluctance of walking carrying a vehicle up a flight of stairs. */ @@ -95,15 +95,15 @@ public boolean equals(Object o) { return ( speed == that.speed && reluctance == that.reluctance && - Objects.equals(hopTime, that.hopTime) && - Objects.equals(hopCost, that.hopCost) && + Objects.equals(mountDismountTime, that.mountDismountTime) && + Objects.equals(mountDismountCost, that.mountDismountCost) && stairsReluctance == that.stairsReluctance ); } @Override public int hashCode() { - return Objects.hash(speed, reluctance, hopTime, hopCost, stairsReluctance); + return Objects.hash(speed, reluctance, mountDismountTime, mountDismountCost, stairsReluctance); } @Override @@ -112,8 +112,8 @@ public String toString() { .of(VehicleWalkingPreferences.class) .addNum("speed", speed, DEFAULT.speed) .addNum("reluctance", reluctance, DEFAULT.reluctance) - .addObj("hopTime", hopTime, DEFAULT.hopTime) - .addObj("hopCost", hopCost, DEFAULT.hopCost) + .addObj("mountDismountTime", mountDismountTime, DEFAULT.mountDismountTime) + .addObj("mountDismountCost", mountDismountCost, DEFAULT.mountDismountCost) .addNum("stairsReluctance", stairsReluctance, DEFAULT.stairsReluctance) .toString(); } @@ -123,16 +123,16 @@ public static class Builder { private final VehicleWalkingPreferences original; private double speed; private double reluctance; - private int hopTime; - private int hopCost; + private int mountDismountTime; + private int mountDismountCost; private double stairsReluctance; private Builder(VehicleWalkingPreferences original) { this.original = original; this.speed = original.speed; this.reluctance = original.reluctance; - this.hopTime = (int) original.hopTime.toSeconds(); - this.hopCost = original.hopCost.toSeconds(); + this.mountDismountTime = (int) original.mountDismountTime.toSeconds(); + this.mountDismountCost = original.mountDismountCost.toSeconds(); this.stairsReluctance = original.stairsReluctance; } @@ -146,18 +146,18 @@ public VehicleWalkingPreferences.Builder withReluctance(double reluctance) { return this; } - public VehicleWalkingPreferences.Builder withHopTime(Duration hopTime) { - this.hopTime = (int) hopTime.toSeconds(); + public VehicleWalkingPreferences.Builder withMountDismountTime(Duration mountDismountTime) { + this.mountDismountTime = (int) mountDismountTime.toSeconds(); return this; } - public VehicleWalkingPreferences.Builder withHopTime(int hopTime) { - this.hopTime = hopTime; + public VehicleWalkingPreferences.Builder withMountDismountTime(int mountDismountTime) { + this.mountDismountTime = mountDismountTime; return this; } - public VehicleWalkingPreferences.Builder withHopCost(int hopCost) { - this.hopCost = hopCost; + public VehicleWalkingPreferences.Builder withMountDismountCost(int mountDismountCost) { + this.mountDismountCost = mountDismountCost; return this; } diff --git a/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java b/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java index 830a80c39a5..047e44f229a 100644 --- a/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java +++ b/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java @@ -16,7 +16,7 @@ static void addFlexLocations(StreetEdge edge, IntersectionVertex v0, StopModel s if (edge.getPermission().allows(StreetTraversalPermission.PEDESTRIAN_AND_CAR)) { Point p = GeometryUtils.getGeometryFactory().createPoint(v0.getCoordinate()); Envelope env = p.getEnvelopeInternal(); - for (AreaStop areaStop : stopModel.queryLocationIndex(env)) { + for (AreaStop areaStop : stopModel.findAreaStops(env)) { if (!areaStop.getGeometry().disjoint(p)) { v0.addAreaStops(Set.of(areaStop)); } diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index fa3a7069e2d..bbc6733a40c 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -114,7 +114,7 @@ public interface OtpServerRequestContext { TraverseVisitor traverseVisitor(); default GraphFinder graphFinder() { - return GraphFinder.getInstance(graph(), transitService()::findRegularStop); + return GraphFinder.getInstance(graph(), transitService()::findRegularStops); } FlexConfig flexConfig(); diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java index ce880058005..0e048a2e3e7 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.Optional; import org.opentripplanner.framework.doc.DocumentedEnum; +import org.opentripplanner.framework.lang.StringUtils; public class EnumMapper { @@ -23,11 +24,7 @@ public static Optional> mapToEnum2(String text, Class en) { - return kebabCase(en.name()); - } - - public static String kebabCase(String input) { - return input.toLowerCase().replace('_', '-'); + return StringUtils.kebabCase(en.name()); } /** diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java index 296c07805f9..06675475fd1 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java @@ -7,6 +7,7 @@ import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder; /** @@ -137,7 +138,7 @@ public List> enumTypeValues() { */ public String toMarkdownString(Object value) { if (enumType != null) { - value = EnumMapper.kebabCase(value.toString()); + value = StringUtils.kebabCase(value.toString()); } return type.quote(value); } diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index f1d90d0ec40..088a7794585 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -303,14 +303,15 @@ public > Map asEnumMap(Class enumType, Class el */ public > Map asEnumMap( Class enumType, - Function typeMapper + Function typeMapper, + Map defaultValue ) { info.withOptional().withEnumMap(enumType, OBJECT); var mapNode = buildObject(); if (mapNode.isEmpty()) { - return Map.of(); + return defaultValue; } EnumMap result = new EnumMap<>(enumType); 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..c4b0b0bd8cb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -15,12 +15,16 @@ import static org.opentripplanner.standalone.config.routerequest.WheelchairConfig.mapWheelchairPreferences; import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; import org.opentripplanner.api.parameter.QualifiedModeSet; import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.routing.api.request.RequestModes; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; +import org.opentripplanner.routing.api.request.preference.AccessEgressPreferences; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; @@ -455,7 +459,9 @@ private static void mapStreetPreferences(NodeAdapter c, StreetPreferences.Builde the access legs used. In other cases where the access(CAR) is faster than transit the performance will be better. - The default is no penalty, if not configured. + The default values are + + %s Example: `"car-to-park" : { "timePenalty": "10m + 1.5t", "costFactor": 2.5 }` @@ -470,9 +476,15 @@ the access legs used. In other cases where the access(CAR) is faster than transi The `costFactor` is used to add an additional cost to the leg´s generalized-cost. The time-penalty is multiplied with the cost-factor. A cost-factor of zero, gives no extra cost, while 1.0 will add the same amount to both time and cost. - """ + """.formatted( + formatPenaltyDefaultValues(dftAccessEgress) + ) + ) + .asEnumMap( + StreetMode.class, + TimeAndCostPenaltyMapper::map, + dftAccessEgress.penalty().asEnumMap() ) - .asEnumMap(StreetMode.class, TimeAndCostPenaltyMapper::map) ) .withMaxDuration( cae @@ -569,6 +581,16 @@ The street search(AStar) aborts after this duration and any paths found are retu ); } + private static String formatPenaltyDefaultValues(AccessEgressPreferences dftAccessEgress) { + return dftAccessEgress + .penalty() + .asEnumMap() + .entrySet() + .stream() + .map(s -> "- `%s` = %s".formatted(StringUtils.kebabCase(s.getKey().toString()), s.getValue())) + .collect(Collectors.joining("\n")); + } + private static void mapCarPreferences(NodeAdapter root, CarPreferences.Builder builder) { var dft = builder.original(); NodeAdapter c = root.of("car").since(V2_5).summary("Car preferences.").asObject(); diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java index f2ba922c8e3..2b20e12f755 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java @@ -43,9 +43,9 @@ private static void mapVehicleWalkingPreferences( ) .asDouble(dft.reluctance()) ) - .withHopTime( + .withMountDismountTime( c - .of("hopTime") + .of("mountDismountTime") .since(V2_0) .summary("The time it takes the user to hop on or off a vehicle.") .description( @@ -54,11 +54,11 @@ private static void mapVehicleWalkingPreferences( for controlling the duration of those events. """ ) - .asDuration(dft.hopTime()) + .asDuration(dft.mountDismountTime()) ) - .withHopCost( + .withMountDismountCost( c - .of("hopCost") + .of("mountDismountCost") .since(V2_0) .summary("The cost of hopping on or off a vehicle.") .description( @@ -67,7 +67,7 @@ private static void mapVehicleWalkingPreferences( not meant for controlling the cost of those events. """ ) - .asInt(dft.hopCost().toSeconds()) + .asInt(dft.mountDismountCost().toSeconds()) ) .withStairsReluctance( c diff --git a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java index 799a5b006e6..7f61982434d 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java +++ b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java @@ -17,8 +17,10 @@ default void switchToWalkingBike(RoutingPreferences preferences, StateEditor edi editor.setBackWalkingBike(true); if (shouldIncludeCost) { - editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds()); - editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds()); + editor.incrementWeight(preferences.bike().walking().mountDismountCost().toSeconds()); + editor.incrementTimeInSeconds( + (int) preferences.bike().walking().mountDismountTime().toSeconds() + ); } } @@ -28,8 +30,10 @@ default void switchToBiking(RoutingPreferences preferences, StateEditor editor) editor.setBackWalkingBike(false); if (shouldIncludeCost) { - editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds()); - editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds()); + editor.incrementWeight(preferences.bike().walking().mountDismountCost().toSeconds()); + editor.incrementTimeInSeconds( + (int) preferences.bike().walking().mountDismountTime().toSeconds() + ); } } diff --git a/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java b/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java index edee00e27e1..c674d588238 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java +++ b/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java @@ -94,13 +94,12 @@ public Geometry getGeometry() { /** * Returns the geometry of the area that encompasses the bounds of this StopLocation group. If the - * group is defined only as a list of stops, this will return the same as getGeometry. If on the - * other hand the group is defined as an area and the stops are inferred from that area, then this - * will return the geometry of the area. + * group is defined as all the stops within an area, then this will return the geometry of the + * area. If the group is defined simply as a list of stops, this will return an empty optional. */ @Override public Optional getEncompassingAreaGeometry() { - return Optional.ofNullable(encompassingAreaGeometry).or(() -> Optional.of(geometry)); + return Optional.ofNullable(encompassingAreaGeometry); } @Override diff --git a/src/main/java/org/opentripplanner/transit/model/site/GroupStopBuilder.java b/src/main/java/org/opentripplanner/transit/model/site/GroupStopBuilder.java index 8873e6ede99..64cad9325d1 100644 --- a/src/main/java/org/opentripplanner/transit/model/site/GroupStopBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/site/GroupStopBuilder.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.function.IntSupplier; import javax.annotation.Nonnull; -import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.opentripplanner.framework.geometry.GeometryUtils; @@ -69,6 +68,22 @@ public I18NString name() { } public GroupStopBuilder addLocation(StopLocation location) { + if ( + !( + location.getStopType() == StopType.REGULAR || + location.getStopType() == StopType.FLEXIBLE_AREA + ) + ) { + throw new RuntimeException( + String.format( + "Unsupported location for %s. Must be %s or %s.", + GroupStop.class.getSimpleName(), + StopType.REGULAR, + StopType.FLEXIBLE_AREA + ) + ); + } + stopLocations.add(location); int numGeometries = geometry.getNumGeometries(); @@ -76,17 +91,8 @@ public GroupStopBuilder addLocation(StopLocation location) { for (int i = 0; i < numGeometries; i++) { newGeometries[i] = geometry.getGeometryN(i); } - if (location instanceof RegularStop) { - WgsCoordinate coordinate = location.getCoordinate(); - Envelope envelope = new Envelope(coordinate.asJtsCoordinate()); - double xscale = Math.cos(coordinate.latitude() * Math.PI / 180); - envelope.expandBy(100 / xscale, 100); - newGeometries[numGeometries] = GeometryUtils.getGeometryFactory().toGeometry(envelope); - } else if (location instanceof AreaStop) { - newGeometries[numGeometries] = location.getGeometry(); - } else { - throw new RuntimeException("Unknown location type"); - } + newGeometries[numGeometries] = location.getGeometry(); + geometry = new GeometryCollection(newGeometries, GeometryUtils.getGeometryFactory()); centroid = new WgsCoordinate(geometry.getCentroid()); diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index 0d08bcf34b2..3050fca6a3d 100644 --- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -543,7 +543,7 @@ public ZonedDateTime getTransitServiceStarts() { } @Override - public Collection findRegularStop(Envelope envelope) { + public Collection findRegularStops(Envelope envelope) { OTPRequestTimeoutException.checkForTimeout(); return transitModel.getStopModel().findRegularStops(envelope); } @@ -551,7 +551,7 @@ public Collection findRegularStop(Envelope envelope) { @Override public Collection findAreaStops(Envelope envelope) { OTPRequestTimeoutException.checkForTimeout(); - return transitModel.getStopModel().queryLocationIndex(envelope); + return transitModel.getStopModel().findAreaStops(envelope); } @Override diff --git a/src/main/java/org/opentripplanner/transit/service/StopModel.java b/src/main/java/org/opentripplanner/transit/service/StopModel.java index dd6b65e5b33..763aa504c40 100644 --- a/src/main/java/org/opentripplanner/transit/service/StopModel.java +++ b/src/main/java/org/opentripplanner/transit/service/StopModel.java @@ -108,7 +108,7 @@ public StopModelBuilder withContext() { } /** - * Return a regular transit stop if found(not flex stops). + * Return a regular transit stop if found (not flex stops). */ public RegularStop getRegularStop(FeedScopedId id) { return regularStopById.get(id); @@ -121,6 +121,9 @@ public Collection listRegularStops() { return regularStopById.values(); } + /** + * Find regular stops within a geographical area. + */ public Collection findRegularStops(Envelope envelope) { return index.findRegularStops(envelope); } @@ -131,21 +134,30 @@ public boolean hasAreaStops() { /** * Flex locations are generated by GTFS graph builder, but consumed only after the street graph is - * built + * built. */ @Nullable public AreaStop getAreaStop(FeedScopedId id) { return areaStopById.get(id); } + /** + * Return all flex stops, not regular transit stops and flex group of stops. + */ public Collection listAreaStops() { return areaStopById.values(); } - public Collection queryLocationIndex(Envelope envelope) { + /** + * Find flex stops within a geographical area. + */ + public Collection findAreaStops(Envelope envelope) { return index.findAreaStops(envelope); } + /** + * Return all flex groups of stops. + */ public Collection listGroupStops() { return groupStopById.values(); } diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java index d0664aa292d..fdb6dda8749 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -187,7 +187,7 @@ List stopTimesForPatternAtStop( boolean transitFeedCovers(Instant dateTime); - Collection findRegularStop(Envelope envelope); + Collection findRegularStops(Envelope envelope); Collection findAreaStops(Envelope envelope); diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 25cf95ca2f9..dad106418fe 100644 --- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -774,7 +774,7 @@ type QueryType { "Input type for executing a travel search for a trip between two locations. Returns trip patterns describing suggested alternatives for the trip." trip( "Time and cost penalty on access/egress modes." - accessEgressPenalty: [PenaltyForStreetMode!] = [], + accessEgressPenalty: [PenaltyForStreetMode!] = [{streetMode : car_park, timePenalty : "20m + 2.0 t", costFactor : 1.5}, {streetMode : flexible, timePenalty : "20m + 2.0 t", costFactor : 1.5}], "The alightSlack is the minimum extra time after exiting a public transport vehicle. This is the default value used, if not overridden by the 'alightSlackList'." alightSlackDefault: Int = 0, "List of alightSlack for a given set of modes. Defaults: []" diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java index b05dd77e2a9..85736291ada 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -55,7 +55,7 @@ class RouteRequestMapperTest implements PlanTestConstants { graph.getVehicleParkingService(), new DefaultVehicleRentalService(), new DefaultRealtimeVehicleService(transitService), - GraphFinder.getInstance(graph, transitService::findRegularStop), + GraphFinder.getInstance(graph, transitService::findRegularStops), new RouteRequest() ); } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java index 1d47337a312..0289c24e837 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java @@ -109,10 +109,10 @@ void testToString() { "routingTimeout: 3s, " + "elevator: ElevatorPreferences{boardTime: 2m}, " + "intersectionTraversalModel: CONSTANT, " + - "accessEgress: AccessEgressPreferences{" + - "penalty: TimeAndCostPenaltyForEnum{CAR_TO_PARK: " + + "accessEgress: AccessEgressPreferences{penalty: TimeAndCostPenaltyForEnum{CAR_TO_PARK: " + CAR_PENALTY + - "}, " + + ", CAR_RENTAL: (timePenalty: 20m + 2.0 t, costFactor: 1.50), CAR_HAILING: (timePenalty: 20m + 2.0 t, costFactor: 1.50), " + + "FLEXIBLE: (timePenalty: 20m + 2.0 t, costFactor: 1.50)}, " + "maxDuration: DurationForStreetMode{default:5m}" + "}, " + "maxDirectDuration: DurationForStreetMode{default:10m}" + diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java index 9571eee8cc5..fdc416d7c0f 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java @@ -11,8 +11,8 @@ class VehicleWalkingPreferencesTest { private static final double SPEED = 1.45; private static final double RELUCTANCE = 5.5; - private static final Duration HOP_TIME = Duration.ofSeconds(15); - private static final Cost HOP_COST = Cost.costOfSeconds(20); + private static final Duration MOUNT_DISMOUNT_TIME = Duration.ofSeconds(15); + private static final Cost MOUNT_DISMOUNT_COST = Cost.costOfSeconds(20); private static final double STAIRS_RELUCTANCE = 11; private final VehicleWalkingPreferences subject = createPreferences(); @@ -28,13 +28,13 @@ void reluctance() { } @Test - void hopTime() { - assertEquals(HOP_TIME, subject.hopTime()); + void mountDismountTime() { + assertEquals(MOUNT_DISMOUNT_TIME, subject.mountDismountTime()); } @Test - void hopCost() { - assertEquals(HOP_COST, subject.hopCost()); + void mountDismountCost() { + assertEquals(MOUNT_DISMOUNT_COST, subject.mountDismountCost()); } @Test @@ -57,8 +57,8 @@ void testToString() { "VehicleWalkingPreferences{" + "speed: 1.45, " + "reluctance: 5.5, " + - "hopTime: PT15S, " + - "hopCost: $20, " + + "mountDismountTime: PT15S, " + + "mountDismountCost: $20, " + "stairsReluctance: 11.0}", subject.toString() ); @@ -69,8 +69,8 @@ private VehicleWalkingPreferences createPreferences() { .of() .withSpeed(SPEED) .withReluctance(RELUCTANCE) - .withHopTime(HOP_TIME) - .withHopCost(HOP_COST.toSeconds()) + .withMountDismountTime(MOUNT_DISMOUNT_TIME) + .withMountDismountCost(MOUNT_DISMOUNT_COST.toSeconds()) .withStairsReluctance(STAIRS_RELUCTANCE) .build(); } diff --git a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java index f975579cbf5..be44d1ea86f 100644 --- a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java +++ b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java @@ -225,15 +225,23 @@ public void asEnumMapWithCustomType() { NodeAdapter subject = newNodeAdapterForTest("{ key : { A: {a:'Foo'} } }"); assertEquals( Map.of(AnEnum.A, new ARecord("Foo")), - subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson) + subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson, Map.of()) ); assertEquals( Collections.emptyMap(), - subject.of("missing-key").asEnumMap(AnEnum.class, ARecord::fromJson) + subject.of("missing-key").asEnumMap(AnEnum.class, ARecord::fromJson, Map.of()) ); assertEquals(NON_UNUSED_PARAMETERS, unusedParams(subject)); } + @Test + public void asEnumMapWithDefaultValue() { + var subject = newNodeAdapterForTest("{}"); + final Map dflt = Map.of(AnEnum.A, new ARecord("Foo")); + assertEquals(dflt, subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson, dflt)); + assertEquals(NON_UNUSED_PARAMETERS, unusedParams(subject)); + } + @Test public void asEnumMapWithUnknownKey() { NodeAdapter subject = newNodeAdapterForTest("{ enumMap : { unknown : 7 } }"); diff --git a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java index 417f1b87347..41ec677b3bb 100644 --- a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java +++ b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java @@ -375,7 +375,10 @@ private List runStreetSearchAndCreateDescriptor( preferences .withWalk(w -> w.withSpeed(10)) .withBike(it -> - it.withSpeed(20d).withWalking(w -> w.withSpeed(5d).withHopTime(100).withHopCost(1000)) + it + .withSpeed(20d) + .withWalking(w -> w.withSpeed(5d).withMountDismountTime(100).withMountDismountCost(1000) + ) ) ); request.setArriveBy(arriveBy); 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..ed5768d4a1a 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java @@ -221,7 +221,7 @@ public void testBikeSwitch() { StreetSearchRequestBuilder noPenalty = StreetSearchRequest.copyOf(proto); noPenalty.withPreferences(p -> - p.withBike(it -> it.withWalking(w -> w.withHopTime(0).withHopCost(0))) + p.withBike(it -> it.withWalking(w -> w.withMountDismountTime(0).withMountDismountCost(0))) ); State s0 = new State(v0, noPenalty.withMode(StreetMode.BIKE).build()); @@ -231,7 +231,7 @@ public void testBikeSwitch() { StreetSearchRequestBuilder withPenalty = StreetSearchRequest.copyOf(proto); withPenalty.withPreferences(p -> - p.withBike(it -> it.withWalking(w -> w.withHopTime(42).withHopCost(23))) + p.withBike(it -> it.withWalking(w -> w.withMountDismountTime(42).withMountDismountCost(23))) ); State s4 = new State(v0, withPenalty.withMode(StreetMode.BIKE).build()); diff --git a/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java b/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java index 17938131eef..b57566b68ed 100644 --- a/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java +++ b/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java @@ -6,7 +6,12 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Geometry; +import org.opentripplanner._support.geometry.Coordinates; +import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.transit.model._data.TransitModelForTest; @@ -14,12 +19,14 @@ class GroupStopTest { - private static TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final String ID = "1"; private static final I18NString NAME = new NonLocalizedString("name"); - private static final StopLocation STOP_LOCATION = TEST_MODEL.stop("1:stop", 1d, 1d).build(); + private static final StopLocation STOP_LOCATION = TEST_MODEL + .stop("1:stop", Coordinates.BERLIN.getX(), Coordinates.BERLIN.getY()) + .build(); private static final GroupStop subject = StopModel .of() .groupStop(TransitModelForTest.id(ID)) @@ -27,6 +34,53 @@ class GroupStopTest { .addLocation(STOP_LOCATION) .build(); + @Test + void testGroupStopGeometry() { + StopLocation stopLocation1 = TEST_MODEL + .stop("1:stop", Coordinates.BERLIN.getX(), Coordinates.BERLIN.getY()) + .build(); + StopLocation stopLocation2 = TEST_MODEL + .stop("2:stop", Coordinates.HAMBURG.getX(), Coordinates.HAMBURG.getY()) + .build(); + + GroupStop groupStop = StopModel + .of() + .groupStop(TransitModelForTest.id(ID)) + .withName(NAME) + .addLocation(stopLocation1) + .addLocation(stopLocation2) + .build(); + + Geometry groupStopGeometry1 = Objects.requireNonNull(groupStop.getGeometry()).getGeometryN(0); + assertEquals(stopLocation1.getGeometry(), groupStopGeometry1); + + Geometry groupStopGeometry2 = Objects.requireNonNull(groupStop.getGeometry()).getGeometryN(1); + assertEquals(stopLocation2.getGeometry(), groupStopGeometry2); + } + + @Test + void testGroupStopEncompassingAreaGeometry() { + StopLocation stopLocation = TEST_MODEL + .stop("1:stop", Coordinates.BERLIN.getX(), Coordinates.BERLIN.getY()) + .build(); + + GroupStop groupStop = StopModel + .of() + .groupStop(TransitModelForTest.id(ID)) + .withName(NAME) + .addLocation(stopLocation) + .withEncompassingAreaGeometries(List.of(Polygons.BERLIN)) + .build(); + + Geometry groupStopGeometry = Objects.requireNonNull(groupStop.getGeometry()).getGeometryN(0); + assertEquals(stopLocation.getGeometry(), groupStopGeometry); + + assertEquals( + Polygons.BERLIN, + groupStop.getEncompassingAreaGeometry().orElseThrow().getGeometryN(0) + ); + } + @Test void copy() { assertEquals(ID, subject.getId().getId()); diff --git a/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java b/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java index 2a26dc681be..f04fe782a0a 100644 --- a/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java +++ b/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java @@ -43,6 +43,8 @@ static void setup() { .build(); var transitModel = new TransitModel(stopModel, new Deduplicator()); + transitModel.addTripPattern(RAIL_PATTERN.getId(), RAIL_PATTERN); + transitModel.index(); service = new DefaultTransitService(transitModel) {