diff --git a/docs/Changelog.md b/docs/Changelog.md index c6bb826884c..df2ecd4b8fc 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -86,6 +86,9 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Pass-through should override transit-group-priority [#5638](https://github.com/opentripplanner/OpenTripPlanner/pull/5638) - 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 7c7f30988b2..359b05e1ace 100644 --- a/pom.xml +++ b/pom.xml @@ -56,18 +56,18 @@ - 140 + 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 @@ -545,7 +545,7 @@ com.google.cloud libraries-bom - 26.27.0 + 26.31.0 pom import @@ -713,7 +713,7 @@ org.mockito mockito-core - 5.9.0 + 5.10.0 test @@ -739,7 +739,7 @@ com.google.guava guava - 32.1.3-jre + 33.0.0-jre @@ -876,7 +876,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.3 + 5.3.1 commons-cli diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java index 90468b091f9..ac11886c208 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java @@ -12,7 +12,6 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.Money; @@ -34,7 +33,6 @@ void shouldAddFare() { assertEquals(ItineraryFares.empty(), i1.getFares()); var fares = new ItineraryFares(); - fares.addFare(FareType.regular, Money.euros(2.80f)); var leg = i1.getLegs().get(1); var fp = new FareProduct(id("fp"), "fare product", Money.euros(10.00f), null, null, null); diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/AtlantaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/AtlantaFareServiceTest.java index b75c58923e1..3662d45affa 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/AtlantaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/AtlantaFareServiceTest.java @@ -21,7 +21,6 @@ import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; -import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.Place; @@ -226,9 +225,17 @@ void fullItinerary() { * used. This will be the same for all cash fare types except when overriden above. */ private static void calculateFare(List rides, Money expectedFare) { - ItineraryFares fare = new ItineraryFares(); - atlFareService.populateFare(fare, USD, FareType.electronicRegular, rides, null); - assertEquals(expectedFare, fare.getFare(FareType.electronicRegular)); + var fare = atlFareService.calculateFaresForType(USD, FareType.electronicRegular, rides, null); + assertEquals( + expectedFare, + fare + .getItineraryProducts() + .stream() + .filter(fp -> fp.name().equals(FareType.electronicRegular.name())) + .findFirst() + .get() + .price() + ); var fareProducts = fare .getItineraryProducts() @@ -284,10 +291,9 @@ private static Itinerary createItinerary(String agencyId, String shortName, long .build(); int start = (int) (T11_00 + (startTimeMins * 60)); - var itin = newItinerary(Place.forStop(firstStop), start) + return newItinerary(Place.forStop(firstStop), start) .bus(route, 1, start, T11_12, Place.forStop(lastStop)) .build(); - return itin; } private static class TestAtlantaFareService extends AtlantaFareService { diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java index 350bae04dce..3f237cf509f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/CombinedInterlinedLegsFareServiceTest.java @@ -65,9 +65,6 @@ void modes(CombinationMode mode, Itinerary itinerary, Money totalPrice, String h var fare = service.calculateFares(itinerary); assertNotNull(fare); - var price = fare.getFare(FareType.regular); - assertEquals(totalPrice, price); - var firstLeg = itinerary.getTransitLeg(0); var uses = fare.getLegProducts().get(firstLeg); assertEquals(1, uses.size()); @@ -75,6 +72,15 @@ void modes(CombinationMode mode, Itinerary itinerary, Money totalPrice, String h var secondLeg = itinerary.getTransitLeg(1); uses = fare.getLegProducts().get(secondLeg); assertEquals(1, uses.size()); + + var sum = fare + .getLegProducts() + .values() + .stream() + .distinct() + .map(p -> p.product().price()) + .reduce(Money.ZERO_USD, Money::plus); + assertEquals(totalPrice, sum); } @Test diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java index c5e26cc3c9b..cae23f60800 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.opentripplanner.ext.fares.impl.FareModelForTest.AIRPORT_STOP; import static org.opentripplanner.ext.fares.impl.FareModelForTest.AIRPORT_TO_CITY_CENTER_SET; import static org.opentripplanner.ext.fares.impl.FareModelForTest.CITY_CENTER_A_STOP; @@ -19,6 +18,9 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.model.fare.FareProduct; +import org.opentripplanner.model.fare.FareProductUse; +import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.core.FareType; @@ -28,7 +30,12 @@ class DefaultFareServiceTest implements PlanTestConstants { private static final Money TEN_DOLLARS = Money.usDollars(10); - private static final Money TWENTY_DOLLARS = Money.usDollars(20); + private static final FareProduct OTHER_FEED_PRODUCT = FareProduct + .of(OTHER_FEED_ATTRIBUTE.getId(), "regular", TEN_DOLLARS) + .build(); + private static final FareProduct AIRPORT_TO_CITY_CENTER_PRODUCT = FareProduct + .of(AIRPORT_TO_CITY_CENTER_SET.getFareAttribute().getId(), "regular", TEN_DOLLARS) + .build(); @Test void noRules() { @@ -36,7 +43,7 @@ void noRules() { service.addFareRules(FareType.regular, List.of()); var itin = newItinerary(A, T11_00).bus(1, T11_05, T11_12, B).build(); var fare = service.calculateFares(itin); - assertNull(fare); + assertEquals(ItineraryFares.empty(), fare); } @Test @@ -49,18 +56,15 @@ void simpleZoneBasedFare() { var fare = service.calculateFares(itin); assertNotNull(fare); - var price = fare.getFare(FareType.regular); - - assertEquals(TEN_DOLLARS, price); - - var fp = fare.getItineraryProducts().get(0); - assertEquals(TEN_DOLLARS, fp.price()); - assertEquals("F:regular", fp.id().toString()); - - var lp = fare.getLegProducts(); - assertEquals(1, lp.size()); - var product = lp.values().iterator().next().product(); - assertEquals(TEN_DOLLARS, product.price()); + var legProducts = fare.getLegProducts().get(itin.getTransitLeg(0)); + assertEquals( + List.of( + // reminder: the FareProductUse's id are a (non-random) UUID computed from the start time of the leg + // plus some properties from the product itself like the id, price, rider category and medium + new FareProductUse("1d270201-412b-3b86-80f6-92ab144fa2e5", AIRPORT_TO_CITY_CENTER_PRODUCT) + ), + legProducts + ); } @Test @@ -84,10 +88,20 @@ void applyToSeveralLegs() { var secondProducts = legProducts.get(secondLeg); assertEquals(firstProducts, secondProducts); - assertEquals( - "[FareProductUse[id=ddbf1572-18bc-3724-8b64-e1c7d5c8b6c6, product=FareProduct{id: 'F:free-transfers', amount: $20.00}]]", - firstProducts.toString() + List.of( + new FareProductUse( + "ddbf1572-18bc-3724-8b64-e1c7d5c8b6c6", + FareProduct + .of( + FREE_TRANSFERS_IN_CITY_SET.getFareAttribute().getId(), + "regular", + TEN_DOLLARS.plus(TEN_DOLLARS) + ) + .build() + ) + ), + firstProducts ); } @@ -113,23 +127,22 @@ void shouldNotCombineInterlinedLegs() { var fare = service.calculateFares(itin); assertNotNull(fare); - var price = fare.getFare(FareType.regular); - - assertEquals(TWENTY_DOLLARS, price); - var legProducts = fare.getLegProducts(); var firstLeg = itin.getLegs().getFirst(); - var products = List.copyOf(legProducts.get(firstLeg)); - - assertEquals(TEN_DOLLARS, products.getFirst().product().price()); - var secondLeg = itin.getLegs().get(1); - products = List.copyOf(legProducts.get(secondLeg)); - assertEquals(TEN_DOLLARS, products.getFirst().product().price()); - - assertEquals(1, fare.getItineraryProducts().size()); - assertEquals(TWENTY_DOLLARS, fare.getItineraryProducts().getFirst().price()); + assertEquals( + List.of( + new FareProductUse("ccadd1d3-f284-31a4-9d58-0a300198950f", AIRPORT_TO_CITY_CENTER_PRODUCT) + ), + legProducts.get(firstLeg) + ); + assertEquals( + List.of( + new FareProductUse("c58974dd-9a2f-3f42-90ec-c62a7b0dfd51", AIRPORT_TO_CITY_CENTER_PRODUCT) + ), + legProducts.get(secondLeg) + ); } @Test @@ -145,9 +158,6 @@ void unknownLeg() { var fare = service.calculateFares(itin); assertNotNull(fare); - var price = fare.getFare(FareType.regular); - assertEquals(Money.usDollars(-0.01f), price); - var fareProducts = List.copyOf(fare.getLegProducts().values()); assertEquals(1, fareProducts.size()); @@ -155,9 +165,6 @@ void unknownLeg() { assertEquals(AIRPORT_TO_CITY_CENTER_SET.getFareAttribute().getId(), fp.id()); assertEquals(TEN_DOLLARS, fp.price()); - var firstBusLeg = itin.firstTransitLeg().get(); - //assertEquals(List.of(firstBusLeg), fp.legs()); - var legProducts = fare.getLegProducts(); assertEquals(1, legProducts.size()); } @@ -185,8 +192,19 @@ void multipleFeeds() { fareProductIds ); - var resultPrice = result.getFare(FareType.regular); - assertEquals(TWENTY_DOLLARS, resultPrice); + var legProducts = result.getLegProducts(); + var firstBusLeg = itin.getTransitLeg(0); + assertEquals( + List.of( + new FareProductUse("1d270201-412b-3b86-80f6-92ab144fa2e5", AIRPORT_TO_CITY_CENTER_PRODUCT) + ), + legProducts.get(firstBusLeg) + ); + var secondBusLeg = itin.getTransitLeg(2); + assertEquals( + List.of(new FareProductUse("678d201c-e839-35c3-ae7b-1bc3834da5e5", OTHER_FEED_PRODUCT)), + legProducts.get(secondBusLeg) + ); } @Test @@ -209,20 +227,19 @@ void multipleFeedsWithTransfersWithinFeed() { var finalBusLeg = itin.getTransitLeg(4); assertEquals( - "[FareProductUse[id=5d0d58f4-b97a-38db-921c-8b5fc6392b54, product=FareProduct{id: 'F2:other-feed-attribute', amount: $10.00}]]", - legProducts.get(firstBusLeg).toString() + List.of(new FareProductUse("5d0d58f4-b97a-38db-921c-8b5fc6392b54", OTHER_FEED_PRODUCT)), + legProducts.get(firstBusLeg) ); assertEquals( - "[FareProductUse[id=1d270201-412b-3b86-80f6-92ab144fa2e5, product=FareProduct{id: 'F:airport-to-city-center', amount: $10.00}]]", - legProducts.get(secondBusLeg).toString() + List.of( + new FareProductUse("1d270201-412b-3b86-80f6-92ab144fa2e5", AIRPORT_TO_CITY_CENTER_PRODUCT) + ), + legProducts.get(secondBusLeg) ); assertEquals( - "[FareProductUse[id=5d0d58f4-b97a-38db-921c-8b5fc6392b54, product=FareProduct{id: 'F2:other-feed-attribute', amount: $10.00}]]", - legProducts.get(finalBusLeg).toString() + List.of(new FareProductUse("5d0d58f4-b97a-38db-921c-8b5fc6392b54", OTHER_FEED_PRODUCT)), + legProducts.get(finalBusLeg) ); - - var resultPrice = result.getFare(FareType.regular); - assertEquals(TWENTY_DOLLARS, resultPrice); } @Test @@ -241,8 +258,6 @@ void multipleFeedsWithUnknownFareLegs() { .stream() .map(r -> r.product().id()) .toList(); - var resultPrice = result.getFare(FareType.regular); assertEquals(List.of(OTHER_FEED_ATTRIBUTE.getId()), resultProductIds); - assertEquals(Money.usDollars(-0.01f), resultPrice); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FaresIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FaresIntegrationTest.java index d6a1432a75b..7b822b03059 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FaresIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FaresIntegrationTest.java @@ -19,7 +19,6 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; -import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.basic.Money; @@ -45,7 +44,9 @@ public void testBasic() { var to = GenericLocation.fromStopId("Destination", feedId, "Mountain View Caltrain"); ItineraryFares fare = getFare(from, to, start, serverContext); - assertEquals(fare.getFare(FareType.regular), Money.usDollars(4.25f)); + var product = fare.getLegProducts().values().iterator().next().product(); + assertEquals(Money.usDollars(4.25f), product.price()); + assertEquals("OW_2", product.id().getId().toString()); } @Test @@ -75,8 +76,12 @@ public void testPortland() { .toInstant(); ItineraryFares fare = getFare(from, to, startTime, serverContext); + var fpu = List.copyOf(fare.getLegProducts().values()); + assertEquals(1, fpu.size()); - assertEquals(Money.usDollars(2), fare.getFare(FareType.regular)); + var fp = fpu.getFirst(); + assertEquals(Money.usDollars(2f), fp.product().price()); + assertEquals("prt:19", fp.product().id().toString()); // long trip diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java index 4e1347cae16..17c381f1bd6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HSLFareServiceTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import static org.opentripplanner.transit.model._data.TransitModelForTest.FEED_ID; import static org.opentripplanner.transit.model.basic.Money.euros; @@ -450,6 +451,6 @@ void unknownFare() { .bus(1, T11_20, T11_30, PlanTestConstants.E) .build(); var result = service.calculateFares(outsideHsl); - assertNull(result); + assertTrue(result.isEmpty()); } } diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareServiceTest.java index 5664dcc765d..e9aa17ba35f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareServiceTest.java @@ -18,7 +18,6 @@ import org.opentripplanner.model.fare.FareProduct; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.basic.TransitMode; @@ -43,18 +42,10 @@ public void canCalculateFare( Money expectedFare ) { var fares = fareService.calculateFares(i); - assertEquals(expectedFare, fares.getFare(FareType.regular)); assertFalse(fares.getItineraryProducts().isEmpty()); - for (var type : fares.getFareTypes()) { - var prices = fares - .getItineraryProducts() - .stream() - .filter(fp -> fp.name().equals(type.name())) - .map(FareProduct::price) - .toList(); - assertEquals(List.of(expectedFare), prices); - } + var prices = fares.getItineraryProducts().stream().map(FareProduct::price).toList(); + assertEquals(List.of(expectedFare), prices); } private static List createTestCases() { diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index e30fb2d8b18..4d04281ebb6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -81,7 +81,16 @@ public static void setUpClass() { private static void calculateFare(List legs, FareType fareType, Money expectedPrice) { var itinerary = new Itinerary(legs); var itineraryFares = orcaFareService.calculateFares(itinerary); - assertEquals(expectedPrice, itineraryFares.getFare(fareType)); + assertEquals( + expectedPrice, + itineraryFares + .getItineraryProducts() + .stream() + .filter(fareProduct -> fareProduct.name().equals(fareType.name())) + .findFirst() + .get() + .price() + ); } private static void assertLegFareEquals( @@ -167,8 +176,7 @@ void calculateFareWithNoFreeTransfer() { @Test void calculateFareByLeg() { List rides = List.of(getLeg(KITSAP_TRANSIT_AGENCY_ID, 0), getLeg(COMM_TRANS_AGENCY_ID, 2)); - ItineraryFares fares = new ItineraryFares(); - orcaFareService.populateFare(fares, USD, FareType.electronicRegular, rides, null); + var fares = orcaFareService.calculateFaresForType(USD, FareType.electronicRegular, rides, null); assertLegFareEquals(349, rides.get(0), fares, false); assertLegFareEquals(0, rides.get(1), fares, true); @@ -489,9 +497,8 @@ void nullLongName(FareType type) { ) ); - var fare = new ItineraryFares(); - orcaFareService.populateFare(fare, USD, type, legs, null); - assertNotNull(fare.getFare(type)); + var fare = orcaFareService.calculateFaresForType(USD, type, legs, null); + assertFalse(fare.getLegProducts().isEmpty()); } @ParameterizedTest @@ -511,9 +518,8 @@ void nullShortName(FareType type) { ) ); - var fare = new ItineraryFares(); - orcaFareService.populateFare(fare, USD, type, legs, null); - assertNotNull(fare.getFare(type)); + var fare = orcaFareService.calculateFaresForType(USD, type, legs, null); + assertFalse(fare.getLegProducts().isEmpty()); } @Test 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/flex/trip/ScheduledDeviatedTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index a65c236e533..143388cac0f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -33,7 +33,6 @@ import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; -import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.NearbyStop; @@ -42,7 +41,6 @@ import org.opentripplanner.street.model.vertex.StreetLocation; import org.opentripplanner.street.search.request.StreetSearchRequest; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.service.DefaultTransitService; @@ -148,10 +146,9 @@ void calculateDirectFare() { var itineraries = router.createFlexOnlyItineraries().stream().peek(filter::decorate).toList(); - var itinerary = itineraries.iterator().next(); - assertFalse(itinerary.getFares().getFareTypes().isEmpty()); + var itinerary = itineraries.getFirst(); - assertEquals(Money.usDollars(2.5f), itinerary.getFares().getFare(FareType.regular)); + assertFalse(itinerary.getFares().getLegProducts().isEmpty()); OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false)); } 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/restapi/mapping/FareMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/FareMapperTest.java deleted file mode 100644 index 4b455cce0f7..00000000000 --- a/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/FareMapperTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.opentripplanner.ext.restapi.mapping; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; - -import java.util.Locale; -import org.junit.jupiter.api.Test; -import org.opentripplanner.model.fare.ItineraryFares; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.core.FareType; -import org.opentripplanner.transit.model.basic.Money; - -class FareMapperTest implements PlanTestConstants { - - @Test - public void emptyDetails() { - var fare = new ItineraryFares(); - fare.addFare(FareType.regular, Money.usDollars(5)); - - Itinerary itinerary = newItinerary(A, 30).bus(1, 30, 60, B).bus(2, 90, 120, C).build(); - - itinerary.setFare(fare); - - var mapper = new FareMapper(Locale.US); - var apiFare = mapper.mapFare(itinerary); - - var apiMoney = apiFare.fare().get(FareType.regular.name()); - assertEquals(500, apiMoney.cents()); - assertEquals("USD", apiMoney.currency().currency()); - } -} diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/AtlantaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/AtlantaFareService.java index d2d9d3e2a1b..fdeb7a975d2 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/AtlantaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/AtlantaFareService.java @@ -1,8 +1,11 @@ package org.opentripplanner.ext.fares.impl; +import static org.opentripplanner.transit.model.basic.Money.ZERO_USD; import static org.opentripplanner.transit.model.basic.Money.usDollars; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; import java.time.Duration; import java.time.ZoneId; import java.util.ArrayList; @@ -69,7 +72,7 @@ public boolean routeNamesContains(@Nullable String s) { private static class ATLTransfer { List legs = new ArrayList<>(); - List fares = new ArrayList<>(); + Multimap fares = ArrayListMultimap.create(); final FareType fareType; final Currency currency; Money lastFareWithTransfer; @@ -89,12 +92,10 @@ public ATLTransfer(Currency currency, FareType fareType) { */ public boolean addLeg(Leg leg, Money defaultFare) { // A transfer will always contain at least one ride. - ItineraryFares fare = new ItineraryFares(); RideType toRideType = classify(leg); if (legs.size() == 0) { legs.add(leg); - fare.addFare(fareType, defaultFare); - fares.add(fare); + fares.put(fareType, defaultFare); lastFareWithTransfer = defaultFare; maxRides = getMaxTransfers(toRideType); transferWindow = getTransferWindow(toRideType); @@ -128,12 +129,8 @@ public boolean addLeg(Leg leg, Money defaultFare) { } } - // The transfer is valid for this ride, so create a fare object. - // TODO: Change this fare object out for the one on the Ride object when Fare-by-leg is added - fares.add(fare); - if (transferClassification.type.equals(TransferType.NO_TRANSFER)) { - fare.addFare(fareType, defaultFare); + fares.put(fareType, defaultFare); // Full fare is charged, but transfer is still valid. // Ride is not added to rides list since it doesn't count towards transfer limit. // NOTE: Rides and fares list will not always be in sync because of this. @@ -143,7 +140,7 @@ public boolean addLeg(Leg leg, Money defaultFare) { // All conditions below this point "use" the transfer, so we add the ride. legs.add(leg); if (transferClassification.type.equals(TransferType.FREE_TRANSFER)) { - fare.addFare(fareType, Money.ofFractionalAmount(currency, 0)); + fares.put(fareType, Money.ofFractionalAmount(currency, 0)); lastFareWithTransfer = defaultFare; return true; } else if (transferClassification.type.equals(TransferType.TRANSFER_PAY_DIFFERENCE)) { @@ -151,11 +148,11 @@ public boolean addLeg(Leg leg, Money defaultFare) { if (defaultFare.greaterThan(lastFareWithTransfer)) { newCost = defaultFare.minus(lastFareWithTransfer); } - fare.addFare(fareType, newCost); + fares.put(fareType, newCost); lastFareWithTransfer = defaultFare; return true; } else if (transferClassification.type.equals(TransferType.TRANSFER_WITH_UPCHARGE)) { - fare.addFare(fareType, transferClassification.upcharge); + fares.put(fareType, transferClassification.upcharge); lastFareWithTransfer = defaultFare; return true; } @@ -163,11 +160,7 @@ public boolean addLeg(Leg leg, Money defaultFare) { } public Money getTotal() { - Money total = Money.ZERO_USD; - for (ItineraryFares f : fares) { - total = total.plus(f.getFare(fareType)); - } - return total; + return fares.get(fareType).stream().reduce(ZERO_USD, Money::plus); } } @@ -385,15 +378,14 @@ protected Collection fareRulesForFeed(FareType fareType, String fee } @Override - public boolean populateFare( - ItineraryFares fare, + public ItineraryFares calculateFaresForType( Currency currency, FareType fareType, - List rides, + List legs, Collection fareRules ) { List transfers = new ArrayList<>(); - for (var ride : rides) { + for (var ride : legs) { Money defaultFare = getLegPrice(ride, fareType, fareRules); if (transfers.isEmpty()) { transfers.add(new ATLTransfer(currency, fareType)); @@ -419,9 +411,8 @@ public boolean populateFare( null, null ); + var fare = ItineraryFares.empty(); fare.addItineraryProducts(List.of(fareProduct)); - fare.addFare(fareType, cost); - - return true; + return fare; } } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java index 0bc4408b635..03e6eeff9fd 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -75,8 +75,6 @@ public class DefaultFareService implements FareService { private static final Logger LOG = LoggerFactory.getLogger(DefaultFareService.class); - private final float UNKNOWN_FARE_PRICE = -0.01f; - /** For each fare type (regular, student, etc...) the collection of rules that apply. */ protected Map> fareRulesPerType; @@ -119,80 +117,25 @@ public ItineraryFares calculateFares(Itinerary itinerary) { var fareLegsByFeed = fareLegsByFeed(fareLegs); ItineraryFares fare = ItineraryFares.empty(); - boolean hasFare = false; for (FareType fareType : fareRulesPerType.keySet()) { - final Multimap fareProducts = LinkedHashMultimap.create(); - List fares = new ArrayList<>(); - boolean legWithoutRulesFound = false; - boolean legsWithoutMatchingRulesFound = false; for (String feedId : fareLegsByFeed.keySet()) { - ItineraryFares currentFare = ItineraryFares.empty(); var fareRules = fareRulesForFeed(fareType, feedId); // Get the currency from the first fareAttribute, assuming that all tickets use the same currency. if (fareRules != null && !fareRules.isEmpty()) { Currency currency = fareRules.iterator().next().getFareAttribute().getPrice().currency(); - boolean feedHasFare = populateFare( - currentFare, + ItineraryFares computedFaresForType = calculateFaresForType( currency, fareType, fareLegsByFeed.get(feedId), fareRules ); - if (!feedHasFare) { - legsWithoutMatchingRulesFound = true; - } - hasFare = feedHasFare || hasFare; // Other feeds might still have fare for some legs - - fareProducts.putAll(currentFare.getLegProducts()); - fare.addFare(fareType, currentFare.getFare(fareType)); - - fares.add(currentFare.getFare(fareType)); - - // If all the legs are from one feed, consider itinerary products - if (fareLegs.equals(fareLegsByFeed.get(feedId))) { - currentFare - .getFareTypes() - .forEach(type -> { - var money = currentFare.getFare(type); - var fareProduct = FareProduct - .of(new FeedScopedId(feedId, type.name()), type.name(), money) - .build(); - fare.addItineraryProducts(List.of(fareProduct)); - }); - } - } else { - legWithoutRulesFound = true; + fare.add(computedFaresForType); } } - - fare.addFareProductUses(fareProducts); - - // No fares will be discovered after this point - if (!hasFare) { - continue; - } - - // Accumulate the final price of the fare or indicate that no final fare could be found - if (legWithoutRulesFound || legsWithoutMatchingRulesFound) { - fare.addFare( - fareType, - Money.ofFractionalAmount(fares.get(0).currency(), UNKNOWN_FARE_PRICE) - ); - } else { - fare.addFare( - fareType, - fares - .stream() - .reduce( - Money.ofFractionalAmount(fare.getFare(fareType).currency(), 0), - (r1, r2) -> r1.plus(r2) - ) - ); - } } - return hasFare ? fare : null; + return fare; } /** @@ -232,8 +175,7 @@ protected Collection fareRulesForFeed(FareType fareType, String fee * If our only rule were A-B with a fare of 10, we would have no lowest fare, but we will still * have one fare detail with fare 10 for the route A-B. B-C will not just not be listed at all. */ - protected boolean populateFare( - ItineraryFares fare, + protected ItineraryFares calculateFaresForType( Currency currency, FareType fareType, List legs, @@ -242,7 +184,6 @@ protected boolean populateFare( FareSearch r = performSearch(fareType, legs, fareRules); Multimap fareProductUses = LinkedHashMultimap.create(); - int count = 0; int start = 0; int end = legs.size() - 1; while (start <= end) { @@ -287,14 +228,12 @@ protected boolean populateFare( }); } - ++count; start = via + 1; } - var amount = r.resultTable[0][legs.size() - 1]; - fare.addFare(fareType, Money.ofFractionalAmount(currency, amount)); + var fare = ItineraryFares.empty(); fare.addFareProductUses(fareProductUses); - return count > 0; + return fare; } protected Optional calculateCost( diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareService.java index 1f1c112e63c..3cc9398f7c7 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HighestFareInFreeTransferWindowFareService.java @@ -48,8 +48,7 @@ public Duration freeTransferWindow() { * additional free transfers from there. */ @Override - protected boolean populateFare( - ItineraryFares fare, + protected ItineraryFares calculateFaresForType( Currency currency, FareType fareType, List legs, @@ -85,7 +84,6 @@ protected boolean populateFare( currentTransferWindowCost = Money.max(currentTransferWindowCost, rideCost.orElse(zero)); } cost = cost.plus(currentTransferWindowCost); - fare.addFare(fareType, cost); var fp = new FareProduct( new FeedScopedId("fares", fareType.name()), fareType.name(), @@ -94,9 +92,11 @@ protected boolean populateFare( null, null ); - fare.addItineraryProducts(List.of(fp)); - fare.addFare(fareType, cost); - return cost.greaterThan(zero); + var fare = ItineraryFares.empty(); + if (cost.greaterThan(zero)) { + fare.addItineraryProducts(List.of(fp)); + } + return fare; } @Override diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java index 4879d858e86..a02f37417ec 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareService.java @@ -396,13 +396,13 @@ protected Optional getRidePrice( * one. */ @Override - public boolean populateFare( - ItineraryFares fare, + public ItineraryFares calculateFaresForType( Currency currency, FareType fareType, List legs, Collection fareRules ) { + var fare = ItineraryFares.empty(); ZonedDateTime freeTransferStartTime = null; Money cost = Money.ZERO_USD; Money orcaFareDiscount = Money.ZERO_USD; @@ -480,11 +480,12 @@ public boolean populateFare( } cost = cost.plus(orcaFareDiscount); if (cost.fractionalAmount().floatValue() < Float.MAX_VALUE) { - fare.addFare(fareType, cost); - return true; - } else { - return false; + var fp = FareProduct + .of(new FeedScopedId(FEED_ID, fareType.name()), fareType.name(), cost) + .build(); + fare.addItineraryProducts(List.of(fp)); } + return fare; } /** 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/mapping/FareMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/FareMapper.java index e8582607ce5..0e9f452b5c1 100644 --- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/FareMapper.java +++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/FareMapper.java @@ -1,14 +1,11 @@ package org.opentripplanner.ext.restapi.mapping; import com.google.common.collect.Multimap; -import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.opentripplanner.ext.restapi.model.ApiCurrency; import org.opentripplanner.ext.restapi.model.ApiFareProduct; @@ -19,7 +16,6 @@ import org.opentripplanner.model.fare.FareMedium; import org.opentripplanner.model.fare.FareProduct; import org.opentripplanner.model.fare.FareProductUse; -import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.fare.RiderCategory; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -35,10 +31,9 @@ public FareMapper(Locale locale) { public ApiItineraryFares mapFare(Itinerary itinerary) { var fares = itinerary.getFares(); - Map apiFare = toApiMoneys(fares); return new ApiItineraryFares( - apiFare, + Map.of(), Map.of(), toApiFareProducts(fares.getItineraryProducts()), toApiLegProducts(itinerary, fares.getLegProducts()) @@ -102,17 +97,6 @@ private List toApiFareProducts(Collection product) } } - private Map toApiMoneys(ItineraryFares fare) { - return fare - .getFareTypes() - .stream() - .map(key -> { - var money = toApiMoney(fare.getFare(key)); - return new SimpleEntry<>(key, money); - }) - .collect(Collectors.toMap(e -> e.getKey().name(), Entry::getValue)); - } - private ApiMoney toApiMoney(Money m) { var c = m.currency(); return new ApiMoney( 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/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/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 7101d132834..ff3e8681ce8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -77,7 +77,6 @@ import org.opentripplanner.apis.gtfs.datafetchers.VehicleRentalStationImpl; import org.opentripplanner.apis.gtfs.datafetchers.debugOutputImpl; import org.opentripplanner.apis.gtfs.datafetchers.elevationProfileComponentImpl; -import org.opentripplanner.apis.gtfs.datafetchers.fareImpl; import org.opentripplanner.apis.gtfs.datafetchers.placeAtDistanceImpl; import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; @@ -128,7 +127,6 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(debugOutputImpl.class)) .type(typeWiring.build(DepartureRowImpl.class)) .type(typeWiring.build(elevationProfileComponentImpl.class)) - .type(typeWiring.build(fareImpl.class)) .type(typeWiring.build(FeedImpl.class)) .type(typeWiring.build(FeedImpl.class)) .type(typeWiring.build(GeometryImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java index 63104ecc79a..c7ae82a2355 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/ItineraryImpl.java @@ -2,14 +2,10 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.mapping.NumberMapper; import org.opentripplanner.model.SystemNotice; -import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.plan.Emissions; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; @@ -42,24 +38,8 @@ public DataFetcher endTime() { } @Override - public DataFetcher>> fares() { - return environment -> { - ItineraryFares fare = getSource(environment).getFares(); - if (fare == null) { - return null; - } - return fare - .getFareTypes() - .stream() - .map(fareKey -> { - Map result = new HashMap<>(); - result.put("name", fareKey); - result.put("fare", fare.getFare(fareKey)); - result.put("details", List.of()); - return result; - }) - .collect(Collectors.toList()); - }; + public DataFetcher> fares() { + return environment -> List.of(); } @Override 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/datafetchers/fareImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/fareImpl.java deleted file mode 100644 index fb2c6ee184c..00000000000 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/fareImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.opentripplanner.apis.gtfs.datafetchers; - -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import java.util.List; -import java.util.Map; -import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; -import org.opentripplanner.transit.model.basic.Money; - -public class fareImpl implements GraphQLDataFetchers.GraphQLFare { - - @Override - public DataFetcher cents() { - return environment -> ((Money) getSource(environment).get("fare")).minorUnitAmount(); - } - - @Override - public DataFetcher> components() { - return environment -> List.of(); - } - - @Override - public DataFetcher currency() { - return environment -> ((Money) getSource(environment).get("fare")).currency().getCurrencyCode(); - } - - @Override - public DataFetcher type() { - return environment -> getSource(environment).get("name").toString(); - } - - private Map getSource(DataFetchingEnvironment environment) { - return environment.getSource(); - } -} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index cd002ec187b..2f39f7f4030 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -402,7 +402,7 @@ public interface GraphQLItinerary { public DataFetcher endTime(); - public DataFetcher>> fares(); + public DataFetcher> fares(); public DataFetcher generalizedCost(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index c720fc5a119..c141266d2e6 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -54,7 +54,6 @@ config: DepartureRow: org.opentripplanner.routing.graphfinder.PatternAtStop#PatternAtStop elevationProfileComponent: org.opentripplanner.model.plan.ElevationProfile.Step Emissions: org.opentripplanner.model.plan.Emissions#Emissions - fare: java.util.Map#Map Feed: String Geometry: org.locationtech.jts.geom.Geometry#Geometry InputField: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLInputField#GraphQLInputField 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/fare/FareProduct.java b/src/main/java/org/opentripplanner/model/fare/FareProduct.java index c82e128f550..189b73807e7 100644 --- a/src/main/java/org/opentripplanner/model/fare/FareProduct.java +++ b/src/main/java/org/opentripplanner/model/fare/FareProduct.java @@ -51,6 +51,7 @@ public String toString() { return ToStringBuilder .of(FareProduct.class) .addStr("id", id.toString()) + .addStr("name", name) .addObj("amount", price) .addDuration("duration", validity) .addObj("category", category) diff --git a/src/main/java/org/opentripplanner/model/fare/ItineraryFares.java b/src/main/java/org/opentripplanner/model/fare/ItineraryFares.java index d60dbfb249e..1a64f21c41b 100644 --- a/src/main/java/org/opentripplanner/model/fare/ItineraryFares.java +++ b/src/main/java/org/opentripplanner/model/fare/ItineraryFares.java @@ -4,18 +4,13 @@ import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; import org.opentripplanner.framework.lang.Sandbox; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.core.FareType; -import org.opentripplanner.transit.model.basic.Money; /** * @@ -38,14 +33,6 @@ public class ItineraryFares { */ private final Multimap legProducts = LinkedHashMultimap.create(); - /** - * Holds the "fares" for the entire itinerary. The definition of a fare is not clear so - * this is deprecated. - * @deprecated Exists only for backwards compatibility and will be removed in the future. - */ - @Deprecated - private final Map fares = new HashMap<>(); - public static ItineraryFares empty() { return new ItineraryFares(); } @@ -64,19 +51,6 @@ public Multimap getLegProducts() { return ImmutableMultimap.copyOf(legProducts); } - /** - * Add a "fare". This is an ill-defined concept (is it for the entire itinerary or only some - * legs?) from the early days of OTP which will be removed in the future. - *

    - * @deprecated It only exists for backwards-compatibility. - * Use {@link ItineraryFares#addFareProduct(Leg, FareProduct)}, - * {@link ItineraryFares#addItineraryProducts(Collection)} instead. - */ - @Deprecated - public void addFare(FareType fareType, Money money) { - fares.put(fareType, money); - } - /** * Add fare products that cover the entire itinerary, i.e. are valid for all legs. */ @@ -84,29 +58,6 @@ public void addItineraryProducts(Collection products) { itineraryProducts.addAll(products); } - /** - * - * Get the "fare" for a specific fare type. - *

    - * It is ill-defined what this actually means (entire itinerary?, some legs?). - *

    - * Use {@link ItineraryFares#getItineraryProducts()} or {@link ItineraryFares#getLegProducts()} - * instead. - */ - @Nullable - @Deprecated - public Money getFare(FareType type) { - return fares.get(type); - } - - /** - * Return the set of {@link FareType}s that are contained in this instance. - */ - @Deprecated - public Set getFareTypes() { - return fares.keySet(); - } - @Override public int hashCode() { return Objects.hash(itineraryProducts, legProducts); @@ -152,4 +103,19 @@ public void addFareProduct(Leg leg, Collection fareProduct) { public void addFareProductUses(Multimap fareProducts) { legProducts.putAll(fareProducts); } + + /** + * Add the contents of another instance to this one. + */ + public void add(ItineraryFares fare) { + itineraryProducts.addAll(fare.itineraryProducts); + legProducts.putAll(fare.legProducts); + } + + /** + * Does this instance contain any fare products? + */ + public boolean isEmpty() { + return itineraryProducts.isEmpty() && legProducts.isEmpty(); + } } 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/core/FareType.java b/src/main/java/org/opentripplanner/routing/core/FareType.java index 8efe12246d2..007f9e2cef6 100644 --- a/src/main/java/org/opentripplanner/routing/core/FareType.java +++ b/src/main/java/org/opentripplanner/routing/core/FareType.java @@ -10,10 +10,7 @@ @Deprecated public enum FareType implements Serializable { regular, - student, senior, - tram, - special, youth, electronicRegular, electronicSenior, 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/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/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index ac2413caeae..daeb57d655e 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -933,6 +933,10 @@ type Emissions { co2: Grams } +""" +This type is only here for backwards-compatibility and this API will never return it anymore. +Please use the leg's `fareProducts` instead. +""" type fare { type: String @deprecated @@ -949,7 +953,10 @@ type fare { components: [fareComponent] @deprecated } -"""Component of the fare (i.e. ticket) for a part of the itinerary""" +""" +This type is only here for backwards-compatibility and this API will never return it anymore. +Please use the leg's `fareProducts` instead. +""" type fareComponent { """ID of the ticket type. Corresponds to `fareId` in **TicketType**.""" fareId: String @deprecated @@ -1574,7 +1581,7 @@ type Itinerary { """ Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface - will be removed in the future. + and always returns an empty list. Use the leg's `fareProducts` instead. """ fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") } 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/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 6e99e096c2c..d89afea6f9d 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -183,7 +183,6 @@ static void setup() { var railLeg = (ScheduledTransitLeg) i1.getTransitLeg(2); var fares = new ItineraryFares(); - fares.addFare(FareType.regular, Money.euros(3.1f)); var dayPass = fareProduct("day-pass"); fares.addItineraryProducts(List.of(dayPass)); 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/algorithm/mapping/__snapshots__/BikeRentalSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/BikeRentalSnapshotTest.snap index 41bc4278ca1..5e2a04c07d7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/BikeRentalSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/BikeRentalSnapshotTest.snap @@ -7,33 +7,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:32:24.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -493,33 +468,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:38:09.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -937,33 +887,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:40:10.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1277,33 +1202,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:45:24.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1763,33 +1663,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:54:24.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -2249,33 +2124,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.accessBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:56:10.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -3264,33 +3114,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:28:51.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -3802,33 +3627,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:35:24.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -4220,33 +4020,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:37:21.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -4612,33 +4387,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:43:51.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -5150,33 +4900,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:51:24.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -5568,33 +5293,8 @@ org.opentripplanner.routing.algorithm.mapping.BikeRentalSnapshotTest.egressBikeR "elevationLost" : 0.0, "endTime" : "2009-10-21T23:56:21.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/ElevationSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/ElevationSnapshotTest.snap index 6579e4bbd37..390185cd96f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/ElevationSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/ElevationSnapshotTest.snap @@ -535,33 +535,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 4.23, "endTime" : "2009-10-21T23:31:21.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -929,33 +904,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 23.58, "endTime" : "2009-10-21T23:35:04.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1349,33 +1299,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 1.09, "endTime" : "2009-10-21T23:37:44.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1743,33 +1668,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 4.76, "endTime" : "2009-10-21T23:46:18.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -2215,33 +2115,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 4.23, "endTime" : "2009-10-21T23:46:21.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -2609,33 +2484,8 @@ org.opentripplanner.routing.algorithm.mapping.ElevationSnapshotTest.transit=[ "elevationLost" : 23.58, "endTime" : "2009-10-21T23:51:04.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap index d5da342f9a6..adc5ee69635 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap @@ -235,33 +235,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:38:41.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -718,33 +693,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:39:22.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1266,33 +1216,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:53:55.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -1749,33 +1674,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:55:32.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -2297,33 +2197,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T19:08:19.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -3044,33 +2919,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:38:40.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -3530,33 +3380,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T18:54:10.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -4016,33 +3841,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T19:09:40.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -4502,33 +4302,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T19:11:51.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ @@ -5028,33 +4803,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "elevationLost" : 0.0, "endTime" : "2009-11-17T19:25:05.000+00:00", "fare" : { - "coveringItinerary" : [ - { - "amount" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - }, - "id" : "prt:regular", - "name" : "regular" - } - ], "details" : { }, - "fare" : { - "regular" : { - "cents" : 200, - "currency" : { - "currency" : "USD", - "currencyCode" : "USD", - "defaultFractionDigits" : 2, - "symbol" : "$" - } - } - }, + "fare" : { }, "legProducts" : [ { "legIndices" : [ 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/routing/core/ItineraryFaresTest.java b/src/test/java/org/opentripplanner/routing/core/ItineraryFaresTest.java index 2b22bc9e41f..3ad3b918ce1 100644 --- a/src/test/java/org/opentripplanner/routing/core/ItineraryFaresTest.java +++ b/src/test/java/org/opentripplanner/routing/core/ItineraryFaresTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.routing.core; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.model.plan.PlanTestConstants.A; import static org.opentripplanner.model.plan.PlanTestConstants.B; import static org.opentripplanner.model.plan.PlanTestConstants.C; @@ -59,6 +60,11 @@ void legProduct() { ); } + @Test + void empty() { + assertTrue(ItineraryFares.empty().isEmpty()); + } + @Nonnull private static FareProduct fareProduct(String id) { return new FareProduct(id(id), id, Money.euros(10), null, null, null); 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) { diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-fares.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-fares.json index 9ee3d4706b3..1defc81ded1 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-fares.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/plan-fares.json @@ -183,14 +183,7 @@ "fareProducts" : [ ] } ], - "fares" : [ - { - "type" : "regular", - "cents" : 310, - "currency" : "EUR", - "components" : [] - } - ] + "fares" : [ ] } ] }