From 636f36ba3bc42d77cb2aad252a3fddaa4f85f954 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 5 Jul 2023 22:01:10 +0300 Subject: [PATCH 001/118] Add cost function to RemoveTransitIfStreetOnlyIsBetter filter --- .../ItineraryListFilterChainBuilder.java | 19 ++++++++---- ...moveTransitIfStreetOnlyIsBetterFilter.java | 20 ++++++++---- .../RouteRequestToFilterChainMapper.java | 4 ++- .../ItineraryFilterPreferences.java | 31 ++++++++++++++++++- .../routerequest/ItineraryFiltersConfig.java | 19 ++++++++++++ ...TransitIfStreetOnlyIsBetterFilterTest.java | 13 +++++--- 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 3597558978b..2cefe6dde11 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -56,7 +56,7 @@ public class ItineraryListFilterChainBuilder { private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF; private int maxNumberOfItineraries = NOT_SET; private ListSection maxNumberOfItinerariesCrop = ListSection.TAIL; - private boolean removeTransitWithHigherCostThanBestOnStreetOnly = true; + private DoubleAlgorithmFunction removeTransitWithHigherCostThanBestOnStreetOnly; private boolean removeWalkAllTheWayResults; private boolean sameFirstOrLastTripFilter; private TransitGeneralizedCostFilterParams transitGeneralizedCostFilterParams; @@ -193,7 +193,7 @@ public ItineraryListFilterChainBuilder withParkAndRideDurationRatio(double value * exist. */ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOnStreetOnly( - boolean value + DoubleAlgorithmFunction value ) { this.removeTransitWithHigherCostThanBestOnStreetOnly = value; return this; @@ -337,6 +337,17 @@ public ItineraryListFilterChain build() { ); } + // Filter transit itineraries by comparing against non-transit using generalized-cost + if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { + filters.add( + new DeletionFlaggingFilter( + new RemoveTransitIfStreetOnlyIsBetterFilter( + removeTransitWithHigherCostThanBestOnStreetOnly + ) + ) + ); + } + // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that // remove elements/ based on the given itinerary properties - not considering other // itineraries. This may remove itineraries in the "groupBy" filters that are considered @@ -347,10 +358,6 @@ public ItineraryListFilterChain build() { // is worse). B is removed by the {@link LatestDepartureTimeFilter} below. This is exactly // what we want, since both itineraries are none optimal. { - if (removeTransitWithHigherCostThanBestOnStreetOnly) { - filters.add(new DeletionFlaggingFilter(new RemoveTransitIfStreetOnlyIsBetterFilter())); - } - if (removeWalkAllTheWayResults) { filters.add(new DeletionFlaggingFilter(new RemoveWalkOnlyFilter())); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index 7296f9b22de..ce554666f36 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -2,9 +2,10 @@ import java.util.Comparator; import java.util.List; -import java.util.Optional; +import java.util.OptionalDouble; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.api.request.framework.DoubleAlgorithmFunction; /** * Filter itineraries based on generalizedCost, compared with a on-street-all-the-way itinerary(if @@ -13,6 +14,12 @@ */ public class RemoveTransitIfStreetOnlyIsBetterFilter implements ItineraryDeletionFlagger { + private final DoubleAlgorithmFunction costLimitFunction; + + public RemoveTransitIfStreetOnlyIsBetterFilter(DoubleAlgorithmFunction costLimitFunction) { + this.costLimitFunction = costLimitFunction; + } + /** * Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain}, * to know which filters removed @@ -27,18 +34,19 @@ public String name() { @Override public List flagForRemoval(List itineraries) { // Find the best walk-all-the-way option - Optional bestStreetOp = itineraries + OptionalDouble minStreetCost = itineraries .stream() .filter(Itinerary::isOnStreetAllTheWay) - .min(Comparator.comparingInt(Itinerary::getGeneralizedCost)); + .mapToDouble(Itinerary::getGeneralizedCost) + .min(); - if (bestStreetOp.isEmpty()) { + if (minStreetCost.isEmpty()) { return List.of(); } - final long limit = bestStreetOp.get().getGeneralizedCost(); + final double limit = costLimitFunction.calculate(minStreetCost.getAsDouble()); - // Filter away itineraries that have higher cost than the best non-transit option. + // Filter away itineraries that have higher cost than limit cost computed above return itineraries .stream() .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 7f5c72c261c..e9c2b63f9fe 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -60,6 +60,9 @@ public static ItineraryListFilterChain createFilterChain( .withBikeRentalDistanceRatio(params.bikeRentalDistanceRatio()) .withParkAndRideDurationRatio(params.parkAndRideDurationRatio()) .withNonTransitGeneralizedCostLimit(params.nonTransitGeneralizedCostLimit()) + .withRemoveTransitWithHigherCostThanBestOnStreetOnlyh( + params.removeTransitWithHigherCostThanBestOnStreetOnly() + ) .withSameFirstOrLastTripFilter(params.filterItinerariesWithSameFirstOrLastTrip()) .withAccessibilityScore( params.useAccessibilityScore() && request.wheelchair(), @@ -74,7 +77,6 @@ public static ItineraryListFilterChain createFilterChain( context.transitService().getTransitAlertService(), context.transitService()::getMultiModalStationForStation ) - .withRemoveTransitWithHigherCostThanBestOnStreetOnly(true) .withLatestDepartureTimeLimit(filterOnLatestDepartureTime) .withMaxLimitReachedSubscriber(maxLimitReachedSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index bc43df55733..5f65196e581 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -45,6 +45,8 @@ private ItineraryFilterPreferences() { this.removeItinerariesWithSameRoutesAndStops = false; this.transitGeneralizedCostLimit = new TransitGeneralizedCostFilterParams(RequestFunctions.createLinearFunction(900, 1.5), 0.4); + this.removeTransitWithHigherCostThanBestOnStreetOnly = + RequestFunctions.createLinearFunction(200, 1.5); } private ItineraryFilterPreferences(Builder builder) { @@ -63,6 +65,8 @@ private ItineraryFilterPreferences(Builder builder) { this.parkAndRideDurationRatio = Units.ratio(builder.parkAndRideDurationRatio); this.removeItinerariesWithSameRoutesAndStops = builder.removeItinerariesWithSameRoutesAndStops; this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit); + this.removeTransitWithHigherCostThanBestOnStreetOnly = + Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly); } public static Builder of() { @@ -121,6 +125,10 @@ public TransitGeneralizedCostFilterParams transitGeneralizedCostLimit() { return transitGeneralizedCostLimit; } + public DoubleAlgorithmFunction removeTransitWithHigherCostThanBestOnStreetOnly() { + return removeTransitWithHigherCostThanBestOnStreetOnly; + } + @Override public String toString() { return ToStringBuilder @@ -163,6 +171,11 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) + .addObj( + "removeTransitWithHigherCostThanBestOnStreetOnly", + nonTransitGeneralizedCostLimit, + DEFAULT.nonTransitGeneralizedCostLimit + ) .toString(); } @@ -187,6 +200,10 @@ public boolean equals(Object o) { Double.compare(that.parkAndRideDurationRatio, parkAndRideDurationRatio) == 0 && removeItinerariesWithSameRoutesAndStops == that.removeItinerariesWithSameRoutesAndStops && Objects.equals(nonTransitGeneralizedCostLimit, that.nonTransitGeneralizedCostLimit) && + Objects.equals( + removeTransitWithHigherCostThanBestOnStreetOnly, + that.removeTransitWithHigherCostThanBestOnStreetOnly + ) && Objects.equals(transitGeneralizedCostLimit, that.transitGeneralizedCostLimit) ); } @@ -205,7 +222,8 @@ public int hashCode() { nonTransitGeneralizedCostLimit, parkAndRideDurationRatio, removeItinerariesWithSameRoutesAndStops, - transitGeneralizedCostLimit + transitGeneralizedCostLimit, + removeTransitWithHigherCostThanBestOnStreetOnly ); } @@ -224,6 +242,7 @@ public static class Builder { private double parkAndRideDurationRatio; private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; + private DoubleAlgorithmFunction removeTransitWithHigherCostThanBestOnStreetOnly; public ItineraryFilterPreferences original() { return original; @@ -299,6 +318,14 @@ public Builder withTransitGeneralizedCostLimit( return this; } + public Builder withRemoveTransitWithHigherCostThanBestOnStreetOnly( + DoubleAlgorithmFunction removeTransitWithHigherCostThanBestOnStreetOnly + ) { + this.removeTransitWithHigherCostThanBestOnStreetOnly = + removeTransitWithHigherCostThanBestOnStreetOnly; + return this; + } + public Builder(ItineraryFilterPreferences original) { this.original = original; this.accessibilityScore = original.accessibilityScore; @@ -316,6 +343,8 @@ public Builder(ItineraryFilterPreferences original) { this.removeItinerariesWithSameRoutesAndStops = original.removeItinerariesWithSameRoutesAndStops; this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; + this.removeTransitWithHigherCostThanBestOnStreetOnly = + original.removeTransitWithHigherCostThanBestOnStreetOnly; } public Builder apply(Consumer body) { diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index aa6c88bf44b..bf97fe4254a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -154,6 +154,25 @@ public static void mapItineraryFilterParams( ) .asLinearFunction(dft.nonTransitGeneralizedCostLimit()) ) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly( + c + .of("removeTransitWithHigherCostThanBestOnStreetOnly") + .since(V2_1) + .summary( + "Limit function for generalized-cost computed from non-transit itineries for transit itineraries." + ) + .description( + """ +The max-limit is applied to itineraries with transit *legs*, and only itineraries +without transit legs are considered when calculating the minimum cost. The smallest +generalized-cost value is used as input to the function. The function is used to calculate a +*max-limit*. The max-limit is then used to filter *transit* itineraries by +*generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result +set. +""" + ) + .asLinearFunction(dft.removeTransitWithHigherCostThanBestOnStreetOnly()) + ) .withBikeRentalDistanceRatio( c .of("bikeRentalDistanceRatio") diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java index 5598c97cd57..0d086b51cd0 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.routing.api.request.framework.RequestFunctions; public class RemoveTransitIfStreetOnlyIsBetterFilterTest implements PlanTestConstants { @@ -20,7 +21,7 @@ public void filterAwayNothingIfNoWalking() { // When: List result = DeletionFlaggerTestHelper.process( List.of(i1, i2), - new RemoveTransitIfStreetOnlyIsBetterFilter() + new RemoveTransitIfStreetOnlyIsBetterFilter(RequestFunctions.createLinearFunction(200, 1.2)) ); // Then: @@ -33,20 +34,22 @@ public void filterAwayLongTravelTimeWithoutWaitTime() { Itinerary walk = newItinerary(A, 6).walk(1, E).build(); walk.setGeneralizedCost(300); - // Given: a bicycle itinerary with low cost - transit with higher cost is removed + // Given: a bicycle itinerary with low cost - transit with clearly higher cost are removed Itinerary bicycle = newItinerary(A).bicycle(6, 8, E).build(); bicycle.setGeneralizedCost(200); + // transit with almost equal cost should not be dropped Itinerary i1 = newItinerary(A).bus(21, 6, 8, E).build(); - i1.setGeneralizedCost(199); + i1.setGeneralizedCost(220); + // transit with considerably higher cost will be dropped Itinerary i2 = newItinerary(A).bus(31, 6, 8, E).build(); - i2.setGeneralizedCost(200); + i2.setGeneralizedCost(360); // When: List result = DeletionFlaggerTestHelper.process( List.of(i2, bicycle, walk, i1), - new RemoveTransitIfStreetOnlyIsBetterFilter() + new RemoveTransitIfStreetOnlyIsBetterFilter(RequestFunctions.createLinearFunction(60, 1.2)) ); // Then: From 3621260ea483868d011b6f0cf0d457080cd22b06 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 6 Jul 2023 12:05:06 +0300 Subject: [PATCH 002/118] Apply hard filter for walking Softer cost limit function based filtering will sometimes preserve transit itineraries, which contain more walking than a plain walk itinerary. Therefore, apply harder filtering logic wrt. walking. --- ...moveTransitIfStreetOnlyIsBetterFilter.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index ce554666f36..e0f38307843 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -5,6 +5,7 @@ import java.util.OptionalDouble; import java.util.stream.Collectors; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.api.request.framework.DoubleAlgorithmFunction; /** @@ -31,6 +32,14 @@ public String name() { return TAG; } + private double getWalkDistance(Itinerary it) { + return it + .getStreetLegs() + .filter(l -> l.isWalkingLeg()) + .mapToDouble(Leg::getDistanceMeters) + .sum(); + } + @Override public List flagForRemoval(List itineraries) { // Find the best walk-all-the-way option @@ -47,10 +56,33 @@ public List flagForRemoval(List itineraries) { final double limit = costLimitFunction.calculate(minStreetCost.getAsDouble()); // Filter away itineraries that have higher cost than limit cost computed above - return itineraries + List filtered = itineraries .stream() .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) .collect(Collectors.toList()); + + // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary + // This never makes sense + OptionalDouble walkDistance = itineraries + .stream() + .filter(Itinerary::isWalkingAllTheWay) + .mapToDouble(Itinerary::distanceMeters) + .min(); + + if (walkDistance.isEmpty()) { + return filtered; + } + final double walkLimit = walkDistance.getAsDouble(); + List filtered2 = itineraries + .stream() + .filter(it -> getWalkDistance(it) > walkLimit) + .collect(Collectors.toList()); + + // remove duplicates + filtered2.removeAll(filtered); + filtered.addAll(filtered2); + + return filtered; } @Override From bfe2b61610b43057937875d99f27f1a664ab97e4 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 6 Jul 2023 13:42:19 +0300 Subject: [PATCH 003/118] Pass linear function as parameter for RemoveTransitIfStreetOnlyIsBetterFilter --- docs/RouteRequest.md | 254 ++++++++++-------- .../RouteRequestToFilterChainMapper.java | 2 +- .../ItineraryFilterPreferences.java | 13 +- .../routerequest/ItineraryFiltersConfig.java | 3 +- .../ItineraryListFilterChainTest.java | 17 +- .../ItineraryFilterPreferencesTest.java | 8 +- 6 files changed, 164 insertions(+), 133 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 77580b29a59..01a185ce24e 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -13,125 +13,126 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|--------------------------|:-----:| -| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | -| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | -| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | -| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | -| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | -| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | -| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | -| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | -| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | -| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | -| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | -| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | -| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | -| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | -| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | -| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | -| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | -| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | -| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| [modes](RoutingModes.md) | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | -| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | -| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | -| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | -| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | -| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | -| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | -| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | -| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | -| [unpreferredCost](#rd_unpreferredCost) | `linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"f(x) = 0 + 1.0 x"` | 2.2 | -| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | -| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | -| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | -| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | -|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | -|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | -|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | -|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | -|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | -|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | -|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | -|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"f(x) = 3,600 + 2.0 x"` | 2.1 | -|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | -|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | -|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `linear-function` | The base function used by the filter. | *Optional* | `"f(x) = 900 + 1.5 x"` | 2.2 | -|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | -| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | -| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | -|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | -|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | -|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | -|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | -| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | -|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | -|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | -|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | -|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | -|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | -|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | -|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | -|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | -|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | -|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | -|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|--------------------------|:-----:| +| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | +| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | +| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | +| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | +| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | +| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | +| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | +| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | +| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | +| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | +| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | +| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | +| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | +| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | +| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | +| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | +| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | +| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | +| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | +| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | +| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | +| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | +| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | +| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | +| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | +| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | +| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | +| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | +| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | +| [modes](RoutingModes.md) | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | +| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | +| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | +| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | +| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | +| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | +| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | +| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | +| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | +| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | +| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | +| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | +| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | +| [unpreferredCost](#rd_unpreferredCost) | `linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"f(x) = 0 + 1.0 x"` | 2.2 | +| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | +| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | +| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | +| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | +| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | +| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | +| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | +| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | +|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | +|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | +|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | +|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | +|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | +|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | +|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | +|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | +|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"f(x) = 3,600 + 2.0 x"` | 2.1 | +|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | +|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | +|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `linear-function` | Limit function for generalized-cost computed from non-transit itineries for transit itineraries. | *Optional* | `"f(x) = 60 + 1.3 x"` | 2.4 | +|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | +|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `linear-function` | The base function used by the filter. | *Optional* | `"f(x) = 900 + 1.5 x"` | 2.2 | +|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | +| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | +| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | +| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | +|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | +|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | +|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | +|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | +| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | +| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | +|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | +|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | +| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | +|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | +|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | +|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | +|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | +|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | +|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | +|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | +|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | +|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | +|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | @@ -611,6 +612,21 @@ Set to true if you want to list only the first itinerary which goes through the Itineraries visiting the same set of stops and riding the exact same routes, departing later are removed from the result. +

removeTransitWithHigherCostThanBestOnStreetOnly

+ +**Since version:** `2.4` ∙ **Type:** `linear-function` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"f(x) = 60 + 1.3 x"` +**Path:** /routingDefaults/itineraryFilters + +Limit function for generalized-cost computed from non-transit itineries for transit itineraries. + +The max-limit is applied to itineraries with transit *legs*, and only itineraries +without transit legs are considered when calculating the minimum cost. The smallest +generalized-cost value is used as input to the function. The function is used to calculate a +*max-limit*. The max-limit is then used to filter *transit* itineraries by +*generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result +set. + +

transitGeneralizedCostLimit

**Since version:** `2.1` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index e9c2b63f9fe..b98077dc1db 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -60,7 +60,7 @@ public static ItineraryListFilterChain createFilterChain( .withBikeRentalDistanceRatio(params.bikeRentalDistanceRatio()) .withParkAndRideDurationRatio(params.parkAndRideDurationRatio()) .withNonTransitGeneralizedCostLimit(params.nonTransitGeneralizedCostLimit()) - .withRemoveTransitWithHigherCostThanBestOnStreetOnlyh( + .withRemoveTransitWithHigherCostThanBestOnStreetOnly( params.removeTransitWithHigherCostThanBestOnStreetOnly() ) .withSameFirstOrLastTripFilter(params.filterItinerariesWithSameFirstOrLastTrip()) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 5f65196e581..3c898e20301 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -30,6 +30,7 @@ public final class ItineraryFilterPreferences { private final double parkAndRideDurationRatio; private final boolean removeItinerariesWithSameRoutesAndStops; private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; + private final DoubleAlgorithmFunction removeTransitWithHigherCostThanBestOnStreetOnly; private ItineraryFilterPreferences() { this.accessibilityScore = false; @@ -46,7 +47,7 @@ private ItineraryFilterPreferences() { this.transitGeneralizedCostLimit = new TransitGeneralizedCostFilterParams(RequestFunctions.createLinearFunction(900, 1.5), 0.4); this.removeTransitWithHigherCostThanBestOnStreetOnly = - RequestFunctions.createLinearFunction(200, 1.5); + RequestFunctions.createLinearFunction(60, 1.3); } private ItineraryFilterPreferences(Builder builder) { @@ -167,15 +168,15 @@ public String toString() { transitGeneralizedCostLimit, DEFAULT.transitGeneralizedCostLimit ) + .addObj( + "removeTransitWithHigherCostThanBestOnStreetOnly", + removeTransitWithHigherCostThanBestOnStreetOnly, + DEFAULT.removeTransitWithHigherCostThanBestOnStreetOnly + ) .addBoolIfTrue( "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) - .addObj( - "removeTransitWithHigherCostThanBestOnStreetOnly", - nonTransitGeneralizedCostLimit, - DEFAULT.nonTransitGeneralizedCostLimit - ) .toString(); } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index bf97fe4254a..2a441d68287 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -5,6 +5,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.framework.RequestFunctions; @@ -157,7 +158,7 @@ public static void mapItineraryFilterParams( .withRemoveTransitWithHigherCostThanBestOnStreetOnly( c .of("removeTransitWithHigherCostThanBestOnStreetOnly") - .since(V2_1) + .since(V2_4) .summary( "Limit function for generalized-cost computed from non-transit itineries for transit itineraries." ) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index c7fe995ba26..5ebbdc44222 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -21,6 +21,7 @@ import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.routing.alertpatch.StopCondition; +import org.opentripplanner.routing.api.request.framework.RequestFunctions; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.services.TransitAlertService; @@ -162,7 +163,9 @@ public void testSameFirstOrLastTripFilter() { void testRoutingErrorsOriginDestinationTooCloseTest() { ItineraryListFilterChain chain = createBuilder(false, false, 20) .withRemoveWalkAllTheWayResults(true) - .withRemoveTransitWithHigherCostThanBestOnStreetOnly(true) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly( + RequestFunctions.createLinearFunction(0, 1.0) + ) .build(); Itinerary walk = newItinerary(A, T11_06).walk(D10m, E).build(); @@ -243,7 +246,9 @@ private ItineraryListFilterChainBuilder createBuilder( var sortOrder = arriveBy ? STREET_AND_DEPARTURE_TIME : STREET_AND_ARRIVAL_TIME; return new ItineraryListFilterChainBuilder(sortOrder) .withMaxNumberOfItineraries(numOfItineraries) - .withRemoveTransitWithHigherCostThanBestOnStreetOnly(true) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly( + RequestFunctions.createLinearFunction(0, 1.0) + ) .withDebugEnabled(ofDebugEnabled(debug)); } @@ -297,9 +302,9 @@ public void setUpItineraries() { @Test public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { - // Disable filter and allow none optimal bus itinerary pass through + // Allow non-optimal bus itinerary pass through ItineraryListFilterChain chain = builder - .withRemoveTransitWithHigherCostThanBestOnStreetOnly(false) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly(null) .build(); assertEquals(toStr(List.of(walk, bus)), toStr(chain.filter(List.of(walk, bus)))); } @@ -308,7 +313,9 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { public void removeTransitWithHigherCostThanBestOnStreetOnlyEnabled() { // Enable filter and remove bus itinerary ItineraryListFilterChain chain = builder - .withRemoveTransitWithHigherCostThanBestOnStreetOnly(true) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly( + RequestFunctions.createLinearFunction(0, 1.0) + ) .build(); assertEquals(toStr(List.of(walk)), toStr(chain.filter(List.of(walk, bus)))); } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index 80c3ab99ef0..781ebfe1677 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java @@ -28,6 +28,10 @@ class ItineraryFilterPreferencesTest { RequestFunctions.createLinearFunction(3.5, 5.0), 3.0 ); + private static final DoubleAlgorithmFunction TRANSIT_BEST_STREET_COST_LIMIT = RequestFunctions.createLinearFunction( + 60, + 1.3 + ); private final ItineraryFilterPreferences subject = ItineraryFilterPreferences .of() @@ -42,6 +46,7 @@ class ItineraryFilterPreferencesTest { .withNonTransitGeneralizedCostLimit(NON_TRANSIT_GENERALIZED_COST_LIMIT) .withParkAndRideDurationRatio(PARK_AND_RIDE_DURATION_RATIO) .withTransitGeneralizedCostLimit(TRANSIT_GENERALIZED_COST_LIMIT) + .withRemoveTransitWithHigherCostThanBestOnStreetOnly(TRANSIT_BEST_STREET_COST_LIMIT) .build(); @Test @@ -133,7 +138,8 @@ void testToString() { "minBikeParkingDistance: 2,000.0, " + "nonTransitGeneralizedCostLimit: f(x) = 4 + 5.0 x, " + "parkAndRideDurationRatio: 0.44, " + - "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=f(x) = 4 + 5.0 x, intervalRelaxFactor=3.0]" + + "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=f(x) = 4 + 5.0 x, intervalRelaxFactor=3.0], " + + "removeTransitWithHigherCostThanBestOnStreetOnly: f(x) = 60 + 1.3 x" + "}", subject.toString() ); From 709a94ae3c1e4cd376b24ec24e30fe2157742a35 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 6 Jul 2023 14:34:34 +0300 Subject: [PATCH 004/118] Add two missing itinerary filter configuration examples --- src/test/resources/standalone/config/router-config.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index b3bfb71d11f..9b5ebb63ebb 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -59,6 +59,8 @@ "costLimitFunction": "900 + 1.5 x", "intervalRelaxFactor": 0.4 }, + "nonTransitGeneralizedCostLimit": "400 + 1.5x", + "removeTransitWithHigherCostThanBestOnStreetOnly": "60 + 1.3x", "bikeRentalDistanceRatio": 0.3, "accessibilityScore": true, "minBikeParkingDistance": 300 From 3463eb6238061f01228093ceefb38e514844c62a Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 6 Jul 2023 14:47:47 +0300 Subject: [PATCH 005/118] Update docs --- docs/RouteRequest.md | 5 ++++- docs/RouterConfiguration.md | 2 ++ .../config/routerequest/ItineraryFiltersConfig.java | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 01a185ce24e..dbeab075ea2 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -624,7 +624,8 @@ without transit legs are considered when calculating the minimum cost. The small generalized-cost value is used as input to the function. The function is used to calculate a *max-limit*. The max-limit is then used to filter *transit* itineraries by *generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result -set. + set. Walking is handled with a different logic: if a transit itinerary has more walking than + a plain walk itinerary, it will be removed even if the cost limit function would keep it.

transitGeneralizedCostLimit

@@ -930,6 +931,8 @@ include stairs as a last result. "costLimitFunction" : "900 + 1.5 x", "intervalRelaxFactor" : 0.4 }, + "nonTransitGeneralizedCostLimit" : "400 + 1.5x", + "removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x", "bikeRentalDistanceRatio" : 0.3, "accessibilityScore" : true, "minBikeParkingDistance" : 300 diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 485ceaff448..7d6d0163820 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -493,6 +493,8 @@ HTTP headers to add to the request. Any header key, value can be inserted. "costLimitFunction" : "900 + 1.5 x", "intervalRelaxFactor" : 0.4 }, + "nonTransitGeneralizedCostLimit" : "400 + 1.5x", + "removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x", "bikeRentalDistanceRatio" : 0.3, "accessibilityScore" : true, "minBikeParkingDistance" : 300 diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 2a441d68287..81093e4bdf8 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -169,7 +169,8 @@ public static void mapItineraryFilterParams( generalized-cost value is used as input to the function. The function is used to calculate a *max-limit*. The max-limit is then used to filter *transit* itineraries by *generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result -set. + set. Walking is handled with a different logic: if a transit itinerary has more walking than + a plain walk itinerary, it will be removed even if the cost limit function would keep it. """ ) .asLinearFunction(dft.removeTransitWithHigherCostThanBestOnStreetOnly()) From e9811216bc550ae742bb04f653103c759de5a493 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 18 Jul 2023 11:41:43 +0300 Subject: [PATCH 006/118] Update auto generated docs --- docs/RouteRequest.md | 127 +----------------------------------- docs/RouterConfiguration.md | 2 + 2 files changed, 4 insertions(+), 125 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 8594ff60563..bf17b5b939c 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -13,7 +13,6 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe -<<<<<<< HEAD | Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | |--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|--------------------------|:-----:| | [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | @@ -46,7 +45,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | | elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | | elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | na | +| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | | geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | | ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | | [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | @@ -54,7 +53,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe | [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | | [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | | [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| [modes](RoutingModes.md) | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | +| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | | nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | | numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | | [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | @@ -135,128 +134,6 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | |       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | |       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -======= -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|--------------------------|:-----:| -| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | -| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | -| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | -| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | -| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | -| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | -| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | -| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | -| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | -| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | -| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | -| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | -| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | -| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | -| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | -| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | -| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | -| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | -| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | -| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | -| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | -| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | -| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | -| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | -| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | -| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | -| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | -| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | -| [unpreferredCost](#rd_unpreferredCost) | `linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"f(x) = 0 + 1.0 x"` | 2.2 | -| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | -| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | -| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | -| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | -|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | -|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | -|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | -|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | -|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | -|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | -|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | -|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"f(x) = 3,600 + 2.0 x"` | 2.1 | -|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | -|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | -|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `linear-function` | The base function used by the filter. | *Optional* | `"f(x) = 900 + 1.5 x"` | 2.2 | -|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | -| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | -| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | -|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | -|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | -|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | -|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | -| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | -|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | -|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | -|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | -|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | -|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | -|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | -|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | -|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | -|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | -|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | -|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | ->>>>>>> otp/dev-2.x diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index bdb39df2bf6..bcbf798a70e 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -494,6 +494,8 @@ HTTP headers to add to the request. Any header key, value can be inserted. "costLimitFunction" : "900 + 1.5 x", "intervalRelaxFactor" : 0.4 }, + "nonTransitGeneralizedCostLimit" : "400 + 1.5x", + "removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x", "bikeRentalDistanceRatio" : 0.3, "accessibilityScore" : true, "minBikeParkingDistance" : 300 From 1828c8975da09797bffd0727b6802bdffd71200d Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 10 Aug 2023 13:17:58 +0300 Subject: [PATCH 007/118] Consider feed id in HSL fare service --- .../ext/fares/impl/HSLFareServiceTest.java | 28 +++++++++++++++++++ .../ext/fares/impl/HSLFareServiceImpl.java | 17 ++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) 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 8960128ff0e..1f89be2849e 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 @@ -61,6 +61,12 @@ private static List createTestCases() { .withTimezone("Europe/Helsinki") .build(); + Agency agency3 = Agency + .of(new FeedScopedId("FEED2", "AG3")) + .withName("Agency 3") + .withTimezone("Europe/Helsinki") + .build(); + FareZone A = FareZone.of(new FeedScopedId(FEED_ID, "A")).build(); FareZone B = FareZone.of(new FeedScopedId(FEED_ID, "B")).build(); FareZone C = FareZone.of(new FeedScopedId(FEED_ID, "C")).build(); @@ -211,6 +217,13 @@ private static List createTestCases() { .withMode(TransitMode.BUS) .build(); + Route routeAgency3 = Route + .of(new FeedScopedId("FEED2", "R3")) + .withAgency(agency3) + .withLongName(new NonLocalizedString("Route agency 3")) + .withMode(TransitMode.BUS) + .build(); + // Itineraries within zone A Itinerary A1_A2 = newItinerary(A1, T11_06).bus(1, T11_06, T11_12, A2).build(); @@ -366,6 +379,21 @@ private static List createTestCases() { ) ); + // Multifeed case + Itinerary A1_A2_2 = newItinerary(A1, T11_06) + .bus(routeAgency3, 1, T11_06, T11_14, A2) + .bus(routeAgency1, 2, T11_30, T11_50, A1) + .build(); + + args.add( + Arguments.of( + "Bus ride within zone A with two legs using different agencies from different feeds ", + hslFareService, + A1_A2_2, + List.of(fareAttributeAB.getId()) + ) + ); + return args; } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java index e8ec215b149..23dde8b2a17 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.fares.impl; +import com.google.common.collect.Sets; import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collection; @@ -7,6 +8,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.RouteOriginDestination; @@ -51,6 +53,17 @@ protected Optional getBestFareAndId( String agency = null; boolean singleAgency = true; + // Do not consider fares for legs that do not have fare rules in the same feed + Set fareRuleFeedIds = fareRules + .stream() + .map(fr -> fr.getFareAttribute().getId().getFeedId()) + .collect(Collectors.toSet()); + Set legFeedIds = legs + .stream() + .map(leg -> leg.getAgency().getId().getFeedId()) + .collect(Collectors.toSet()); + if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) return Optional.ofNullable(null); + for (Leg leg : legs) { lastRideStartTime = leg.getStartTime(); if (agency == null) { @@ -58,11 +71,12 @@ protected Optional getBestFareAndId( } else if (agency != leg.getAgency().getId().getId().toString()) { singleAgency = false; } + /* HSL specific logic: all exception routes start and end from the defined zone set, but visit temporarily (maybe 1 stop only) an 'external' zone */ Money bestSpecialFare = MAX_PRICE; - Set ruleZones = null; + Set ruleZones = null; for (FareRuleSet ruleSet : fareRules) { if ( ruleSet.hasAgencyDefined() && @@ -114,6 +128,7 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */ } } } + if (ruleZones != null) { // the special case // evaluate boolean ride.zones AND rule.zones Set zoneIntersection = new HashSet( From 9c073741bee9d92cabbcd88e90e20014a90b0f31 Mon Sep 17 00:00:00 2001 From: Viljami N Date: Thu, 10 Aug 2023 14:43:19 +0300 Subject: [PATCH 008/118] Apply feedback from review Co-authored-by: Leonard Ehrenfried --- .../org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java index 23dde8b2a17..9b8693b43f4 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java @@ -62,7 +62,7 @@ protected Optional getBestFareAndId( .stream() .map(leg -> leg.getAgency().getId().getFeedId()) .collect(Collectors.toSet()); - if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) return Optional.ofNullable(null); + if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) return Optional.empty(); for (Leg leg : legs) { lastRideStartTime = leg.getStartTime(); From 9cb7e8de6fd50113e06866edba8b70709ad45db0 Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 17 Aug 2023 15:40:33 +0300 Subject: [PATCH 009/118] Consider each feed separately in HSL fare service --- .../ext/fares/impl/HSLFareServiceTest.java | 40 +++++++++++++++- .../ext/fares/impl/HSLFareServiceImpl.java | 46 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) 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 1f89be2849e..d590f43ebe6 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 @@ -153,6 +153,12 @@ private static List createTestCases() { .setAgency(agency2.getId()) .build(); + FareAttribute fareAttributeAgency3 = FareAttribute + .of(new FeedScopedId("FEED2", "attribute")) + .setCurrencyType("EUR") + .setAgency(agency3.getId()) + .build(); + // Fare rule sets FareRuleSet ruleSetAB = new FareRuleSet(fareAttributeAB); ruleSetAB.addContains("A"); @@ -189,6 +195,9 @@ private static List createTestCases() { ruleSetD2.addContains("D"); ruleSetD2.setAgency(agency2.getId()); + FareRuleSet ruleSetAgency3 = new FareRuleSet(fareAttributeAgency3); + ruleSetAgency3.addContains("B"); + hslFareService.addFareRules( FareType.regular, List.of( @@ -199,7 +208,8 @@ private static List createTestCases() { ruleSetBCD, ruleSetABCD, ruleSetD, - ruleSetD2 + ruleSetD2, + ruleSetAgency3 ) ); @@ -394,6 +404,34 @@ private static List createTestCases() { ) ); + Itinerary i = newItinerary(D1, T11_06) + .bus(routeAgency1, 1, T11_06, T11_10, D2) + .walk(10, D1) + .bus(routeAgency2, 2, T11_20, T11_30, D2) + .build(); + + args.add( + Arguments.of( + "Multi-agency itinerary", + hslFareService, + i, + List.of(fareAttributeD.getId(), fareAttributeD2.getId()) + ) + ); + + Itinerary i2 = newItinerary(B1) + .bus(routeAgency1, 1, T11_06, T11_12, B1) + .bus(routeAgency3, 1, T11_14, T11_15, B2) + .build(); + + args.add( + Arguments.of( + "", + hslFareService, + i2, + List.of(fareAttributeAB.getId(), fareAttributeAgency3.getId()) + ) + ); return args; } diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java index 23dde8b2a17..ac2f1fcfd85 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java @@ -3,7 +3,9 @@ import com.google.common.collect.Sets; import java.time.Duration; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Collection; +import java.util.Currency; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -12,8 +14,11 @@ import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.RouteOriginDestination; +import org.opentripplanner.model.fare.ItineraryFares; +import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.model.plan.ScheduledTransitLeg; +import org.opentripplanner.routing.core.FareComponent; import org.opentripplanner.routing.core.FareType; import org.opentripplanner.transit.model.basic.Money; import org.slf4j.Logger; @@ -196,4 +201,45 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */ .ofNullable(bestAttribute) .map(attribute -> new FareAndId(finalBestFare, attribute.getId())); } + + @Override + public ItineraryFares calculateFares(Itinerary itinerary) { + // Group legs and fare rules by the feed they are from + var legsByFeed = itinerary + .getLegs() + .stream() + .filter(leg -> leg instanceof ScheduledTransitLeg) + .collect(Collectors.groupingBy(leg -> leg.getAgency().getId().getFeedId())); + var fareRulesByFeed = fareRulesPerType + .get(FareType.regular) + .stream() + .collect(Collectors.groupingBy(flr -> flr.getFareAttribute().getId().getFeedId())); + + // Accumulate fares from different feeds + ItineraryFares res = ItineraryFares.empty(); + List components = new ArrayList<>(); + Money total = Money.euros(0); + boolean hasFare = false; + + for (String feed : legsByFeed.keySet()) { + if (fareRulesByFeed.get(feed) == null || fareRulesByFeed.get(feed).isEmpty()) continue; + ItineraryFares fare = ItineraryFares.empty(); + hasFare = + populateFare( + fare, + Currency.getInstance("EUR"), + FareType.regular, + legsByFeed.get(feed), + fareRulesByFeed.get(feed) + ) || + hasFare; + components.addAll(fare.getComponents(FareType.regular)); + total = total.plus(fare.getFare(FareType.regular)); + } + + res.addFareComponent(FareType.regular, components); + res.addFare(FareType.regular, total); + + return hasFare ? res : null; + } } From 8e3e2c18ba0b0266199412aafd7525dc34bd0649 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 21 Aug 2023 12:00:35 +0300 Subject: [PATCH 010/118] Use CostLinearFunction --- docs/RouteRequest.md | 367 ++++++------------ ...moveTransitIfStreetOnlyIsBetterFilter.java | 9 +- .../ItineraryListFilterChainTest.java | 9 +- ...TransitIfStreetOnlyIsBetterFilterTest.java | 11 +- .../ItineraryFilterPreferencesTest.java | 16 +- 5 files changed, 153 insertions(+), 259 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 121514588bd..bfe06777618 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -13,250 +13,127 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe -<<<<<<< HEAD -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|--------------------------|:-----:| -| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | -| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | -| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | -| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | -| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | -| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | -| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | -| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | -| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | -| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | -| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | -| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | -| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | -| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | -| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | -| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | -| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | -| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | -| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | -| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | -| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | -| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | -| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | -| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | -| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | -| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | -| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | -| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | -| [unpreferredCost](#rd_unpreferredCost) | `linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"f(x) = 0 + 1.0 x"` | 2.2 | -| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | -| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | -| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | -| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | -|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | -|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | -|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | -|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | -|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | -|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | -|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | -|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"f(x) = 3,600 + 2.0 x"` | 2.1 | -|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | -|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `linear-function` | Limit function for generalized-cost computed from non-transit itineries for transit itineraries. | *Optional* | `"f(x) = 60 + 1.3 x"` | 2.4 | -|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | -|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `linear-function` | The base function used by the filter. | *Optional* | `"f(x) = 900 + 1.5 x"` | 2.2 | -|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | -| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | -| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | -|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | -|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | -|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | -|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | -| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | -|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | -|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | -|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | -|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | -|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | -|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | -|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | -|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | -|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | -|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | -|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -======= -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:| -| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | -| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | -| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | -| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | -| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | -| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | -| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | -| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | -| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | -| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | -| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | -| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | -| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | -| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | -| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | -| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | -| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | -| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | -| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | -| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | -| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | -| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | -| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | -| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | -| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | -| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | -| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | -| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | -| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | -| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | -| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | -| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | -| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 | -| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | -| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | -| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | -| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | -|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | -|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | -|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | -|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | -|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | -|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | -|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | -|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 | -|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | -|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | -|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | -|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | -| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | -| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | -|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | -|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | -|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | -|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | -| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | -|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | -|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | -|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | -|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | -|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | -|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | -|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | -|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | -|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | -|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | -|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | ->>>>>>> otp/dev-2.x +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:| +| [alightSlack](#rd_alightSlack) | `duration` | The minimum extra time after exiting a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | +| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | +| [bikeBoardCost](#rd_bikeBoardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. | *Optional* | `600` | 2.0 | +| bikeParkCost | `integer` | Cost to park a bike. | *Optional* | `120` | 2.0 | +| bikeParkTime | `integer` | Time to park a bike. | *Optional* | `60` | 2.0 | +| bikeReluctance | `double` | A multiplier for how bad biking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| bikeSpeed | `double` | Max bike speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | +| bikeStairsReluctance | `double` | How bad is it to walk the bicycle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | +| bikeSwitchCost | `integer` | The cost of the user fetching their bike and parking it again. | *Optional* | `0` | 2.0 | +| bikeSwitchTime | `integer` | The time it takes the user to fetch their bike and park it again in seconds. | *Optional* | `0` | 2.0 | +| bikeTriangleSafetyFactor | `double` | For bike triangle routing, how much safety matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeTriangleSlopeFactor | `double` | For bike triangle routing, how much slope matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeTriangleTimeFactor | `double` | For bike triangle routing, how much time matters (range 0-1). | *Optional* | `0.0` | 2.0 | +| bikeWalkingReluctance | `double` | A multiplier for how bad walking with a bike is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | +| bikeWalkingSpeed | `double` | The user's bike walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | +| [boardSlack](#rd_boardSlack) | `duration` | The boardSlack is the minimum extra time to board a public transport vehicle. | *Optional* | `"PT0S"` | 2.0 | +| carAccelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +| carDecelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +| carDropoffTime | `integer` | Time to park a car in a park and ride, w/o taking into account driving and walking cost. | *Optional* | `120` | 2.0 | +| carParkCost | `integer` | Cost of parking a car. | *Optional* | `120` | 2.1 | +| carParkTime | `integer` | Time to park a car | *Optional* | `60` | 2.1 | +| carPickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | +| carPickupTime | `integer` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `60` | 2.1 | +| carReluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| carSpeed | `double` | Max car speed along streets, in meters per second | *Optional* | `40.0` | 2.0 | +| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | +| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | +| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | +| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | +| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | +| escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, realtime updates are ignored during this search. | *Optional* | `false` | 2.0 | +| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | +| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | +| [maxAccessEgressDuration](#rd_maxAccessEgressDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | +| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | +| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | +| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | +| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | +| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | +| [optimize](#rd_optimize) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe"` | 2.0 | +| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | +| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | +| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | +| stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | +| [stairsTimeFactor](#rd_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | +| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | +| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | +| [transferSlack](#rd_transferSlack) | `integer` | The extra time needed to make a safe transfer in seconds. | *Optional* | `120` | 2.0 | +| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | +| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 | +| [unpreferredVehicleParkingTagCost](#rd_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | +| walkBoardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | +| [walkReluctance](#rd_walkReluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +| [walkSafetyFactor](#rd_walkSafetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | +| walkSpeed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | +| [accessEgressPenalty](#rd_accessEgressPenalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | +| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | +| [bannedVehicleParkingTags](#rd_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | +| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | +|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | +|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | +|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | +|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | +|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | +|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | +|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | +|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | +|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 | +|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | +|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | +|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from non-transit itineries for transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 | +|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | +|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | +|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | +| [maxAccessEgressDurationForMode](#rd_maxAccessEgressDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | +| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | +| [preferredVehicleParkingTags](#rd_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +| [requiredVehicleParkingTags](#rd_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | +|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | +|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | +|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | +|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | +| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | +| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | +|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | +|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | +| vehicleRental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|    allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|    dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|    dropOffTime | `integer` | Time to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|    keepingAtDestinationCost | `double` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0.0` | 2.2 | +|    pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|    pickupTime | `integer` | Time to rent a vehicle. | *Optional* | `60` | 2.0 | +|    useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|    [allowedNetworks](#rd_vehicleRental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|    [bannedNetworks](#rd_vehicleRental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | +|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | +|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | +|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | +|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | +|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | +|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | +|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | +|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | @@ -728,7 +605,7 @@ Itineraries visiting the same set of stops and riding the exact same routes, dep

removeTransitWithHigherCostThanBestOnStreetOnly

-**Since version:** `2.4` ∙ **Type:** `linear-function` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"f(x) = 60 + 1.3 x"` +**Since version:** `2.4` ∙ **Type:** `cost-linear-function` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"1m + 1.30 t"` **Path:** /routingDefaults/itineraryFilters Limit function for generalized-cost computed from non-transit itineries for transit itineraries. diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index 87fef627568..e32fc9f4d9c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -3,6 +3,7 @@ import java.util.Comparator; import java.util.List; import java.util.OptionalDouble; +import java.util.OptionalInt; import java.util.stream.Collectors; import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.Itinerary; @@ -44,17 +45,19 @@ private double getWalkDistance(Itinerary it) { @Override public List flagForRemoval(List itineraries) { // Find the best walk-all-the-way option - OptionalDouble minStreetCost = itineraries + OptionalInt minStreetCost = itineraries .stream() .filter(Itinerary::isOnStreetAllTheWay) - .mapToDouble(Itinerary::getGeneralizedCost) + .mapToInt(Itinerary::getGeneralizedCost) .min(); if (minStreetCost.isEmpty()) { return List.of(); } - final Cost limit = costLimitFunction.calculate(minStreetCost.getAsDouble()); + var limit = costLimitFunction + .calculate(Cost.costOfSeconds(minStreetCost.getAsInt())) + .toSeconds(); // Filter away itineraries that have higher cost than limit cost computed above List filtered = itineraries diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index 5ebbdc44222..065695b09a2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -11,6 +11,7 @@ import static org.opentripplanner.model.plan.TestItineraryBuilder.newTime; import static org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile.ofDebugEnabled; +import java.time.Duration; import java.time.Instant; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -21,7 +22,7 @@ import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.model.plan.TestItineraryBuilder; import org.opentripplanner.routing.alertpatch.StopCondition; -import org.opentripplanner.routing.api.request.framework.RequestFunctions; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.services.TransitAlertService; @@ -164,7 +165,7 @@ void testRoutingErrorsOriginDestinationTooCloseTest() { ItineraryListFilterChain chain = createBuilder(false, false, 20) .withRemoveWalkAllTheWayResults(true) .withRemoveTransitWithHigherCostThanBestOnStreetOnly( - RequestFunctions.createLinearFunction(0, 1.0) + CostLinearFunction.of(Duration.ofSeconds(0), 1.0) ) .build(); @@ -247,7 +248,7 @@ private ItineraryListFilterChainBuilder createBuilder( return new ItineraryListFilterChainBuilder(sortOrder) .withMaxNumberOfItineraries(numOfItineraries) .withRemoveTransitWithHigherCostThanBestOnStreetOnly( - RequestFunctions.createLinearFunction(0, 1.0) + CostLinearFunction.of(Duration.ofSeconds(0), 1.0) ) .withDebugEnabled(ofDebugEnabled(debug)); } @@ -314,7 +315,7 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyEnabled() { // Enable filter and remove bus itinerary ItineraryListFilterChain chain = builder .withRemoveTransitWithHigherCostThanBestOnStreetOnly( - RequestFunctions.createLinearFunction(0, 1.0) + CostLinearFunction.of(Duration.ofSeconds(0), 1.0) ) .build(); assertEquals(toStr(List.of(walk)), toStr(chain.filter(List.of(walk, bus)))); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java index 0d086b51cd0..931953e013f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilterTest.java @@ -4,11 +4,12 @@ import static org.opentripplanner.model.plan.Itinerary.toStr; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; +import java.time.Duration; import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.routing.api.request.framework.RequestFunctions; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; public class RemoveTransitIfStreetOnlyIsBetterFilterTest implements PlanTestConstants { @@ -21,7 +22,9 @@ public void filterAwayNothingIfNoWalking() { // When: List result = DeletionFlaggerTestHelper.process( List.of(i1, i2), - new RemoveTransitIfStreetOnlyIsBetterFilter(RequestFunctions.createLinearFunction(200, 1.2)) + new RemoveTransitIfStreetOnlyIsBetterFilter( + CostLinearFunction.of(Duration.ofSeconds(200), 1.2) + ) ); // Then: @@ -49,7 +52,9 @@ public void filterAwayLongTravelTimeWithoutWaitTime() { // When: List result = DeletionFlaggerTestHelper.process( List.of(i2, bicycle, walk, i1), - new RemoveTransitIfStreetOnlyIsBetterFilter(RequestFunctions.createLinearFunction(60, 1.2)) + new RemoveTransitIfStreetOnlyIsBetterFilter( + CostLinearFunction.of(Duration.ofSeconds(60), 1.2) + ) ); // Then: diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index 9da4304c18b..f59586b0af9 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java @@ -28,8 +28,8 @@ class ItineraryFilterPreferencesTest { CostLinearFunction.of(Duration.ofSeconds(4), 5.0), 3.0 ); - private static final DoubleAlgorithmFunction TRANSIT_BEST_STREET_COST_LIMIT = RequestFunctions.createLinearFunction( - 60, + private static final CostLinearFunction TRANSIT_BEST_STREET_COST_LIMIT = CostLinearFunction.of( + Duration.ofSeconds(30), 1.3 ); @@ -110,6 +110,14 @@ void transitGeneralizedCostLimit() { assertEquals(TRANSIT_GENERALIZED_COST_LIMIT, subject.transitGeneralizedCostLimit()); } + @Test + void removeTransitWithHigherCostThanBestOnStreetOnly() { + assertEquals( + TRANSIT_BEST_STREET_COST_LIMIT, + subject.removeTransitWithHigherCostThanBestOnStreetOnly() + ); + } + @Test void testCopyOfEqualsAndHashCode() { // Return same object if no value is set @@ -138,8 +146,8 @@ void testToString() { "minBikeParkingDistance: 2,000.0, " + "nonTransitGeneralizedCostLimit: 4s + 5.0 t, " + "parkAndRideDurationRatio: 0.44, " + - "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=4s + 5.0 t, intervalRelaxFactor=3.0]" + - "removeTransitWithHigherCostThanBestOnStreetOnly: 1m + 1.3 t" + + "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=4s + 5.0 t, intervalRelaxFactor=3.0], " + + "removeTransitWithHigherCostThanBestOnStreetOnly: 30s + 1.30 t" + "}", subject.toString() ); From 4d12fd8ec7d67212a15aa7d358f42ad313b00cb1 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 21 Aug 2023 15:22:53 +0300 Subject: [PATCH 011/118] Split RemoveTransitIfStreetOnlyIsBetterFilter.java into two simpler filters --- .../ItineraryListFilterChainBuilder.java | 19 +++++- .../filterchain/RoutingErrorsAttacher.java | 12 +++- ...moveTransitIfStreetOnlyIsBetterFilter.java | 27 +------- .../RemoveTransitWithMoreWalking.java | 62 +++++++++++++++++++ .../RouteRequestToFilterChainMapper.java | 1 + .../ItineraryFilterPreferences.java | 19 +++++- 6 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index c3baa491a0d..4394b9430e8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -23,6 +23,7 @@ import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveItinerariesWithShortStreetLeg; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveParkAndRideWithMostlyWalkingFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; +import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitWithMoreWalking; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveWalkOnlyFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.TransitGeneralizedCostFilter; import org.opentripplanner.routing.algorithm.filterchain.filter.DeletionFlaggingFilter; @@ -72,6 +73,7 @@ public class ItineraryListFilterChainBuilder { private Function getMultiModalStation; private boolean removeItinerariesWithSameRoutesAndStops; private double minBikeParkingDistance; + private boolean removeTransitWithMoreWalking = true; @Sandbox private ItineraryListFilter rideHailingFilter; @@ -184,7 +186,7 @@ public ItineraryListFilterChainBuilder withParkAndRideDurationRatio(double value /** * The direct street search(walk, bicycle, car) is not pruning the transit search, so in some * cases we get "silly" transit itineraries that is marginally better on travel-duration compared - * with a on-street-all-the-way itinerary. Use this method to turn this filter on/off. + * with a on-street-all-the-way itinerary. Use this method to turn filter worse enough itineraries. *

* The filter remove all itineraries with a generalized-cost that is higher than the best * on-street-all-the-way itinerary. @@ -199,6 +201,17 @@ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOn return this; } + /** + * An itinerary which has transit legs and more walking than the plain walk itinerary are silly. + * This filter removes such itineraries. + *

+ * This filter only have an effect, if an on-street-all-the-way(WALK) itinerary exist. + */ + public ItineraryListFilterChainBuilder withRemoveTransitWithMoreWalking(boolean value) { + this.removeTransitWithMoreWalking = value; + return this; + } + /** * This will NOT delete itineraries, but tag them as deleted using the {@link * Itinerary#getSystemNotices()}. @@ -351,6 +364,10 @@ public ItineraryListFilterChain build() { ); } + if (removeTransitWithMoreWalking) { + filters.add(new DeletionFlaggingFilter(new RemoveTransitWithMoreWalking())); + } + // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that // remove elements/ based on the given itinerary properties - not considering other // itineraries. This may remove itineraries in the "groupBy" filters that are considered diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java index f09d601b04f..357104682d9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java @@ -11,6 +11,7 @@ import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; +import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitWithMoreWalking; import org.opentripplanner.routing.api.response.RoutingError; /** @@ -50,7 +51,16 @@ static List computeErrors( .getSystemNotices() .stream() .anyMatch(notice -> notice.tag.equals(RemoveTransitIfStreetOnlyIsBetterFilter.TAG)); - if (filteredItineraries.stream().allMatch(isOnStreetAllTheWay.or(isWorseThanStreet))) { + Predicate isWorseThanWalking = it -> + it + .getSystemNotices() + .stream() + .anyMatch(notice -> notice.tag.equals(RemoveTransitWithMoreWalking.TAG)); + if ( + filteredItineraries + .stream() + .allMatch(isOnStreetAllTheWay.or(isWorseThanStreet).or(isWorseThanWalking)) + ) { var nonTransitIsWalking = filteredItineraries .stream() .flatMap(Itinerary::getStreetLegs) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index e32fc9f4d9c..d3783d94695 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -12,7 +12,7 @@ /** * Filter itineraries based on generalizedCost, compared with a on-street-all-the-way itinerary(if - * it exist). If an itinerary is slower than the best all-the-way-on-street itinerary, then the + * it exist). If an itinerary cost exceeds the limit computed from the best all-the-way-on-street itinerary, then the * transit itinerary is removed. */ public class RemoveTransitIfStreetOnlyIsBetterFilter implements ItineraryDeletionFlagger { @@ -60,33 +60,10 @@ public List flagForRemoval(List itineraries) { .toSeconds(); // Filter away itineraries that have higher cost than limit cost computed above - List filtered = itineraries + return itineraries .stream() .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) .collect(Collectors.toList()); - - // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary - // This never makes sense - OptionalDouble walkDistance = itineraries - .stream() - .filter(Itinerary::isWalkingAllTheWay) - .mapToDouble(Itinerary::distanceMeters) - .min(); - - if (walkDistance.isEmpty()) { - return filtered; - } - final double walkLimit = walkDistance.getAsDouble(); - List filtered2 = itineraries - .stream() - .filter(it -> getWalkDistance(it) > walkLimit) - .collect(Collectors.toList()); - - // remove duplicates - filtered2.removeAll(filtered); - filtered.addAll(filtered2); - - return filtered; } @Override diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java new file mode 100644 index 00000000000..2e8b2e17b02 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java @@ -0,0 +1,62 @@ +package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; + +import java.util.Comparator; +import java.util.List; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; + +/** + * Filter itineraries which contain more walking than a pure walk itinerary + */ +public class RemoveTransitWithMoreWalking implements ItineraryDeletionFlagger { + + /** + * Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain}, + * to know which filters removed + */ + public static final String TAG = "transit-vs-plain-walk-filter"; + + @Override + public String name() { + return TAG; + } + + private double getWalkDistance(Itinerary it) { + return it + .getStreetLegs() + .filter(l -> l.isWalkingLeg()) + .mapToDouble(Leg::getDistanceMeters) + .sum(); + } + + @Override + public List flagForRemoval(List itineraries) { + // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary + // This never makes sense + OptionalDouble walkDistance = itineraries + .stream() + .filter(Itinerary::isWalkingAllTheWay) + .mapToDouble(Itinerary::distanceMeters) + .min(); + + if (walkDistance.isEmpty()) { + return List.of(); + } + + final double walkLimit = walkDistance.getAsDouble(); + return itineraries + .stream() + .filter(it -> !it.isOnStreetAllTheWay() && getWalkDistance(it) > walkLimit) + .collect(Collectors.toList()); + } + + @Override + public boolean skipAlreadyFlaggedItineraries() { + return false; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 4bf14a00ad4..c5af34f573a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -85,6 +85,7 @@ public static ItineraryListFilterChain createFilterChain( .withLatestDepartureTimeLimit(filterOnLatestDepartureTime) .withMaxLimitReachedSubscriber(maxLimitReachedSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) + .withRemoveTransitWithMoreWalking(true) .withDebugEnabled(params.debug()); if (!context.rideHailingServices().isEmpty()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 04b517b09fd..569cb21e5f5 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -31,6 +31,7 @@ public final class ItineraryFilterPreferences { private final boolean removeItinerariesWithSameRoutesAndStops; private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private final CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; + private final boolean removeTransitWithMoreWalking; private ItineraryFilterPreferences() { this.accessibilityScore = false; @@ -51,6 +52,7 @@ private ItineraryFilterPreferences() { ); this.removeTransitWithHigherCostThanBestOnStreetOnly = CostLinearFunction.of(Duration.ofMinutes(1), 1.3); + this.removeTransitWithMoreWalking = false; } private ItineraryFilterPreferences(Builder builder) { @@ -71,6 +73,7 @@ private ItineraryFilterPreferences(Builder builder) { this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit); this.removeTransitWithHigherCostThanBestOnStreetOnly = Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly); + this.removeTransitWithMoreWalking = builder.removeTransitWithMoreWalking; } public static Builder of() { @@ -133,6 +136,10 @@ public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() { return removeTransitWithHigherCostThanBestOnStreetOnly; } + public boolean removeTransitWithMoreWalking() { + return removeTransitWithMoreWalking; + } + @Override public String toString() { return ToStringBuilder @@ -180,6 +187,7 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) + .addBoolIfTrue("removeTransitWithMoreWalking", removeTransitWithMoreWalking) .toString(); } @@ -192,6 +200,7 @@ public boolean equals(Object o) { accessibilityScore == that.accessibilityScore && Double.compare(that.bikeRentalDistanceRatio, bikeRentalDistanceRatio) == 0 && debug == that.debug && + removeTransitWithMoreWalking == that.removeTransitWithMoreWalking && filterItinerariesWithSameFirstOrLastTrip == that.filterItinerariesWithSameFirstOrLastTrip && Double.compare( that.groupedOtherThanSameLegsMaxCostMultiplier, @@ -227,7 +236,8 @@ public int hashCode() { parkAndRideDurationRatio, removeItinerariesWithSameRoutesAndStops, transitGeneralizedCostLimit, - removeTransitWithHigherCostThanBestOnStreetOnly + removeTransitWithHigherCostThanBestOnStreetOnly, + removeTransitWithMoreWalking ); } @@ -247,6 +257,7 @@ public static class Builder { private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; + private boolean removeTransitWithMoreWalking; public ItineraryFilterPreferences original() { return original; @@ -330,6 +341,11 @@ public Builder withRemoveTransitWithHigherCostThanBestOnStreetOnly( return this; } + public Builder withRemoveTransitWithMoreWalking(boolean removeTransitWithMoreWalking) { + this.removeTransitWithMoreWalking = removeTransitWithMoreWalking; + return this; + } + public Builder(ItineraryFilterPreferences original) { this.original = original; this.accessibilityScore = original.accessibilityScore; @@ -349,6 +365,7 @@ public Builder(ItineraryFilterPreferences original) { this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; this.removeTransitWithHigherCostThanBestOnStreetOnly = original.removeTransitWithHigherCostThanBestOnStreetOnly; + this.removeTransitWithMoreWalking = original.removeTransitWithMoreWalking; } public Builder apply(Consumer body) { From 94a738d3a4058ea1e6cb6b0c15a28683e3aef742 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Aug 2023 10:03:29 +0300 Subject: [PATCH 012/118] Add test for the new itinerary filter --- .../RemoveTransitWithMoreWalkingTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java new file mode 100644 index 00000000000..3b7404fc6e7 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java @@ -0,0 +1,52 @@ +package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.plan.Itinerary.toStr; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.PlanTestConstants; + +public class RemoveTransitWithMoreWalkingTest implements PlanTestConstants { + + @Test + public void filterAwayNothingIfNoWalking() { + // Given: + Itinerary i1 = newItinerary(A).bus(21, 6, 7, E).build(); + Itinerary i2 = newItinerary(A).rail(110, 6, 9, E).build(); + + // When: + List result = DeletionFlaggerTestHelper.process( + List.of(i1, i2), + new RemoveTransitWithMoreWalking() + ); + + // Then: + assertEquals(toStr(List.of(i1, i2)), toStr(result)); + } + + @Test + public void filterAwayLongTravelTimeWithoutWaitTime() { + // a walk itinerary + Itinerary walk = newItinerary(A, 6).walk(D2m, E).build(); + + // a bicycle itinerary will not be filtered + Itinerary bicycle = newItinerary(A).bicycle(6, 9, E).build(); + + // transit which has more walking as plain walk should be dropped + Itinerary i1 = newItinerary(A, 6).walk(D3m, D).bus(1, 9, 10, E).build(); + + // transit which has less walking than plain walk should be kept + Itinerary i2 = newItinerary(A, 6).walk(D1m, B).bus(2, 7, 10, E).build(); + + List result = DeletionFlaggerTestHelper.process( + List.of(i1, i2, bicycle, walk), + new RemoveTransitWithMoreWalking() + ); + + assertEquals(toStr(List.of(i2, bicycle, walk)), toStr(result)); + } +} From acc2bb988131cf53004f91a2051eb4ed9f7ebc5f Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Aug 2023 10:32:00 +0300 Subject: [PATCH 013/118] Update test name --- .../deletionflagger/RemoveTransitWithMoreWalkingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java index 3b7404fc6e7..6d192787eeb 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java @@ -29,7 +29,7 @@ public void filterAwayNothingIfNoWalking() { } @Test - public void filterAwayLongTravelTimeWithoutWaitTime() { + public void filterAwayTransitWithLongerWalk() { // a walk itinerary Itinerary walk = newItinerary(A, 6).walk(D2m, E).build(); From fc4f3cefe0fd7be319062eb6d4bdaf65e12c226e Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Aug 2023 12:14:08 +0300 Subject: [PATCH 014/118] Remove unused function --- .../RemoveTransitIfStreetOnlyIsBetterFilter.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index d3783d94695..60c6f8d6225 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -34,14 +34,6 @@ public String name() { return TAG; } - private double getWalkDistance(Itinerary it) { - return it - .getStreetLegs() - .filter(l -> l.isWalkingLeg()) - .mapToDouble(Leg::getDistanceMeters) - .sum(); - } - @Override public List flagForRemoval(List itineraries) { // Find the best walk-all-the-way option From e34e4e95e40ac124454abe9392045c5232c3b929 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Aug 2023 12:14:32 +0300 Subject: [PATCH 015/118] Use cost, not distance --- .../RemoveTransitWithMoreWalking.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java index 2e8b2e17b02..1438cfa14dc 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java @@ -26,32 +26,26 @@ public String name() { return TAG; } - private double getWalkDistance(Itinerary it) { - return it - .getStreetLegs() - .filter(l -> l.isWalkingLeg()) - .mapToDouble(Leg::getDistanceMeters) - .sum(); - } - @Override public List flagForRemoval(List itineraries) { // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary // This never makes sense - OptionalDouble walkDistance = itineraries + + OptionalInt minWalkCost = itineraries .stream() .filter(Itinerary::isWalkingAllTheWay) - .mapToDouble(Itinerary::distanceMeters) + .mapToInt(Itinerary::getGeneralizedCost) .min(); - if (walkDistance.isEmpty()) { + if (minWalkCost.isEmpty()) { return List.of(); } - final double walkLimit = walkDistance.getAsDouble(); + var limit = minWalkCost.getAsInt(); + return itineraries .stream() - .filter(it -> !it.isOnStreetAllTheWay() && getWalkDistance(it) > walkLimit) + .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) .collect(Collectors.toList()); } From f2e021fbecaf5ad221484512959c44ee2bbf15f3 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Aug 2023 12:14:59 +0300 Subject: [PATCH 016/118] Update test --- .../algorithm/filterchain/ItineraryListFilterChainTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index 065695b09a2..d9bb3f7dc80 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -306,6 +306,7 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { // Allow non-optimal bus itinerary pass through ItineraryListFilterChain chain = builder .withRemoveTransitWithHigherCostThanBestOnStreetOnly(null) + .withRemoveTransitWithMoreWalking(false) .build(); assertEquals(toStr(List.of(walk, bus)), toStr(chain.filter(List.of(walk, bus)))); } From aa60e1bb1ce59f957562dd8ce3492dcf7f486123 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 25 Aug 2023 16:25:56 +0200 Subject: [PATCH 017/118] Clean up some of the OSM relation handling --- .../graph_builder/module/osm/OsmDatabase.java | 63 +++++-------------- .../graph_builder/module/osm/OsmModule.java | 2 +- .../openstreetmap/model/OSMRelation.java | 38 ++++++++++- .../openstreetmap/model/OSMWithTags.java | 10 ++- .../module/osm/OpenStreetMapParserTest.java | 3 +- .../module/osm/WalkableAreaBuilderTest.java | 2 +- .../model/BicycleNetworkRelationsTest.java | 3 +- 7 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 0a4713c228c..e7631157865 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; @@ -34,7 +33,6 @@ import org.opentripplanner.graph_builder.issues.DisconnectedOsmNode; import org.opentripplanner.graph_builder.issues.InvalidOsmGeometry; import org.opentripplanner.graph_builder.issues.LevelAmbiguous; -import org.opentripplanner.graph_builder.issues.TooManyAreasInRelation; import org.opentripplanner.graph_builder.issues.TurnRestrictionBad; import org.opentripplanner.graph_builder.issues.TurnRestrictionException; import org.opentripplanner.graph_builder.issues.TurnRestrictionUnknown; @@ -132,11 +130,9 @@ public class OsmDatabase { * the United States. This does not affect floor names from level maps. */ public boolean noZeroLevels = true; - private final Set boardingAreaRefTags; - public OsmDatabase(DataImportIssueStore issueStore, Set boardingAreaRefTags) { + public OsmDatabase(DataImportIssueStore issueStore) { this.issueStore = issueStore; - this.boardingAreaRefTags = boardingAreaRefTags; } public OSMNode getNode(Long nodeId) { @@ -298,7 +294,7 @@ public void addRelation(OSMRelation relation) { } if ( - relation.isTag("type", "multipolygon") && + relation.isMultiPolygon() && (OsmFilter.isOsmEntityRoutable(relation) || relation.isParkAndRide()) || relation.isBikeParking() ) { @@ -316,18 +312,12 @@ public void addRelation(OSMRelation relation) { } applyLevelsForWay(relation); } else if ( - !(relation.isTag("type", "restriction")) && - !(relation.isTag("type", "route") && relation.isTag("route", "road")) && - !(relation.isTag("type", "multipolygon") && OsmFilter.isOsmEntityRoutable(relation)) && - !(relation.isTag("type", "level_map")) && - !( - relation.isTag("type", "public_transport") && - relation.isTag("public_transport", "stop_area") - ) && - !( - relation.isTag("type", "route") && - (relation.isTag("route", "road") || relation.isTag("route", "bicycle")) - ) + !relation.isRestriction() && + !relation.isRoadRoute() && + !(relation.isMultiPolygon() && OsmFilter.isOsmEntityRoutable(relation)) && + !relation.isLevelMap() && + !relation.isStopArea() && + !(relation.isRoadRoute() || relation.isBicycleRoute()) ) { return; } @@ -740,7 +730,7 @@ private void processMultipolygonRelations() { } if ( !( - relation.isTag("type", "multipolygon") && + relation.isMultiPolygon() && ( OsmFilter.isOsmEntityRoutable(relation) || relation.isParkAndRide() || @@ -842,14 +832,14 @@ private void processRelations() { LOG.debug("Processing relations..."); for (OSMRelation relation : relationsById.valueCollection()) { - if (relation.isTag("type", "restriction")) { + if (relation.isRestriction()) { processRestriction(relation); - } else if (relation.isTag("type", "level_map")) { + } else if (relation.isLevelMap()) { processLevelMap(relation); - } else if (relation.isTag("type", "route")) { + } else if (relation.isRoute()) { processRoad(relation); processBicycleRoute(relation); - } else if (relation.isTag("type", "public_transport")) { + } else if (relation.isPublicTransport()) { processPublicTransportStopArea(relation); } } @@ -861,28 +851,10 @@ private void processRelations() { * @see "https://wiki.openstreetmap.org/wiki/Tag:route%3Dbicycle" */ private void processBicycleRoute(OSMRelation relation) { - if (relation.isTag("route", "bicycle")) { - var network = relation.getTag("network"); - - if (network == null) network = "lcn"; - switch (network) { - case "lcn": - setNetworkForAllMembers(relation, "lcn"); - break; - case "rcn": - setNetworkForAllMembers(relation, "rcn"); - break; - case "ncn": - setNetworkForAllMembers(relation, "ncn"); - break; - case "icn": - setNetworkForAllMembers(relation, "icn"); - break; - // we treat networks without known network type like local networks - default: - setNetworkForAllMembers(relation, "lcn"); - break; - } + if (relation.isBicycleRoute()) { + // we treat networks without known network type like local networks + var network = relation.getTagOpt("network").orElse("lcn"); + setNetworkForAllMembers(relation, network); } } @@ -1102,7 +1074,6 @@ private void processRoad(OSMRelation relation) { private void processPublicTransportStopArea(OSMRelation relation) { Set platformAreas = new HashSet<>(); Set platformNodes = new HashSet<>(); - boolean skipped = false; for (OSMRelationMember member : relation.getMembers()) { switch (member.getType()) { case NODE: diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index bfab1097ad7..6917345ad4c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -65,7 +65,7 @@ public class OsmModule implements GraphBuilderModule { this.graph = graph; this.issueStore = issueStore; this.params = params; - this.osmdb = new OsmDatabase(issueStore, params.boardingAreaRefTags()); + this.osmdb = new OsmDatabase(issueStore); this.vertexGenerator = new VertexGenerator(osmdb, graph, params.boardingAreaRefTags()); this.normalizer = new SafetyValueNormalizer(graph, issueStore); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java index e0c0e8750c8..45f89d6ccab 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java @@ -21,6 +21,42 @@ public String toString() { @Override public String getOpenStreetMapLink() { - return String.format("http://www.openstreetmap.org/relation/%d", getId()); + return String.format("https://www.openstreetmap.org/relation/%d", getId()); + } + + public boolean isBicycleRoute() { + return isRoute() && isTag("route", "bicycle"); + } + + public boolean isRoute() { + return isType("route"); + } + + public boolean isRoadRoute() { + return isRoute() && isTag("route", "road"); + } + + public boolean isLevelMap() { + return isType("level_map"); + } + + public boolean isRestriction() { + return isType("restriction"); + } + + public boolean isPublicTransport() { + return isType("public_transport"); + } + + public boolean isMultiPolygon() { + return isType("multipolygon"); + } + + public boolean isStopArea() { + return isPublicTransport() && isTag("public_transport", "stop_area"); + } + + private boolean isType(String type) { + return isTag("type", type); } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 2c2f3167ab1..de324d7e36f 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -2,15 +2,17 @@ import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.OptionalInt; import java.util.Set; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.i18n.TranslatedString; @@ -148,6 +150,7 @@ public boolean doesTagAllowAccess(String tag) { } /** @return a tag's value, converted to lower case. */ + @Nullable public String getTag(String tag) { tag = tag.toLowerCase(); if (tags != null && tags.containsKey(tag)) { @@ -476,6 +479,11 @@ public void setOsmProvider(OsmProvider provider) { this.osmProvider = provider; } + @Nonnull + public Optional getTagOpt(String network) { + return Optional.ofNullable(getTag(network)); + } + /** * Returns true if this tag is explicitly access to this entity. */ diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapParserTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapParserTest.java index 2fdb2b4be1f..f0a3b8254f0 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapParserTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapParserTest.java @@ -7,7 +7,6 @@ import java.io.File; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.openstreetmap.OsmProvider; @@ -22,7 +21,7 @@ public void testBinaryParser() { URLDecoder.decode(getClass().getResource("map.osm.pbf").getPath(), StandardCharsets.UTF_8) ); OsmProvider pr = new OsmProvider(osmFile, true); - OsmDatabase osmdb = new OsmDatabase(DataImportIssueStore.NOOP, Set.of()); + OsmDatabase osmdb = new OsmDatabase(DataImportIssueStore.NOOP); pr.readOSM(osmdb); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java index 4e8b90863f0..73f3932e8c0 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java @@ -39,7 +39,7 @@ public Graph buildGraph(final TestInfo testInfo) { final boolean platformEntriesLinking = true; final Set boardingAreaRefTags = Set.of(); - final OsmDatabase osmdb = new OsmDatabase(DataImportIssueStore.NOOP, boardingAreaRefTags); + final OsmDatabase osmdb = new OsmDatabase(DataImportIssueStore.NOOP); final File file = new File(testInfo.getTestClass().get().getResource(osmFile).getFile()); assertTrue(file.exists()); diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/BicycleNetworkRelationsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/BicycleNetworkRelationsTest.java index 96b8a13ebf8..be8fca3864d 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/BicycleNetworkRelationsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/BicycleNetworkRelationsTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.File; -import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.OsmDatabase; @@ -19,7 +18,7 @@ public class BicycleNetworkRelationsTest { @Test public void testBicycleRouteRelations() { var issueStore = DataImportIssueStore.NOOP; - var osmdb = new OsmDatabase(issueStore, Set.of()); + var osmdb = new OsmDatabase(issueStore); var provider = new OsmProvider( new File("src/test/resources/germany/ehningen-minimal.osm.pbf"), true From 26da2d5a84574b210f7e9d73298736939d859375 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 25 Aug 2023 21:57:37 +0200 Subject: [PATCH 018/118] Move routable() check into OSMWithTags --- .../graph_builder/module/osm/OsmDatabase.java | 14 ++++--------- .../graph_builder/module/osm/OsmFilter.java | 20 +------------------ .../openstreetmap/model/OSMWithTags.java | 18 +++++++++++++++++ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index e7631157865..7c88b26cdfe 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -295,7 +295,7 @@ public void addRelation(OSMRelation relation) { if ( relation.isMultiPolygon() && - (OsmFilter.isOsmEntityRoutable(relation) || relation.isParkAndRide()) || + (relation.isRoutable() || relation.isParkAndRide()) || relation.isBikeParking() ) { // OSM MultiPolygons are ferociously complicated, and in fact cannot be processed @@ -314,7 +314,7 @@ public void addRelation(OSMRelation relation) { } else if ( !relation.isRestriction() && !relation.isRoadRoute() && - !(relation.isMultiPolygon() && OsmFilter.isOsmEntityRoutable(relation)) && + !(relation.isMultiPolygon() && relation.isRoutable()) && !relation.isLevelMap() && !relation.isStopArea() && !(relation.isRoadRoute() || relation.isBicycleRoute()) @@ -731,11 +731,7 @@ private void processMultipolygonRelations() { if ( !( relation.isMultiPolygon() && - ( - OsmFilter.isOsmEntityRoutable(relation) || - relation.isParkAndRide() || - relation.isBikeParking() - ) + (relation.isRoutable() || relation.isParkAndRide() || relation.isBikeParking()) ) ) { continue; @@ -811,9 +807,7 @@ private void newArea(Area area) { area.parent, StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); - if ( - OsmFilter.isOsmEntityRoutable(area.parent) && permissions != StreetTraversalPermission.NONE - ) { + if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) { walkableAreas.add(area); } // Please note: the same area can be both car P+R AND bike park. diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index 5901266b401..115cd8bf972 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -11,24 +11,6 @@ */ public class OsmFilter { - /** - * Determines whether this OSM way is considered routable. The majority of routable ways are those - * with a highway= tag (which includes everything from motorways to hiking trails). Anything with - * a public_transport=platform or railway=platform tag is also considered routable even if it - * doesn't have a highway tag. Platforms are however filtered out if they are marked - * usage=tourism. This prevents miniature tourist railways like the one in Portland's Zoo from - * receiving a better score and pulling search endpoints away from real transit stops. - */ - public static boolean isOsmEntityRoutable(OSMWithTags osmEntity) { - if (osmEntity.hasTag("highway")) { - return true; - } - if (osmEntity.isPlatform()) { - return !("tourism".equals(osmEntity.getTag("usage"))); - } - return false; - } - public static StreetTraversalPermission getPermissionsForEntity( OSMWithTags entity, StreetTraversalPermission def @@ -224,7 +206,7 @@ public static boolean isLink(OSMWithTags way) { * A whitelist for highway tags is an alternative to a blacklist. */ static boolean isWayRoutable(OSMWithTags way) { - if (!isOsmEntityRoutable(way)) { + if (!(way.isRoutable())) { return false; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index de324d7e36f..0d2889522da 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -484,6 +484,24 @@ public Optional getTagOpt(String network) { return Optional.ofNullable(getTag(network)); } + /** + * Determines whether this OSM way is considered routable. The majority of routable ways are those + * with a highway= tag (which includes everything from motorways to hiking trails). Anything with + * a public_transport=platform or railway=platform tag is also considered routable even if it + * doesn't have a highway tag. Platforms are however filtered out if they are marked + * usage=tourism. This prevents miniature tourist railways like the one in Portland's Zoo from + * receiving a better score and pulling search endpoints away from real transit stops. + */ + public boolean isRoutable() { + if (hasTag("highway")) { + return true; + } + if (isPlatform()) { + return !("tourism".equals(getTag("usage"))); + } + return false; + } + /** * Returns true if this tag is explicitly access to this entity. */ From a4d440f1280558bae153bf06fd84ef2e6abcdea4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 25 Aug 2023 22:14:13 +0200 Subject: [PATCH 019/118] Move link check into OSMWithTags --- .../opentripplanner/graph_builder/module/osm/OsmFilter.java | 5 ----- .../opentripplanner/graph_builder/module/osm/OsmModule.java | 2 +- .../graph_builder/module/osm/WalkableAreaBuilder.java | 4 ++-- .../org/opentripplanner/openstreetmap/model/OSMWithTags.java | 5 +++++ 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index 115cd8bf972..cb4182d755d 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -189,11 +189,6 @@ public static StreetTraversalPermissionPair getPermissions( return new StreetTraversalPermissionPair(permissionsFront, permissionsBack); } - public static boolean isLink(OSMWithTags way) { - String highway = way.getTag("highway"); - return highway != null && highway.endsWith(("_link")); - } - /** * Determine whether any mode can or should ever traverse the given way. If not, we leave the way * out of the OTP graph. Potentially routable ways are those that have the tags : highway=* diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 6917345ad4c..10cea613f84 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -528,7 +528,7 @@ private StreetEdge getEdgeForStreet( .withPermission(permissions) .withBack(back) .withCarSpeed(carSpeed) - .withLink(OsmFilter.isLink(way)) + .withLink(way.isLink()) .withRoundabout(way.isRoundabout()) .withSlopeOverride(way.getOsmProvider().getWayPropertySet().getSlopeOverride(way)) .withStairs(way.isSteps()); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index a0a3d33de44..3beaa6fb473 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -543,7 +543,7 @@ private Set createSegments( streetEdgeBuilder.withWheelchairAccessible(false); } - streetEdgeBuilder.withLink(OsmFilter.isLink(areaEntity)); + streetEdgeBuilder.withLink(areaEntity.isLink()); label = "way (area) " + @@ -573,7 +573,7 @@ private Set createSegments( backStreetEdgeBuilder.withWheelchairAccessible(false); } - backStreetEdgeBuilder.withLink(OsmFilter.isLink(areaEntity)); + backStreetEdgeBuilder.withLink(areaEntity.isLink()); if (!wayPropertiesCache.containsKey(areaEntity)) { WayProperties wayData = areaEntity diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 0d2889522da..08c28c9a1cd 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -502,6 +502,11 @@ public boolean isRoutable() { return false; } + public boolean isLink() { + String highway = getTag("highway"); + return highway != null && highway.endsWith(("_link")); + } + /** * Returns true if this tag is explicitly access to this entity. */ From 9c37eaae862cf1c70da72ada30fa694c393b0cc7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 27 Aug 2023 20:58:06 +0200 Subject: [PATCH 020/118] Simplify handling of routes --- .../graph_builder/module/osm/OsmDatabase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 7c88b26cdfe..15a62b1ccd5 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -831,8 +831,7 @@ private void processRelations() { } else if (relation.isLevelMap()) { processLevelMap(relation); } else if (relation.isRoute()) { - processRoad(relation); - processBicycleRoute(relation); + processRoute(relation); } else if (relation.isPublicTransport()) { processPublicTransportStopArea(relation); } @@ -1020,7 +1019,7 @@ private void processLevelMap(OSMRelation relation) { /** * Handle route=road relations. */ - private void processRoad(OSMRelation relation) { + private void processRoute(OSMRelation relation) { for (OSMRelationMember member : relation.getMembers()) { if (!(member.hasType(OSMMemberType.WAY) && waysById.containsKey(member.getRef()))) { continue; @@ -1052,6 +1051,7 @@ private void processRoad(OSMRelation relation) { } } } + processBicycleRoute(relation); } /** From b2756bbaaf92f116d4a9df45c984df16e67ab2e4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 27 Aug 2023 21:24:05 +0200 Subject: [PATCH 021/118] Clean up relations handling a little more --- .../graph_builder/module/osm/OsmDatabase.java | 25 +++++++++---------- .../model/OSMRelationMember.java | 12 +++++++++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 15a62b1ccd5..627d931b97e 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -740,7 +740,6 @@ private void processMultipolygonRelations() { ArrayList innerWays = new ArrayList<>(); ArrayList outerWays = new ArrayList<>(); for (OSMRelationMember member : relation.getMembers()) { - String role = member.getRole(); OSMWay way = areaWaysById.get(member.getRef()); if (way == null) { // relation includes way which does not exist in the data. Skip. @@ -757,12 +756,12 @@ private void processMultipolygonRelations() { continue RELATION; } } - if (role.equals("inner")) { + if (member.hasRoleInner()) { innerWays.add(way); - } else if (role.equals("outer")) { + } else if (member.hasRoleOuter()) { outerWays.add(way); } else { - LOG.warn("Unexpected role {} in multipolygon", role); + LOG.warn("Unexpected role {} in multipolygon", member.getRole()); } } processedAreas.add(relation); @@ -857,7 +856,7 @@ private void setNetworkForAllMembers(OSMRelation relation, String key) { .forEach(member -> { var isOsmWay = member.hasType(OSMMemberType.WAY); var way = waysById.get(member.getRef()); - // if it is an OSM way (rather than a node) and it it doesn't already contain the tag + // if it is an OSM way (rather than a node) and it doesn't already contain the tag // we add it if (way != null && isOsmWay && !way.hasTag(key)) { way.addTag(key, "yes"); @@ -1070,22 +1069,22 @@ private void processPublicTransportStopArea(OSMRelation relation) { Set platformNodes = new HashSet<>(); for (OSMRelationMember member : relation.getMembers()) { switch (member.getType()) { - case NODE: + case NODE -> { var node = nodesById.get(member.getRef()); if (node != null && (node.isEntrance() || node.isBoardingLocation())) { platformNodes.add(node); } - break; - case WAY: - if ("platform".equals(member.getRole()) && areaWayIds.contains(member.getRef())) { + } + case WAY -> { + if (member.hasRolePlatform() && areaWayIds.contains(member.getRef())) { platformAreas.add(areaWaysById.get(member.getRef())); } - break; - case RELATION: - if ("platform".equals(member.getRole()) && relationsById.containsKey(member.getRef())) { + } + case RELATION -> { + if (member.hasRolePlatform() && relationsById.containsKey(member.getRef())) { platformAreas.add(relationsById.get(member.getRef())); } - break; + } } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java index 8174a458d9b..7323c612c5e 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java @@ -36,6 +36,18 @@ public void setRole(String role) { this.role = role; } + public boolean hasRoleOuter() { + return "outer".equals(role); + } + + public boolean hasRoleInner() { + return "inner".equals(role); + } + + public boolean hasRolePlatform() { + return "platform".equals(role); + } + @Override public String toString() { return "osm rel " + type + ":" + role + ":" + ref; From 52948f3242306745df622fcbbdb2a7fbd13f13c4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 27 Aug 2023 21:49:24 +0200 Subject: [PATCH 022/118] Update tag extraction --- .../module/osm/ElevatorProcessor.java | 6 ++---- .../graph_builder/module/osm/OsmDatabase.java | 9 ++------- .../openstreetmap/model/OSMWay.java | 6 +++++- .../openstreetmap/model/OSMWithTags.java | 20 +++++++++++-------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java index 3a453c9e67e..b3a7f1d76f1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java @@ -208,12 +208,10 @@ private static void createElevatorHopEdges( } private boolean isElevatorWay(OSMWay way) { - if (!way.hasTag("highway")) { - return false; - } - if (!"elevator".equals(way.getTag("highway"))) { + if (!way.isElevator()) { return false; } + if (osmdb.isAreaWay(way.getId())) { return false; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 627d931b97e..e6b819da9fb 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -261,12 +261,7 @@ public void addWay(OSMWay way) { /* An area can be specified as such, or be one by default as an amenity */ if ( - ( - way.isTag("area", "yes") || - way.isTag("amenity", "parking") || - way.isTag("amenity", "bicycle_parking") || - way.isBoardingArea() - ) && + (way.isArea() || way.isParkAndRide() || way.isBikeParking() || way.isBoardingArea()) && way.getNodeRefs().size() > 2 ) { // this is an area that's a simple polygon. So we can just add it straight @@ -788,7 +783,7 @@ private void processMultipolygonRelations() { way.addTag(tag, relation.getTag(tag)); } } - if (relation.isTag("railway", "platform") && !way.hasTag("railway")) { + if (relation.isPlatform() && !way.hasTag("railway")) { way.addTag("railway", "platform"); } if (relation.isPlatform() && !way.hasTag("public_transport")) { diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index 69c9c84c2b8..de7a09a1472 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -129,7 +129,7 @@ public boolean isOpposableCycleway() { public boolean isEscalator() { return ( - "steps".equals(this.getTag("highway")) && + isTag("highway", "steps") && this.getTag("conveying") != null && Set.of("yes", "forward", "backward", "reversible").contains(this.getTag("conveying")) ); @@ -147,4 +147,8 @@ public boolean isBackwardEscalator() { public String getOpenStreetMapLink() { return String.format("https://www.openstreetmap.org/way/%d", getId()); } + + public boolean isArea() { + return isTag("area", "yes"); + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 08c28c9a1cd..83b6d6835ec 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -185,7 +185,7 @@ public OptionalInt getTagAsInt(String tag, Consumer errorHandler) { /** * Checks is a tag contains the specified value. */ - public Boolean isTag(String tag, String value) { + public boolean isTag(String tag, String value) { tag = tag.toLowerCase(); if (tags != null && tags.containsKey(tag) && value != null) { return value.equals(tags.get(tag)); @@ -408,18 +408,18 @@ public boolean isParkAndRide() { */ public boolean isBoardingLocation() { return ( - "bus_stop".equals(getTag("highway")) || - "tram_stop".equals(getTag("railway")) || - "station".equals(getTag("railway")) || - "halt".equals(getTag("railway")) || - "bus_station".equals(getTag("amenity")) || - "ferry_terminal".equals(getTag("amenity")) || + isTag("highway", "bus_stop") || + isTag("railway", "tram_stop") || + isTag("railway", "station") || + isTag("railway", "halt") || + isTag("amenity", "bus_station") || + isTag("amenity", "ferry_terminal") || isPlatform() ); } public boolean isPlatform() { - return "platform".equals(getTag("public_transport")) || "platform".equals(getTag("railway")); + return isTag("public_transport", "platform") || isTag("railway", "platform"); } /** @@ -507,6 +507,10 @@ public boolean isLink() { return highway != null && highway.endsWith(("_link")); } + public boolean isElevator() { + return isTag("highway", "elevator"); + } + /** * Returns true if this tag is explicitly access to this entity. */ From 0a82f70899a8b0030ea52864bb04ca390aef1640 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 13:52:00 +0200 Subject: [PATCH 023/118] Remove duplicate method, clean up --- .../graph_builder/module/osm/OsmDatabase.java | 11 +++++------ .../openstreetmap/model/OSMNode.java | 2 +- .../openstreetmap/model/OSMRelationMember.java | 10 ++++++---- .../openstreetmap/model/OSMWay.java | 4 ++-- .../openstreetmap/model/OSMWithTags.java | 16 +++++++--------- .../wayproperty/specifier/Condition.java | 7 +++---- .../openstreetmap/model/OSMWithTagsTest.java | 11 +++++++++++ 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index e6b819da9fb..c41949c9813 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -39,7 +39,6 @@ import org.opentripplanner.graph_builder.module.osm.TurnRestrictionTag.Direction; import org.opentripplanner.openstreetmap.model.OSMLevel; import org.opentripplanner.openstreetmap.model.OSMLevel.Source; -import org.opentripplanner.openstreetmap.model.OSMMemberType; import org.opentripplanner.openstreetmap.model.OSMNode; import org.opentripplanner.openstreetmap.model.OSMRelation; import org.opentripplanner.openstreetmap.model.OSMRelationMember; @@ -769,7 +768,7 @@ private void processMultipolygonRelations() { for (OSMRelationMember member : relation.getMembers()) { // multipolygons for attribute mapping - if (!(member.hasType(OSMMemberType.WAY) && waysById.containsKey(member.getRef()))) { + if (!(member.hasTypeWay() && waysById.containsKey(member.getRef()))) { continue; } @@ -849,7 +848,7 @@ private void setNetworkForAllMembers(OSMRelation relation, String key) { relation .getMembers() .forEach(member -> { - var isOsmWay = member.hasType(OSMMemberType.WAY); + var isOsmWay = member.hasTypeWay(); var way = waysById.get(member.getRef()); // if it is an OSM way (rather than a node) and it doesn't already contain the tag // we add it @@ -992,7 +991,7 @@ private void processLevelMap(OSMRelation relation) { issueStore ); for (OSMRelationMember member : relation.getMembers()) { - if (member.hasType(OSMMemberType.WAY) && waysById.containsKey(member.getRef())) { + if (member.hasTypeWay() && waysById.containsKey(member.getRef())) { OSMWay way = waysById.get(member.getRef()); if (way != null) { String role = member.getRole(); @@ -1011,11 +1010,11 @@ private void processLevelMap(OSMRelation relation) { } /** - * Handle route=road relations. + * Handle route=road and route=bicycle relations. */ private void processRoute(OSMRelation relation) { for (OSMRelationMember member : relation.getMembers()) { - if (!(member.hasType(OSMMemberType.WAY) && waysById.containsKey(member.getRef()))) { + if (!(member.hasTypeWay() && waysById.containsKey(member.getRef()))) { continue; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java index a4dd4554342..b394dc71858 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java @@ -35,7 +35,7 @@ public int getCapacity() throws NumberFormatException { * @author mattwigway */ public boolean isMultiLevel() { - return hasTag("highway") && "elevator".equals(getTag("highway")); + return isElevator(); } public boolean hasHighwayTrafficLight() { diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java index 7323c612c5e..e2ad2ff9691 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelationMember.java @@ -1,5 +1,7 @@ package org.opentripplanner.openstreetmap.model; +import static org.opentripplanner.openstreetmap.model.OSMMemberType.WAY; + public class OSMRelationMember { private OSMMemberType type; @@ -16,10 +18,6 @@ public void setType(OSMMemberType type) { this.type = type; } - public boolean hasType(OSMMemberType type) { - return this.type == type; - } - public long getRef() { return ref; } @@ -48,6 +46,10 @@ public boolean hasRolePlatform() { return "platform".equals(role); } + public boolean hasTypeWay() { + return type == WAY; + } + @Override public String toString() { return "osm rel " + type + ":" + role + ":" + ref; diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index de7a09a1472..bcc7c613e1b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -130,8 +130,8 @@ public boolean isOpposableCycleway() { public boolean isEscalator() { return ( isTag("highway", "steps") && - this.getTag("conveying") != null && - Set.of("yes", "forward", "backward", "reversible").contains(this.getTag("conveying")) + hasTag("conveying") && + Set.of("yes", "forward", "backward", "reversible").contains(getTag("conveying")) ); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 83b6d6835ec..e61b3042054 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -160,10 +160,12 @@ public String getTag(String tag) { } /** - * Returns true if both key and value matches. + * + * @return A tags value converted to lower case. An empty Optional if tags is not present. */ - public boolean matchesKeyValue(String key, String value) { - return hasTag(key) && getTag(key).equals(value); + @Nonnull + public Optional getTagOpt(String network) { + return Optional.ofNullable(getTag(network)); } /** @@ -460,6 +462,7 @@ public String getOpenStreetMapLink() { *

* Values are split by semicolons. */ + @Nonnull public Set getMultiTagValues(Set refTags) { return refTags .stream() @@ -468,7 +471,7 @@ public Set getMultiTagValues(Set refTags) { .flatMap(v -> Arrays.stream(v.split(";"))) .map(String::strip) .filter(v -> !v.isBlank()) - .collect(Collectors.toSet()); + .collect(Collectors.toUnmodifiableSet()); } public OsmProvider getOsmProvider() { @@ -479,11 +482,6 @@ public void setOsmProvider(OsmProvider provider) { this.osmProvider = provider; } - @Nonnull - public Optional getTagOpt(String network) { - return Optional.ofNullable(getTag(network)); - } - /** * Determines whether this OSM way is considered routable. The majority of routable ways are those * with a highway= tag (which includes everything from motorways to hiking trails). Anything with diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java index dcc155b6623..4d8381963b3 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/specifier/Condition.java @@ -110,7 +110,7 @@ enum MatchResult { record Equals(String key, String value) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { - return way.hasTag(exKey) && way.matchesKeyValue(exKey, value); + return way.hasTag(exKey) && way.isTag(exKey, value); } } @@ -165,7 +165,7 @@ public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { record EqualsAnyIn(String key, String... values) implements Condition { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { - return Arrays.stream(values).anyMatch(value -> way.matchesKeyValue(exKey, value)); + return Arrays.stream(values).anyMatch(value -> way.isTag(exKey, value)); } } @@ -178,8 +178,7 @@ public EqualsAnyInOrAbsent(String key) { @Override public boolean isExtendedKeyMatch(OSMWithTags way, String exKey) { return ( - !way.hasTag(exKey) || - Arrays.stream(values).anyMatch(value -> way.matchesKeyValue(exKey, value)) + !way.hasTag(exKey) || Arrays.stream(values).anyMatch(value -> way.isTag(exKey, value)) ); } } diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java index d2f6b4d3196..85bafc576ff 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java @@ -84,6 +84,17 @@ public void testIsTagFalseOrTrue() { assertFalse(o.isTagTrue("FOO")); } + @Test + public void isTag() { + var name = "Brendan"; + var osm = new OSMWithTags(); + osm.addTag("NAME", name); + + assertTrue(osm.isTag("name", name)); + assertTrue(osm.isTag("NAME", name)); + assertFalse(osm.isTag("NAMEE", name)); + } + @Test public void testDoesAllowTagAccess() { OSMWithTags o = new OSMWithTags(); From afce5fd8f03b2594242082a4ce2bbc3f55385090 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 14:19:14 +0200 Subject: [PATCH 024/118] Centralise parsing of wheelchair-accessibility --- .../graph_builder/module/osm/OsmModule.java | 5 +---- .../module/osm/WalkableAreaBuilder.java | 8 ++------ .../openstreetmap/model/OSMWay.java | 10 +++++++++- .../openstreetmap/model/OSMWithTags.java | 11 ++++++++++- .../openstreetmap/model/OSMWayTest.java | 13 +++++++++++++ .../openstreetmap/model/OSMWithTagsTest.java | 14 ++++++++++++++ 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 10cea613f84..eb83f0ae080 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -536,10 +536,7 @@ private StreetEdge getEdgeForStreet( if (!way.hasTag("name") && !way.hasTag("ref")) { seb.withBogusName(true); } - /* TODO: This should probably generalized somehow? */ - if ((way.isTagFalse("wheelchair") || (way.isSteps() && !way.isTagTrue("wheelchair")))) { - seb.withWheelchairAccessible(false); - } + seb.withWheelchairAccessible(way.isWheelchairAccessible()); // < 0.04: account for if (carSpeed < 0.04) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 3beaa6fb473..f87ee04db97 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -539,9 +539,7 @@ private Set createSegments( streetEdgeBuilder.withBogusName(true); } - if (areaEntity.isTagFalse("wheelchair")) { - streetEdgeBuilder.withWheelchairAccessible(false); - } + streetEdgeBuilder.withWheelchairAccessible(areaEntity.isWheelchairAccessible()); streetEdgeBuilder.withLink(areaEntity.isLink()); @@ -569,9 +567,7 @@ private Set createSegments( backStreetEdgeBuilder.withBogusName(true); } - if (areaEntity.isTagFalse("wheelchair")) { - backStreetEdgeBuilder.withWheelchairAccessible(false); - } + backStreetEdgeBuilder.withWheelchairAccessible(areaEntity.isWheelchairAccessible()); backStreetEdgeBuilder.withLink(areaEntity.isLink()); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index bcc7c613e1b..88f1d8c3810 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -57,7 +57,15 @@ public boolean isBicycleDismountForced() { * Returns true if these are steps. */ public boolean isSteps() { - return "steps".equals(getTag("highway")); + return isTag("highway", "steps"); + } + + public boolean isWheelchairAccessible() { + if (isSteps()) { + return isTagTrue("wheelchair"); + } else { + return super.isWheelchairAccessible(); + } } /** diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index e61b3042054..5b1d074adbe 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -132,7 +132,7 @@ public boolean isTagTrue(String tag) { return isTrue(getTag(tag)); } - public boolean doesTagAllowAccess(String tag) { + protected boolean doesTagAllowAccess(String tag) { if (tags == null) { return false; } @@ -509,6 +509,15 @@ public boolean isElevator() { return isTag("highway", "elevator"); } + /** + * @return true if there is no explicit tag that makes this unsuitable for wheelchair use. + * In other words: we assume that something is wheelchair-accessible in the absence + * of other information. + */ + public boolean isWheelchairAccessible() { + return !isTagFalse("wheelchair"); + } + /** * Returns true if this tag is explicitly access to this entity. */ diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index 8ec384601f0..911c3fc1968 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -33,6 +33,19 @@ public void testIsSteps() { assertTrue(way.isSteps()); } + @Test + public void wheelchairAccessibleStairs() { + var osm1 = new OSMWay(); + osm1.addTag("highway", "steps"); + assertFalse(osm1.isWheelchairAccessible()); + + // explicitly suitable for wheelchair users, perhaps because of a ramp + var osm2 = new OSMWay(); + osm2.addTag("highway", "steps"); + osm2.addTag("wheelchair", "yes"); + assertTrue(osm2.isWheelchairAccessible()); + } + @Test public void testIsRoundabout() { OSMWay way = new OSMWay(); diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java index 85bafc576ff..3a7f54a93e7 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java @@ -194,6 +194,20 @@ public void shouldNotReturnNull() { assertEquals(Set.of(), osm.getMultiTagValues(Set.of("ref3"))); } + @Test + public void isWheelchairAccessible() { + var osm1 = new OSMWithTags(); + assertTrue(osm1.isWheelchairAccessible()); + + var osm2 = new OSMWithTags(); + osm2.addTag("wheelchair", "no"); + assertFalse(osm2.isWheelchairAccessible()); + + var osm3 = new OSMWithTags(); + osm3.addTag("wheelchair", "yes"); + assertTrue(osm3.isWheelchairAccessible()); + } + @Test public void testGenerateI18NForPattern() { OSMWithTags osmTags = new OSMWithTags(); From 8476448fb602867c83a63e5f032019c302d8a03e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 14:49:38 +0200 Subject: [PATCH 025/118] Extract plaform check --- .../graph_builder/module/osm/WalkableAreaBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index f87ee04db97..d64aac748e1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -245,7 +245,7 @@ public void buildWithVisibility(AreaGroup group) { // variable to indicate if some additional entrance points have been added to area boolean linkPointsAdded = !entrances.isEmpty(); // Add unconnected entries to area if platformEntriesLinking parameter is true - if (platformEntriesLinking && "platform".equals(area.parent.getTag("public_transport"))) { + if (platformEntriesLinking && area.parent.isPlatform()) { List endpointsWithin = platformLinkingEndpoints .stream() .filter(t -> From d68884fab276d16822d19d2847d1dd54e59585b9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 14:57:15 +0200 Subject: [PATCH 026/118] Remove unused code --- .../ext/siri/SiriTimetableSnapshotSource.java | 8 ++------ .../ext/siri/mapper/AffectsMapper.java | 19 ------------------- .../issues/TooManyAreasInRelation.java | 19 ------------------- .../module/geometry/GeometryProcessor.java | 9 +++------ .../gtfs/mapping/TransferMapper.java | 7 ------- .../plan/pagecursor/PageCursorSerializer.java | 8 -------- .../netex/mapping/NoticeAssignmentMapper.java | 4 ---- .../routing/algorithm/RoutingWorker.java | 8 -------- .../framework/json/ParameterBuilder.java | 8 -------- 9 files changed, 5 insertions(+), 85 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/graph_builder/issues/TooManyAreasInRelation.java diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index a5e6f2bcae6..937adc112ab 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -218,7 +218,7 @@ private Result apply( } /* commit */ - return addTripToGraphAndBuffer(result.successValue(), journey, entityResolver); + return addTripToGraphAndBuffer(result.successValue()); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", @@ -359,11 +359,7 @@ private Result handleModifiedTrip( /** * Add a (new) trip to the transitModel and the buffer */ - private Result addTripToGraphAndBuffer( - TripUpdate tripUpdate, - EstimatedVehicleJourney estimatedVehicleJourney, - EntityResolver entityResolver - ) { + private Result addTripToGraphAndBuffer(TripUpdate tripUpdate) { Trip trip = tripUpdate.tripTimes().getTrip(); LocalDate serviceDate = tripUpdate.serviceDate(); diff --git a/src/ext/java/org/opentripplanner/ext/siri/mapper/AffectsMapper.java b/src/ext/java/org/opentripplanner/ext/siri/mapper/AffectsMapper.java index e789aa9a11d..734cc8edbd3 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/mapper/AffectsMapper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/mapper/AffectsMapper.java @@ -378,25 +378,6 @@ private static FeedScopedId getStop( return null; } - private static FeedScopedId getTripId( - String vehicleJourney, - String feedId, - TransitService transitService - ) { - Trip trip = transitService.getTripForId(new FeedScopedId(feedId, vehicleJourney)); - if (trip != null) { - return trip.getId(); - } - //Attempt to find trip using datedServiceJourneys - TripOnServiceDate tripOnServiceDate = transitService.getTripOnServiceDateById( - new FeedScopedId(feedId, vehicleJourney) - ); - if (tripOnServiceDate != null) { - return tripOnServiceDate.getTrip().getId(); - } - return null; - } - private static Set resolveStopConditions( List stopConditions ) { diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/TooManyAreasInRelation.java b/src/main/java/org/opentripplanner/graph_builder/issues/TooManyAreasInRelation.java deleted file mode 100644 index 25efd1f9b0f..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/issues/TooManyAreasInRelation.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.opentripplanner.graph_builder.issues; - -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.openstreetmap.model.OSMWithTags; - -public record TooManyAreasInRelation(OSMWithTags entity) implements DataImportIssue { - private static final String FMT = "Too many areas in relation %s"; - private static final String HTMLFMT = "Too many areas in relation '%s'"; - - @Override - public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), entity.getId()); - } - - @Override - public String getMessage() { - return String.format(FMT, entity.getId()); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessor.java b/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessor.java index 810ef2bac20..360ad9f5865 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessor.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessor.java @@ -123,7 +123,7 @@ private LineString[] createGeometry(FeedScopedId shapeId, List stopTim // this trip has a shape_id, but no such shape exists, and no shape_dist in stop_times // create straight line segments between stops for each hop issueStore.add(new MissingShapeGeometry(stopTimes.get(0).getTrip().getId(), shapeId)); - return createStraightLineHopeGeometries(stopTimes, shapeId); + return createStraightLineHopGeometries(stopTimes); } List locations = getLinearLocations(stopTimes, shapeLineString); @@ -132,7 +132,7 @@ private LineString[] createGeometry(FeedScopedId shapeId, List stopTim // their stop sequence. So we'll fall back to trivial stop-to-stop // linking, even though theoretically we could do better. issueStore.add(new ShapeGeometryTooFar(stopTimes.get(0).getTrip().getId(), shapeId)); - return createStraightLineHopeGeometries(stopTimes, shapeId); + return createStraightLineHopGeometries(stopTimes); } return getGeometriesByShape(stopTimes, shapeId, shapeLineString, locations); @@ -267,10 +267,7 @@ private List getLinearLocations(List stopTimes, LineSt return getStopLocations(possibleSegmentsForStop, stopTimes, 0, -1); } - private LineString[] createStraightLineHopeGeometries( - List stopTimes, - FeedScopedId shapeId - ) { + private LineString[] createStraightLineHopGeometries(List stopTimes) { LineString[] geoms = new LineString[stopTimes.size() - 1]; StopTime st0; for (int i = 0; i < stopTimes.size() - 1; ++i) { diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java index 3ebe4b16233..7eabbbb3c78 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java @@ -297,11 +297,4 @@ private int alightStopPosition(Trip trip, RegularStop stop, Station station) { } return -1; } - - private boolean sameBlockId(Trip a, Trip b) { - if (a == null || b == null) { - return false; - } - return a.getGtfsBlockId() != null && a.getGtfsBlockId().equals(b.getGtfsBlockId()); - } } diff --git a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java index dbbdc56af64..b3a25182678 100644 --- a/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java +++ b/src/main/java/org/opentripplanner/model/plan/pagecursor/PageCursorSerializer.java @@ -108,14 +108,6 @@ private static Duration readDuration(ObjectInputStream in) throws IOException { return Duration.ofSeconds(in.readInt()); } - private static void writeBoolean(boolean value, ObjectOutputStream out) throws IOException { - out.writeBoolean(value); - } - - private static boolean readBoolean(ObjectInputStream in) throws IOException { - return in.readBoolean(); - } - private static > void writeEnum(T value, ObjectOutputStream out) throws IOException { out.writeUTF(value.name()); diff --git a/src/main/java/org/opentripplanner/netex/mapping/NoticeAssignmentMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NoticeAssignmentMapper.java index 02cced2b3f3..fc8cb3804f6 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NoticeAssignmentMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NoticeAssignmentMapper.java @@ -116,10 +116,6 @@ Multimap map(NoticeAssignment noticeAssignment) { return noticiesByEntity; } - private StopTimeKey lookupStopTimeKey(String timeTablePassingTimeId) { - return stopTimesByNetexId.get(timeTablePassingTimeId).getId(); - } - @Nullable private Notice getOrMapNotice(NoticeAssignment assignment) { org.rutebanken.netex.model.Notice notice = assignment.getNotice() != null diff --git a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index 245e20bb33d..f83069c1683 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -175,14 +175,6 @@ public RoutingResponse route() { ); } - private List rideHailingServices() { - if (request.journey().modes().contains(StreetMode.CAR_HAILING)) { - return serverContext.rideHailingServices(); - } else { - return List.of(); - } - } - private static AdditionalSearchDays createAdditionalSearchDays( RaptorTuningParameters raptorTuningParameters, ZoneId zoneId, 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 a3aa9afb82c..3bc66ce6ea5 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 @@ -647,14 +647,6 @@ private Duration parseDuration(String value) { } } - private Duration parseDuration(String value, ChronoUnit unit) { - try { - return DurationUtils.duration(value, unit); - } catch (DateTimeParseException e) { - throw error("The parameter value '%s' is not a duration.".formatted(value), e); - } - } - /** * Somehow Java do not provide a parse method for parsing Locale on the standard form * {@code [_[_]]}, so this little utility method does that. From 53db8019d51d7213712a29750e86d95db9e0a07e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 15:10:48 +0200 Subject: [PATCH 027/118] Add test for isRoutable --- .../opentripplanner/openstreetmap/model/OSMWithTags.java | 2 +- .../openstreetmap/model/OSMWithTagsTest.java | 6 ++++++ .../openstreetmap/wayproperty/specifier/WayTestData.java | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 5b1d074adbe..dc4e2b23be2 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -495,7 +495,7 @@ public boolean isRoutable() { return true; } if (isPlatform()) { - return !("tourism".equals(getTag("usage"))); + return !isTag("usage", "tourism"); } return false; } diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java index 3a7f54a93e7..6f4f81fc8d3 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; +import org.opentripplanner.openstreetmap.wayproperty.specifier.WayTestData; public class OSMWithTagsTest { @@ -208,6 +209,11 @@ public void isWheelchairAccessible() { assertTrue(osm3.isWheelchairAccessible()); } + @Test + public void isRoutable() { + assertFalse(WayTestData.zooPlatform().isRoutable()); + } + @Test public void testGenerateI18NForPattern() { OSMWithTags osmTags = new OSMWithTags(); diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java index f7ec49b925a..798c776fa56 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java @@ -208,4 +208,12 @@ public static OSMWithTags excellentSmoothness() { way.addTag("smoothness", "excellent"); return way; } + + public static OSMWithTags zooPlatform() { + // https://www.openstreetmap.org/way/119108622 + var way = new OSMWithTags(); + way.addTag("public_transport", "platform"); + way.addTag("usage", "tourism"); + return way; + } } From 077f44044c63429a5b350aee73fbc13fba9d7af0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 29 Aug 2023 15:25:05 +0200 Subject: [PATCH 028/118] Remove 'public' modifier from tests --- .../openstreetmap/model/OSMWayTest.java | 28 +++++++-------- .../openstreetmap/model/OSMWithTagsTest.java | 36 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index 911c3fc1968..a3319a995f5 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -13,7 +13,7 @@ public class OSMWayTest { @Test - public void testIsBicycleDismountForced() { + void testIsBicycleDismountForced() { OSMWay way = new OSMWay(); assertFalse(way.isBicycleDismountForced()); @@ -22,7 +22,7 @@ public void testIsBicycleDismountForced() { } @Test - public void testIsSteps() { + void testIsSteps() { OSMWay way = new OSMWay(); assertFalse(way.isSteps()); @@ -34,7 +34,7 @@ public void testIsSteps() { } @Test - public void wheelchairAccessibleStairs() { + void wheelchairAccessibleStairs() { var osm1 = new OSMWay(); osm1.addTag("highway", "steps"); assertFalse(osm1.isWheelchairAccessible()); @@ -47,7 +47,7 @@ public void wheelchairAccessibleStairs() { } @Test - public void testIsRoundabout() { + void testIsRoundabout() { OSMWay way = new OSMWay(); assertFalse(way.isRoundabout()); @@ -59,7 +59,7 @@ public void testIsRoundabout() { } @Test - public void testIsOneWayDriving() { + void testIsOneWayDriving() { OSMWay way = new OSMWay(); assertFalse(way.isOneWayForwardDriving()); assertFalse(way.isOneWayReverseDriving()); @@ -78,7 +78,7 @@ public void testIsOneWayDriving() { } @Test - public void testIsOneWayBicycle() { + void testIsOneWayBicycle() { OSMWay way = new OSMWay(); assertFalse(way.isOneWayForwardBicycle()); assertFalse(way.isOneWayReverseBicycle()); @@ -97,7 +97,7 @@ public void testIsOneWayBicycle() { } @Test - public void testIsOneDirectionSidepath() { + void testIsOneDirectionSidepath() { OSMWay way = new OSMWay(); assertFalse(way.isForwardDirectionSidepath()); assertFalse(way.isReverseDirectionSidepath()); @@ -112,7 +112,7 @@ public void testIsOneDirectionSidepath() { } @Test - public void testIsOpposableCycleway() { + void testIsOpposableCycleway() { OSMWay way = new OSMWay(); assertFalse(way.isOpposableCycleway()); @@ -136,7 +136,7 @@ public void testIsOpposableCycleway() { * Check for bug #1878 and PR #1880 */ @Test - public void testCarPermission() { + void testCarPermission() { OSMWay way = new OSMWay(); way.addTag("highway", "unclassified"); @@ -153,7 +153,7 @@ public void testCarPermission() { * is no */ @Test - public void testMotorCarTagAllowedPermissions() { + void testMotorCarTagAllowedPermissions() { OSMWay way = new OSMWay(); way.addTag("highway", "residential"); var permissionPair = getWayProperties(way); @@ -187,7 +187,7 @@ public void testMotorCarTagAllowedPermissions() { * access is yes */ @Test - public void testMotorCarTagDeniedPermissions() { + void testMotorCarTagDeniedPermissions() { OSMWay way = new OSMWay(); way.addTag("highway", "residential"); var permissionPair = getWayProperties(way); @@ -219,7 +219,7 @@ public void testMotorCarTagDeniedPermissions() { * Support for motor_vehicle was added in #1881 */ @Test - public void testMotorVehicleTagAllowedPermissions() { + void testMotorVehicleTagAllowedPermissions() { OSMWay way = new OSMWay(); way.addTag("highway", "residential"); var permissionPair = getWayProperties(way); @@ -255,7 +255,7 @@ public void testMotorVehicleTagAllowedPermissions() { * Support for motor_vehicle was added in #1881 */ @Test - public void testMotorVehicleTagDeniedPermissions() { + void testMotorVehicleTagDeniedPermissions() { OSMWay way = new OSMWay(); way.addTag("highway", "residential"); var permissionPair = getWayProperties(way); @@ -281,7 +281,7 @@ public void testMotorVehicleTagDeniedPermissions() { } @Test - public void testSidepathPermissions() { + void testSidepathPermissions() { OSMWay way = new OSMWay(); way.addTag("bicycle", "use_sidepath"); way.addTag("highway", "primary"); diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java index 6f4f81fc8d3..c4b52b6e67a 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java @@ -15,7 +15,7 @@ public class OSMWithTagsTest { @Test - public void testHasTag() { + void testHasTag() { OSMWithTags o = new OSMWithTags(); assertFalse(o.hasTag("foo")); assertFalse(o.hasTag("FOO")); @@ -26,7 +26,7 @@ public void testHasTag() { } @Test - public void testGetTag() { + void testGetTag() { OSMWithTags o = new OSMWithTags(); assertNull(o.getTag("foo")); assertNull(o.getTag("FOO")); @@ -37,7 +37,7 @@ public void testGetTag() { } @Test - public void testIsFalse() { + void testIsFalse() { assertTrue(OSMWithTags.isFalse("no")); assertTrue(OSMWithTags.isFalse("0")); assertTrue(OSMWithTags.isFalse("false")); @@ -51,7 +51,7 @@ public void testIsFalse() { } @Test - public void testIsTrue() { + void testIsTrue() { assertTrue(OSMWithTags.isTrue("yes")); assertTrue(OSMWithTags.isTrue("1")); assertTrue(OSMWithTags.isTrue("true")); @@ -65,7 +65,7 @@ public void testIsTrue() { } @Test - public void testIsTagFalseOrTrue() { + void testIsTagFalseOrTrue() { OSMWithTags o = new OSMWithTags(); assertFalse(o.isTagFalse("foo")); assertFalse(o.isTagFalse("FOO")); @@ -86,7 +86,7 @@ public void testIsTagFalseOrTrue() { } @Test - public void isTag() { + void isTag() { var name = "Brendan"; var osm = new OSMWithTags(); osm.addTag("NAME", name); @@ -97,7 +97,7 @@ public void isTag() { } @Test - public void testDoesAllowTagAccess() { + void testDoesAllowTagAccess() { OSMWithTags o = new OSMWithTags(); assertFalse(o.doesTagAllowAccess("foo")); @@ -112,7 +112,7 @@ public void testDoesAllowTagAccess() { } @Test - public void testIsGeneralAccessDenied() { + void testIsGeneralAccessDenied() { OSMWithTags o = new OSMWithTags(); assertFalse(o.isGeneralAccessDenied()); @@ -127,7 +127,7 @@ public void testIsGeneralAccessDenied() { } @Test - public void testBicycleDenied() { + void testBicycleDenied() { OSMWithTags tags = new OSMWithTags(); assertFalse(tags.isBicycleExplicitlyDenied()); @@ -143,7 +143,7 @@ public void testBicycleDenied() { } @Test - public void getReferenceTags() { + void getReferenceTags() { var osm = new OSMWithTags(); osm.addTag("ref", "A"); @@ -152,7 +152,7 @@ public void getReferenceTags() { } @Test - public void getEmptyRefList() { + void getEmptyRefList() { var osm = new OSMWithTags(); osm.addTag("ref", "A"); @@ -160,7 +160,7 @@ public void getEmptyRefList() { } @Test - public void ignoreRefCase() { + void ignoreRefCase() { var osm = new OSMWithTags(); osm.addTag("ref:IFOPT", "A"); @@ -168,7 +168,7 @@ public void ignoreRefCase() { } @Test - public void readSemicolonSeparated() { + void readSemicolonSeparated() { var osm = new OSMWithTags(); osm.addTag("ref:A", "A;A;B"); @@ -176,7 +176,7 @@ public void readSemicolonSeparated() { } @Test - public void removeBlankRef() { + void removeBlankRef() { var osm = new OSMWithTags(); osm.addTag("ref1", " "); osm.addTag("ref2", ""); @@ -186,7 +186,7 @@ public void removeBlankRef() { } @Test - public void shouldNotReturnNull() { + void shouldNotReturnNull() { var osm = new OSMWithTags(); osm.addTag("ref1", " "); osm.addTag("ref2", ""); @@ -196,7 +196,7 @@ public void shouldNotReturnNull() { } @Test - public void isWheelchairAccessible() { + void isWheelchairAccessible() { var osm1 = new OSMWithTags(); assertTrue(osm1.isWheelchairAccessible()); @@ -210,12 +210,12 @@ public void isWheelchairAccessible() { } @Test - public void isRoutable() { + void isRoutable() { assertFalse(WayTestData.zooPlatform().isRoutable()); } @Test - public void testGenerateI18NForPattern() { + void testGenerateI18NForPattern() { OSMWithTags osmTags = new OSMWithTags(); osmTags.addTag("note", "Note EN"); osmTags.addTag("description:fr", "Description FR"); From 934f0ffecb15ca79c9de1e3aeb9beb1e80103e11 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:42:44 +0000 Subject: [PATCH 029/118] Update dependency io.github.ci-cmg:mapbox-vector-tile to v4.0.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9f639a50b23..0cddb21b49f 100644 --- a/pom.xml +++ b/pom.xml @@ -949,7 +949,7 @@ io.github.ci-cmg mapbox-vector-tile - 4.0.5 + 4.0.6 net.objecthunter From 70a06d763ba8165fa1bf86b449d01afedc69cea8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 30 Aug 2023 12:54:29 +0200 Subject: [PATCH 030/118] Introduce isOneOfTags --- .../openstreetmap/model/OSMWay.java | 12 +++++++----- .../openstreetmap/model/OSMWithTags.java | 7 +++++++ .../openstreetmap/model/OSMWayTest.java | 16 ++++++++++++++++ .../wayproperty/specifier/WayTestData.java | 5 +++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index 88f1d8c3810..8d0404abfbd 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -6,6 +6,12 @@ public class OSMWay extends OSMWithTags { + private static final Set ESCALATOR_CONVEYING_TAGS = Set.of( + "yes", + "forward", + "backward", + "reversible" + ); private final TLongList nodes = new TLongArrayList(); public void addNodeRef(long nodeRef) { @@ -136,11 +142,7 @@ public boolean isOpposableCycleway() { } public boolean isEscalator() { - return ( - isTag("highway", "steps") && - hasTag("conveying") && - Set.of("yes", "forward", "backward", "reversible").contains(getTag("conveying")) - ); + return (isTag("highway", "steps") && isOneOfTags("conveying", ESCALATOR_CONVEYING_TAGS)); } public boolean isForwardEscalator() { diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index dc4e2b23be2..e0dc829dd4c 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -196,6 +196,13 @@ public boolean isTag(String tag, String value) { return false; } + /** + * Takes a tag key and checks if the value is any of those in {@code onOfTags}. + */ + public boolean isOneOfTags(String key, Set oneOfTags) { + return oneOfTags.stream().anyMatch(value -> isTag(key, value)); + } + /** * Returns a name-like value for an entity (if one exists). The otp: namespaced tags are created * by {@link OsmModule} diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index a3319a995f5..05eb6c02852 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -8,6 +8,7 @@ import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair; import org.opentripplanner.openstreetmap.wayproperty.WayProperties; import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; +import org.opentripplanner.openstreetmap.wayproperty.specifier.WayTestData; import org.opentripplanner.street.model.StreetTraversalPermission; public class OSMWayTest { @@ -339,6 +340,21 @@ void testSidepathPermissions() { assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); } + @Test + void escalator() { + assertFalse(WayTestData.cycleway().isEscalator()); + + var escalator = new OSMWay(); + escalator.addTag("highway", "steps"); + assertFalse(escalator.isEscalator()); + + escalator.addTag("conveying", "yes"); + assertTrue(escalator.isEscalator()); + + escalator.addTag("conveying", "whoknows?"); + assertFalse(escalator.isEscalator()); + } + private StreetTraversalPermissionPair getWayProperties(OSMWay way) { WayPropertySet wayPropertySet = new WayPropertySet(); WayProperties wayData = wayPropertySet.getDataForWay(way); diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java index 798c776fa56..20dbcbb5a78 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/specifier/WayTestData.java @@ -1,5 +1,6 @@ package org.opentripplanner.openstreetmap.wayproperty.specifier; +import org.opentripplanner.openstreetmap.model.OSMWay; import org.opentripplanner.openstreetmap.model.OSMWithTags; public class WayTestData { @@ -99,8 +100,8 @@ public static OSMWithTags threeLanes() { return way; } - public static OSMWithTags cycleway() { - var way = new OSMWithTags(); + public static OSMWay cycleway() { + var way = new OSMWay(); way.addTag("highway", "residential"); way.addTag("cycleway", "lane"); return way; From c84240f69e64e76413666c0b8368896d2da86b27 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 30 Aug 2023 11:39:30 +0200 Subject: [PATCH 031/118] Remove banDiscouraged* functionality --- docs/BuildConfiguration.md | 2 -- docs/examples/entur/build-config.json | 2 -- .../module/configure/GraphBuilderModules.java | 2 -- .../graph_builder/module/osm/OsmFilter.java | 14 ++------------ .../graph_builder/module/osm/OsmModule.java | 2 -- .../module/osm/OsmModuleBuilder.java | 16 +--------------- .../osm/parameters/OsmProcessingParameters.java | 8 +------- .../standalone/config/BuildConfig.java | 14 -------------- test/performance/norway/build-config.json | 2 -- 9 files changed, 4 insertions(+), 58 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index fba7f888f39..84818d29538 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -20,8 +20,6 @@ Sections follow that describe particular settings in more depth. | Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | |--------------------------------------------------------------------------|:-----------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| | [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | -| banDiscouragedBiking | `boolean` | Should biking be allowed on OSM ways tagged with `bicycle=discouraged` | *Optional* | `false` | 2.0 | -| banDiscouragedWalking | `boolean` | Should walking be allowed on OSM ways tagged with `foot=discouraged` | *Optional* | `false` | 2.0 | | [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | | [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | | [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index 5102107c7d6..3829e975f34 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -13,8 +13,6 @@ "islandWithoutStopsMaxSize": 5, "islandWithStopsMaxSize": 5 }, - "banDiscouragedWalking": false, - "banDiscouragedBiking": false, "maxTransferDuration": "30m", "distanceBetweenElevationSamples": 25, "multiThreadElevationCalculations": true, diff --git a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 9b8852977f3..98ec013aea0 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -73,8 +73,6 @@ static OsmModule provideOpenStreetMapModule( .withPlatformEntriesLinking(config.platformEntriesLinking) .withStaticParkAndRide(config.staticParkAndRide) .withStaticBikeParkAndRide(config.staticBikeParkAndRide) - .withBanDiscouragedWalking(config.banDiscouragedWalking) - .withBanDiscouragedBiking(config.banDiscouragedBiking) .withMaxAreaNodes(config.maxAreaNodes) .withBoardingAreaRefTags(config.boardingLocationTags) .withIssueStore(issueStore) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index 5901266b401..d93a0b1ddb9 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -90,8 +90,6 @@ public static StreetTraversalPermission getPermissionsForEntity( public static StreetTraversalPermission getPermissionsForWay( OSMWay way, StreetTraversalPermission def, - boolean banDiscouragedWalking, - boolean banDiscouragedBiking, DataImportIssueStore issueStore ) { StreetTraversalPermission permissions = getPermissionsForEntity(way, def); @@ -121,11 +119,6 @@ public static StreetTraversalPermission getPermissionsForWay( permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); } - // Check for foot=discouraged, if applicable - if (banDiscouragedWalking && way.hasTag("foot") && way.getTag("foot").equals("discouraged")) { - permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); - } - // Compute bike permissions, check consistency. boolean forceBikes = false; if (way.isBicycleExplicitlyAllowed()) { @@ -133,10 +126,7 @@ public static StreetTraversalPermission getPermissionsForWay( forceBikes = true; } - if ( - way.isBicycleDismountForced() || - (banDiscouragedBiking && way.hasTag("bicycle") && way.getTag("bicycle").equals("discouraged")) - ) { + if (way.isBicycleDismountForced()) { permissions = permissions.remove(StreetTraversalPermission.BICYCLE); if (forceBikes) { issueStore.add(new ConflictingBikeTags(way)); @@ -150,7 +140,7 @@ public static StreetTraversalPermission getPermissionsForWay( OSMWay way, StreetTraversalPermission def ) { - return getPermissionsForWay(way, def, false, false, DataImportIssueStore.NOOP); + return getPermissionsForWay(way, def, DataImportIssueStore.NOOP); } /** diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index bfab1097ad7..94f3751b510 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -250,8 +250,6 @@ private void buildBasicGraph() { StreetTraversalPermission permissions = OsmFilter.getPermissionsForWay( way, wayData.getPermission(), - params.banDiscouragedWalking(), - params.banDiscouragedBiking(), issueStore ); if (!OsmFilter.isWayRoutable(way) || permissions.allowsNothing()) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index e73db9eb318..a99752ca2b9 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -23,8 +23,6 @@ public class OsmModuleBuilder { private boolean platformEntriesLinking = false; private boolean staticParkAndRide = false; private boolean staticBikeParkAndRide = false; - private boolean banDiscouragedWalking = false; - private boolean banDiscouragedBiking = false; private int maxAreaNodes; OsmModuleBuilder(Collection providers, Graph graph) { @@ -67,16 +65,6 @@ public OsmModuleBuilder withStaticBikeParkAndRide(boolean staticBikeParkAndRide) return this; } - public OsmModuleBuilder withBanDiscouragedWalking(boolean banDiscouragedWalking) { - this.banDiscouragedWalking = banDiscouragedWalking; - return this; - } - - public OsmModuleBuilder withBanDiscouragedBiking(boolean banDiscouragedBiking) { - this.banDiscouragedBiking = banDiscouragedBiking; - return this; - } - public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) { this.maxAreaNodes = maxAreaNodes; return this; @@ -94,9 +82,7 @@ public OsmModule build() { areaVisibility, platformEntriesLinking, staticParkAndRide, - staticBikeParkAndRide, - banDiscouragedWalking, - banDiscouragedBiking + staticBikeParkAndRide ) ); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java index 7f8b87c96df..52bf8d65314 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java @@ -13,10 +13,6 @@ * @param platformEntriesLinking Whether platform entries should be linked * @param staticParkAndRide Whether we should create car P+R stations from OSM data. * @param staticBikeParkAndRide Whether we should create bike P+R stations from OSM data. - * @param banDiscouragedWalking Whether ways tagged foot=discouraged should be marked as - * inaccessible. - * @param banDiscouragedBiking Whether ways tagged bicycle=discouraged should be marked as - * inaccessible. */ public record OsmProcessingParameters( Set boardingAreaRefTags, @@ -25,9 +21,7 @@ public record OsmProcessingParameters( boolean areaVisibility, boolean platformEntriesLinking, boolean staticParkAndRide, - boolean staticBikeParkAndRide, - boolean banDiscouragedWalking, - boolean banDiscouragedBiking + boolean staticBikeParkAndRide ) { public OsmProcessingParameters { boardingAreaRefTags = Set.copyOf(Objects.requireNonNull(boardingAreaRefTags)); diff --git a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index aab9b0193c6..de3979563f3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -148,8 +148,6 @@ public class BuildConfig implements OtpDataStoreConfig { /** See {@link IslandPruningConfig}. */ public final IslandPruningConfig islandPruning; - public final boolean banDiscouragedWalking; - public final boolean banDiscouragedBiking; public final Duration maxTransferDuration; public final NetexFeedParameters netexDefaults; public final GtfsFeedParameters gtfsDefaults; @@ -211,18 +209,6 @@ public BuildConfig(NodeAdapter root, boolean logUnusedParams) { """ ) .asBoolean(false); - banDiscouragedWalking = - root - .of("banDiscouragedWalking") - .since(V2_0) - .summary("Should walking be allowed on OSM ways tagged with `foot=discouraged`") - .asBoolean(false); - banDiscouragedBiking = - root - .of("banDiscouragedBiking") - .since(V2_0) - .summary("Should biking be allowed on OSM ways tagged with `bicycle=discouraged`") - .asBoolean(false); configVersion = root .of("configVersion") diff --git a/test/performance/norway/build-config.json b/test/performance/norway/build-config.json index 50ea45afc2f..5fa7f03ed58 100644 --- a/test/performance/norway/build-config.json +++ b/test/performance/norway/build-config.json @@ -14,8 +14,6 @@ "islandWithoutStopsMaxSize": 5, "islandWithStopsMaxSize": 5 }, - "banDiscouragedWalking": false, - "banDiscouragedBiking": false, "distanceBetweenElevationSamples": 25, "multiThreadElevationCalculations": true, "boardingLocationTags": [], From caf9795e6ef3db5a790d7333d8b690eab7c09302 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 30 Aug 2023 12:06:28 +0200 Subject: [PATCH 032/118] Add mixins for foot=discouraged, bicycle=discouraged --- .../openstreetmap/model/OSMWithTags.java | 6 ++-- .../tagmapping/DefaultMapper.java | 4 +++ .../tagmapping/DefaultMapperTest.java | 36 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 2c2f3167ab1..e432878b2f4 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -2,7 +2,6 @@ import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; @@ -70,12 +69,13 @@ public void addTag(OSMTag tag) { /** * Adds a tag. */ - public void addTag(String key, String value) { - if (key == null || value == null) return; + public OSMWithTags addTag(String key, String value) { + if (key == null || value == null) return this; if (tags == null) tags = new HashMap<>(); tags.put(key.toLowerCase(), value); + return this; } /** diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java index 9185f1ccf3b..01cf213b03b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.openstreetmap.tagmapping; import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; +import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofWalkSafety; import static org.opentripplanner.openstreetmap.wayproperty.WayPropertiesBuilder.withModes; import static org.opentripplanner.street.model.StreetTraversalPermission.ALL; import static org.opentripplanner.street.model.StreetTraversalPermission.BICYCLE_AND_CAR; @@ -618,6 +619,9 @@ public void populateProperties(WayPropertySet props) { props.setMixinProperties("CCGIS:bicycle:right=caution_area", ofBicycleSafety(1.45, 1)); props.setMixinProperties("CCGIS:bicycle:left=caution_area", ofBicycleSafety(1, 1.45)); + props.setMixinProperties("foot=discouraged", ofWalkSafety(3)); + props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3)); + populateNotesAndNames(props); // slope overrides diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java index cdb4e5518ac..4465380806c 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java @@ -2,8 +2,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.street.model.StreetTraversalPermission.ALL; import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.SpeedPicker; @@ -13,12 +16,15 @@ public class DefaultMapperTest { - static WayPropertySet wps = new WayPropertySet(); + private WayPropertySet wps; float epsilon = 0.01f; - static { + @BeforeEach + public void setup() { + var wps = new WayPropertySet(); DefaultMapper source = new DefaultMapper(); source.populateProperties(wps); + this.wps = wps; } /** @@ -111,6 +117,32 @@ void stairs() { assertEquals(PEDESTRIAN, props.getPermission()); } + @Test + void footDiscouraged() { + var regular = WayTestData.pedestrianTunnel(); + var props = wps.getDataForWay(regular); + assertEquals(PEDESTRIAN_AND_BICYCLE, props.getPermission()); + assertEquals(1, props.getWalkSafetyFeatures().forward()); + + var discouraged = WayTestData.pedestrianTunnel().addTag("foot", "discouraged"); + var discouragedProps = wps.getDataForWay(discouraged); + assertEquals(PEDESTRIAN_AND_BICYCLE, discouragedProps.getPermission()); + assertEquals(3, discouragedProps.getWalkSafetyFeatures().forward()); + } + + @Test + void bicycleDiscouraged() { + var regular = WayTestData.southeastLaBonitaWay(); + var props = wps.getDataForWay(regular); + assertEquals(ALL, props.getPermission()); + assertEquals(.98, props.getBicycleSafetyFeatures().forward()); + + var discouraged = WayTestData.pedestrianTunnel().addTag("bicycle", "discouraged"); + var discouragedProps = wps.getDataForWay(discouraged); + assertEquals(PEDESTRIAN_AND_BICYCLE, discouragedProps.getPermission()); + assertEquals(3.3, discouragedProps.getBicycleSafetyFeatures().forward(), epsilon); + } + /** * Test that two values are within epsilon of each other. */ From b5839997d29566b56fbe3c73f8ba8dfc0337f622 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 31 Aug 2023 17:00:02 +0200 Subject: [PATCH 033/118] Update test --- .../openstreetmap/tagmapping/DefaultMapperTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java index 4465380806c..f30d07458d0 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java @@ -137,10 +137,10 @@ void bicycleDiscouraged() { assertEquals(ALL, props.getPermission()); assertEquals(.98, props.getBicycleSafetyFeatures().forward()); - var discouraged = WayTestData.pedestrianTunnel().addTag("bicycle", "discouraged"); + var discouraged = WayTestData.southeastLaBonitaWay().addTag("bicycle", "discouraged"); var discouragedProps = wps.getDataForWay(discouraged); - assertEquals(PEDESTRIAN_AND_BICYCLE, discouragedProps.getPermission()); - assertEquals(3.3, discouragedProps.getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(ALL, discouragedProps.getPermission()); + assertEquals(2.94, discouragedProps.getBicycleSafetyFeatures().forward(), epsilon); } /** From d8c6511dce5d23063aa44b6183105d87d1bace0c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Sep 2023 12:39:08 +0200 Subject: [PATCH 034/118] Add Houston to list of deployments [ci skip] --- docs/Deployments.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Deployments.md b/docs/Deployments.md index 12ce8cdefad..f2946a9f0ba 100644 --- a/docs/Deployments.md +++ b/docs/Deployments.md @@ -44,6 +44,7 @@ The following are known deployments of OTP in a government- or agency-sponsored the [OneBusAway native apps](http://onebusaway.org/) in the Puget Sound region. Technical details are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional) . +* [**Ride Metro Houston**](https://planyourtrip.ridemetro.org/) * **Tampa, Florida** Hillsoborough Area Regional Transit uses an OpenTripPlanner server to power the trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in their region. Technical details From 23fe6893eea761005cdfaa9a9c1831c4c0ec0010 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:26:55 +0000 Subject: [PATCH 035/118] Update slf4j.version to v2.0.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f8dca923ca4..46077c58adc 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ 5.5.3 1.4.11 9.7.0 - 2.0.7 + 2.0.9 2.0.14 1.22 3.0.2 From 828cb401764fb41620653f773f1c4df44376552c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:28:24 +0000 Subject: [PATCH 036/118] Update dependency com.graphql-java:graphql-java to v21.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 46077c58adc..aedd1cf4c3f 100644 --- a/pom.xml +++ b/pom.xml @@ -917,7 +917,7 @@ com.graphql-java graphql-java - 21.0 + 21.1 com.graphql-java From e0561b233dbecc38f6aa64174a4a94a048d7d08a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 22:49:57 +0000 Subject: [PATCH 037/118] Update dependency org.onebusaway:onebusaway-gtfs to v1.4.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 46077c58adc..73e68bfe501 100644 --- a/pom.xml +++ b/pom.xml @@ -888,7 +888,7 @@ org.onebusaway onebusaway-gtfs - 1.4.4 + 1.4.5 From d3030454d14da7e73797f3fbfc6b5ee896b717d8 Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 7 Sep 2023 08:09:30 +0300 Subject: [PATCH 038/118] Add multifeed handling to DefaultFareService --- .../ext/fares/impl/DefaultFareService.java | 97 ++++++++++++++++--- 1 file changed, 83 insertions(+), 14 deletions(-) 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 50871355951..258355d1007 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRuleSet; @@ -100,23 +101,91 @@ public ItineraryFares calculateFares(Itinerary itinerary) { // If there are no rides, there's no fare. if (fareLegs.isEmpty()) { + System.out.println("No legs"); return null; } + var fareLegsByFeed = fareLegs + .stream() + .collect(Collectors.groupingBy(leg -> leg.getAgency().getId().getFeedId())); + var fareRulesByTypeAndFeed = fareRulesPerType + .entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + rules -> + rules + .getValue() + .stream() + .collect(Collectors.groupingBy(rule -> rule.getFareAttribute().getId().getFeedId())) + ) + ); ItineraryFares fare = ItineraryFares.empty(); boolean hasFare = false; - for (Map.Entry> kv : fareRulesPerType.entrySet()) { - FareType fareType = kv.getKey(); - Collection fareRules = kv.getValue(); - // Get the currency from the first fareAttribute, assuming that all tickets use the same currency. - if (fareRules.size() > 0) { - Currency currency = Currency.getInstance( - fareRules.iterator().next().getFareAttribute().getCurrencyType() + for (FareType fareType : fareRulesPerType.keySet()) { + List components = new ArrayList<>(); + List fares = new ArrayList<>(); + ItineraryFares currentFare = ItineraryFares.empty(); + boolean legWithoutRulesFound = false; + for (String feedId : fareLegsByFeed.keySet()) { + var fareRules = fareRulesByTypeAndFeed.get(fareType).get(feedId); + System.out.println( + fareLegsByFeed + .get(feedId) + .stream() + .map(r -> r.getFareZones() + ", " + r.getFrom() + " - " + r.getTo()) + .toList() + ); + // Get the currency from the first fareAttribute, assuming that all tickets use the same currency. + if (fareRules != null && fareRules.size() > 0) { + Currency currency = Currency.getInstance( + fareRules.iterator().next().getFareAttribute().getCurrencyType() + ); + hasFare = + populateFare(currentFare, currency, fareType, fareLegsByFeed.get(feedId), fareRules) || + hasFare; // Other feeds might still have fare for some legs + + components.addAll(currentFare.getComponents(fareType)); + 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))) fare.addItineraryProducts( + currentFare.getItineraryProducts() + ); + } else { + legWithoutRulesFound = true; + } + } + + fare.addFareComponent(fareType, components); + + // No fares were found + if (!hasFare) { + System.out.println("No fares found"); + return null; + } + + // Accumulate the final price of the fare or indicate that no final fare could be found + if (legWithoutRulesFound) { + fare.addFare( + fareType, + Money.ofFractionalAmount(fares.get(0).currency(), Float.POSITIVE_INFINITY) + ); + } else { + fare.addFare( + fareType, + fares + .stream() + .reduce( + Money.ofFractionalAmount(fare.getFare(fareType).currency(), 0), + (r1, r2) -> r1.plus(r2) + ) ); - hasFare = populateFare(fare, currency, fareType, fareLegs, fareRules); } } - return hasFare ? fare : null; + return fare; } /** @@ -202,14 +271,14 @@ protected Optional getBestFareAndId( String startZone = firstRide.getFrom().stop.getFirstZoneAsString(); String endZone = null; // stops don't really have an agency id, they have the per-feed default id - String feedId = firstRide.getTrip().getId().getFeedId(); + String feedId = firstRide.getAgency().getId().getFeedId();//getTrip().getId().getFeedId(); ZonedDateTime lastRideStartTime = null; ZonedDateTime lastRideEndTime = null; for (var leg : legs) { - if (!leg.getTrip().getId().getFeedId().equals(feedId)) { - LOG.debug("skipped multi-feed ride sequence {}", legs); - return Optional.empty(); - } +// if (!leg.getTrip().getId().getFeedId().equals(feedId)) { +// LOG.debug("skipped multi-feed ride sequence {}", legs); +// return Optional.empty(); +// } lastRideStartTime = leg.getStartTime(); lastRideEndTime = leg.getEndTime(); endZone = leg.getTo().stop.getFirstZoneAsString(); From 3cd90afc067dc65902e120c2d866ddd142c79e16 Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 7 Sep 2023 09:11:09 +0300 Subject: [PATCH 039/118] Remove calculateFares from HSL fare service --- .../ext/fares/impl/DefaultFareService.java | 31 +++++--------- .../ext/fares/impl/HSLFareServiceImpl.java | 41 ------------------- 2 files changed, 11 insertions(+), 61 deletions(-) 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 258355d1007..f19a24f1cb0 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -61,11 +61,10 @@ class FareSearch { record FareAndId(Money fare, FeedScopedId fareId) {} /** - * This fare service module handles the cases that GTFS handles within a single feed. It cannot - * necessarily handle multi-feed graphs, because a rule-less fare attribute might be applied to - * rides on routes in another feed, for example. For more interesting fare structures like New - * York's MTA, or cities with multiple feeds and inter-feed transfer rules, you get to implement - * your own FareService. See this thread on gtfs-changes explaining the proper interpretation of + * This fare service module handles GTFS fares in multiple feeds separately so that each fare attribute + * is only applicable for legs that operated by an agency within the same feed. Interfeed transfer rules + * are not considered in this fare service and for those situations you get to implement your own Fare Service + * See this thread on gtfs-changes explaining the proper interpretation of * fares.txt: * http://groups.google.com/group/gtfs-changes/browse_thread/thread/8a4a48ae1e742517/4f81b826cb732f3b */ @@ -101,7 +100,6 @@ public ItineraryFares calculateFares(Itinerary itinerary) { // If there are no rides, there's no fare. if (fareLegs.isEmpty()) { - System.out.println("No legs"); return null; } var fareLegsByFeed = fareLegs @@ -130,13 +128,7 @@ public ItineraryFares calculateFares(Itinerary itinerary) { boolean legWithoutRulesFound = false; for (String feedId : fareLegsByFeed.keySet()) { var fareRules = fareRulesByTypeAndFeed.get(fareType).get(feedId); - System.out.println( - fareLegsByFeed - .get(feedId) - .stream() - .map(r -> r.getFareZones() + ", " + r.getFrom() + " - " + r.getTo()) - .toList() - ); + // Get the currency from the first fareAttribute, assuming that all tickets use the same currency. if (fareRules != null && fareRules.size() > 0) { Currency currency = Currency.getInstance( @@ -161,9 +153,8 @@ public ItineraryFares calculateFares(Itinerary itinerary) { fare.addFareComponent(fareType, components); - // No fares were found + // No fares will be discovered after this point if (!hasFare) { - System.out.println("No fares found"); return null; } @@ -271,14 +262,14 @@ protected Optional getBestFareAndId( String startZone = firstRide.getFrom().stop.getFirstZoneAsString(); String endZone = null; // stops don't really have an agency id, they have the per-feed default id - String feedId = firstRide.getAgency().getId().getFeedId();//getTrip().getId().getFeedId(); + String feedId = firstRide.getAgency().getId().getFeedId(); //getTrip().getId().getFeedId(); ZonedDateTime lastRideStartTime = null; ZonedDateTime lastRideEndTime = null; for (var leg : legs) { -// if (!leg.getTrip().getId().getFeedId().equals(feedId)) { -// LOG.debug("skipped multi-feed ride sequence {}", legs); -// return Optional.empty(); -// } + // if (!leg.getTrip().getId().getFeedId().equals(feedId)) { + // LOG.debug("skipped multi-feed ride sequence {}", legs); + // return Optional.empty(); + // } lastRideStartTime = leg.getStartTime(); lastRideEndTime = leg.getEndTime(); endZone = leg.getTo().stop.getFirstZoneAsString(); diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java index 1dd813d13a6..98a6b02ab55 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java @@ -201,45 +201,4 @@ but visit temporarily (maybe 1 stop only) an 'external' zone */ .ofNullable(bestAttribute) .map(attribute -> new FareAndId(finalBestFare, attribute.getId())); } - - @Override - public ItineraryFares calculateFares(Itinerary itinerary) { - // Group legs and fare rules by the feed they are from - var legsByFeed = itinerary - .getLegs() - .stream() - .filter(leg -> leg instanceof ScheduledTransitLeg) - .collect(Collectors.groupingBy(leg -> leg.getAgency().getId().getFeedId())); - var fareRulesByFeed = fareRulesPerType - .get(FareType.regular) - .stream() - .collect(Collectors.groupingBy(flr -> flr.getFareAttribute().getId().getFeedId())); - - // Accumulate fares from different feeds - ItineraryFares res = ItineraryFares.empty(); - List components = new ArrayList<>(); - Money total = Money.euros(0); - boolean hasFare = false; - - for (String feed : legsByFeed.keySet()) { - if (fareRulesByFeed.get(feed) == null || fareRulesByFeed.get(feed).isEmpty()) continue; - ItineraryFares fare = ItineraryFares.empty(); - hasFare = - populateFare( - fare, - Currency.getInstance("EUR"), - FareType.regular, - legsByFeed.get(feed), - fareRulesByFeed.get(feed) - ) || - hasFare; - components.addAll(fare.getComponents(FareType.regular)); - total = total.plus(fare.getFare(FareType.regular)); - } - - res.addFareComponent(FareType.regular, components); - res.addFare(FareType.regular, total); - - return hasFare ? res : null; - } } From aef5b149e4dbe21c46d727f690bd8f3d4f49fa3b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 11 Sep 2023 15:53:18 +0200 Subject: [PATCH 040/118] Make nullable fields in alerts explicit --- .../mapping/StreetNoteMapperTest.java | 8 +++---- .../datafetchers/AlertImpl.java | 22 ++++++++---------- .../model/siri/sx/PtSituationElementType.java | 23 +++++++++++-------- .../api/mapping/AlertMapper.java | 9 ++------ .../routing/alertpatch/TransitAlert.java | 10 ++++---- .../alertpatch/TransitAlertBuilder.java | 4 ++-- .../alert/AlertsUpdateHandlerTest.java | 11 +++++---- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java index 976db86fbe5..9b00aeac4eb 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.gtfsgraphqlapi.mapping; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Date; import java.util.Locale; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.street.model.note.StreetNote; @@ -26,8 +26,8 @@ void mapRegularAlert() { var note = note(); TransitAlert alert = StreetNoteMapper.mapStreetNoteToAlert(note); assertEquals(TEST_STREET_NOTE_HEADER, alert.headerText().toString(Locale.ROOT)); - assertEquals(TEST_STREET_NOTE_DESCRIPTION, alert.descriptionText().toString(Locale.ROOT)); - assertEquals(TEST_STREET_NOTE_URL, alert.url().toString(Locale.ROOT)); + assertEquals(TEST_STREET_NOTE_DESCRIPTION, alert.descriptionText().get().toString(Locale.ROOT)); + assertEquals(TEST_STREET_NOTE_URL, alert.url().get().toString(Locale.ROOT)); assertEquals(START_INSTANCE, alert.getEffectiveStartDate()); assertEquals(END_INSTANCE, alert.getEffectiveEndDate()); } @@ -37,7 +37,7 @@ void mapNullUrl() { var note = note(); note.url = null; TransitAlert alert = StreetNoteMapper.mapStreetNoteToAlert(note); - assertNull(alert.url()); + assertEquals(Optional.empty(), alert.url()); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java index 5708a151c31..db863ff42a9 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java @@ -63,15 +63,16 @@ public DataFetcher alertCause() { @Override public DataFetcher alertDescriptionText() { return environment -> - getSource(environment).descriptionText().toString(environment.getLocale()); + getSource(environment) + .descriptionText() + .map(t -> t.toString(environment.getLocale())) + .orElse(null); } @Override public DataFetcher>> alertDescriptionTextTranslations() { - return environment -> { - var text = getSource(environment).descriptionText(); - return getTranslations(text); - }; + return environment -> + getSource(environment).descriptionText().map(this::getTranslations).orElse(null); } @Override @@ -115,18 +116,13 @@ public DataFetcher alertSeverityLevel() { @Override public DataFetcher alertUrl() { - return environment -> { - var alertUrl = getSource(environment).url(); - return alertUrl == null ? null : alertUrl.toString(environment.getLocale()); - }; + return environment -> + getSource(environment).url().map(u -> u.toString(environment.getLocale())).orElse(null); } @Override public DataFetcher>> alertUrlTranslations() { - return environment -> { - var url = getSource(environment).url(); - return getTranslations(url); - }; + return environment -> getSource(environment).url().map(this::getTranslations).orElse(List.of()); } @Override diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java index f9239d16c37..927bef6e3c7 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java @@ -193,16 +193,19 @@ public static GraphQLObjectType create( .name("description") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(multilingualStringType)))) .description("Description of situation in all different translations available") - .dataFetcher(environment -> { - I18NString descriptionText = environment.getSource().descriptionText(); - if (descriptionText instanceof TranslatedString translatedString) { - return translatedString.getTranslations(); - } else if (descriptionText != null) { - return List.of(new AbstractMap.SimpleEntry<>(null, descriptionText.toString())); - } else { - return emptyList(); - } - }) + .dataFetcher(environment -> + environment + .getSource() + .descriptionText() + .map(descriptionText -> { + if (descriptionText instanceof TranslatedString translatedString) { + return translatedString.getTranslations(); + } else { + return List.of(new AbstractMap.SimpleEntry<>(null, descriptionText.toString())); + } + }) + .orElse(emptyList()) + ) .build() ) .field( diff --git a/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java b/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java index 24a1aeb07c5..c2b6a60a329 100644 --- a/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java @@ -34,13 +34,8 @@ ApiAlert mapToApi(TransitAlert domain) { api.alertHeaderText = domain.headerText().toString(locale); } - if (domain.descriptionText() != null) { - api.alertDescriptionText = domain.descriptionText().toString(locale); - } - - if (domain.url() != null) { - api.alertUrl = domain.url().toString(locale); - } + api.alertDescriptionText = domain.descriptionText().map(t -> t.toString(locale)).orElse(null); + api.alertUrl = domain.url().map(u -> u.toString(locale)).orElse(null); api.effectiveStartDate = ofNullableInstant(domain.getEffectiveStartDate()); api.effectiveEndDate = ofNullableInstant(domain.getEffectiveEndDate()); diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java index 56bd353e25a..3910894eef2 100644 --- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java +++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java @@ -6,6 +6,7 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -65,8 +66,8 @@ public I18NString headerText() { return headerText; } - public I18NString descriptionText() { - return descriptionText; + public Optional descriptionText() { + return Optional.ofNullable(descriptionText); } public I18NString detailText() { @@ -77,9 +78,8 @@ public I18NString adviceText() { return adviceText; } - @Nullable - public I18NString url() { - return url; + public Optional url() { + return Optional.ofNullable(url); } public List siriUrls() { diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java index 4d9378cbaa6..af0c2920fdb 100644 --- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java +++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java @@ -37,10 +37,10 @@ public class TransitAlertBuilder extends AbstractEntityBuilder> translations = - ((TranslatedString) transitAlert.url()).getTranslations(); + ((TranslatedString) transitAlert.url().get()).getTranslations(); assertEquals(2, translations.size()); assertEquals("en", translations.get(0).getKey()); assertEquals("https://www.opentripplanner.org/", translations.get(0).getValue()); @@ -196,7 +197,7 @@ public void testWithoutDescriptionTranslations() { ) .build(); TransitAlert transitAlert = processOneAlert(alert); - assertEquals("Description", transitAlert.descriptionText().toString()); + assertEquals("Description", transitAlert.descriptionText().get().toString()); } @Test @@ -221,7 +222,7 @@ public void testWithDescriptionTranslations() { TransitAlert transitAlert = processOneAlert(alert); List> translations = - ((TranslatedString) transitAlert.descriptionText()).getTranslations(); + ((TranslatedString) transitAlert.descriptionText().get()).getTranslations(); assertEquals(2, translations.size()); assertEquals("en", translations.get(0).getKey()); assertEquals("Description", translations.get(0).getValue()); From 2bc1138227bd446d62f110cb89251a5ded420efa Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 11 Sep 2023 16:16:05 +0200 Subject: [PATCH 041/118] Add test for nullable fields --- .../GraphQLIntegrationTest.java | 32 +++++++++++++++---- .../gtfsgraphqlapi/expectations/alerts.json | 18 +++++++++++ .../gtfsgraphqlapi/queries/alerts.graphql | 8 +++++ .../datafetchers/AlertImpl.java | 10 ++++-- 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json create mode 100644 src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index 1d358e17a71..c727c6cd84f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -62,6 +62,8 @@ import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; +import org.opentripplanner.routing.impl.TransitAlertServiceImpl; +import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; @@ -128,7 +130,7 @@ static void setup() { TransitModelForTest .route(m.name()) .withMode(m) - .withLongName(new NonLocalizedString("Long name for %s".formatted(m))) + .withLongName(I18NString.of("Long name for %s".formatted(m))) .build() ) .toList(); @@ -175,27 +177,45 @@ static void setup() { railLeg.withAccessibilityScore(.3f); + var entitySelector = new EntitySelector.Stop(A.stop.getId()); var alert = TransitAlert .of(id("an-alert")) - .withHeaderText(new NonLocalizedString("A header")) - .withDescriptionText(new NonLocalizedString("A description")) - .withUrl(new NonLocalizedString("https://example.com")) + .withHeaderText(I18NString.of("A header")) + .withDescriptionText(I18NString.of("A description")) + .withUrl(I18NString.of("https://example.com")) .withCause(AlertCause.MAINTENANCE) .withEffect(AlertEffect.REDUCED_SERVICE) .withSeverity(AlertSeverity.VERY_SEVERE) - .addEntity(new EntitySelector.Stop(A.stop.getId())) + .addEntity(entitySelector) .addTimePeriod( new TimePeriod(ALERT_START_TIME.getEpochSecond(), ALERT_END_TIME.getEpochSecond()) ) .build(); + + var alertWithNulls = TransitAlert + .of(id("nulls")) + .withHeaderText(I18NString.of("Just a header")) + .addEntity(entitySelector) + .build(); + railLeg.addAlert(alert); var transitService = new DefaultTransitService(transitModel) { + private final TransitAlertService alertService = new TransitAlertServiceImpl(transitModel); + @Override public List getModesOfStopLocation(StopLocation stop) { return List.of(BUS, FERRY); } + + @Override + public TransitAlertService getTransitAlertService() { + return alertService; + } }; + + transitService.getTransitAlertService().setAlerts(List.of(alert, alertWithNulls)); + context = new GraphQLRequestContext( new TestRoutingService(List.of(i1)), @@ -246,7 +266,7 @@ void graphQL(Path path) throws IOException { private static WalkStepBuilder walkStep(String name) { return WalkStep .builder() - .withDirectionText(new NonLocalizedString(name)) + .withDirectionText(I18NString.of(name)) .withStartLocation(WgsCoordinate.GREENWICH) .withAngle(10); } diff --git a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json new file mode 100644 index 00000000000..08ba61fc5f5 --- /dev/null +++ b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json @@ -0,0 +1,18 @@ +{ + "data" : { + "alerts" : [ + { + "id" : "QWxlcnQ6Rjphbi1hbGVydA", + "alertHeaderText" : "A header", + "alertDescriptionText" : "A description", + "alertUrl" : "https://example.com" + }, + { + "id" : "QWxlcnQ6RjpudWxscw", + "alertHeaderText" : "Just a header", + "alertDescriptionText" : "Just a header", + "alertUrl" : null + } + ] + } +} \ No newline at end of file diff --git a/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql new file mode 100644 index 00000000000..311b8923efe --- /dev/null +++ b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql @@ -0,0 +1,8 @@ +{ + alerts { + id + alertHeaderText + alertDescriptionText + alertUrl + } +} \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java index db863ff42a9..0d20b68956b 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import org.opentripplanner.ext.gtfsgraphqlapi.GraphQLRequestContext; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers; @@ -62,11 +63,14 @@ public DataFetcher alertCause() { @Override public DataFetcher alertDescriptionText() { - return environment -> - getSource(environment) + return environment -> { + var alert = getSource(environment); + return alert .descriptionText() + .or(() -> Optional.ofNullable(alert.headerText())) .map(t -> t.toString(environment.getLocale())) - .orElse(null); + .orElse(""); + }; } @Override From 56e938edcadd8804dc57944faa6a44c2c18eecd9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 11 Sep 2023 17:17:00 +0200 Subject: [PATCH 042/118] Exclude OBA's slf4j-simple --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 4f297ea84d2..00691ce767b 100644 --- a/pom.xml +++ b/pom.xml @@ -889,6 +889,12 @@ org.onebusaway onebusaway-gtfs 1.4.5 + + + org.slf4j + slf4j-simple + + From 1938f63e8d2cd20de8211edf5a416485e3232e4b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 11 Sep 2023 17:17:00 +0200 Subject: [PATCH 043/118] Exclude OBA's slf4j-simple --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 4f297ea84d2..00691ce767b 100644 --- a/pom.xml +++ b/pom.xml @@ -889,6 +889,12 @@ org.onebusaway onebusaway-gtfs 1.4.5 + + + org.slf4j + slf4j-simple + + From 5d5cf7ffb220d086a39f0140350bb19a9abcb5d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 20:30:33 +0000 Subject: [PATCH 044/118] Update micrometer.version to v1.11.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f297ea84d2..040aa878a05 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 2.15.2 3.1.3 5.10.0 - 1.11.3 + 1.11.4 5.5.3 1.4.11 9.7.0 From 57cbe99d01042ce98b613910b8b756f57a9b52c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 22:13:56 +0000 Subject: [PATCH 045/118] Update dependency org.apache.commons:commons-compress to v1.24.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f297ea84d2..9e066c1e988 100644 --- a/pom.xml +++ b/pom.xml @@ -985,7 +985,7 @@ org.apache.commons commons-compress - 1.23.0 + 1.24.0 test From 4845e9ae308c601aed0308f064a1f8443cc998e6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 12 Sep 2023 08:59:13 +0200 Subject: [PATCH 046/118] Update src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java Co-authored-by: Joel Lappalainen --- .../org/opentripplanner/openstreetmap/model/OSMWithTags.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index e0dc829dd4c..d0425fb69a8 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -197,7 +197,7 @@ public boolean isTag(String tag, String value) { } /** - * Takes a tag key and checks if the value is any of those in {@code onOfTags}. + * Takes a tag key and checks if the value is any of those in {@code oneOfTags}. */ public boolean isOneOfTags(String key, Set oneOfTags) { return oneOfTags.stream().anyMatch(value -> isTag(key, value)); From 13d8663b08f16fe8d3a2aba8a6883a57b7e8ce4b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 12 Sep 2023 11:40:22 +0200 Subject: [PATCH 047/118] Only check if it's a railway platform --- .../graph_builder/module/osm/OsmDatabase.java | 2 +- .../opentripplanner/openstreetmap/model/OSMWithTags.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index c41949c9813..47a9bc34e23 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -782,7 +782,7 @@ private void processMultipolygonRelations() { way.addTag(tag, relation.getTag(tag)); } } - if (relation.isPlatform() && !way.hasTag("railway")) { + if (relation.isRailwayPlatform() && !way.hasTag("railway")) { way.addTag("railway", "platform"); } if (relation.isPlatform() && !way.hasTag("public_transport")) { diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index d0425fb69a8..7b10e580353 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -428,7 +428,11 @@ public boolean isBoardingLocation() { } public boolean isPlatform() { - return isTag("public_transport", "platform") || isTag("railway", "platform"); + return isTag("public_transport", "platform") || isRailwayPlatform(); + } + + public boolean isRailwayPlatform() { + return isTag("railway", "platform"); } /** From bf15416cf5fb07f7e2a6bf7d59d8adc2d941f2fa Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Tue, 12 Sep 2023 13:15:55 +0300 Subject: [PATCH 048/118] Add multifeed tests for DefaultFareService --- .../fares/impl/DefaultFareServiceTest.java | 50 +++++++++++++++++++ .../ext/fares/impl/FareModelForTest.java | 38 ++++++++++++++ .../ext/fares/impl/DefaultFareService.java | 8 +-- .../model/_data/TransitModelForTest.java | 10 ++++ 4 files changed, 102 insertions(+), 4 deletions(-) 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 d93acc8de74..db80c8976dc 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 @@ -8,6 +8,10 @@ import static org.opentripplanner.ext.fares.impl.FareModelForTest.CITY_CENTER_A_STOP; import static org.opentripplanner.ext.fares.impl.FareModelForTest.CITY_CENTER_B_STOP; import static org.opentripplanner.ext.fares.impl.FareModelForTest.INSIDE_CITY_CENTER_SET; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_ATTRIBUTE; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_ROUTE; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_SET; +import static org.opentripplanner.ext.fares.impl.FareModelForTest.OTHER_FEED_STOP; import static org.opentripplanner.ext.fares.impl.FareModelForTest.SUBURB_STOP; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; @@ -98,4 +102,50 @@ void unknownLeg() { var firstBusLeg = itin.firstTransitLeg().get(); assertEquals(List.of(firstBusLeg), component.legs()); } + + @Test + void multipleFeeds() { + var service = new DefaultFareService(); + service.addFareRules(FareType.regular, List.of(AIRPORT_TO_CITY_CENTER_SET, OTHER_FEED_SET)); + var itin = newItinerary(Place.forStop(AIRPORT_STOP)) + .bus(1, T11_00, T11_05, Place.forStop(CITY_CENTER_A_STOP)) + .walk(10, Place.forStop(OTHER_FEED_STOP)) + .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP)) + .build(); + var result = service + .calculateFares(itin) + .getComponents(FareType.regular) + .stream() + .map(r -> r.fareId()) + .toList(); + + assertEquals( + List.of(AIRPORT_TO_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()), + result + ); + } + + @Test + void multipleFeedsWithTransfersWithinFeed() { + var service = new DefaultFareService(); + service.addFareRules(FareType.regular, List.of(INSIDE_CITY_CENTER_SET, OTHER_FEED_SET)); + var itin = newItinerary(Place.forStop(OTHER_FEED_STOP)) + .bus(OTHER_FEED_ROUTE, 2, T11_00, T11_05, Place.forStop(OTHER_FEED_STOP)) + .walk(10, Place.forStop(CITY_CENTER_A_STOP)) + .bus(1, T11_00, T11_05, Place.forStop(CITY_CENTER_A_STOP)) + .walk(10, Place.forStop(OTHER_FEED_STOP)) + .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP)) + .build(); + var result = service + .calculateFares(itin) + .getComponents(FareType.regular) + .stream() + .map(r -> r.fareId()) + .toList(); + + assertEquals( + List.of(INSIDE_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()), + result + ); + } } diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java index 74c9baa5422..5c8b52548e7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/FareModelForTest.java @@ -1,11 +1,17 @@ package org.opentripplanner.ext.fares.impl; +import static org.opentripplanner.transit.model._data.TransitModelForTest.FEED_ID; +import static org.opentripplanner.transit.model._data.TransitModelForTest.OTHER_AGENCY; +import static org.opentripplanner.transit.model._data.TransitModelForTest.OTHER_FEED_AGENCY; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.site.FareZone; import org.opentripplanner.transit.model.site.RegularStop; @@ -14,6 +20,10 @@ public class FareModelForTest { public static final FareZone AIRPORT_ZONE = FareZone.of(id("airport-zone")).build(); public static final FareZone CITY_CENTER_ZONE = FareZone.of(id("city-center")).build(); + public static final FareZone OTHER_FEED_ZONE = FareZone + .of(FeedScopedId.ofNullable("F2", "other-feed-zone")) + .build(); + static RegularStop AIRPORT_STOP = RegularStop .of(id("airport")) .withCoordinate(new WgsCoordinate(1, 1)) @@ -39,16 +49,33 @@ public class FareModelForTest { .withName(new NonLocalizedString("Suburb")) .build(); + static RegularStop OTHER_FEED_STOP = RegularStop + .of(FeedScopedId.ofNullable("F2", "other-feed-stop")) + .withCoordinate(new WgsCoordinate(1, 5)) + .withName(new NonLocalizedString("Other feed stop")) + .addFareZones(OTHER_FEED_ZONE) + .build(); static FareAttribute TEN_DOLLARS = FareAttribute .of(id("airport-to-city-center")) .setCurrencyType("USD") .setPrice(10) .setTransfers(0) .build(); + + static FareAttribute OTHER_FEED_ATTRIBUTE = FareAttribute + .of(FeedScopedId.ofNullable("F2", "other-feed-attribute")) + .setCurrencyType("USD") + .setPrice(10) + .setTransfers(1) + .setAgency(OTHER_FEED_AGENCY.getId()) + .build(); + // Fare rule sets static FareRuleSet AIRPORT_TO_CITY_CENTER_SET = new FareRuleSet(TEN_DOLLARS); static FareRuleSet INSIDE_CITY_CENTER_SET = new FareRuleSet(TEN_DOLLARS); + static FareRuleSet OTHER_FEED_SET = new FareRuleSet(OTHER_FEED_ATTRIBUTE); + static { AIRPORT_TO_CITY_CENTER_SET.addOriginDestination( AIRPORT_ZONE.getId().getId(), @@ -58,5 +85,16 @@ public class FareModelForTest { CITY_CENTER_ZONE.getId().getId(), CITY_CENTER_ZONE.getId().getId() ); + OTHER_FEED_SET.addOriginDestination( + OTHER_FEED_ZONE.getId().getId(), + OTHER_FEED_ZONE.getId().getId() + ); } + + static Route OTHER_FEED_ROUTE = Route + .of(new FeedScopedId("F2", "other-feed-route")) + .withAgency(OTHER_FEED_AGENCY) + .withLongName(new NonLocalizedString("other-feed-route")) + .withMode(TransitMode.BUS) + .build(); } 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 f19a24f1cb0..828e6d01b51 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -266,10 +266,10 @@ protected Optional getBestFareAndId( ZonedDateTime lastRideStartTime = null; ZonedDateTime lastRideEndTime = null; for (var leg : legs) { - // if (!leg.getTrip().getId().getFeedId().equals(feedId)) { - // LOG.debug("skipped multi-feed ride sequence {}", legs); - // return Optional.empty(); - // } + if (!leg.getAgency().getId().getFeedId().equals(feedId)) { + LOG.debug("skipped multi-feed ride sequence {}", legs); + return Optional.empty(); + } lastRideStartTime = leg.getStartTime(); lastRideEndTime = leg.getEndTime(); endZone = leg.getTo().stop.getFirstZoneAsString(); diff --git a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java index dd3ac1924f2..c4703c216e4 100644 --- a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java +++ b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java @@ -53,6 +53,12 @@ public class TransitModelForTest { .withTimezone(TIME_ZONE_ID) .withUrl("https://www.otheragency.com") .build(); + public static final Agency OTHER_FEED_AGENCY = Agency + .of(FeedScopedId.ofNullable("F2", "other.feed-agency")) + .withName("Other feed agency") + .withTimezone(TIME_ZONE_ID) + .withUrl("https:/www.otherfeedagency.com") + .build(); public static FeedScopedId id(String id) { return new FeedScopedId(FEED_ID, id); @@ -88,6 +94,10 @@ public static TripBuilder trip(String id) { return Trip.of(id(id)).withRoute(route("R" + id).build()); } + public static TripBuilder trip(String feedId, String tripId) { + return Trip.of(FeedScopedId.ofNullable(feedId, tripId)).withRoute(route("R" + tripId).build()); + } + public static RegularStop stopForTest( String idAndName, Accessibility wheelchair, From bd40f501d778983d8f1e13148228874772ef7f59 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 12 Sep 2023 14:47:42 +0200 Subject: [PATCH 049/118] Make alertHeader also return an Optional --- .../GraphQLIntegrationTest.java | 33 +++++++++++++++---- .../mapping/StreetNoteMapperTest.java | 2 +- .../gtfsgraphqlapi/expectations/alerts.json | 26 +++++++++++++-- .../gtfsgraphqlapi/queries/alerts.graphql | 10 ++++++ .../datafetchers/AlertImpl.java | 26 ++++++++------- .../model/siri/sx/PtSituationElementType.java | 23 +++++++------ .../api/mapping/AlertMapper.java | 5 +-- .../routing/alertpatch/TransitAlert.java | 4 +-- .../alertpatch/TransitAlertBuilder.java | 2 +- .../alert/AlertsUpdateHandlerTest.java | 4 +-- 10 files changed, 94 insertions(+), 41 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index c727c6cd84f..e7b6deebbf4 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -31,6 +31,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.stream.Stream; import javax.annotation.Nonnull; import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; import org.junit.jupiter.api.BeforeAll; @@ -38,6 +39,7 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.fares.FaresToItineraryMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; +import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.NonLocalizedString; @@ -72,6 +74,7 @@ import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.AbstractBuilder; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; @@ -192,12 +195,6 @@ static void setup() { ) .build(); - var alertWithNulls = TransitAlert - .of(id("nulls")) - .withHeaderText(I18NString.of("Just a header")) - .addEntity(entitySelector) - .build(); - railLeg.addAlert(alert); var transitService = new DefaultTransitService(transitModel) { @@ -214,7 +211,8 @@ public TransitAlertService getTransitAlertService() { } }; - transitService.getTransitAlertService().setAlerts(List.of(alert, alertWithNulls)); + var alerts = ListUtils.combine(List.of(alert), getTransitAlert(entitySelector)); + transitService.getTransitAlertService().setAlerts(alerts); context = new GraphQLRequestContext( @@ -262,6 +260,27 @@ void graphQL(Path path) throws IOException { assertEqualJson(expectedJson, actualJson); } + @Nonnull + private static List getTransitAlert(EntitySelector.Stop entitySelector) { + var alertWithoutDescription = TransitAlert + .of(id("no-description")) + .withHeaderText(I18NString.of("Just a header")) + .addEntity(entitySelector); + + var alertWithoutHeader = TransitAlert + .of(id("no-header")) + .withDescriptionText(I18NString.of("Just a description")) + .addEntity(entitySelector); + var alertWithNothing = TransitAlert + .of(id("neither-header-nor-description")) + .addEntity(entitySelector); + + return Stream + .of(alertWithoutDescription, alertWithoutHeader, alertWithNothing) + .map(AbstractBuilder::build) + .toList(); + } + @Nonnull private static WalkStepBuilder walkStep(String name) { return WalkStep diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java index 9b00aeac4eb..5c7cd8be008 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/StreetNoteMapperTest.java @@ -25,7 +25,7 @@ class StreetNoteMapperTest { void mapRegularAlert() { var note = note(); TransitAlert alert = StreetNoteMapper.mapStreetNoteToAlert(note); - assertEquals(TEST_STREET_NOTE_HEADER, alert.headerText().toString(Locale.ROOT)); + assertEquals(TEST_STREET_NOTE_HEADER, alert.headerText().get().toString(Locale.ROOT)); assertEquals(TEST_STREET_NOTE_DESCRIPTION, alert.descriptionText().get().toString(Locale.ROOT)); assertEquals(TEST_STREET_NOTE_URL, alert.url().get().toString(Locale.ROOT)); assertEquals(START_INSTANCE, alert.getEffectiveStartDate()); diff --git a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json index 08ba61fc5f5..76851f06fa4 100644 --- a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json +++ b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json @@ -1,17 +1,37 @@ { "data" : { "alerts" : [ + { + "id" : "QWxlcnQ6RjpuZWl0aGVyLWhlYWRlci1ub3ItZGVzY3JpcHRpb24", + "alertHeaderText" : "", + "alertDescriptionText" : "", + "alertUrl" : null, + "alertDescriptionTextTranslations" : [ ], + "alertHeaderTextTranslations" : [ ] + }, + { + "id" : "QWxlcnQ6Rjpuby1oZWFkZXI", + "alertHeaderText" : "Just a description", + "alertDescriptionText" : "Just a description", + "alertUrl" : null, + "alertDescriptionTextTranslations" : [ ], + "alertHeaderTextTranslations" : [ ] + }, { "id" : "QWxlcnQ6Rjphbi1hbGVydA", "alertHeaderText" : "A header", "alertDescriptionText" : "A description", - "alertUrl" : "https://example.com" + "alertUrl" : "https://example.com", + "alertDescriptionTextTranslations" : [ ], + "alertHeaderTextTranslations" : [ ] }, { - "id" : "QWxlcnQ6RjpudWxscw", + "id" : "QWxlcnQ6Rjpuby1kZXNjcmlwdGlvbg", "alertHeaderText" : "Just a header", "alertDescriptionText" : "Just a header", - "alertUrl" : null + "alertUrl" : null, + "alertDescriptionTextTranslations" : [ ], + "alertHeaderTextTranslations" : [ ] } ] } diff --git a/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql index 311b8923efe..923d9f027cb 100644 --- a/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql +++ b/src/ext-test/resources/gtfsgraphqlapi/queries/alerts.graphql @@ -4,5 +4,15 @@ alertHeaderText alertDescriptionText alertUrl + # these translations are a bit questionable, the above fields are already translated into the + # language selected in the request + alertDescriptionTextTranslations { + language + text + } + alertHeaderTextTranslations { + text + language + } } } \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java index 0d20b68956b..b9e7c650b93 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/AlertImpl.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; import org.opentripplanner.ext.gtfsgraphqlapi.GraphQLRequestContext; import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers; @@ -24,7 +23,6 @@ import org.opentripplanner.ext.gtfsgraphqlapi.model.StopOnRouteModel; import org.opentripplanner.ext.gtfsgraphqlapi.model.StopOnTripModel; import org.opentripplanner.ext.gtfsgraphqlapi.model.UnknownModel; -import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.TranslatedString; import org.opentripplanner.routing.alertpatch.EntitySelector; @@ -41,6 +39,8 @@ public class AlertImpl implements GraphQLDataFetchers.GraphQLAlert { + private static final String FALLBACK_EMPTY_STRING = ""; + @Override public DataFetcher agency() { return environment -> @@ -67,16 +67,16 @@ public DataFetcher alertDescriptionText() { var alert = getSource(environment); return alert .descriptionText() - .or(() -> Optional.ofNullable(alert.headerText())) + .or(alert::headerText) .map(t -> t.toString(environment.getLocale())) - .orElse(""); + .orElse(FALLBACK_EMPTY_STRING); }; } @Override public DataFetcher>> alertDescriptionTextTranslations() { return environment -> - getSource(environment).descriptionText().map(this::getTranslations).orElse(null); + getSource(environment).descriptionText().map(this::getTranslations).orElse(List.of()); } @Override @@ -101,16 +101,20 @@ public DataFetcher alertHash() { @Override public DataFetcher alertHeaderText() { - return environment -> - GraphQLUtils.getTranslation(getSource(environment).headerText(), environment); + return environment -> { + var alert = getSource(environment); + return alert + .headerText() + .or(alert::descriptionText) + .map(h -> h.toString(environment.getLocale())) + .orElse(FALLBACK_EMPTY_STRING); + }; } @Override public DataFetcher>> alertHeaderTextTranslations() { - return environment -> { - var text = getSource(environment).headerText(); - return getTranslations(text); - }; + return environment -> + getSource(environment).headerText().map(this::getTranslations).orElse(List.of()); } @Override diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java index 927bef6e3c7..7364f5f40f6 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java @@ -175,16 +175,19 @@ public static GraphQLObjectType create( .name("summary") .type(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(multilingualStringType)))) .description("Summary of situation in all different translations available") - .dataFetcher(environment -> { - I18NString headerText = environment.getSource().headerText(); - if (headerText instanceof TranslatedString translatedString) { - return translatedString.getTranslations(); - } else if (headerText != null) { - return List.of(new AbstractMap.SimpleEntry<>(null, headerText.toString())); - } else { - return emptyList(); - } - }) + .dataFetcher(environment -> + environment + .getSource() + .headerText() + .map(headerText -> { + if (headerText instanceof TranslatedString translatedString) { + return translatedString.getTranslations(); + } else { + return List.of(new AbstractMap.SimpleEntry<>(null, headerText.toString())); + } + }) + .orElse(null) + ) .build() ) .field( diff --git a/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java b/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java index c2b6a60a329..e01961a5dca 100644 --- a/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java +++ b/src/main/java/org/opentripplanner/api/mapping/AlertMapper.java @@ -30,10 +30,7 @@ public List mapToApi(Collection alerts) { ApiAlert mapToApi(TransitAlert domain) { ApiAlert api = new ApiAlert(); - if (domain.headerText() != null) { - api.alertHeaderText = domain.headerText().toString(locale); - } - + api.alertHeaderText = domain.headerText().map(h -> h.toString(locale)).orElse(null); api.alertDescriptionText = domain.descriptionText().map(t -> t.toString(locale)).orElse(null); api.alertUrl = domain.url().map(u -> u.toString(locale)).orElse(null); diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java index 3910894eef2..98127555e56 100644 --- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java +++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlert.java @@ -62,8 +62,8 @@ public static TransitAlertBuilder of(FeedScopedId id) { return new TransitAlertBuilder(id); } - public I18NString headerText() { - return headerText; + public Optional headerText() { + return Optional.ofNullable(headerText); } public Optional descriptionText() { diff --git a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java index af0c2920fdb..fda6b571585 100644 --- a/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java +++ b/src/main/java/org/opentripplanner/routing/alertpatch/TransitAlertBuilder.java @@ -36,7 +36,7 @@ public class TransitAlertBuilder extends AbstractEntityBuilder> translations = - ((TranslatedString) transitAlert.headerText()).getTranslations(); + ((TranslatedString) transitAlert.headerText().get()).getTranslations(); assertEquals(2, translations.size()); assertEquals("en", translations.get(0).getKey()); assertEquals("Title", translations.get(0).getValue()); From 92279b6acce5e1206a6cadff98a8166461ff0be7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 12 Sep 2023 15:25:22 +0200 Subject: [PATCH 050/118] Use better method names --- .../ext/gtfsgraphqlapi/GraphQLIntegrationTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index e7b6deebbf4..80da83267c2 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -240,10 +240,10 @@ void graphQL(Path path) throws IOException { Locale.ENGLISH, context ); - var actualJson = extracted(response); + var actualJson = responseBody(response); assertEquals(200, response.getStatus()); - Path expectationFile = getPath(path); + Path expectationFile = getExpectation(path); if (!expectationFile.toFile().exists()) { Files.writeString( @@ -307,7 +307,7 @@ private static FareProduct fareProduct(String name) { * subdirectories are expected to be in the same directory. */ @Nonnull - private static Path getPath(Path path) { + private static Path getExpectation(Path path) { return path .getParent() .getParent() @@ -315,7 +315,7 @@ private static Path getPath(Path path) { .resolve(path.getFileName().toString().replace(".graphql", ".json")); } - private static String extracted(Response response) { + private static String responseBody(Response response) { if (response instanceof OutboundJaxrsResponse outbound) { return (String) outbound.getContext().getEntity(); } From 00ce28383345c43ed0de7b6398ea8c8922353a04 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 12 Sep 2023 17:32:25 +0200 Subject: [PATCH 051/118] Move return statements to their own line --- .../opentripplanner/openstreetmap/model/OSMWithTags.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index e432878b2f4..60f026f2677 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -70,9 +70,13 @@ public void addTag(OSMTag tag) { * Adds a tag. */ public OSMWithTags addTag(String key, String value) { - if (key == null || value == null) return this; + if (key == null || value == null) { + return this; + } - if (tags == null) tags = new HashMap<>(); + if (tags == null) { + tags = new HashMap<>(); + } tags.put(key.toLowerCase(), value); return this; From 6fedd5dc72c914e958a44cfe785488b83d83aeff Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:10:55 +0300 Subject: [PATCH 052/118] Rename transit vs walking filter --- .../RemoveTransitIfWalkingIsBetter.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java new file mode 100644 index 00000000000..0199f0e3b66 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java @@ -0,0 +1,56 @@ +package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; + +import java.util.Comparator; +import java.util.List; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; + +/** + * Filter itineraries which contain more walking than a pure walk itinerary + */ +public class RemoveTransitIfWalkingIsBetterFilter implements ItineraryDeletionFlagger { + + /** + * Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain}, + * to know which filters removed + */ + public static final String TAG = "transit-vs-walk-filter"; + + @Override + public String name() { + return TAG; + } + + @Override + public List flagForRemoval(List itineraries) { + // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary + // This never makes sense + + OptionalInt minWalkCost = itineraries + .stream() + .filter(Itinerary::isWalkingAllTheWay) + .mapToInt(Itinerary::getGeneralizedCost) + .min(); + + if (minWalkCost.isEmpty()) { + return List.of(); + } + + var limit = minWalkCost.getAsInt(); + + return itineraries + .stream() + .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) + .collect(Collectors.toList()); + } + + @Override + public boolean skipAlreadyFlaggedItineraries() { + return false; + } +} From 07683a99b714cb76a995e4df2101a24ceca2ac33 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:11:59 +0300 Subject: [PATCH 053/118] Update documentation to reflect changes in the filter --- .../RemoveTransitIfStreetOnlyIsBetterFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java index 60c6f8d6225..08307c16ade 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfStreetOnlyIsBetterFilter.java @@ -36,7 +36,7 @@ public String name() { @Override public List flagForRemoval(List itineraries) { - // Find the best walk-all-the-way option + // Find the best street-all-the-way option OptionalInt minStreetCost = itineraries .stream() .filter(Itinerary::isOnStreetAllTheWay) From 4985bd64c9ebece2a071a35915b001731f1a6533 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:15:07 +0300 Subject: [PATCH 054/118] update docs Co-authored-by: Thomas Gran --- .../algorithm/filterchain/ItineraryListFilterChainBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 4394b9430e8..093d071f104 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -205,7 +205,7 @@ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOn * An itinerary which has transit legs and more walking than the plain walk itinerary are silly. * This filter removes such itineraries. *

- * This filter only have an effect, if an on-street-all-the-way(WALK) itinerary exist. + * This filter only have an effect, if an walk-all-the-way itinerary exist. */ public ItineraryListFilterChainBuilder withRemoveTransitWithMoreWalking(boolean value) { this.removeTransitWithMoreWalking = value; From c42861c86bdb55ec63ec6b1f683fb16776aa2c6c Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:15:27 +0300 Subject: [PATCH 055/118] Use new class name Co-authored-by: Thomas Gran --- .../algorithm/filterchain/ItineraryListFilterChainBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 093d071f104..2742879742d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -207,7 +207,7 @@ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOn *

* This filter only have an effect, if an walk-all-the-way itinerary exist. */ - public ItineraryListFilterChainBuilder withRemoveTransitWithMoreWalking(boolean value) { + public ItineraryListFilterChainBuilder withRemoveTransitIfWalkingIsBetter(boolean value) { this.removeTransitWithMoreWalking = value; return this; } From db41b92473439899d49c859c59789f9ce1e07824 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:15:42 +0300 Subject: [PATCH 056/118] Use new class name Co-authored-by: Thomas Gran --- .../algorithm/filterchain/ItineraryListFilterChainBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 2742879742d..d48aac5e5d0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -208,7 +208,7 @@ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOn * This filter only have an effect, if an walk-all-the-way itinerary exist. */ public ItineraryListFilterChainBuilder withRemoveTransitIfWalkingIsBetter(boolean value) { - this.removeTransitWithMoreWalking = value; + this.removeTransitIfWalkingIsBetter = value; return this; } From 056f8f22db2665443921c0374f7135ff25b55e60 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:16:38 +0300 Subject: [PATCH 057/118] Remove old class --- .../RemoveTransitWithMoreWalking.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java deleted file mode 100644 index 1438cfa14dc..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalking.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.opentripplanner.routing.algorithm.filterchain.deletionflagger; - -import java.util.Comparator; -import java.util.List; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.stream.Collectors; -import org.opentripplanner.framework.model.Cost; -import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Leg; -import org.opentripplanner.routing.api.request.framework.CostLinearFunction; - -/** - * Filter itineraries which contain more walking than a pure walk itinerary - */ -public class RemoveTransitWithMoreWalking implements ItineraryDeletionFlagger { - - /** - * Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain}, - * to know which filters removed - */ - public static final String TAG = "transit-vs-plain-walk-filter"; - - @Override - public String name() { - return TAG; - } - - @Override - public List flagForRemoval(List itineraries) { - // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary - // This never makes sense - - OptionalInt minWalkCost = itineraries - .stream() - .filter(Itinerary::isWalkingAllTheWay) - .mapToInt(Itinerary::getGeneralizedCost) - .min(); - - if (minWalkCost.isEmpty()) { - return List.of(); - } - - var limit = minWalkCost.getAsInt(); - - return itineraries - .stream() - .filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit) - .collect(Collectors.toList()); - } - - @Override - public boolean skipAlreadyFlaggedItineraries() { - return false; - } -} From ca179775ec41c52203bf27138edb9fb6e17f3115 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:24:34 +0300 Subject: [PATCH 058/118] Rename references to changed filter class name --- .../filterchain/ItineraryListFilterChainBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index d48aac5e5d0..d2c1073dedb 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -23,7 +23,7 @@ import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveItinerariesWithShortStreetLeg; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveParkAndRideWithMostlyWalkingFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitWithMoreWalking; +import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveWalkOnlyFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.TransitGeneralizedCostFilter; import org.opentripplanner.routing.algorithm.filterchain.filter.DeletionFlaggingFilter; @@ -73,7 +73,7 @@ public class ItineraryListFilterChainBuilder { private Function getMultiModalStation; private boolean removeItinerariesWithSameRoutesAndStops; private double minBikeParkingDistance; - private boolean removeTransitWithMoreWalking = true; + private boolean removeTransitIfWalkingIsBetter = true; @Sandbox private ItineraryListFilter rideHailingFilter; @@ -364,8 +364,8 @@ public ItineraryListFilterChain build() { ); } - if (removeTransitWithMoreWalking) { - filters.add(new DeletionFlaggingFilter(new RemoveTransitWithMoreWalking())); + if (removeTransitIfWalkingIsBetter) { + filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter())); } // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that From 10949a0e0132ac9c6c88508c0a50903d47336ed4 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:25:56 +0300 Subject: [PATCH 059/118] Move street vs transit filters to the block of absolute filters --- .../ItineraryListFilterChainBuilder.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index d2c1073dedb..f38d0f6da36 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -353,21 +353,6 @@ public ItineraryListFilterChain build() { ); } - // Filter transit itineraries by comparing against non-transit using generalized-cost - if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { - filters.add( - new DeletionFlaggingFilter( - new RemoveTransitIfStreetOnlyIsBetterFilter( - removeTransitWithHigherCostThanBestOnStreetOnly - ) - ) - ); - } - - if (removeTransitIfWalkingIsBetter) { - filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter())); - } - // Apply all absolute filters AFTER the groupBy filters. Absolute filters are filters that // remove elements/ based on the given itinerary properties - not considering other // itineraries. This may remove itineraries in the "groupBy" filters that are considered @@ -378,6 +363,21 @@ public ItineraryListFilterChain build() { // is worse). B is removed by the {@link LatestDepartureTimeFilter} below. This is exactly // what we want, since both itineraries are none optimal. { + // Filter transit itineraries by comparing against non-transit using generalized-cost + if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { + filters.add( + new DeletionFlaggingFilter( + new RemoveTransitIfStreetOnlyIsBetterFilter( + removeTransitWithHigherCostThanBestOnStreetOnly + ) + ) + ); + } + + if (removeTransitIfWalkingIsBetter) { + filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter())); + } + if (removeWalkAllTheWayResults) { filters.add(new DeletionFlaggingFilter(new RemoveWalkOnlyFilter())); } From 089a4a7091bbe0a444f957bb143dd8bbdfb937f3 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 08:46:38 +0300 Subject: [PATCH 060/118] Rename remaining references to old filter class name and rename the tests --- .../filterchain/ItineraryListFilterChainBuilder.java | 2 +- .../algorithm/filterchain/RoutingErrorsAttacher.java | 4 ++-- ...etter.java => RemoveTransitIfWalkingIsBetterFilter.java} | 0 .../algorithm/mapping/RouteRequestToFilterChainMapper.java | 2 +- .../algorithm/filterchain/ItineraryListFilterChainTest.java | 2 +- ...ingTest.java => RemoveTransitIfWalkingIsBetterTest.java} | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/{RemoveTransitIfWalkingIsBetter.java => RemoveTransitIfWalkingIsBetterFilter.java} (100%) rename src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/{RemoveTransitWithMoreWalkingTest.java => RemoveTransitIfWalkingIsBetterTest.java} (89%) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index f38d0f6da36..e81e82664ed 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -363,7 +363,7 @@ public ItineraryListFilterChain build() { // is worse). B is removed by the {@link LatestDepartureTimeFilter} below. This is exactly // what we want, since both itineraries are none optimal. { - // Filter transit itineraries by comparing against non-transit using generalized-cost + // Filter transit itineraries by comparing against non-transit using generalized-cost if (removeTransitWithHigherCostThanBestOnStreetOnly != null) { filters.add( new DeletionFlaggingFilter( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java index 357104682d9..c7433f93a22 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/RoutingErrorsAttacher.java @@ -11,7 +11,7 @@ import org.opentripplanner.model.plan.StreetLeg; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter; import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter; -import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitWithMoreWalking; +import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter; import org.opentripplanner.routing.api.response.RoutingError; /** @@ -55,7 +55,7 @@ static List computeErrors( it .getSystemNotices() .stream() - .anyMatch(notice -> notice.tag.equals(RemoveTransitWithMoreWalking.TAG)); + .anyMatch(notice -> notice.tag.equals(RemoveTransitIfWalkingIsBetterFilter.TAG)); if ( filteredItineraries .stream() diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java similarity index 100% rename from src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetter.java rename to src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index c5af34f573a..e8a3f226d8a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -85,7 +85,7 @@ public static ItineraryListFilterChain createFilterChain( .withLatestDepartureTimeLimit(filterOnLatestDepartureTime) .withMaxLimitReachedSubscriber(maxLimitReachedSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) - .withRemoveTransitWithMoreWalking(true) + .withRemoveTransitIfWalkingIsBetter(true) .withDebugEnabled(params.debug()); if (!context.rideHailingServices().isEmpty()) { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index d9bb3f7dc80..2bbb5a25e3b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -306,7 +306,7 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { // Allow non-optimal bus itinerary pass through ItineraryListFilterChain chain = builder .withRemoveTransitWithHigherCostThanBestOnStreetOnly(null) - .withRemoveTransitWithMoreWalking(false) + .withRemoveTransitIfWalkingIsBetter(false) .build(); assertEquals(toStr(List.of(walk, bus)), toStr(chain.filter(List.of(walk, bus)))); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java similarity index 89% rename from src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java rename to src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java index 6d192787eeb..b68ac9c46a2 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitWithMoreWalkingTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterTest.java @@ -10,7 +10,7 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; -public class RemoveTransitWithMoreWalkingTest implements PlanTestConstants { +public class RemoveTransitIfWalkingIsBetterTest implements PlanTestConstants { @Test public void filterAwayNothingIfNoWalking() { @@ -21,7 +21,7 @@ public void filterAwayNothingIfNoWalking() { // When: List result = DeletionFlaggerTestHelper.process( List.of(i1, i2), - new RemoveTransitWithMoreWalking() + new RemoveTransitIfWalkingIsBetterFilter() ); // Then: @@ -44,7 +44,7 @@ public void filterAwayTransitWithLongerWalk() { List result = DeletionFlaggerTestHelper.process( List.of(i1, i2, bicycle, walk), - new RemoveTransitWithMoreWalking() + new RemoveTransitIfWalkingIsBetterFilter() ); assertEquals(toStr(List.of(i2, bicycle, walk)), toStr(result)); From 43e00c9d2dbef8edc8e447d4dc1e7f05ee562866 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 09:17:22 +0300 Subject: [PATCH 061/118] Update docs --- .../config/routerequest/ItineraryFiltersConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 27e488ca894..418d46f9cf4 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -168,8 +168,8 @@ public static void mapItineraryFilterParams( generalized-cost value is used as input to the function. The function is used to calculate a *max-limit*. The max-limit is then used to filter *transit* itineraries by *generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result - set. Walking is handled with a different logic: if a transit itinerary has more walking than - a plain walk itinerary, it will be removed even if the cost limit function would keep it. +set. Walking is handled with a different logic: if a transit itinerary has higher cost than +a plain walk itinerary, it will be removed even if the cost limit function would keep it. """ ) .asCostLinearFunction(dft.removeTransitWithHigherCostThanBestOnStreetOnly()) From 59f114b56b1e50785d193ae98fa4aab16e9f1a13 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 09:22:17 +0300 Subject: [PATCH 062/118] Update RouteRequest doc --- docs/RouteRequest.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index db6324d3e7c..e9b4da3eacf 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -641,8 +641,8 @@ without transit legs are considered when calculating the minimum cost. The small generalized-cost value is used as input to the function. The function is used to calculate a *max-limit*. The max-limit is then used to filter *transit* itineraries by *generalized-cost*. Itineraries with a cost higher than the max-limit are dropped from the result - set. Walking is handled with a different logic: if a transit itinerary has more walking than - a plain walk itinerary, it will be removed even if the cost limit function would keep it. +set. Walking is handled with a different logic: if a transit itinerary has higher cost than +a plain walk itinerary, it will be removed even if the cost limit function would keep it.

transitGeneralizedCostLimit

From d917e14d85d0a1320ee3ee43359c9f812d5ee8f1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 09:30:35 +0200 Subject: [PATCH 063/118] Add test for translated strings --- .../GraphQLIntegrationTest.java | 5 ++- .../gtfsgraphqlapi/expectations/alerts.json | 38 ++++++++++++++++--- .../_support/text/I18NStrings.java | 22 +++++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/opentripplanner/_support/text/I18NStrings.java diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index 80da83267c2..56466db1e03 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -36,6 +36,7 @@ import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; +import org.opentripplanner._support.text.I18NStrings; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.fares.FaresToItineraryMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; @@ -264,12 +265,12 @@ void graphQL(Path path) throws IOException { private static List getTransitAlert(EntitySelector.Stop entitySelector) { var alertWithoutDescription = TransitAlert .of(id("no-description")) - .withHeaderText(I18NString.of("Just a header")) + .withHeaderText(I18NStrings.TRANSLATED_STRING_1) .addEntity(entitySelector); var alertWithoutHeader = TransitAlert .of(id("no-header")) - .withDescriptionText(I18NString.of("Just a description")) + .withDescriptionText(I18NStrings.TRANSLATED_STRING_2) .addEntity(entitySelector); var alertWithNothing = TransitAlert .of(id("neither-header-nor-description")) diff --git a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json index 76851f06fa4..eb7b3d24154 100644 --- a/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json +++ b/src/ext-test/resources/gtfsgraphqlapi/expectations/alerts.json @@ -11,10 +11,23 @@ }, { "id" : "QWxlcnQ6Rjpuby1oZWFkZXI", - "alertHeaderText" : "Just a description", - "alertDescriptionText" : "Just a description", + "alertHeaderText" : "Second string", + "alertDescriptionText" : "Second string", "alertUrl" : null, - "alertDescriptionTextTranslations" : [ ], + "alertDescriptionTextTranslations" : [ + { + "language" : null, + "text" : "Second string" + }, + { + "language" : "de", + "text" : "Zweite Zeichenabfolge" + }, + { + "language" : "fi", + "text" : "Etkö ole varma, mitä tämä tarkoittaa" + } + ], "alertHeaderTextTranslations" : [ ] }, { @@ -27,11 +40,24 @@ }, { "id" : "QWxlcnQ6Rjpuby1kZXNjcmlwdGlvbg", - "alertHeaderText" : "Just a header", - "alertDescriptionText" : "Just a header", + "alertHeaderText" : "First string", + "alertDescriptionText" : "First string", "alertUrl" : null, "alertDescriptionTextTranslations" : [ ], - "alertHeaderTextTranslations" : [ ] + "alertHeaderTextTranslations" : [ + { + "text" : "First string", + "language" : null + }, + { + "text" : "Erste Zeichenabfolge", + "language" : "de" + }, + { + "text" : "Minulla ei ole aavistustakaan kuinka puhua suomea", + "language" : "fi" + } + ] } ] } diff --git a/src/test/java/org/opentripplanner/_support/text/I18NStrings.java b/src/test/java/org/opentripplanner/_support/text/I18NStrings.java new file mode 100644 index 00000000000..245d1f48473 --- /dev/null +++ b/src/test/java/org/opentripplanner/_support/text/I18NStrings.java @@ -0,0 +1,22 @@ +package org.opentripplanner._support.text; + +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.i18n.TranslatedString; + +public class I18NStrings { + + public static final I18NString TRANSLATED_STRING_1 = TranslatedString.getI18NString( + "First string", + "de", + "Erste Zeichenabfolge", + "fi", + "Minulla ei ole aavistustakaan kuinka puhua suomea" + ); + public static final I18NString TRANSLATED_STRING_2 = TranslatedString.getI18NString( + "Second string", + "de", + "Zweite Zeichenabfolge", + "fi", + "Etkö ole varma, mitä tämä tarkoittaa" + ); +} From 865d1cca6889abe646a4518024ebe8714caff33e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 09:38:58 +0200 Subject: [PATCH 064/118] Return emtpy list in Transmodel API --- .../ext/transmodelapi/model/siri/sx/PtSituationElementType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java index 7364f5f40f6..6cc72568aab 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java @@ -186,7 +186,7 @@ public static GraphQLObjectType create( return List.of(new AbstractMap.SimpleEntry<>(null, headerText.toString())); } }) - .orElse(null) + .orElse(emptyList()) ) .build() ) From 5c08a001456e29e8423e53196d819e85ba470889 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 09:55:50 +0200 Subject: [PATCH 065/118] Add date to container image tag only if Maven version contains SNAPSHOT --- .github/workflows/cibuild.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 3d087b646d1..bf0fe0be37d 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -220,7 +220,14 @@ jobs: version_with_snapshot=`mvn -q help:evaluate -Dexpression=project.version -q -DforceStdout` version=${version_with_snapshot/-SNAPSHOT/} - image_date=`date +%Y-%m-%dT%H-%M` - image_version="${version}_${image_date}" + + image_version=${version} + + ## if the Maven version contains SNAPSHOT, then add date to tag + if [[ $version_with_snapshot == *"SNAPSHOT"* ]]; then + image_date=`date +%Y-%m-%dT%H-%M` + image_version="${version}_${image_date}" + echo "Maven version ${version_with_snapshot} contains SNAPSHOT, adding date to container image tag" + fi mvn --batch-mode -P prettierSkip compile com.google.cloud.tools:jib-maven-plugin:build -Djib.to.tags=latest,$image_version From 238c2f159a4756ae2f61b41550153b71d76dbe30 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 10:09:44 +0200 Subject: [PATCH 066/118] Update version in documentation from 2.3.0 to 2.4.0 --- doc-templates/Configuration.md | 4 ++-- docs/Basic-Tutorial.md | 16 ++++++++-------- docs/Configuration.md | 4 ++-- docs/Getting-OTP.md | 12 ++++++------ docs/RouterConfiguration.md | 2 +- .../standalone/config/router-config.json | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc-templates/Configuration.md b/doc-templates/Configuration.md index ebbe5501b61..003fe28d794 100644 --- a/doc-templates/Configuration.md +++ b/doc-templates/Configuration.md @@ -146,7 +146,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`). Variable substitution is performed on configuration file after the include file directive; Hence variable substitution is also performed on the text in the injected file. -Here is an example including variable substitution, assuming version 2.3.0 of OTP: +Here is an example including variable substitution, assuming version 2.4.0 of OTP: ```JSON // build-config.json @@ -170,7 +170,7 @@ The result will look like this: { "transitFeeds": [ { - "source": "netex-v2.3.0.obj" + "source": "netex-v2.4.0.obj" } ] } diff --git a/docs/Basic-Tutorial.md b/docs/Basic-Tutorial.md index 33473c5d526..788c85aaad0 100644 --- a/docs/Basic-Tutorial.md +++ b/docs/Basic-Tutorial.md @@ -18,9 +18,9 @@ JAR containing all other libraries needed for OTP to work, and is available from repository. You will be able to go to [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), navigate to -the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/), +the [directory of releases](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/), and download -the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/otp-2.3.0-shaded.jar) +the [file with `shaded.jar` suffix](https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar) . You may also want to get your own copy of the OTP source code @@ -127,7 +127,7 @@ below and in other tutorials. The simplest way to use OTP is to build a graph in a single step and start a server immediately, without saving it to disk. The command to do so is: - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --build --serve /home/username/otp + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --serve /home/username/otp where `/home/username/otp` should be the directory where you put your configuration and input files. @@ -151,13 +151,13 @@ build a graph from street and transit data then save it to a file using the `--b command line parameters together. If for example your current working directory (`.`) contains the input files and the OTP JAR file, you can use this command: - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --build --save . + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --build --save . This will produce a file called `graph.obj` in the same directory as the inputs. The server can then be started later using the `--load` parameter, and will read this file instead of building the graph from scratch: - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --load . + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load . Another reason to perform these two phases separately is that the building process loads the entire GTFS and OSM data sets into memory, so can require significantly more memory than just running a @@ -174,16 +174,16 @@ graph once, and then layer transit data on top of the streets to make the final Again assuming the input files and OTP JAR file are in the current working directory, you can build a street graph with OSM and elevation data only (ignoring transit input files) with this command: - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --buildStreet . + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --buildStreet . Then, to build a graph layering transit data on top of the saved street graph (built using the previous command): - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --loadStreet --save . + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --loadStreet --save . Finally, the server can be started using the `--load` parameter: - $ java -Xmx2G -jar otp-2.3.0-shaded.jar --load . + $ java -Xmx2G -jar otp-2.4.0-shaded.jar --load . ## Command Line Switches diff --git a/docs/Configuration.md b/docs/Configuration.md index 4a36c764c19..5a33c70fd43 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -173,7 +173,7 @@ text inserted is valid JSON (starts with `{` and ends with `}`). Variable substitution is performed on configuration file after the include file directive; Hence variable substitution is also performed on the text in the injected file. -Here is an example including variable substitution, assuming version 2.3.0 of OTP: +Here is an example including variable substitution, assuming version 2.4.0 of OTP: ```JSON // build-config.json @@ -197,7 +197,7 @@ The result will look like this: { "transitFeeds": [ { - "source": "netex-v2.3.0.obj" + "source": "netex-v2.4.0.obj" } ] } diff --git a/docs/Getting-OTP.md b/docs/Getting-OTP.md index 35aeef48f65..bafb5fc61c0 100644 --- a/docs/Getting-OTP.md +++ b/docs/Getting-OTP.md @@ -9,8 +9,8 @@ the [release pages on GitHub](https://github.com/opentripplanner/OpenTripPlanner or [the OTP directory at Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/), navigate to the highest version number, and download the file whose name ends with `shaded.jar`. -Note that version numbers like `v2.1.0-rc1` or `v2.3.0-SNAPSHOT` refer to development builds _ -before_ the release version `v2.3.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean +Note that version numbers like `v2.1.0-rc1` or `v2.4.0-SNAPSHOT` refer to development builds _ +before_ the release version `v2.4.0`. The existence of a build `vX.Y.Z-SNAPSHOT` does not mean that `vX.Y.Z` has been released yet. We use the [Github Actions CI system](https://github.com/opentripplanner/OpenTripPlanner/actions) to @@ -87,7 +87,7 @@ For example, you could do the following: ```bash cd OpenTripPlanner -git checkout v2.3.0 +git checkout v2.4.0 git clean -df mvn clean package -DskipTests ``` @@ -110,8 +110,8 @@ file) to the Maven repository, from which it can be automatically included in ot This repository is machine-readable (by Maven or other build systems) and also provides human readable directory listings via HTTP. You can fetch an OTP JAR from this repository by constructing -the proper URL for the release you want. For example, release 2.3.0 will be found -at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.3.0/otp-2.3.0-shaded.jar`. +the proper URL for the release you want. For example, release 2.4.0 will be found +at `https://repo1.maven.org/maven2/org/opentripplanner/otp/2.4.0/otp-2.4.0-shaded.jar`. To make use of OTP in another Maven project, you must specify it as a dependency in that project's `pom.xml`: @@ -120,6 +120,6 @@ project's `pom.xml`: org.opentripplanner otp - 2.3.0 + 2.4.0 ``` diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 709988d042c..8253e9444a4 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -434,7 +434,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ```JSON // router-config.json { - "configVersion" : "v2.3.0-EN000121", + "configVersion" : "v2.4.0-EN000121", "server" : { "apiProcessingTimeout" : "7s", "traceParameters" : [ diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 390714b9107..1b6cb173e6d 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -1,5 +1,5 @@ { - "configVersion": "v2.3.0-EN000121", + "configVersion": "v2.4.0-EN000121", "server": { "apiProcessingTimeout": "7s", "traceParameters": [ From 7113be5d1081083fc2b8126cdaa71b23b7b5dbfb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 10:33:49 +0200 Subject: [PATCH 067/118] Update changelog in prepartion of the release --- docs/Changelog.md | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 9b91d562dea..dc26a5ca934 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -3,28 +3,35 @@ The changelog lists most feature changes between each release. The list is automatically created based on merged pull requests. Search GitHub issues and pull requests for smaller issues. -## 2.4.0 (in progress) +## 2.4.0 (2023-09-13) + +### Notable Changes + +- Improved support for Fares V2 [#4917](https://github.com/opentripplanner/OpenTripPlanner/pull/4917) [#5227](https://github.com/opentripplanner/OpenTripPlanner/pull/5227) +- Improved error and timeout handling [#5047](https://github.com/opentripplanner/OpenTripPlanner/pull/5047) [#5092](https://github.com/opentripplanner/OpenTripPlanner/pull/5092) [#5121](https://github.com/opentripplanner/OpenTripPlanner/pull/5121) [#5114](https://github.com/opentripplanner/OpenTripPlanner/pull/5114) [#5133](https://github.com/opentripplanner/OpenTripPlanner/pull/5133) [#5130](https://github.com/opentripplanner/OpenTripPlanner/pull/5130) [#5192](https://github.com/opentripplanner/OpenTripPlanner/pull/5192) +- Enable GTFS GraphQL API by default, remove the word "legacy" from its name and documentation [#5202](https://github.com/opentripplanner/OpenTripPlanner/pull/5202) +- Access and egress penalty on time and cost [#5180](https://github.com/opentripplanner/OpenTripPlanner/pull/5180) +- Improve support for GBFS geofencing zones [#5201](https://github.com/opentripplanner/OpenTripPlanner/pull/5201) +- Reduce memory consumption by 5-8% [#5223](https://github.com/opentripplanner/OpenTripPlanner/pull/5223) +- Stop count limit for access/egress routing and new accessEgress configuration object [#5214](https://github.com/opentripplanner/OpenTripPlanner/pull/5214) + +### Detailed changes by Pull Request - Generate static documentation for GTFS GraphQL API [#5069](https://github.com/opentripplanner/OpenTripPlanner/pull/5069) - Create a valid area even when it has too many vertices [#5019](https://github.com/opentripplanner/OpenTripPlanner/pull/5019) - Constant speed street routing [#5057](https://github.com/opentripplanner/OpenTripPlanner/pull/5057) - Remove dead build configuration parameter (extraEdgesStopPlatformLink) [#5080](https://github.com/opentripplanner/OpenTripPlanner/pull/5080) - Configure HTTP WebServer Transaction timeouts [#5047](https://github.com/opentripplanner/OpenTripPlanner/pull/5047) -- Add Fares v2 to GraphQL API [#4917](https://github.com/opentripplanner/OpenTripPlanner/pull/4917) - Improve Graph updaters shutdown [#5092](https://github.com/opentripplanner/OpenTripPlanner/pull/5092) -- Add deduplicated "stop clusters" to geocoding sandbox API [#5084](https://github.com/opentripplanner/OpenTripPlanner/pull/5084) - Fix flex timeshift [#5063](https://github.com/opentripplanner/OpenTripPlanner/pull/5063) - Merge norway traversal calculator into default [#5106](https://github.com/opentripplanner/OpenTripPlanner/pull/5106) - Use correct GTFS sequence number in vehicle position matcher [#5090](https://github.com/opentripplanner/OpenTripPlanner/pull/5090) -- Fix SIRI ET PubSub updater shutdown [#5104](https://github.com/opentripplanner/OpenTripPlanner/pull/5104) - OSM: Break out of processing a malformed level map [#5096](https://github.com/opentripplanner/OpenTripPlanner/pull/5096) -- Create unique SIRI-ET PubSub subscription [#5118](https://github.com/opentripplanner/OpenTripPlanner/pull/5118) - Handle JsonParseException [#5121](https://github.com/opentripplanner/OpenTripPlanner/pull/5121) -- Add modes to geocoding [#5115](https://github.com/opentripplanner/OpenTripPlanner/pull/5115) - Do not enforce API processing timeout for parallel routing [#5114](https://github.com/opentripplanner/OpenTripPlanner/pull/5114) - Rename bikeRental options to vehicleRental [#5089](https://github.com/opentripplanner/OpenTripPlanner/pull/5089) - Fare sandbox cleanup, remove MultipleFareService [#5100](https://github.com/opentripplanner/OpenTripPlanner/pull/5100) -- Add validation of NeTEX timetabled passing times [#5081](https://github.com/opentripplanner/OpenTripPlanner/pull/5081) +- Add validation of NeTEx timetabled passing times [#5081](https://github.com/opentripplanner/OpenTripPlanner/pull/5081) - Return WALKING_BETTER_THAN_TRANSIT only on a fully walking leg [#5091](https://github.com/opentripplanner/OpenTripPlanner/pull/5091) - Handle CoercingParseValueException [#5133](https://github.com/opentripplanner/OpenTripPlanner/pull/5133) - Remove broken Jersey tracing configuration [#5142](https://github.com/opentripplanner/OpenTripPlanner/pull/5142) @@ -33,56 +40,39 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix vertex removal race condition [#5141](https://github.com/opentripplanner/OpenTripPlanner/pull/5141) - Comment out replacing DSJ-ID from planned data with ID from realtime-data [#5140](https://github.com/opentripplanner/OpenTripPlanner/pull/5140) - Remove San Francisco and vehicle rental fare calculators [#5145](https://github.com/opentripplanner/OpenTripPlanner/pull/5145) -- Make update frequency a Duration [#5152](https://github.com/opentripplanner/OpenTripPlanner/pull/5152) - Remove batch query from Transmodel API [#5147](https://github.com/opentripplanner/OpenTripPlanner/pull/5147) - Fix nullable absolute direction in GTFS GraphQL API [#5159](https://github.com/opentripplanner/OpenTripPlanner/pull/5159) - Fix error in flex validation [#5161](https://github.com/opentripplanner/OpenTripPlanner/pull/5161) - Check service dates instead of service ids for block id transfers [#5162](https://github.com/opentripplanner/OpenTripPlanner/pull/5162) -- Improve updater log messages [#5168](https://github.com/opentripplanner/OpenTripPlanner/pull/5168) - Add support for mapping NeTEx operating day in operating period [#5167](https://github.com/opentripplanner/OpenTripPlanner/pull/5167) -- Relax validity check on flex trip with null duration [#5169](https://github.com/opentripplanner/OpenTripPlanner/pull/5169) -- Log unexpected vehicle type in GBFS update [#5175](https://github.com/opentripplanner/OpenTripPlanner/pull/5175) - Validate to/from in routing request [#5164](https://github.com/opentripplanner/OpenTripPlanner/pull/5164) - Changing default value for earlyStartSec [#5165](https://github.com/opentripplanner/OpenTripPlanner/pull/5165) - Add GTFS stop sequence to GTFS GraphQL API [#5153](https://github.com/opentripplanner/OpenTripPlanner/pull/5153) - Remove walk leg in a stay seated transfer [#5135](https://github.com/opentripplanner/OpenTripPlanner/pull/5135) - Validate distinct from/to temporary vertices [#5181](https://github.com/opentripplanner/OpenTripPlanner/pull/5181) -- Add support for taxi mode [#5183](https://github.com/opentripplanner/OpenTripPlanner/pull/5183) +- Add support for NeTEx taxi mode [#5183](https://github.com/opentripplanner/OpenTripPlanner/pull/5183) - Fix bike triangle in Transmodel API [#5179](https://github.com/opentripplanner/OpenTripPlanner/pull/5179) - Bug fixes in stop area relation processing [#5166](https://github.com/opentripplanner/OpenTripPlanner/pull/5166) -- Enable GTFS GraphQL API by default, remove the word "legacy" from its documentation [#5202](https://github.com/opentripplanner/OpenTripPlanner/pull/5202) - Allow underscores in GTFS feed IDs [#5191](https://github.com/opentripplanner/OpenTripPlanner/pull/5191) - Area vertex linking improvements [#5209](https://github.com/opentripplanner/OpenTripPlanner/pull/5209) - Allow multiple FlexibleAreas in a FlexibleStopPlace [#4922](https://github.com/opentripplanner/OpenTripPlanner/pull/4922) -- Preemptively make a GraphQL FareProduct an interface [#5207](https://github.com/opentripplanner/OpenTripPlanner/pull/5207) -- Access and egress penalty on time and cost [#5180](https://github.com/opentripplanner/OpenTripPlanner/pull/5180) - Prevent NPE in vehicle position matching [#5212](https://github.com/opentripplanner/OpenTripPlanner/pull/5212) - Empty stop_headsign will fall back to trip_headsign [#5205](https://github.com/opentripplanner/OpenTripPlanner/pull/5205) -- Refactor HTTP client [#5213](https://github.com/opentripplanner/OpenTripPlanner/pull/5213) -- Add escalator reluctance parameter [#5046](https://github.com/opentripplanner/OpenTripPlanner/pull/5046) -- Handle reverse search when starting in no-drop-off zone [#5201](https://github.com/opentripplanner/OpenTripPlanner/pull/5201) -- Refactor edge constructors [#5221](https://github.com/opentripplanner/OpenTripPlanner/pull/5221) -- Upgrade to Apache HTTP Client 5 [#5228](https://github.com/opentripplanner/OpenTripPlanner/pull/5228) -- Deduplicate vertex labels to save memory [#5223](https://github.com/opentripplanner/OpenTripPlanner/pull/5223) -- Fix duplicate publishing of speed test data [#5237](https://github.com/opentripplanner/OpenTripPlanner/pull/5237) +- Treat escalator differently from stairs, add escalator reluctance [#5046](https://github.com/opentripplanner/OpenTripPlanner/pull/5046) - Flex build time and memory optimization for large zones [#5233](https://github.com/opentripplanner/OpenTripPlanner/pull/5233) - Fix pathway traversal time calculation when none is supplied [#5242](https://github.com/opentripplanner/OpenTripPlanner/pull/5242) - Add check for null value of serviceCodesRunning in TripPatternForDateMapper [#5239](https://github.com/opentripplanner/OpenTripPlanner/pull/5239) - Improve error handling in TransmodelGraph [#5192](https://github.com/opentripplanner/OpenTripPlanner/pull/5192) -- Fix SIRI SX retry logic [#5262](https://github.com/opentripplanner/OpenTripPlanner/pull/5262) - Fix filtering by submode [#5261](https://github.com/opentripplanner/OpenTripPlanner/pull/5261) - Add leg.headsign to GTFS GraphQL API [#5290](https://github.com/opentripplanner/OpenTripPlanner/pull/5290) - Return client error for invalid Transmodel query JSON format [#5277](https://github.com/opentripplanner/OpenTripPlanner/pull/5277) - Validate missing intermediate location in via requests [#5253](https://github.com/opentripplanner/OpenTripPlanner/pull/5253) - Support Fares v2 FareMedium and update spec implementation [#5227](https://github.com/opentripplanner/OpenTripPlanner/pull/5227) -- Replace DoubleFunction with linear function [#5267](https://github.com/opentripplanner/OpenTripPlanner/pull/5267) - Improve walk step narrative for entering/exiting stations and signposted pathways [#5285](https://github.com/opentripplanner/OpenTripPlanner/pull/5285) - Fix walk board cost comparison and add escalatorReluctance to hash [#5310](https://github.com/opentripplanner/OpenTripPlanner/pull/5310) -- Stop count limit for access/egress routing and new accessEgress configuration object [#5214](https://github.com/opentripplanner/OpenTripPlanner/pull/5214) - Remove pathway id from REST API [#5303](https://github.com/opentripplanner/OpenTripPlanner/pull/5303) - Remove Winkki street note updater [#5305](https://github.com/opentripplanner/OpenTripPlanner/pull/5305) - Extend stop area relation linking to include bus stop and platform nodes [#5319](https://github.com/opentripplanner/OpenTripPlanner/pull/5319) -- Document in-station navigation [#5321](https://github.com/opentripplanner/OpenTripPlanner/pull/5321) - Add access/egress penalty transmodel api [#5268](https://github.com/opentripplanner/OpenTripPlanner/pull/5268) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) From 9eb6817d33335d84274ebea385281487ad09eb0a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 10:36:02 +0200 Subject: [PATCH 068/118] Update src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java Co-authored-by: Johan Torin --- .../ext/transmodelapi/model/siri/sx/PtSituationElementType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java index 6cc72568aab..31092fec405 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java @@ -186,7 +186,7 @@ public static GraphQLObjectType create( return List.of(new AbstractMap.SimpleEntry<>(null, headerText.toString())); } }) - .orElse(emptyList()) + .orElse(List.of()) ) .build() ) From 9dbe5c5d10b43d1e5f597afa6a421e4e053e4da4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 11:04:42 +0200 Subject: [PATCH 069/118] Also build container on master branch --- .github/workflows/cibuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index bf0fe0be37d..565b9dccaed 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -193,7 +193,7 @@ jobs: run: mvn --batch-mode compile -DskipTests -P prettierSkip container-image: - if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && github.ref == 'refs/heads/dev-2.x' + if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master') runs-on: ubuntu-latest needs: - build-windows From 3921232cfbb54516375be13fdda1468627b44758 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 11:05:43 +0200 Subject: [PATCH 070/118] Remove enunciate REST API documentation generator --- docs/ReleaseChecklist.md | 12 ------------ enunciate.xml | 29 ----------------------------- pom.xml | 24 ------------------------ 3 files changed, 65 deletions(-) delete mode 100644 enunciate.xml diff --git a/docs/ReleaseChecklist.md b/docs/ReleaseChecklist.md index 372222d104f..5215b6c8457 100644 --- a/docs/ReleaseChecklist.md +++ b/docs/ReleaseChecklist.md @@ -48,18 +48,6 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * You can also use the `package` goal instead of the `install` goal to avoid signing if you don't have the GPG certificate installed. * All tests should pass - * Run `MAVEN_OPTS="--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" mvn com.webcohesion.enunciate:enunciate-maven-plugin:docs` - * This build will also create Enunciate API docs and Javadoc with the correct non-snapshot - version number -* Deploy the documentation to AWS S3 - * You have to do this right after the test release build to ensure the right version number in - the docs - * You will need AWSCLI tools (`sudo pip install -U awscli`) - * You will need AWS credentials with write access to the bucket `s3://dev.opentripplanner.org` - * `aws s3 cp --recursive target/site/apidocs s3://dev.opentripplanner.org/javadoc/x.y.z --acl public-read` - * `aws s3 cp --recursive target/site/enunciate/apidocs s3://dev.opentripplanner.org/apidoc/x.y.z --acl public-read` - * Check that docs are readable and show the correct version via - the [development documentation landing page](http://dev.opentripplanner.org). * Finally, if everything looks good, tag and push this release to make it official * `git tag -a vX.Y.Z -m "release X.Y.Z"` * `git push origin vX.Y.Z` diff --git a/enunciate.xml b/enunciate.xml deleted file mode 100644 index 5b5a0cbfaeb..00000000000 --- a/enunciate.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index bef2c8b5fb4..c9ed7e7d10c 100644 --- a/pom.xml +++ b/pom.xml @@ -182,23 +182,6 @@ - - com.webcohesion.enunciate - enunciate-maven-plugin - 2.15.1 - - - - site - - docs - - - - - ${project.build.directory}/site/enunciate - - org.apache.maven.plugins maven-source-plugin @@ -256,13 +239,6 @@ - - - org.apache.maven.plugins - maven-site-plugin - 3.12.1 - - org.apache.maven.plugins maven-surefire-plugin From 138d96f640752e3024302af905ef36986057eb31 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 13 Sep 2023 12:10:27 +0300 Subject: [PATCH 071/118] Remove ice roads from routing using Finland specific tag mapping --- .../openstreetmap/tagmapping/FinlandMapper.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java index fb30309eb09..253bb385aa3 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapper.java @@ -83,6 +83,11 @@ else if (speedLimit <= 16.65f) { props.setProperties("highway=service;access=private", withModes(NONE)); props.setProperties("highway=trail", withModes(NONE)); + // Remove ice/winter roads + props.setProperties("highway=*;seasonal=winter", withModes(NONE)); + props.setProperties("highway=*;ice_road=yes", withModes(NONE)); + props.setProperties("highway=*;winter_road=yes", withModes(NONE)); + // No biking on designated footways/sidewalks props.setProperties("highway=footway", withModes(PEDESTRIAN)); props.setProperties("footway=sidewalk;highway=footway", withModes(PEDESTRIAN)); From 8ae4e310d50bce72b8eb32b15ba032e3e00057eb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 11:12:25 +0200 Subject: [PATCH 072/118] Update documentation and release checklist --- docs/ReleaseChecklist.md | 14 +++++++------- docs/index.md | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/ReleaseChecklist.md b/docs/ReleaseChecklist.md index 5215b6c8457..6a46c0074cb 100644 --- a/docs/ReleaseChecklist.md +++ b/docs/ReleaseChecklist.md @@ -5,16 +5,16 @@ the actions taken by the Maven release plugin. Based on past experience, the Mav can fail at various points in the process leaving the repo in a confusing state. Taking each action manually is more tedious, but keeps eyes on each step and is less prone to failure. -* Make sure the documentation is up to date - * Check all links and references to the release and update to the target release version. Search - all files for with a regular expression: `2\.[012]\.0` and replace if appropriate with the new - version. * Check that your local copy of the dev branch is up to date with no uncommitted changes * `git status` * `git checkout dev-2.x` * `git clean -df` * `git pull` -* Verify that all dependencies in the POM are non-SNAPSHOT versions (e.g. with `grep`) +* Make sure the documentation is up to date + * Check all links and references to the release and update to the target release version. Search + all files for with a regular expression: `2\.[012]\.0` and replace if appropriate with the new + version. + * In `docs/index.md` replace what is the latest version and add a new line for the previous one * Update `docs/Changelog.md` * Lines should have been added or updated as each pull request was merged * If you suspect any changes are not reflected in the Changelog, review the commit log and add @@ -27,8 +27,8 @@ manually is more tedious, but keeps eyes on each step and is less prone to failu * This tells the GH Action that pushes the documentation on master what the name of the current version is. For version 2.3.0 Leonard has already done it: [Example commit](https://github.com/opentripplanner/OpenTripPlanner/commit/3cb061ab1e4253c3977a5d08fa5abab1b0baefd7) -* Check [on GH Actions](https://github.com/opentripplanner/OpenTripPlanner/actions/workflows/) that - the build is currently passing +* Verify that all dependencies in the POM are non-SNAPSHOT versions (e.g. with `grep`) +* Check [on GH Actions](https://github.com/opentripplanner/OpenTripPlanner/actions/workflows/) that the build is currently passing * Switch to the HEAD of master branch, and ensure it's up to date with no uncommitted changes * `git checkout master` * `git status` diff --git a/docs/index.md b/docs/index.md index 49f28850f68..b70c99e4d52 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,8 @@ the selector in the upper left of the published documentation. **Releases** -- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.3 (the git master branch) +- [Latest](http://docs.opentripplanner.org/en/latest) - Version 2.4.0 (the git master branch) +- [v2.3.0](http://docs.opentripplanner.org/en/v2.3.0) - Version 2.3 - [v2.2.0](http://docs.opentripplanner.org/en/v2.2.0) - Version 2.2 - [v2.1.0](http://docs.opentripplanner.org/en/v2.1.0) - Version 2.1 - [v2.0.0](http://docs.opentripplanner.org/en/v2.0.0) - Version 2.0 From 0535840c494b17a520ac0b7f595822acb1cccad3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 11:28:25 +0200 Subject: [PATCH 073/118] prepare release 2.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c9ed7e7d10c..a97d6a25083 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.4.0-SNAPSHOT + 2.4.0 jar From 38d585b57ab3286ef243d691f94294907d83dc34 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 12:13:59 +0200 Subject: [PATCH 074/118] Prepare next development iteration 2.5.0-SNAPSHOT --- docs/Changelog.md | 5 ++++- pom.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index dc26a5ca934..c30a8c7378e 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -3,6 +3,10 @@ The changelog lists most feature changes between each release. The list is automatically created based on merged pull requests. Search GitHub issues and pull requests for smaller issues. +## 2.5.0 (under development) + +[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) + ## 2.4.0 (2023-09-13) ### Notable Changes @@ -74,7 +78,6 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove Winkki street note updater [#5305](https://github.com/opentripplanner/OpenTripPlanner/pull/5305) - Extend stop area relation linking to include bus stop and platform nodes [#5319](https://github.com/opentripplanner/OpenTripPlanner/pull/5319) - Add access/egress penalty transmodel api [#5268](https://github.com/opentripplanner/OpenTripPlanner/pull/5268) -[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.3.0 (2023-04-24) diff --git a/pom.xml b/pom.xml index a97d6a25083..8021f2a218a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ https://opentripplanner.org org.opentripplanner otp - 2.4.0 + 2.5.0-SNAPSHOT jar From ceb1613bc1cad67011c58fc4cefd0c4e8d1927ed Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 12:22:58 +0200 Subject: [PATCH 075/118] Change it back to emptyList() --- .../ext/transmodelapi/model/siri/sx/PtSituationElementType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java index 31092fec405..6cc72568aab 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/sx/PtSituationElementType.java @@ -186,7 +186,7 @@ public static GraphQLObjectType create( return List.of(new AbstractMap.SimpleEntry<>(null, headerText.toString())); } }) - .orElse(List.of()) + .orElse(emptyList()) ) .build() ) From 44b8d7ffc52ba1a5b73e6633afeb8d63fdd131c5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 14:34:55 +0200 Subject: [PATCH 076/118] Update mike to the latest version --- docs/requirements.txt | 4 +--- mkdocs.yml | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index e8863d98147..5cd0396ae85 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,4 @@ mkdocs==1.5.2 mkdocs-material==9.1.17 -mike==1.1.2 +mike@git+https://github.com/jimporter/mike.git@f0522f245e64687dd18384fbd86b721175711474 mkdocs-no-sitemap-plugin==0.0.1 -# remove this after OTP 2.4.0 has been released -mkdocs-redirects==1.2.1 diff --git a/mkdocs.yml b/mkdocs.yml index 417e81c6c8d..27d0a6b2b1e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,10 +13,6 @@ strict: true plugins: - "no-sitemap" - search - # remove this after OTP 2.4.0 has been released - - redirects: - redirect_maps: - 'sandbox/LegacyGraphQLApi.md': 'sandbox/GtfsGraphQlApi.md' theme: name: material From f1dfaf20aff6ec8012d3bc3e6dfb9eed0e921c82 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 14:42:13 +0200 Subject: [PATCH 077/118] Update deploy prefix --- .github/workflows/cibuild.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 565b9dccaed..d7ba8bab111 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -141,9 +141,9 @@ jobs: if [ ${{ github.ref }} = 'refs/heads/master' ]; then - mike deploy --branch $LOCAL_BRANCH --prefix en --title=$MASTER_BRANCH_VERSION --update-aliases v$MASTER_BRANCH_VERSION latest + mike deploy --branch $LOCAL_BRANCH --deploy-prefix en --title=$MASTER_BRANCH_VERSION --update-aliases v$MASTER_BRANCH_VERSION latest else - mike deploy --branch $LOCAL_BRANCH --prefix en dev-2.x + mike deploy --branch $LOCAL_BRANCH --deploy-prefix en dev-2.x fi # commit and push the GraphQL documentation if the schema file is newer than the From 6d72394f5d503625b3ff775e69a11b7733773e9b Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 13 Sep 2023 19:47:24 +0000 Subject: [PATCH 078/118] Add changelog entry for #5349 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index c30a8c7378e..7f82c090888 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -5,6 +5,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle ## 2.5.0 (under development) +- Gracefully handle nullable fields in TransitAlert [#5349](https://github.com/opentripplanner/OpenTripPlanner/pull/5349) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 6ead90877bd52dd66455d43fe84ca3573575c4d4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Sep 2023 20:44:59 +0200 Subject: [PATCH 079/118] Move permissions check inside WPS --- .../graph_builder/module/osm/OsmFilter.java | 71 ----------------- .../graph_builder/module/osm/OsmModule.java | 10 +-- .../module/osm/WalkableAreaBuilder.java | 2 +- .../openstreetmap/model/OSMWay.java | 8 -- .../openstreetmap/model/OSMWithTags.java | 8 ++ .../wayproperty/WayPropertiesBuilder.java | 7 +- .../wayproperty/WayPropertySet.java | 78 ++++++++++++++++++- .../model/StreetTraversalPermission.java | 13 +--- .../openstreetmap/model/OSMWayTest.java | 7 +- 9 files changed, 99 insertions(+), 105 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index cb4182d755d..06faa10d1f1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -1,7 +1,5 @@ package org.opentripplanner.graph_builder.module.osm; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.graph_builder.issues.ConflictingBikeTags; import org.opentripplanner.openstreetmap.model.OSMWay; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.street.model.StreetTraversalPermission; @@ -66,75 +64,6 @@ public static StreetTraversalPermission getPermissionsForEntity( return permission; } - /** - * Computes permissions for an OSMWay. - */ - public static StreetTraversalPermission getPermissionsForWay( - OSMWay way, - StreetTraversalPermission def, - boolean banDiscouragedWalking, - boolean banDiscouragedBiking, - DataImportIssueStore issueStore - ) { - StreetTraversalPermission permissions = getPermissionsForEntity(way, def); - - /* - * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle - * rules: default: permissions; - * - * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes - * any time it is forbidden to ride them, so the only thing to do here is to remove bike - * permissions - * - * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these - * permissions for bikes only - * - * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set - * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle - * - * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like - * oneway:bicycle=yes - */ - - // Compute pedestrian permissions. - if (way.isPedestrianExplicitlyAllowed()) { - permissions = permissions.add(StreetTraversalPermission.PEDESTRIAN); - } else if (way.isPedestrianExplicitlyDenied()) { - permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); - } - - // Check for foot=discouraged, if applicable - if (banDiscouragedWalking && way.hasTag("foot") && way.getTag("foot").equals("discouraged")) { - permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); - } - - // Compute bike permissions, check consistency. - boolean forceBikes = false; - if (way.isBicycleExplicitlyAllowed()) { - permissions = permissions.add(StreetTraversalPermission.BICYCLE); - forceBikes = true; - } - - if ( - way.isBicycleDismountForced() || - (banDiscouragedBiking && way.hasTag("bicycle") && way.getTag("bicycle").equals("discouraged")) - ) { - permissions = permissions.remove(StreetTraversalPermission.BICYCLE); - if (forceBikes) { - issueStore.add(new ConflictingBikeTags(way)); - } - } - - return permissions; - } - - public static StreetTraversalPermission getPermissionsForWay( - OSMWay way, - StreetTraversalPermission def - ) { - return getPermissionsForWay(way, def, false, false, DataImportIssueStore.NOOP); - } - /** * Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions * for travel along and against the way. diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index eb83f0ae080..6b2b433548f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -247,13 +247,9 @@ private void buildBasicGraph() { WAY:for (OSMWay way : osmdb.getWays()) { WayProperties wayData = way.getOsmProvider().getWayPropertySet().getDataForWay(way); setWayName(way); - StreetTraversalPermission permissions = OsmFilter.getPermissionsForWay( - way, - wayData.getPermission(), - params.banDiscouragedWalking(), - params.banDiscouragedBiking(), - issueStore - ); + + var permissions = wayData.getPermission(); + if (!OsmFilter.isWayRoutable(way) || permissions.allowsNothing()) { continue; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index d64aac748e1..f84879ada4e 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -491,7 +491,7 @@ private Set createSegments( intersects.add(area); } } - if (intersects.size() == 0) { + if (intersects.isEmpty()) { // apparently our intersection here was bogus return Set.of(); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index 8d0404abfbd..a8bc2b34ffe 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -51,14 +51,6 @@ public boolean isBoardingArea() { return isBoardingLocation() && isClosed(); } - /** - * Returns true if bicycle dismounts are forced. - */ - public boolean isBicycleDismountForced() { - String bicycle = getTag("bicycle"); - return isTag("cycleway", "dismount") || "dismount".equals(bicycle); - } - /** * Returns true if these are steps. */ diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 7b10e580353..1d723faf367 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -132,6 +132,14 @@ public boolean isTagTrue(String tag) { return isTrue(getTag(tag)); } + /** + * Returns true if bicycle dismounts are forced. + */ + public boolean isBicycleDismountForced() { + String bicycle = getTag("bicycle"); + return isTag("cycleway", "dismount") || "dismount".equals(bicycle); + } + protected boolean doesTagAllowAccess(String tag) { if (tags == null) { return false; diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java index 30403250ad0..7d701ac7ca1 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java @@ -8,7 +8,7 @@ */ public class WayPropertiesBuilder { - private final StreetTraversalPermission permission; + private StreetTraversalPermission permission; private SafetyFeatures bicycleSafetyFeatures = null; private SafetyFeatures walkSafetyFeatures = null; @@ -62,6 +62,11 @@ public WayPropertiesBuilder walkSafety(double walkSafety, double walkSafetyBack) return this; } + public WayPropertiesBuilder withPermission(StreetTraversalPermission permission) { + this.permission = permission; + return this; + } + public StreetTraversalPermission getPermission() { return permission; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index e7a1bd445ee..e8bbc066aa0 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -14,6 +14,9 @@ import java.util.regex.Pattern; import org.opentripplanner.framework.functional.FunctionUtils.TriFunction; import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.issues.ConflictingBikeTags; +import org.opentripplanner.graph_builder.module.osm.OsmFilter; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.BestMatchSpecifier; import org.opentripplanner.openstreetmap.wayproperty.specifier.OsmSpecifier; @@ -60,6 +63,66 @@ public class WayPropertySet { /** The WayProperties applied to all ways that do not match any WayPropertyPicker. */ private final WayProperties defaultProperties; + /** + * Computes permissions for an OSMWay. + */ + private static StreetTraversalPermission getPermissionsForWay( + OSMWithTags way, + StreetTraversalPermission def, + DataImportIssueStore issueStore + ) { + StreetTraversalPermission permissions = OsmFilter.getPermissionsForEntity(way, def); + + /* + * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle + * rules: default: permissions; + * + * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes + * any time it is forbidden to ride them, so the only thing to do here is to remove bike + * permissions + * + * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these + * permissions for bikes only + * + * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set + * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle + * + * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like + * oneway:bicycle=yes + */ + + // Compute pedestrian permissions. + if (way.isPedestrianExplicitlyAllowed()) { + permissions = permissions.add(StreetTraversalPermission.PEDESTRIAN); + } else if (way.isPedestrianExplicitlyDenied()) { + permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); + } + + // Check for foot=discouraged, if applicable + if (false && way.hasTag("foot") && way.getTag("foot").equals("discouraged")) { + permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); + } + + // Compute bike permissions, check consistency. + boolean forceBikes = false; + if (way.isBicycleExplicitlyAllowed()) { + permissions = permissions.add(StreetTraversalPermission.BICYCLE); + forceBikes = true; + } + + if ( + way.isBicycleDismountForced() || + (false && way.hasTag("bicycle") && way.getTag("bicycle").equals("discouraged")) + ) { + permissions = permissions.remove(StreetTraversalPermission.BICYCLE); + if (forceBikes) { + issueStore.add(new ConflictingBikeTags(way)); + } + } + + return permissions; + } + public List getMixins() { return mixins; } @@ -119,11 +182,22 @@ public WayProperties getDataForWay(OSMWithTags way) { float forwardSpeed = getCarSpeedForWay(way, false); float backSpeed = getCarSpeedForWay(way, true); - StreetTraversalPermission permission = forwardResult.getPermission(); - StreetTraversalPermission backwardPermission = backwardResult.getPermission(); + + StreetTraversalPermission permission = getPermissionsForWay( + way, + forwardResult.getPermission(), + DataImportIssueStore.NOOP + ); + + StreetTraversalPermission backwardPermission = getPermissionsForWay( + way, + backwardResult.getPermission(), + DataImportIssueStore.NOOP + ); WayProperties result = forwardResult .mutate() + .withPermission(permission) .bicycleSafety( forwardResult.getBicycleSafetyFeatures() != null ? forwardResult.getBicycleSafetyFeatures().forward() diff --git a/src/main/java/org/opentripplanner/street/model/StreetTraversalPermission.java b/src/main/java/org/opentripplanner/street/model/StreetTraversalPermission.java index c7ed7ffff41..98f7f2c3c91 100644 --- a/src/main/java/org/opentripplanner/street/model/StreetTraversalPermission.java +++ b/src/main/java/org/opentripplanner/street/model/StreetTraversalPermission.java @@ -65,10 +65,7 @@ public boolean allows(TraverseModeSet modes) { return true; } else if (modes.getBicycle() && allows(StreetTraversalPermission.BICYCLE)) { return true; - } else if (modes.getCar() && allows(StreetTraversalPermission.CAR)) { - return true; - } - return false; + } else return modes.getCar() && allows(StreetTraversalPermission.CAR); } /** @@ -79,10 +76,7 @@ public boolean allows(TraverseMode mode) { return true; } else if (mode.isCyclingIsh() && allows(StreetTraversalPermission.BICYCLE)) { return true; - } else if (mode == TraverseMode.CAR && allows(StreetTraversalPermission.CAR)) { - return true; - } - return false; + } else return mode == TraverseMode.CAR && allows(StreetTraversalPermission.CAR); } /** @@ -93,10 +87,9 @@ public boolean allowsAnything() { } /** - * Returns true if there no modes are by this permission. + * Returns true if this permission allows nothing to traverse */ public boolean allowsNothing() { - // TODO(flamholz): what about CROSSHATCHED? return this == StreetTraversalPermission.NONE; } } diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index 05eb6c02852..453494d580d 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -359,10 +359,7 @@ private StreetTraversalPermissionPair getWayProperties(OSMWay way) { WayPropertySet wayPropertySet = new WayPropertySet(); WayProperties wayData = wayPropertySet.getDataForWay(way); - StreetTraversalPermission permissions = OsmFilter.getPermissionsForWay( - way, - wayData.getPermission() - ); - return OsmFilter.getPermissions(permissions, way); + StreetTraversalPermission def = wayData.getPermission(); + return OsmFilter.getPermissions(def, way); } } From aa992a63c4c0f39c33a741f8e913daeb53cef66f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Sep 2023 23:09:15 +0200 Subject: [PATCH 080/118] Move entity check into way property set --- .../reportapi/model/BicycleSafetyReport.java | 2 +- .../module/osm/SafetyValueNormalizer.java | 8 +- .../module/osm/WalkableAreaBuilder.java | 7 +- .../tagmapping/NorwayMapper.java | 1 - .../wayproperty/SafetyFeatures.java | 4 +- .../wayproperty/WayProperties.java | 44 ++++++++--- .../wayproperty/WayPropertiesBuilder.java | 9 ++- .../wayproperty/WayPropertySet.java | 43 ++++++----- .../module/osm/OsmModuleTest.java | 15 ++-- .../tagmapping/FinlandMapperTest.java | 76 ++++++------------- .../tagmapping/GermanyMapperTest.java | 50 +++++------- .../tagmapping/NorwayMapperTest.java | 8 +- .../tagmapping/OsmTagMapperTest.java | 4 +- .../tagmapping/PortlandMapperTest.java | 2 +- .../wayproperty/WayPropertySetTest.java | 2 +- 15 files changed, 131 insertions(+), 144 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java b/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java index 0c299f824d5..8830189107d 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/model/BicycleSafetyReport.java @@ -31,7 +31,7 @@ public static String makeCsv(OsmTagMapperSource source) { buf.addBoolean(false); buf.addText(p.properties().getPermission().toString()); - var safetyProps = p.properties().getBicycleSafetyFeatures(); + var safetyProps = p.properties().bicycleSafety(); if (safetyProps != null) { buf.addNumber(safetyProps.forward()); buf.addNumber(safetyProps.back()); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java index c8acb8990d6..a9603d59417 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java @@ -90,12 +90,12 @@ void applyWayProperties( boolean walkNoThrough = tagMapperForWay.isWalkNoThroughTrafficExplicitlyDisallowed(way); if (street != null) { - double bicycleSafety = wayData.getBicycleSafetyFeatures().forward(); + double bicycleSafety = wayData.bicycleSafety().forward(); street.setBicycleSafetyFactor((float) bicycleSafety); if (bicycleSafety < bestBikeSafety) { bestBikeSafety = (float) bicycleSafety; } - double walkSafety = wayData.getWalkSafetyFeatures().forward(); + double walkSafety = wayData.walkSafety().forward(); street.setWalkSafetyFactor((float) walkSafety); if (walkSafety < bestWalkSafety) { bestWalkSafety = (float) walkSafety; @@ -111,12 +111,12 @@ void applyWayProperties( } if (backStreet != null) { - double bicycleSafety = wayData.getBicycleSafetyFeatures().back(); + double bicycleSafety = wayData.bicycleSafety().back(); if (bicycleSafety < bestBikeSafety) { bestBikeSafety = (float) bicycleSafety; } backStreet.setBicycleSafetyFactor((float) bicycleSafety); - double walkSafety = wayData.getWalkSafetyFeatures().back(); + double walkSafety = wayData.walkSafety().back(); if (walkSafety < bestWalkSafety) { bestWalkSafety = (float) walkSafety; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index f84879ada4e..0d624769b2d 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -661,13 +661,10 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection wayPropertiesCache.put(areaEntity, wayData); } - double bicycleSafety = wayPropertiesCache - .get(areaEntity) - .getBicycleSafetyFeatures() - .forward(); + double bicycleSafety = wayPropertiesCache.get(areaEntity).bicycleSafety().forward(); namedArea.setBicycleSafetyMultiplier(bicycleSafety); - double walkSafety = wayPropertiesCache.get(areaEntity).getWalkSafetyFeatures().forward(); + double walkSafety = wayPropertiesCache.get(areaEntity).walkSafety().forward(); namedArea.setWalkSafetyMultiplier(walkSafety); namedArea.setOriginalEdges(intersection); diff --git a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java index 1b9ec441e39..d2cd2f3cf96 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java +++ b/src/main/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapper.java @@ -4,7 +4,6 @@ import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofWalkSafety; import static org.opentripplanner.openstreetmap.wayproperty.WayPropertiesBuilder.withModes; import static org.opentripplanner.street.model.StreetTraversalPermission.ALL; -import static org.opentripplanner.street.model.StreetTraversalPermission.BICYCLE_AND_CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java index 9dd78266618..fc166a69927 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/SafetyFeatures.java @@ -3,4 +3,6 @@ /** * Record that holds forward and back safety factors for cycling or walking. */ -public record SafetyFeatures(double forward, double back) {} +public record SafetyFeatures(double forward, double back) { + public static final SafetyFeatures DEFAULT = new SafetyFeatures(1, 1); +} diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java index 8ceb063dc5a..0e0b6d87b31 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java @@ -1,42 +1,68 @@ package org.opentripplanner.openstreetmap.wayproperty; import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.street.model.StreetTraversalPermission; /** * Parameters applied to OSM ways, usually based on their tags: - Which modes can traverse it - * Dangerousness on a bicycle in both directions (OSM ways can be bidirectional). * - * @author novalis */ public class WayProperties { + @Nonnull private final StreetTraversalPermission permission; + + @Nullable private final SafetyFeatures bicycleSafetyFeatures; + + @Nullable private final SafetyFeatures walkSafetyFeatures; - public WayProperties(WayPropertiesBuilder wayPropertiesBuilder) { - permission = wayPropertiesBuilder.getPermission(); + WayProperties(WayPropertiesBuilder wayPropertiesBuilder) { + permission = Objects.requireNonNull(wayPropertiesBuilder.getPermission()); bicycleSafetyFeatures = wayPropertiesBuilder.getBicycleSafetyFeatures(); walkSafetyFeatures = wayPropertiesBuilder.getWalkSafetyFeatures(); } - public SafetyFeatures getBicycleSafetyFeatures() { - return bicycleSafetyFeatures; + @Nonnull + public SafetyFeatures bicycleSafety() { + return Objects.requireNonNullElse(bicycleSafetyFeatures, SafetyFeatures.DEFAULT); } - public SafetyFeatures getWalkSafetyFeatures() { - return walkSafetyFeatures; + @Nonnull + public SafetyFeatures walkSafety() { + return Objects.requireNonNullElse(walkSafetyFeatures, SafetyFeatures.DEFAULT); } + @Nonnull public StreetTraversalPermission getPermission() { return permission; } + public WayPropertiesBuilder mutate() { + return new WayPropertiesBuilder(this); + } + + @Nonnull + public Optional walkSafetyOpt() { + return Optional.ofNullable(walkSafetyFeatures); + } + + @Nonnull + public Optional bicycleSafetyOpt() { + return Optional.ofNullable(bicycleSafetyFeatures); + } + + @Override public int hashCode() { return Objects.hash(bicycleSafetyFeatures, walkSafetyFeatures, permission); } + @Override public boolean equals(Object o) { if (o instanceof WayProperties other) { return ( @@ -47,8 +73,4 @@ public boolean equals(Object o) { } return false; } - - public WayPropertiesBuilder mutate() { - return new WayPropertiesBuilder(this); - } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java index 7d701ac7ca1..fb5f4724c0c 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java @@ -1,5 +1,6 @@ package org.opentripplanner.openstreetmap.wayproperty; +import javax.annotation.Nullable; import org.opentripplanner.street.model.StreetTraversalPermission; /** @@ -16,10 +17,10 @@ public WayPropertiesBuilder(StreetTraversalPermission permission) { this.permission = permission; } - public WayPropertiesBuilder(WayProperties defaultProperties) { + WayPropertiesBuilder(WayProperties defaultProperties) { this.permission = defaultProperties.getPermission(); - this.bicycleSafetyFeatures = defaultProperties.getBicycleSafetyFeatures(); - this.walkSafetyFeatures = defaultProperties.getWalkSafetyFeatures(); + this.bicycleSafetyFeatures = defaultProperties.bicycleSafety(); + this.walkSafetyFeatures = defaultProperties.walkSafety(); } /** @@ -71,10 +72,12 @@ public StreetTraversalPermission getPermission() { return permission; } + @Nullable public SafetyFeatures getBicycleSafetyFeatures() { return bicycleSafetyFeatures; } + @Nullable public SafetyFeatures getWalkSafetyFeatures() { return walkSafetyFeatures; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index e8bbc066aa0..57839da588e 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -199,33 +199,39 @@ public WayProperties getDataForWay(OSMWithTags way) { .mutate() .withPermission(permission) .bicycleSafety( - forwardResult.getBicycleSafetyFeatures() != null - ? forwardResult.getBicycleSafetyFeatures().forward() - : defaultBicycleSafetyForPermission.apply(permission, forwardSpeed, way), - backwardResult.getBicycleSafetyFeatures() != null - ? backwardResult.getBicycleSafetyFeatures().back() - : defaultBicycleSafetyForPermission.apply(backwardPermission, backSpeed, way) + forwardResult + .bicycleSafetyOpt() + .map(SafetyFeatures::forward) + .orElseGet(() -> defaultBicycleSafetyForPermission.apply(permission, forwardSpeed, way)), + backwardResult + .bicycleSafetyOpt() + .map(SafetyFeatures::back) + .orElseGet(() -> + defaultBicycleSafetyForPermission.apply(backwardPermission, backSpeed, way) + ) ) .walkSafety( - forwardResult.getWalkSafetyFeatures() != null - ? forwardResult.getWalkSafetyFeatures().forward() - : defaultWalkSafetyForPermission.apply(permission, forwardSpeed, way), - backwardResult.getWalkSafetyFeatures() != null - ? backwardResult.getWalkSafetyFeatures().back() - : defaultWalkSafetyForPermission.apply(backwardPermission, backSpeed, way) + forwardResult + .walkSafetyOpt() + .map(SafetyFeatures::forward) + .orElseGet(() -> defaultWalkSafetyForPermission.apply(permission, forwardSpeed, way)), + backwardResult + .walkSafetyOpt() + .map(SafetyFeatures::back) + .orElseGet(() -> defaultWalkSafetyForPermission.apply(backwardPermission, backSpeed, way)) ) .build(); /* apply mixins */ - if (backwardMixins.size() > 0) { + if (!backwardMixins.isEmpty()) { result = applyMixins(result, backwardMixins, true); } - if (forwardMixins.size() > 0) { + if (!forwardMixins.isEmpty()) { result = applyMixins(result, forwardMixins, false); } if ( (bestBackwardScore == 0 || bestForwardScore == 0) && - (backwardMixins.size() == 0 || forwardMixins.size() == 0) + (backwardMixins.isEmpty() || forwardMixins.isEmpty()) ) { String all_tags = dumpTags(way); LOG.debug("Used default permissions: {}", all_tags); @@ -324,9 +330,6 @@ public Set getNoteForWay(OSMWithTags way) { out.add(noteProperties.generateNote(way)); } } - if (out.size() == 0) { - return null; - } return out; } @@ -533,10 +536,10 @@ private WayProperties applyMixins( List mixins, boolean backward ) { - SafetyFeatures bicycleSafetyFeatures = result.getBicycleSafetyFeatures(); + SafetyFeatures bicycleSafetyFeatures = result.bicycleSafety(); double forwardBicycle = bicycleSafetyFeatures.forward(); double backBicycle = bicycleSafetyFeatures.back(); - SafetyFeatures walkSafetyFeatures = result.getWalkSafetyFeatures(); + SafetyFeatures walkSafetyFeatures = result.walkSafety(); double forwardWalk = walkSafetyFeatures.forward(); double backWalk = walkSafetyFeatures.back(); for (var mixin : mixins) { diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java index b8016cb1a29..c0c7a68cc37 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java @@ -168,7 +168,6 @@ public void testWayDataSet() { OSMWithTags way = new OSMWay(); way.addTag("highway", "footway"); way.addTag("cycleway", "lane"); - way.addTag("access", "no"); way.addTag("surface", "gravel"); WayPropertySet wayPropertySet = new WayPropertySet(); @@ -176,10 +175,10 @@ public void testWayDataSet() { // where there are no way specifiers, the default is used WayProperties wayData = wayPropertySet.getDataForWay(way); assertEquals(wayData.getPermission(), ALL); - assertEquals(wayData.getWalkSafetyFeatures().forward(), 1.0); - assertEquals(wayData.getWalkSafetyFeatures().back(), 1.0); - assertEquals(wayData.getBicycleSafetyFeatures().forward(), 1.0); - assertEquals(wayData.getBicycleSafetyFeatures().back(), 1.0); + assertEquals(wayData.walkSafety().forward(), 1.0); + assertEquals(wayData.walkSafety().back(), 1.0); + assertEquals(wayData.bicycleSafety().forward(), 1.0); + assertEquals(wayData.bicycleSafety().back(), 1.0); // add two equal matches: lane only... OsmSpecifier lane_only = new BestMatchSpecifier("cycleway=lane"); @@ -217,7 +216,7 @@ public void testWayDataSet() { wayPropertySet.setMixinProperties(gravel, gravel_is_dangerous); dataForWay = wayPropertySet.getDataForWay(way); - assertEquals(dataForWay.getBicycleSafetyFeatures().forward(), 1.5); + assertEquals(dataForWay.bicycleSafety().forward(), 1.5); // test a left-right distinction way = new OSMWay(); @@ -234,9 +233,9 @@ public void testWayDataSet() { wayPropertySet.addProperties(track_only, track_is_safest); dataForWay = wayPropertySet.getDataForWay(way); // right (with traffic) comes from track - assertEquals(0.25, dataForWay.getBicycleSafetyFeatures().forward()); + assertEquals(0.25, dataForWay.bicycleSafety().forward()); // left comes from lane - assertEquals(0.75, dataForWay.getBicycleSafetyFeatures().back()); + assertEquals(0.75, dataForWay.bicycleSafety().back()); way = new OSMWay(); way.addTag("highway", "footway"); diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java index 088acbb3b23..65d49ed5e82 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java @@ -79,79 +79,53 @@ public void testSafety() { cyclewaySegregatedFootwayCrossingWithTrafficLights.addTag("segregated", "yes"); cyclewaySegregatedFootwayCrossingWithTrafficLights.addTag("highway", "cycleway"); cyclewaySegregatedFootwayCrossingWithTrafficLights.addTag("crossing", "traffic_signals"); - assertEquals(2.06, wps.getDataForWay(primaryWay).getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(2.06, wps.getDataForWay(primaryWay).bicycleSafety().forward(), epsilon); // way with high speed limit, has higher walk safety factor - assertEquals(1.8, wps.getDataForWay(primaryWay).getWalkSafetyFeatures().forward(), epsilon); - assertEquals(1.8, wps.getDataForWay(primaryWay).getWalkSafetyFeatures().back(), epsilon); + assertEquals(1.8, wps.getDataForWay(primaryWay).walkSafety().forward(), epsilon); + assertEquals(1.8, wps.getDataForWay(primaryWay).walkSafety().back(), epsilon); // way with low speed limit, has lower walk safety factor - assertEquals( - 1.45, - wps.getDataForWay(livingStreetWay).getWalkSafetyFeatures().forward(), - epsilon - ); - assertEquals(1.1, wps.getDataForWay(footway).getWalkSafetyFeatures().forward(), epsilon); - assertEquals(1.1, wps.getDataForWay(sidewalk).getWalkSafetyFeatures().forward(), epsilon); + assertEquals(1.45, wps.getDataForWay(livingStreetWay).walkSafety().forward(), epsilon); + assertEquals(1.1, wps.getDataForWay(footway).walkSafety().forward(), epsilon); + assertEquals(1.1, wps.getDataForWay(sidewalk).walkSafety().forward(), epsilon); + assertEquals(1.1, wps.getDataForWay(segregatedCycleway).walkSafety().forward(), epsilon); + assertEquals(1.0, wps.getDataForWay(tunnel).walkSafety().forward(), epsilon); + assertEquals(1.0, wps.getDataForWay(bridge).walkSafety().forward(), epsilon); + assertEquals(1.2, wps.getDataForWay(footwayCrossing).walkSafety().forward(), epsilon); assertEquals( 1.1, - wps.getDataForWay(segregatedCycleway).getWalkSafetyFeatures().forward(), - epsilon - ); - assertEquals(1.0, wps.getDataForWay(tunnel).getWalkSafetyFeatures().forward(), epsilon); - assertEquals(1.0, wps.getDataForWay(bridge).getWalkSafetyFeatures().forward(), epsilon); - assertEquals( - 1.2, - wps.getDataForWay(footwayCrossing).getWalkSafetyFeatures().forward(), - epsilon - ); - assertEquals( - 1.1, - wps.getDataForWay(footwayCrossingWithTrafficLights).getWalkSafetyFeatures().forward(), - epsilon - ); - assertEquals( - 1.25, - wps.getDataForWay(cyclewayCrossing).getWalkSafetyFeatures().forward(), - epsilon - ); - assertEquals( - 1.25, - wps.getDataForWay(cyclewayFootwayCrossing).getWalkSafetyFeatures().forward(), + wps.getDataForWay(footwayCrossingWithTrafficLights).walkSafety().forward(), epsilon ); + assertEquals(1.25, wps.getDataForWay(cyclewayCrossing).walkSafety().forward(), epsilon); + assertEquals(1.25, wps.getDataForWay(cyclewayFootwayCrossing).walkSafety().forward(), epsilon); assertEquals( 1.15, - wps.getDataForWay(cyclewayCrossingWithTrafficLights).getWalkSafetyFeatures().forward(), + wps.getDataForWay(cyclewayCrossingWithTrafficLights).walkSafety().forward(), epsilon ); assertEquals( 1.15, - wps.getDataForWay(cyclewayFootwayCrossingWithTrafficLights).getWalkSafetyFeatures().forward(), + wps.getDataForWay(cyclewayFootwayCrossingWithTrafficLights).walkSafety().forward(), epsilon ); assertEquals( 1.2, - wps.getDataForWay(cyclewaySegregatedCrossing).getWalkSafetyFeatures().forward(), + wps.getDataForWay(cyclewaySegregatedCrossing).walkSafety().forward(), epsilon ); assertEquals( 1.2, - wps.getDataForWay(cyclewaySegregatedFootwayCrossing).getWalkSafetyFeatures().forward(), + wps.getDataForWay(cyclewaySegregatedFootwayCrossing).walkSafety().forward(), epsilon ); assertEquals( 1.1, - wps - .getDataForWay(cyclewaySegregatedCrossingWithTrafficLights) - .getWalkSafetyFeatures() - .forward(), + wps.getDataForWay(cyclewaySegregatedCrossingWithTrafficLights).walkSafety().forward(), epsilon ); assertEquals( 1.1, - wps - .getDataForWay(cyclewaySegregatedFootwayCrossingWithTrafficLights) - .getWalkSafetyFeatures() - .forward(), + wps.getDataForWay(cyclewaySegregatedFootwayCrossingWithTrafficLights).walkSafety().forward(), epsilon ); } @@ -164,13 +138,9 @@ public void testSafetyWithMixins() { // surface has mixin bicycle safety of 1.3 but no walk safety wayWithMixins.addTag("surface", "metal"); // 1.0 * 1.3 = 1.3 - assertEquals( - 1.3, - wps.getDataForWay(wayWithMixins).getBicycleSafetyFeatures().forward(), - epsilon - ); + assertEquals(1.3, wps.getDataForWay(wayWithMixins).bicycleSafety().forward(), epsilon); // 1.6 is the default walk safety for a way with ALL permissions and speed limit > 35 and <= 60 kph - assertEquals(1.6, wps.getDataForWay(wayWithMixins).getWalkSafetyFeatures().forward(), epsilon); + assertEquals(1.6, wps.getDataForWay(wayWithMixins).walkSafety().forward(), epsilon); OSMWithTags wayWithMixinsAndCustomSafety = new OSMWithTags(); // highway=service has custom bicycle safety of 1.1 but no custom walk safety @@ -180,13 +150,13 @@ public void testSafetyWithMixins() { // 1.1 * 1.3 = 1.43 assertEquals( 1.43, - wps.getDataForWay(wayWithMixinsAndCustomSafety).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(wayWithMixinsAndCustomSafety).bicycleSafety().forward(), epsilon ); // 1.6 is the default walk safety for a way with ALL permissions and speed limit <= 35 kph assertEquals( 1.45, - wps.getDataForWay(wayWithMixinsAndCustomSafety).getWalkSafetyFeatures().forward(), + wps.getDataForWay(wayWithMixinsAndCustomSafety).walkSafety().forward(), epsilon ); } diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java index 2fc0e699344..2ac9d18a8e6 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java @@ -33,7 +33,7 @@ public void testBikeSafety() { way.addTag("lit", "yes"); way.addTag("oneway", "no"); way.addTag("traffic_sign", "DE:239,1022-10"); - assertEquals(1.2, wps.getDataForWay(way).getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(1.2, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); way = new OSMWithTags(); way.addTag("cycleway", "opposite"); @@ -50,9 +50,9 @@ public void testBikeSafety() { way.addTag("surface", "asphalt"); way.addTag("width", "6.5"); way.addTag("zone:traffic", "DE:urban"); - assertEquals(0.9, wps.getDataForWay(way).getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(0.9, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); // walk safety should be default - assertEquals(1, wps.getDataForWay(way).getWalkSafetyFeatures().forward(), epsilon); + assertEquals(1, wps.getDataForWay(way).walkSafety().forward(), epsilon); // way332589799 (Radschnellweg BW1) way = new OSMWithTags(); @@ -70,7 +70,7 @@ public void testBikeSafety() { way.addTag("source:maxspeed", "sign"); way.addTag("surface", "asphalt"); way.addTag("tracktype", "grade1"); - assertEquals(0.693, wps.getDataForWay(way).getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(0.693, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); way = new OSMWithTags(); way.addTag("highway", "track"); @@ -79,7 +79,7 @@ public void testBikeSafety() { way.addTag("tracktype", "grade1"); way.addTag("traffic_sign", "DE:260,1026-36"); way.addTag("width", "2.5"); - assertEquals(1.0, wps.getDataForWay(way).getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(1.0, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); } @Test @@ -138,18 +138,14 @@ public void lcnAndRcnShouldNotBeAddedUp() { residential.addTag("highway", "residential"); assertEquals( - wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(justLcn).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), epsilon ); - assertEquals(wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), 0.6859, epsilon); + assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); - assertEquals( - wps.getDataForWay(residential).getBicycleSafetyFeatures().forward(), - 0.98, - epsilon - ); + assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); } @Test @@ -179,36 +175,32 @@ public void bicycleRoadAndLcnShouldNotBeAddedUp() { residential.addTag("highway", "residential"); assertEquals( - wps.getDataForWay(justCyclestreet).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(justLcn).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), epsilon ); assertEquals( - wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(justBicycleRoad).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justBicycleRoad).bicycleSafety().forward(), epsilon ); assertEquals( - wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(justCyclestreet).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), epsilon ); assertEquals( - wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(justLcn).getBicycleSafetyFeatures().forward(), + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), epsilon ); - assertEquals(wps.getDataForWay(both).getBicycleSafetyFeatures().forward(), 0.6859, epsilon); + assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); - assertEquals( - wps.getDataForWay(residential).getBicycleSafetyFeatures().forward(), - 0.98, - epsilon - ); + assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); } @Test @@ -239,8 +231,8 @@ public void setCorrectBikeSafetyValuesForBothDirections() { residential.addTag("name", "Auf der Heide"); residential.addTag("surface", "asphalt"); assertEquals( - wps.getDataForWay(residential).getBicycleSafetyFeatures().forward(), - wps.getDataForWay(residential).getBicycleSafetyFeatures().back(), + wps.getDataForWay(residential).bicycleSafety().forward(), + wps.getDataForWay(residential).bicycleSafety().back(), epsilon ); } diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapperTest.java index d659c89a187..cce2bf85cb4 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/NorwayMapperTest.java @@ -93,7 +93,7 @@ static List createLinkRoadLikeMainCases() { @ParameterizedTest(name = "{0} should have a score of {1}") @MethodSource("createExpectedBicycleSafetyForMaxspeedCases") public void testBicycleSafetyForMaxspeed(OSMWithTags way, Double expected) { - var result = wps.getDataForWay(way).getBicycleSafetyFeatures(); + var result = wps.getDataForWay(way).bicycleSafety(); var expectedSafetyFeatures = new SafetyFeatures(expected, expected); assertEquals(expectedSafetyFeatures, result); } @@ -101,7 +101,7 @@ public void testBicycleSafetyForMaxspeed(OSMWithTags way, Double expected) { @ParameterizedTest @MethodSource("createBicycleSafetyWithoutExplicitMaxspeed") public void testBicycleSafetyWithoutMaxspeed(OSMWithTags way, Double expected) { - var result = wps.getDataForWay(way).getBicycleSafetyFeatures(); + var result = wps.getDataForWay(way).bicycleSafety(); var expectedSafetyFeatures = new SafetyFeatures(expected, expected); assertEquals(expectedSafetyFeatures, result); } @@ -109,8 +109,8 @@ public void testBicycleSafetyWithoutMaxspeed(OSMWithTags way, Double expected) { @ParameterizedTest @MethodSource("createLinkRoadLikeMainCases") public void testBicycleSafetyLikeLinkRoad(OSMWithTags mainRoad, OSMWithTags linkRoad) { - var resultMain = wps.getDataForWay(mainRoad).getBicycleSafetyFeatures(); - var resultLink = wps.getDataForWay(linkRoad).getBicycleSafetyFeatures(); + var resultMain = wps.getDataForWay(mainRoad).bicycleSafety(); + var resultLink = wps.getDataForWay(linkRoad).bicycleSafety(); assertEquals(resultMain, resultLink); } diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java index d3a947b217c..23f35623bd5 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java @@ -82,13 +82,13 @@ public void mixin() { var withoutFoo = new OSMWithTags(); withoutFoo.addTag("tag", "imaginary"); - assertEquals(2, wps.getDataForWay(withoutFoo).getBicycleSafetyFeatures().back()); + assertEquals(2, wps.getDataForWay(withoutFoo).bicycleSafety().back()); // the mixin for foo=bar reduces the bike safety factor var withFoo = new OSMWithTags(); withFoo.addTag("tag", "imaginary"); withFoo.addTag("foo", "bar"); - assertEquals(1, wps.getDataForWay(withFoo).getBicycleSafetyFeatures().back()); + assertEquals(1, wps.getDataForWay(withFoo).bicycleSafety().back()); } public void testAccessNo() { diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java index 3abf5f73dc7..06369baddc8 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/PortlandMapperTest.java @@ -53,7 +53,7 @@ public class PortlandMapperTest { void walkSafety(OSMWithTags way, double expected) { var score = wps.getDataForWay(way); - var ws = score.getWalkSafetyFeatures(); + var ws = score.walkSafety(); assertEquals(expected, ws.forward(), delta); assertEquals(expected, ws.back(), delta); } diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java index f26964efb60..f4bac0c7949 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java @@ -34,7 +34,7 @@ void mixinLeftSide() { var cycleway = WayTestData.cyclewayLeft(); WayPropertySet wps = wps(); SafetyFeatures expected = new SafetyFeatures(1, 5); - assertEquals(expected, wps.getDataForWay(cycleway).getBicycleSafetyFeatures()); + assertEquals(expected, wps.getDataForWay(cycleway).bicycleSafety()); } @Nonnull From 4ef39a182635cf1a1a8fc342edfc280fcf47ab2e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 1 Sep 2023 23:18:07 +0200 Subject: [PATCH 081/118] Move method from OSMFilter into instance --- .../graph_builder/module/osm/OsmDatabase.java | 3 +- .../graph_builder/module/osm/OsmFilter.java | 55 ------------------- .../module/osm/VertexGenerator.java | 2 +- .../module/osm/WalkableAreaBuilder.java | 6 +- .../openstreetmap/model/OSMWithTags.java | 53 ++++++++++++++++++ .../wayproperty/WayPropertySet.java | 3 +- .../model/vertex/BarrierVertexTest.java | 20 +++---- 7 files changed, 68 insertions(+), 74 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 47a9bc34e23..a4aa4456be6 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -796,8 +796,7 @@ private void processMultipolygonRelations() { * Handler for a new Area (single way area or multipolygon relations) */ private void newArea(Area area) { - StreetTraversalPermission permissions = OsmFilter.getPermissionsForEntity( - area.parent, + StreetTraversalPermission permissions = area.parent.getPermissionsForEntity( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index 06faa10d1f1..791f339dcbc 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -9,61 +9,6 @@ */ public class OsmFilter { - public static StreetTraversalPermission getPermissionsForEntity( - OSMWithTags entity, - StreetTraversalPermission def - ) { - StreetTraversalPermission permission = null; - - /* - * Only a few tags are examined here, because we only care about modes supported by OTP - * (wheelchairs are not of concern here) - * - * Only a few values are checked for, all other values are presumed to be permissive (=> - * This may not be perfect, but is closer to reality, since most people don't follow the - * rules perfectly ;-) - */ - if (entity.isGeneralAccessDenied()) { - // this can actually be overridden - permission = StreetTraversalPermission.NONE; - } else { - permission = def; - } - - if (entity.isVehicleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } else if (entity.isVehicleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.BICYCLE_AND_CAR); - } - - if (entity.isMotorcarExplicitlyDenied() || entity.isMotorVehicleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.CAR); - } else if (entity.isMotorcarExplicitlyAllowed() || entity.isMotorVehicleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.CAR); - } - - if (entity.isBicycleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.BICYCLE); - } else if (entity.isBicycleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.BICYCLE); - } - - if (entity.isPedestrianExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.PEDESTRIAN); - } else if (entity.isPedestrianExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.PEDESTRIAN); - } - - if (entity.isUnderConstruction()) { - permission = StreetTraversalPermission.NONE; - } - - if (permission == null) { - return def; - } - return permission; - } - /** * Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions * for travel along and against the way. diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 405743ccf71..b7aba9d127b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -92,7 +92,7 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { if (node.isBarrier()) { BarrierVertex bv = vertexFactory.barrier(nid, coordinate); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(node, BarrierVertex.defaultBarrierPermissions) + node.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); iv = bv; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 0d624769b2d..36ceb07a224 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -500,8 +500,7 @@ private Set createSegments( Area area = intersects.get(0); OSMWithTags areaEntity = area.parent; - StreetTraversalPermission areaPermissions = OsmFilter.getPermissionsForEntity( - areaEntity, + StreetTraversalPermission areaPermissions = areaEntity.getPermissionsForEntity( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); @@ -669,8 +668,7 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection namedArea.setOriginalEdges(intersection); - StreetTraversalPermission permission = OsmFilter.getPermissionsForEntity( - areaEntity, + StreetTraversalPermission permission = areaEntity.getPermissionsForEntity( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); namedArea.setPermission(permission); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 1d723faf367..0f5fc42e383 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -19,6 +19,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.graph_builder.module.osm.OsmModule; import org.opentripplanner.openstreetmap.OsmProvider; +import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.transit.model.basic.Accessibility; /** @@ -46,6 +47,58 @@ public static boolean isTrue(String tagValue) { return ("yes".equals(tagValue) || "1".equals(tagValue) || "true".equals(tagValue)); } + public StreetTraversalPermission getPermissionsForEntity(StreetTraversalPermission def) { + StreetTraversalPermission permission; + + /* + * Only a few tags are examined here, because we only care about modes supported by OTP + * (wheelchairs are not of concern here) + * + * Only a few values are checked for, all other values are presumed to be permissive (=> + * This may not be perfect, but is closer to reality, since most people don't follow the + * rules perfectly ;-) + */ + if (isGeneralAccessDenied()) { + // this can actually be overridden + permission = StreetTraversalPermission.NONE; + } else { + permission = def; + } + + if (isVehicleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } else if (isVehicleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE_AND_CAR); + } + + if (isMotorcarExplicitlyDenied() || isMotorVehicleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.CAR); + } else if (isMotorcarExplicitlyAllowed() || isMotorVehicleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.CAR); + } + + if (isBicycleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE); + } else if (isBicycleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE); + } + + if (isPedestrianExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.PEDESTRIAN); + } else if (isPedestrianExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.PEDESTRIAN); + } + + if (isUnderConstruction()) { + permission = StreetTraversalPermission.NONE; + } + + if (permission == null) { + return def; + } + return permission; + } + /** * Gets the id. */ diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index 57839da588e..18472e4ce63 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -16,7 +16,6 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issues.ConflictingBikeTags; -import org.opentripplanner.graph_builder.module.osm.OsmFilter; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.BestMatchSpecifier; import org.opentripplanner.openstreetmap.wayproperty.specifier.OsmSpecifier; @@ -71,7 +70,7 @@ private static StreetTraversalPermission getPermissionsForWay( StreetTraversalPermission def, DataImportIssueStore issueStore ) { - StreetTraversalPermission permissions = OsmFilter.getPermissionsForEntity(way, def); + StreetTraversalPermission permissions = way.getPermissionsForEntity(def); /* * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle diff --git a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java index ab488bdb82a..04754c0720f 100644 --- a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java +++ b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java @@ -8,8 +8,8 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.graph_builder.module.osm.OsmFilter; import org.opentripplanner.openstreetmap.model.OSMNode; +import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model._data.StreetModelForTest; @@ -32,35 +32,35 @@ public void testBarrierPermissions() { String label = "simpleBarrier"; BarrierVertex bv = new BarrierVertex(simpleBarier.lon, simpleBarier.lat, 0); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("foot", "yes"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "yes"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("access", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("motor_vehicle", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(simpleBarier, BarrierVertex.defaultBarrierPermissions) + simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -69,7 +69,7 @@ public void testBarrierPermissions() { complexBarrier.addTag("access", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(complexBarrier, BarrierVertex.defaultBarrierPermissions) + complexBarrier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); @@ -78,7 +78,7 @@ public void testBarrierPermissions() { noBikeBollard.addTag("bicycle", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(noBikeBollard, BarrierVertex.defaultBarrierPermissions) + noBikeBollard.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -87,7 +87,7 @@ public void testBarrierPermissions() { accessBarrier.addTag("access", "no"); bv.setBarrierPermissions( - OsmFilter.getPermissionsForEntity(accessBarrier, BarrierVertex.defaultBarrierPermissions) + accessBarrier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); } From f8b3f9c03b8d26710d7d62b89a95daf2d0545406 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Sep 2023 08:51:39 +0200 Subject: [PATCH 082/118] Move permissions method into OSMWithTags --- .../graph_builder/module/osm/OsmDatabase.java | 2 +- .../module/osm/VertexGenerator.java | 4 +- .../module/osm/WalkableAreaBuilder.java | 4 +- .../openstreetmap/model/OSMWithTags.java | 34 ++++++++- .../wayproperty/WayPropertySet.java | 74 +------------------ .../model/vertex/BarrierVertexTest.java | 19 +++-- 6 files changed, 49 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index a4aa4456be6..a1a795ef112 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -796,7 +796,7 @@ private void processMultipolygonRelations() { * Handler for a new Area (single way area or multipolygon relations) */ private void newArea(Area area) { - StreetTraversalPermission permissions = area.parent.getPermissionsForEntity( + StreetTraversalPermission permissions = area.parent.reducePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index b7aba9d127b..d38a469bdac 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -91,9 +91,7 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { if (node.isBarrier()) { BarrierVertex bv = vertexFactory.barrier(nid, coordinate); - bv.setBarrierPermissions( - node.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) - ); + bv.setBarrierPermissions(node.reducePermissions(BarrierVertex.defaultBarrierPermissions)); iv = bv; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 36ceb07a224..ff2f908874b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -500,7 +500,7 @@ private Set createSegments( Area area = intersects.get(0); OSMWithTags areaEntity = area.parent; - StreetTraversalPermission areaPermissions = areaEntity.getPermissionsForEntity( + StreetTraversalPermission areaPermissions = areaEntity.reducePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); @@ -668,7 +668,7 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection namedArea.setOriginalEdges(intersection); - StreetTraversalPermission permission = areaEntity.getPermissionsForEntity( + StreetTraversalPermission permission = areaEntity.reducePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); namedArea.setPermission(permission); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 0f5fc42e383..b28ab186600 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -47,7 +47,7 @@ public static boolean isTrue(String tagValue) { return ("yes".equals(tagValue) || "1".equals(tagValue) || "true".equals(tagValue)); } - public StreetTraversalPermission getPermissionsForEntity(StreetTraversalPermission def) { + public StreetTraversalPermission reducePermissions(StreetTraversalPermission def) { StreetTraversalPermission permission; /* @@ -96,6 +96,38 @@ public StreetTraversalPermission getPermissionsForEntity(StreetTraversalPermissi if (permission == null) { return def; } + + /* + * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle + * rules: default: permissions; + * + * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes + * any time it is forbidden to ride them, so the only thing to do here is to remove bike + * permissions + * + * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these + * permissions for bikes only + * + * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set + * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle + * + * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like + * oneway:bicycle=yes + */ + + // Compute bike permissions, check consistency. + boolean forceBikes = false; + if (isBicycleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE); + forceBikes = true; + } + + if (isBicycleDismountForced()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE); + if (forceBikes) { + //issueStore.add(new ConflictingBikeTags(way)); + } + } return permission; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index 18472e4ce63..b3ba595c540 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -14,8 +14,6 @@ import java.util.regex.Pattern; import org.opentripplanner.framework.functional.FunctionUtils.TriFunction; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.graph_builder.issues.ConflictingBikeTags; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.specifier.BestMatchSpecifier; import org.opentripplanner.openstreetmap.wayproperty.specifier.OsmSpecifier; @@ -62,66 +60,6 @@ public class WayPropertySet { /** The WayProperties applied to all ways that do not match any WayPropertyPicker. */ private final WayProperties defaultProperties; - /** - * Computes permissions for an OSMWay. - */ - private static StreetTraversalPermission getPermissionsForWay( - OSMWithTags way, - StreetTraversalPermission def, - DataImportIssueStore issueStore - ) { - StreetTraversalPermission permissions = way.getPermissionsForEntity(def); - - /* - * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle - * rules: default: permissions; - * - * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes - * any time it is forbidden to ride them, so the only thing to do here is to remove bike - * permissions - * - * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these - * permissions for bikes only - * - * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set - * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle - * - * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like - * oneway:bicycle=yes - */ - - // Compute pedestrian permissions. - if (way.isPedestrianExplicitlyAllowed()) { - permissions = permissions.add(StreetTraversalPermission.PEDESTRIAN); - } else if (way.isPedestrianExplicitlyDenied()) { - permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); - } - - // Check for foot=discouraged, if applicable - if (false && way.hasTag("foot") && way.getTag("foot").equals("discouraged")) { - permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN); - } - - // Compute bike permissions, check consistency. - boolean forceBikes = false; - if (way.isBicycleExplicitlyAllowed()) { - permissions = permissions.add(StreetTraversalPermission.BICYCLE); - forceBikes = true; - } - - if ( - way.isBicycleDismountForced() || - (false && way.hasTag("bicycle") && way.getTag("bicycle").equals("discouraged")) - ) { - permissions = permissions.remove(StreetTraversalPermission.BICYCLE); - if (forceBikes) { - issueStore.add(new ConflictingBikeTags(way)); - } - } - - return permissions; - } - public List getMixins() { return mixins; } @@ -182,16 +120,10 @@ public WayProperties getDataForWay(OSMWithTags way) { float forwardSpeed = getCarSpeedForWay(way, false); float backSpeed = getCarSpeedForWay(way, true); - StreetTraversalPermission permission = getPermissionsForWay( - way, - forwardResult.getPermission(), - DataImportIssueStore.NOOP - ); + var permission = way.reducePermissions(forwardResult.getPermission()); - StreetTraversalPermission backwardPermission = getPermissionsForWay( - way, - backwardResult.getPermission(), - DataImportIssueStore.NOOP + var backwardPermission = way.reducePermissions( + backwardResult.getPermission() ); WayProperties result = forwardResult diff --git a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java index 04754c0720f..a748e263d7f 100644 --- a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java +++ b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java @@ -9,7 +9,6 @@ import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.openstreetmap.model.OSMNode; -import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model._data.StreetModelForTest; @@ -32,35 +31,35 @@ public void testBarrierPermissions() { String label = "simpleBarrier"; BarrierVertex bv = new BarrierVertex(simpleBarier.lon, simpleBarier.lat, 0); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("foot", "yes"); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "yes"); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("access", "no"); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("motor_vehicle", "no"); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "no"); bv.setBarrierPermissions( - simpleBarier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -69,7 +68,7 @@ public void testBarrierPermissions() { complexBarrier.addTag("access", "no"); bv.setBarrierPermissions( - complexBarrier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + complexBarrier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); @@ -78,7 +77,7 @@ public void testBarrierPermissions() { noBikeBollard.addTag("bicycle", "no"); bv.setBarrierPermissions( - noBikeBollard.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + noBikeBollard.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -87,7 +86,7 @@ public void testBarrierPermissions() { accessBarrier.addTag("access", "no"); bv.setBarrierPermissions( - accessBarrier.getPermissionsForEntity(BarrierVertex.defaultBarrierPermissions) + accessBarrier.reducePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); } From b2d02197140c5a73b73c7a7e5ae226c28cb1a1c1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Sep 2023 22:05:26 +0200 Subject: [PATCH 083/118] Convert static method to instance one --- .../graph_builder/module/osm/OsmFilter.java | 56 ------------------- .../graph_builder/module/osm/OsmModule.java | 2 +- .../openstreetmap/model/OSMWay.java | 53 ++++++++++++++++++ .../wayproperty/WayPropertySet.java | 5 +- .../openstreetmap/model/OSMWayTest.java | 3 +- 5 files changed, 56 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java index 791f339dcbc..9367b6f27d5 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java @@ -1,68 +1,12 @@ package org.opentripplanner.graph_builder.module.osm; -import org.opentripplanner.openstreetmap.model.OSMWay; import org.opentripplanner.openstreetmap.model.OSMWithTags; -import org.opentripplanner.street.model.StreetTraversalPermission; /** * */ public class OsmFilter { - /** - * Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions - * for travel along and against the way. - */ - public static StreetTraversalPermissionPair getPermissions( - StreetTraversalPermission permissions, - OSMWay way - ) { - StreetTraversalPermission permissionsFront = permissions; - StreetTraversalPermission permissionsBack = permissions; - - // Check driving direction restrictions. - if (way.isOneWayForwardDriving() || way.isRoundabout()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } - if (way.isOneWayReverseDriving()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } - - // Check bike direction restrictions. - if (way.isOneWayForwardBicycle()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); - } - if (way.isOneWayReverseBicycle()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); - } - - // TODO(flamholz): figure out what this is for. - String oneWayBicycle = way.getTag("oneway:bicycle"); - if (OSMWithTags.isFalse(oneWayBicycle) || way.isTagTrue("bicycle:backwards")) { - if (permissions.allows(StreetTraversalPermission.BICYCLE)) { - permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE); - permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); - } - } - - //This needs to be after adding permissions for oneway:bicycle=no - //removes bicycle permission when bicycles need to use sidepath - //TAG: bicycle:forward=use_sidepath - if (way.isForwardDirectionSidepath()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); - } - - //TAG bicycle:backward=use_sidepath - if (way.isReverseDirectionSidepath()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); - } - - if (way.isOpposableCycleway()) { - permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); - } - return new StreetTraversalPermissionPair(permissionsFront, permissionsBack); - } - /** * Determine whether any mode can or should ever traverse the given way. If not, we leave the way * out of the OTP graph. Potentially routable ways are those that have the tags : highway=* diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 6b2b433548f..ee9d93b6b0f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -464,7 +464,7 @@ private StreetEdgePair getEdgesForStreet( StreetEdge backStreet = null; double length = getGeometryLengthMeters(geometry); - var permissionPair = OsmFilter.getPermissions(permissions, way); + var permissionPair = way.splitPermissions(permissions); var permissionsFront = permissionPair.main(); var permissionsBack = permissionPair.back(); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index a8bc2b34ffe..f8510ed037c 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -3,6 +3,8 @@ import gnu.trove.list.TLongList; import gnu.trove.list.array.TLongArrayList; import java.util.Set; +import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair; +import org.opentripplanner.street.model.StreetTraversalPermission; public class OSMWay extends OSMWithTags { @@ -14,6 +16,57 @@ public class OSMWay extends OSMWithTags { ); private final TLongList nodes = new TLongArrayList(); + /** + * Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions + * for travel along and against the way. + */ + public StreetTraversalPermissionPair splitPermissions(StreetTraversalPermission permissions) { + StreetTraversalPermission permissionsFront = permissions; + StreetTraversalPermission permissionsBack = permissions; + + // Check driving direction restrictions. + if (isOneWayForwardDriving() || isRoundabout()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } + if (isOneWayReverseDriving()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } + + // Check bike direction restrictions. + if (isOneWayForwardBicycle()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); + } + if (isOneWayReverseBicycle()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); + } + + // TODO(flamholz): figure out what this is for. + String oneWayBicycle = getTag("oneway:bicycle"); + if (isFalse(oneWayBicycle) || isTagTrue("bicycle:backwards")) { + if (permissions.allows(StreetTraversalPermission.BICYCLE)) { + permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE); + permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); + } + } + + //This needs to be after adding permissions for oneway:bicycle=no + //removes bicycle permission when bicycles need to use sidepath + //TAG: bicycle:forward=use_sidepath + if (isForwardDirectionSidepath()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); + } + + //TAG bicycle:backward=use_sidepath + if (isReverseDirectionSidepath()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); + } + + if (isOpposableCycleway()) { + permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); + } + return new StreetTraversalPermissionPair(permissionsFront, permissionsBack); + } + public void addNodeRef(long nodeRef) { nodes.add(nodeRef); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index b3ba595c540..ee4af226ed5 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -26,7 +26,6 @@ /** * Information given to the GraphBuilder about how to assign permissions, safety values, names, etc. * to edges based on OSM tags. - * TODO rename so that the connection with OSM tags is obvious *

* WayPropertyPickers, CreativeNamePickers, SlopeOverridePickers, and SpeedPickers are applied to ways based on how well * their OSMSpecifiers match a given OSM way. Generally one OSMSpecifier will win out over all the others based on the @@ -122,9 +121,7 @@ public WayProperties getDataForWay(OSMWithTags way) { var permission = way.reducePermissions(forwardResult.getPermission()); - var backwardPermission = way.reducePermissions( - backwardResult.getPermission() - ); + var backwardPermission = way.reducePermissions(backwardResult.getPermission()); WayProperties result = forwardResult .mutate() diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index 453494d580d..ce2b9bb77e6 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.graph_builder.module.osm.OsmFilter; import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair; import org.opentripplanner.openstreetmap.wayproperty.WayProperties; import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; @@ -360,6 +359,6 @@ private StreetTraversalPermissionPair getWayProperties(OSMWay way) { WayProperties wayData = wayPropertySet.getDataForWay(way); StreetTraversalPermission def = wayData.getPermission(); - return OsmFilter.getPermissions(def, way); + return way.splitPermissions(def); } } From 84202008bff161a98cc96e51624711c9c3dc2805 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Sep 2023 22:50:29 +0200 Subject: [PATCH 084/118] Move tests to appropriate file --- .../module/osm/ElevatorProcessor.java | 2 +- .../graph_builder/module/osm/OsmModule.java | 4 +- .../wayproperty/MixinPropertiesBuilder.java | 4 +- .../openstreetmap/model/OSMWayTest.java | 217 ------------- .../wayproperty/WayPropertySetTest.java | 299 ++++++++++++++++-- 5 files changed, 270 insertions(+), 256 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java index b3a7f1d76f1..3a0a9fa404b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java @@ -34,7 +34,7 @@ /** * Contains the logic for extracting elevator data from OSM and converting it to edges. *

- * I depends heavily on the idiosyncratic processing of the OSM data in {@link OsmModule} + * It depends heavily on the idiosyncratic processing of the OSM data in {@link OsmModule} * which is the reason this is not a public class. */ class ElevatorProcessor { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index ee9d93b6b0f..4ef3451da60 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -527,12 +527,12 @@ private StreetEdge getEdgeForStreet( .withLink(way.isLink()) .withRoundabout(way.isRoundabout()) .withSlopeOverride(way.getOsmProvider().getWayPropertySet().getSlopeOverride(way)) - .withStairs(way.isSteps()); + .withStairs(way.isSteps()) + .withWheelchairAccessible(way.isWheelchairAccessible()); if (!way.hasTag("name") && !way.hasTag("ref")) { seb.withBogusName(true); } - seb.withWheelchairAccessible(way.isWheelchairAccessible()); // < 0.04: account for if (carSpeed < 0.04) { diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/MixinPropertiesBuilder.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/MixinPropertiesBuilder.java index dcc007caef7..dd16a28e3ec 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/MixinPropertiesBuilder.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/MixinPropertiesBuilder.java @@ -8,8 +8,8 @@ */ public class MixinPropertiesBuilder { - private SafetyFeatures bicycleSafety = new SafetyFeatures(1, 1); - private SafetyFeatures walkSafety = new SafetyFeatures(1, 1); + private SafetyFeatures bicycleSafety = SafetyFeatures.DEFAULT; + private SafetyFeatures walkSafety = SafetyFeatures.DEFAULT; public static MixinPropertiesBuilder ofWalkSafety(double safety) { return new MixinPropertiesBuilder().walkSafety(safety); diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index ce2b9bb77e6..7d17924fe73 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -130,215 +130,6 @@ void testIsOpposableCycleway() { assertTrue(way.isOpposableCycleway()); } - /** - * Tests if cars can drive on unclassified highways with bicycleDesignated - *

- * Check for bug #1878 and PR #1880 - */ - @Test - void testCarPermission() { - OSMWay way = new OSMWay(); - way.addTag("highway", "unclassified"); - - var permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - - way.addTag("bicycle", "designated"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - } - - /** - * Tests that motorcar/bicycle/foot private don't add permissions but yes add permission if access - * is no - */ - @Test - void testMotorCarTagAllowedPermissions() { - OSMWay way = new OSMWay(); - way.addTag("highway", "residential"); - var permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - - way.addTag("access", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - - way.addTag("motorcar", "private"); - way.addTag("bicycle", "private"); - way.addTag("foot", "private"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - - way.addTag("motorcar", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - - way.addTag("bicycle", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE_AND_CAR)); - - way.addTag("foot", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - } - - /** - * Tests that motorcar/bicycle/foot private don't add permissions but no remove permission if - * access is yes - */ - @Test - void testMotorCarTagDeniedPermissions() { - OSMWay way = new OSMWay(); - way.addTag("highway", "residential"); - var permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - - way.addTag("motorcar", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE)); - - way.addTag("bicycle", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN)); - - way.addTag("foot", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - //normal road with specific mode of transport private only is doubtful - /*way.addTag("motorcar", "private"); - way.addTag("bicycle", "private"); - way.addTag("foot", "private"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing());*/ - } - - /** - * Tests that motor_vehicle/bicycle/foot private don't add permissions but yes add permission if - * access is no - *

- * Support for motor_vehicle was added in #1881 - */ - @Test - void testMotorVehicleTagAllowedPermissions() { - OSMWay way = new OSMWay(); - way.addTag("highway", "residential"); - var permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - - way.addTag("access", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - - way.addTag("motor_vehicle", "private"); - way.addTag("bicycle", "private"); - way.addTag("foot", "private"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - - way.addTag("motor_vehicle", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - - way.addTag("bicycle", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE_AND_CAR)); - - way.addTag("foot", "yes"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - } - - /** - * Tests that motor_vehicle/bicycle/foot private don't add permissions but no remove permission if - * access is yes - *

- * Support for motor_vehicle was added in #1881 - */ - @Test - void testMotorVehicleTagDeniedPermissions() { - OSMWay way = new OSMWay(); - way.addTag("highway", "residential"); - var permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); - - way.addTag("motor_vehicle", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE)); - - way.addTag("bicycle", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN)); - - way.addTag("foot", "no"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing()); - //normal road with specific mode of transport private only is doubtful - /*way.addTag("motor_vehicle", "private"); - way.addTag("bicycle", "private"); - way.addTag("foot", "private"); - permissionPair = getWayProperties(way); - assertTrue(permissionPair.main().allowsNothing());*/ - } - - @Test - void testSidepathPermissions() { - OSMWay way = new OSMWay(); - way.addTag("bicycle", "use_sidepath"); - way.addTag("highway", "primary"); - way.addTag("lanes", "2"); - way.addTag("maxspeed", "70"); - way.addTag("oneway", "yes"); - var permissionPair = getWayProperties(way); - - assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); - assertFalse(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); - - way = new OSMWay(); - way.addTag("bicycle:forward", "use_sidepath"); - way.addTag("highway", "tertiary"); - permissionPair = getWayProperties(way); - - assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); - assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - assertTrue(permissionPair.back().allows(StreetTraversalPermission.CAR)); - - way = new OSMWay(); - way.addTag("bicycle:backward", "use_sidepath"); - way.addTag("highway", "tertiary"); - permissionPair = getWayProperties(way); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); - assertFalse(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - assertTrue(permissionPair.back().allows(StreetTraversalPermission.CAR)); - - way = new OSMWay(); - way.addTag("highway", "tertiary"); - way.addTag("oneway", "yes"); - way.addTag("oneway:bicycle", "no"); - permissionPair = getWayProperties(way); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); - assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); - - way.addTag("bicycle:forward", "use_sidepath"); - permissionPair = getWayProperties(way); - assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); - assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); - - assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); - assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); - } - @Test void escalator() { assertFalse(WayTestData.cycleway().isEscalator()); @@ -353,12 +144,4 @@ void escalator() { escalator.addTag("conveying", "whoknows?"); assertFalse(escalator.isEscalator()); } - - private StreetTraversalPermissionPair getWayProperties(OSMWay way) { - WayPropertySet wayPropertySet = new WayPropertySet(); - WayProperties wayData = wayPropertySet.getDataForWay(way); - - StreetTraversalPermission def = wayData.getPermission(); - return way.splitPermissions(def); - } } diff --git a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java index f4bac0c7949..c5ae8b86f7d 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySetTest.java @@ -1,57 +1,288 @@ package org.opentripplanner.openstreetmap.wayproperty; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.openstreetmap.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; import static org.opentripplanner.openstreetmap.wayproperty.WayPropertiesBuilder.withModes; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; import javax.annotation.Nonnull; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair; +import org.opentripplanner.openstreetmap.model.OSMWay; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.tagmapping.OsmTagMapper; import org.opentripplanner.openstreetmap.wayproperty.specifier.ExactMatchSpecifier; import org.opentripplanner.openstreetmap.wayproperty.specifier.WayTestData; +import org.opentripplanner.street.model.StreetTraversalPermission; class WayPropertySetTest { - @Test - public void carTunnel() { - OSMWithTags tunnel = WayTestData.carTunnel(); - WayPropertySet wps = wps(); - assertEquals(CAR, wps.getDataForWay(tunnel).getPermission()); - } + @Nested + class ConditionSpecificity { - @Test - void pedestrianTunnelSpecificity() { - var tunnel = WayTestData.pedestrianTunnel(); - WayPropertySet wps = wps(); - assertEquals(NONE, wps.getDataForWay(tunnel).getPermission()); - } + @Test + public void carTunnel() { + OSMWithTags tunnel = WayTestData.carTunnel(); + WayPropertySet wps = wps(); + assertEquals(CAR, wps.getDataForWay(tunnel).getPermission()); + } + + @Test + void pedestrianTunnelSpecificity() { + var tunnel = WayTestData.pedestrianTunnel(); + WayPropertySet wps = wps(); + assertEquals(NONE, wps.getDataForWay(tunnel).getPermission()); + } - @Test - void mixinLeftSide() { - var cycleway = WayTestData.cyclewayLeft(); - WayPropertySet wps = wps(); - SafetyFeatures expected = new SafetyFeatures(1, 5); - assertEquals(expected, wps.getDataForWay(cycleway).bicycleSafety()); + @Test + void mixinLeftSide() { + var cycleway = WayTestData.cyclewayLeft(); + WayPropertySet wps = wps(); + SafetyFeatures expected = new SafetyFeatures(1, 5); + assertEquals(expected, wps.getDataForWay(cycleway).bicycleSafety()); + } + + @Nonnull + private static WayPropertySet wps() { + var wps = new WayPropertySet(); + var source = new OsmTagMapper() { + @Override + public void populateProperties(WayPropertySet props) { + props.setProperties("highway=primary", withModes(CAR)); + props.setProperties( + new ExactMatchSpecifier("highway=footway;layer=-1;tunnel=yes;indoor=yes"), + withModes(NONE) + ); + props.setMixinProperties("cycleway=lane", ofBicycleSafety(5)); + } + }; + source.populateProperties(wps); + return wps; + } } - @Nonnull - private static WayPropertySet wps() { - var wps = new WayPropertySet(); - var source = new OsmTagMapper() { - @Override - public void populateProperties(WayPropertySet props) { - props.setProperties("highway=primary", withModes(CAR)); - props.setProperties( - new ExactMatchSpecifier("highway=footway;layer=-1;tunnel=yes;indoor=yes"), - withModes(NONE) - ); - props.setMixinProperties("cycleway=lane", ofBicycleSafety(5)); - } - }; - source.populateProperties(wps); - return wps; + @Nested + class NoMapper { + + /** + * Tests if cars can drive on unclassified highways with bicycleDesignated + *

+ * Check for bug #1878 and PR #1880 + */ + @Test + void testCarPermission() { + OSMWay way = new OSMWay(); + way.addTag("highway", "unclassified"); + + var permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + + way.addTag("bicycle", "designated"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + } + + /** + * Tests that motorcar/bicycle/foot private don't add permissions but yes add permission if access + * is no + */ + @Test + void testMotorCarTagAllowedPermissions() { + OSMWay way = new OSMWay(); + way.addTag("highway", "residential"); + var permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + + way.addTag("access", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + + way.addTag("motorcar", "private"); + way.addTag("bicycle", "private"); + way.addTag("foot", "private"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + + way.addTag("motorcar", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + + way.addTag("bicycle", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE_AND_CAR)); + + way.addTag("foot", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + } + + /** + * Tests that motorcar/bicycle/foot private don't add permissions but no remove permission if + * access is yes + */ + @Test + void testMotorCarTagDeniedPermissions() { + OSMWay way = new OSMWay(); + way.addTag("highway", "residential"); + var permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + + way.addTag("motorcar", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE)); + + way.addTag("bicycle", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN)); + + way.addTag("foot", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + //normal road with specific mode of transport private only is doubtful + /*way.addTag("motorcar", "private"); + way.addTag("bicycle", "private"); + way.addTag("foot", "private"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing());*/ + } + + /** + * Tests that motor_vehicle/bicycle/foot private don't add permissions but yes add permission if + * access is no + *

+ * Support for motor_vehicle was added in #1881 + */ + @Test + void testMotorVehicleTagAllowedPermissions() { + OSMWay way = new OSMWay(); + way.addTag("highway", "residential"); + var permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + + way.addTag("access", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + + way.addTag("motor_vehicle", "private"); + way.addTag("bicycle", "private"); + way.addTag("foot", "private"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + + way.addTag("motor_vehicle", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + + way.addTag("bicycle", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE_AND_CAR)); + + way.addTag("foot", "yes"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + } + + /** + * Tests that motor_vehicle/bicycle/foot private don't add permissions but no remove permission if + * access is yes + *

+ * Support for motor_vehicle was added in #1881 + */ + @Test + void testMotorVehicleTagDeniedPermissions() { + OSMWay way = new OSMWay(); + way.addTag("highway", "residential"); + var permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.ALL)); + + way.addTag("motor_vehicle", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE)); + + way.addTag("bicycle", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allows(StreetTraversalPermission.PEDESTRIAN)); + + way.addTag("foot", "no"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing()); + //normal road with specific mode of transport private only is doubtful + /*way.addTag("motor_vehicle", "private"); + way.addTag("bicycle", "private"); + way.addTag("foot", "private"); + permissionPair = getWayProperties(way); + assertTrue(permissionPair.main().allowsNothing());*/ + } + + @Test + void testSidepathPermissions() { + OSMWay way = new OSMWay(); + way.addTag("bicycle", "use_sidepath"); + way.addTag("highway", "primary"); + way.addTag("lanes", "2"); + way.addTag("maxspeed", "70"); + way.addTag("oneway", "yes"); + var permissionPair = getWayProperties(way); + + assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); + assertFalse(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); + + way = new OSMWay(); + way.addTag("bicycle:forward", "use_sidepath"); + way.addTag("highway", "tertiary"); + permissionPair = getWayProperties(way); + + assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); + assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + assertTrue(permissionPair.back().allows(StreetTraversalPermission.CAR)); + + way = new OSMWay(); + way.addTag("bicycle:backward", "use_sidepath"); + way.addTag("highway", "tertiary"); + permissionPair = getWayProperties(way); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); + assertFalse(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + assertTrue(permissionPair.back().allows(StreetTraversalPermission.CAR)); + + way = new OSMWay(); + way.addTag("highway", "tertiary"); + way.addTag("oneway", "yes"); + way.addTag("oneway:bicycle", "no"); + permissionPair = getWayProperties(way); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); + assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); + + way.addTag("bicycle:forward", "use_sidepath"); + permissionPair = getWayProperties(way); + assertFalse(permissionPair.main().allows(StreetTraversalPermission.BICYCLE)); + assertTrue(permissionPair.back().allows(StreetTraversalPermission.BICYCLE)); + + assertTrue(permissionPair.main().allows(StreetTraversalPermission.CAR)); + assertFalse(permissionPair.back().allows(StreetTraversalPermission.CAR)); + } + + private StreetTraversalPermissionPair getWayProperties(OSMWay way) { + WayPropertySet wayPropertySet = new WayPropertySet(); + WayProperties wayData = wayPropertySet.getDataForWay(way); + + StreetTraversalPermission def = wayData.getPermission(); + return way.splitPermissions(def); + } } } From 18945a10d85aecdc4f9bffd99409a3075bd7e824 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Sep 2023 22:59:42 +0200 Subject: [PATCH 085/118] Improve GermanyMapperTest --- .../tagmapping/GermanyMapperTest.java | 310 +++++++++--------- 1 file changed, 164 insertions(+), 146 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java index 2ac9d18a8e6..00b1049bdda 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/GermanyMapperTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; @@ -20,70 +21,83 @@ public class GermanyMapperTest { /** * Test that bike safety factors are calculated accurately */ - @Test - public void testBikeSafety() { - OSMWithTags way; - - // way 361961158 - way = new OSMWithTags(); - way.addTag("bicycle", "yes"); - way.addTag("foot", "designated"); - way.addTag("footway", "sidewalk"); - way.addTag("highway", "footway"); - way.addTag("lit", "yes"); - way.addTag("oneway", "no"); - way.addTag("traffic_sign", "DE:239,1022-10"); - assertEquals(1.2, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); - - way = new OSMWithTags(); - way.addTag("cycleway", "opposite"); - way.addTag("highway", "residential"); - way.addTag("lit", "yes"); - way.addTag("maxspeed", "30"); - way.addTag("name", "Freibadstraße"); - way.addTag("oneway", "yes"); - way.addTag("oneway:bicycle", "no"); - way.addTag("parking:lane:left", "parallel"); - way.addTag("parking:lane:right", "no_parking"); - way.addTag("sidewalk", "both"); - way.addTag("source:maxspeed", "DE:zone:30"); - way.addTag("surface", "asphalt"); - way.addTag("width", "6.5"); - way.addTag("zone:traffic", "DE:urban"); - assertEquals(0.9, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); - // walk safety should be default - assertEquals(1, wps.getDataForWay(way).walkSafety().forward(), epsilon); - - // way332589799 (Radschnellweg BW1) - way = new OSMWithTags(); - way.addTag("bicycle", "designated"); - way.addTag("class:bicycle", "2"); - way.addTag("class:bicycle:roadcycling", "1"); - way.addTag("highway", "track"); - way.addTag("horse", "forestry"); - way.addTag("lcn", "yes"); - way.addTag("lit", "yes"); - way.addTag("maxspeed", "30"); - way.addTag("motor_vehicle", "forestry"); - way.addTag("name", "Römerstraße"); - way.addTag("smoothness", "excellent"); - way.addTag("source:maxspeed", "sign"); - way.addTag("surface", "asphalt"); - way.addTag("tracktype", "grade1"); - assertEquals(0.693, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); - - way = new OSMWithTags(); - way.addTag("highway", "track"); - way.addTag("motor_vehicle", "agricultural"); - way.addTag("surface", "asphalt"); - way.addTag("tracktype", "grade1"); - way.addTag("traffic_sign", "DE:260,1026-36"); - way.addTag("width", "2.5"); - assertEquals(1.0, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); + @Nested + class BikeSafety { + + @Test + void testBikeSafety() { + OSMWithTags way; + + // way 361961158 + way = new OSMWithTags(); + way.addTag("bicycle", "yes"); + way.addTag("foot", "designated"); + way.addTag("footway", "sidewalk"); + way.addTag("highway", "footway"); + way.addTag("lit", "yes"); + way.addTag("oneway", "no"); + way.addTag("traffic_sign", "DE:239,1022-10"); + assertEquals(1.2, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); + } + + @Test + void cyclewayOpposite() { + var way = new OSMWithTags(); + way.addTag("cycleway", "opposite"); + way.addTag("highway", "residential"); + way.addTag("lit", "yes"); + way.addTag("maxspeed", "30"); + way.addTag("name", "Freibadstraße"); + way.addTag("oneway", "yes"); + way.addTag("oneway:bicycle", "no"); + way.addTag("parking:lane:left", "parallel"); + way.addTag("parking:lane:right", "no_parking"); + way.addTag("sidewalk", "both"); + way.addTag("source:maxspeed", "DE:zone:30"); + way.addTag("surface", "asphalt"); + way.addTag("width", "6.5"); + way.addTag("zone:traffic", "DE:urban"); + assertEquals(0.9, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); + // walk safety should be default + assertEquals(1, wps.getDataForWay(way).walkSafety().forward(), epsilon); + } + + @Test + void bikePath() { + // way332589799 (Radschnellweg BW1) + var way = new OSMWithTags(); + way.addTag("bicycle", "designated"); + way.addTag("class:bicycle", "2"); + way.addTag("class:bicycle:roadcycling", "1"); + way.addTag("highway", "track"); + way.addTag("horse", "forestry"); + way.addTag("lcn", "yes"); + way.addTag("lit", "yes"); + way.addTag("maxspeed", "30"); + way.addTag("motor_vehicle", "forestry"); + way.addTag("name", "Römerstraße"); + way.addTag("smoothness", "excellent"); + way.addTag("source:maxspeed", "sign"); + way.addTag("surface", "asphalt"); + way.addTag("tracktype", "grade1"); + assertEquals(0.693, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); + } + + @Test + void track() { + var way = new OSMWithTags(); + way.addTag("highway", "track"); + way.addTag("motor_vehicle", "agricultural"); + way.addTag("surface", "asphalt"); + way.addTag("tracktype", "grade1"); + way.addTag("traffic_sign", "DE:260,1026-36"); + way.addTag("width", "2.5"); + assertEquals(1.0, wps.getDataForWay(way).bicycleSafety().forward(), epsilon); + } } @Test - public void testPermissions() { + void testPermissions() { // https://www.openstreetmap.org/way/124263424 var way = new OSMWithTags(); way.addTag("highway", "track"); @@ -120,91 +134,95 @@ public void testPermissions() { assertEquals(wps.getDataForWay(way).getPermission(), StreetTraversalPermission.ALL); } - @Test - public void lcnAndRcnShouldNotBeAddedUp() { - // https://www.openstreetmap.org/way/26443041 is part of both an lcn and rcn but that shouldn't mean that - // it is to be more heavily favoured than other ways that are part of just one. - - var both = new OSMWithTags(); - both.addTag("highway", "residential"); - both.addTag("rcn", "yes"); - both.addTag("lcn", "yes"); - - var justLcn = new OSMWithTags(); - justLcn.addTag("lcn", "yes"); - justLcn.addTag("highway", "residential"); - - var residential = new OSMWithTags(); - residential.addTag("highway", "residential"); - - assertEquals( - wps.getDataForWay(both).bicycleSafety().forward(), - wps.getDataForWay(justLcn).bicycleSafety().forward(), - epsilon - ); - - assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); - - assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); - } - - @Test - public void bicycleRoadAndLcnShouldNotBeAddedUp() { - // https://www.openstreetmap.org/way/22201321 was tagged as bicycle_road without lcn - // make it so all ways tagged as some kind of cyclestreets are considered as equally safe - - var both = new OSMWithTags(); - both.addTag("highway", "residential"); - both.addTag("bicycle_road", "yes"); - both.addTag("cyclestreet", "yes"); - both.addTag("lcn", "yes"); - - var justBicycleRoad = new OSMWithTags(); - justBicycleRoad.addTag("bicycle_road", "yes"); - justBicycleRoad.addTag("highway", "residential"); - - var justCyclestreet = new OSMWithTags(); - justCyclestreet.addTag("cyclestreet", "yes"); - justCyclestreet.addTag("highway", "residential"); - - var justLcn = new OSMWithTags(); - justLcn.addTag("lcn", "yes"); - justLcn.addTag("highway", "residential"); - - var residential = new OSMWithTags(); - residential.addTag("highway", "residential"); - - assertEquals( - wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), - wps.getDataForWay(justLcn).bicycleSafety().forward(), - epsilon - ); - - assertEquals( - wps.getDataForWay(both).bicycleSafety().forward(), - wps.getDataForWay(justBicycleRoad).bicycleSafety().forward(), - epsilon - ); - - assertEquals( - wps.getDataForWay(both).bicycleSafety().forward(), - wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), - epsilon - ); - - assertEquals( - wps.getDataForWay(both).bicycleSafety().forward(), - wps.getDataForWay(justLcn).bicycleSafety().forward(), - epsilon - ); - - assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); - - assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); + @Nested + class BikeRouteNetworks { + + @Test + void lcnAndRcnShouldNotBeAddedUp() { + // https://www.openstreetmap.org/way/26443041 is part of both an lcn and rcn but that shouldn't mean that + // it is to be more heavily favoured than other ways that are part of just one. + + var both = new OSMWithTags(); + both.addTag("highway", "residential"); + both.addTag("rcn", "yes"); + both.addTag("lcn", "yes"); + + var justLcn = new OSMWithTags(); + justLcn.addTag("lcn", "yes"); + justLcn.addTag("highway", "residential"); + + var residential = new OSMWithTags(); + residential.addTag("highway", "residential"); + + assertEquals( + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), + epsilon + ); + + assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); + + assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); + } + + @Test + void bicycleRoadAndLcnShouldNotBeAddedUp() { + // https://www.openstreetmap.org/way/22201321 was tagged as bicycle_road without lcn + // make it so all ways tagged as some kind of cyclestreets are considered as equally safe + + var both = new OSMWithTags(); + both.addTag("highway", "residential"); + both.addTag("bicycle_road", "yes"); + both.addTag("cyclestreet", "yes"); + both.addTag("lcn", "yes"); + + var justBicycleRoad = new OSMWithTags(); + justBicycleRoad.addTag("bicycle_road", "yes"); + justBicycleRoad.addTag("highway", "residential"); + + var justCyclestreet = new OSMWithTags(); + justCyclestreet.addTag("cyclestreet", "yes"); + justCyclestreet.addTag("highway", "residential"); + + var justLcn = new OSMWithTags(); + justLcn.addTag("lcn", "yes"); + justLcn.addTag("highway", "residential"); + + var residential = new OSMWithTags(); + residential.addTag("highway", "residential"); + + assertEquals( + wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), + epsilon + ); + + assertEquals( + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justBicycleRoad).bicycleSafety().forward(), + epsilon + ); + + assertEquals( + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justCyclestreet).bicycleSafety().forward(), + epsilon + ); + + assertEquals( + wps.getDataForWay(both).bicycleSafety().forward(), + wps.getDataForWay(justLcn).bicycleSafety().forward(), + epsilon + ); + + assertEquals(wps.getDataForWay(both).bicycleSafety().forward(), 0.6859, epsilon); + + assertEquals(wps.getDataForWay(residential).bicycleSafety().forward(), 0.98, epsilon); + } } @Test - public void setCorrectPermissionsForRoundabouts() { + void setCorrectPermissionsForRoundabouts() { // https://www.openstreetmap.org/way/184185551 var residential = new OSMWithTags(); residential.addTag("highway", "residential"); @@ -222,7 +240,7 @@ public void setCorrectPermissionsForRoundabouts() { } @Test - public void setCorrectBikeSafetyValuesForBothDirections() { + void setCorrectBikeSafetyValuesForBothDirections() { // https://www.openstreetmap.org/way/13420871 var residential = new OSMWithTags(); residential.addTag("highway", "residential"); @@ -238,7 +256,7 @@ public void setCorrectBikeSafetyValuesForBothDirections() { } @Test - public void setCorrectPermissionsForSteps() { + void setCorrectPermissionsForSteps() { // https://www.openstreetmap.org/way/64359102 var steps = new OSMWithTags(); steps.addTag("highway", "steps"); @@ -246,7 +264,7 @@ public void setCorrectPermissionsForSteps() { } @Test - public void testGermanAutobahnSpeed() { + void testGermanAutobahnSpeed() { // https://www.openstreetmap.org/way/10879847 var alzentalstr = new OSMWithTags(); alzentalstr.addTag("highway", "residential"); From 06f042439fceee3afd0287130e7c4bf7a06a1216 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 09:15:29 +0200 Subject: [PATCH 086/118] Add test for multipolygon intersections --- .../module/osm/WalkableAreaBuilder.java | 11 ++++------ .../module/osm/WalkableAreaBuilderTest.java | 19 ++++++++++++++++-- .../module/osm/wendlingen-bahnhof.osm.pbf | Bin 0 -> 60837 bytes 3 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/org/opentripplanner/graph_builder/module/osm/wendlingen-bahnhof.osm.pbf diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index ff2f908874b..13bd43d7076 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -139,7 +139,6 @@ public void buildWithoutVisibility(AreaGroup group) { // the points corresponding to concave or hole vertices // or those linked to ways HashSet alreadyAddedEdges = new HashSet<>(); - // we also want to fill in the edges of this area anyway, because we can, // and to avoid the numerical problems that they tend to cause for (Area area : group.areas) { @@ -601,8 +600,7 @@ private Set createSegments( if (lineParts.getLength() > 0.000001) { Coordinate edgeCoordinate = null; // this is either a LineString or a MultiLineString (we hope) - if (lineParts instanceof MultiLineString) { - MultiLineString mls = (MultiLineString) lineParts; + if (lineParts instanceof MultiLineString mls) { boolean found = false; for (int i = 0; i < mls.getNumGeometries(); ++i) { LineString segment = (LineString) mls.getGeometryN(i); @@ -618,8 +616,8 @@ private Set createSegments( } } } - } else if (lineParts instanceof LineString) { - edgeCoordinate = ((LineString) lineParts).getEndPoint().getCoordinate(); + } else if (lineParts instanceof LineString lineString) { + edgeCoordinate = lineString.getEndPoint().getCoordinate(); } else { continue; } @@ -681,8 +679,7 @@ private boolean isPlatformLinkingEndpoint(OsmVertex osmVertex) { boolean isCandidate = false; Vertex start = null; for (Edge e : osmVertex.getIncoming()) { - if (e instanceof StreetEdge && !(e instanceof AreaEdge)) { - StreetEdge se = (StreetEdge) e; + if (e instanceof StreetEdge se && !(e instanceof AreaEdge)) { if (Arrays.asList(1, 2, 3).contains(se.getPermission().code)) { isCandidate = true; start = se.getFromVertex(); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java index 73f3932e8c0..245d919d9cb 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java @@ -17,8 +17,6 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.naming.DefaultNamer; import org.opentripplanner.openstreetmap.OsmProvider; @@ -181,6 +179,23 @@ public void testEntranceStopAreaLinking(TestInfo testInfo) { assertEquals(1, elevatorConnection.size()); } + @Test + @OsmFile("wendlingen-bahnhof.osm.pbf") + @Visibility(true) + @MaxAreaNodes(50) + public void testSeveralIntersections(TestInfo testInfo) { + var graph = buildGraph(testInfo); + var areas = graph + .getEdgesOfType(AreaEdge.class) + .stream() + .filter(a -> a.getToVertex().getLabel().equals(VertexLabel.osm(2522105666L))) + .map(AreaEdge::getArea) + .distinct() + .toList(); + assertEquals(1, areas.size()); + assertFalse(areas.get(0).getAreas().isEmpty()); + } + private static boolean hasNodeId(AreaEdge a, long nodeId) { return ( a.getToVertex().getLabel() instanceof OsmNodeOnLevelLabel label && label.nodeId() == nodeId diff --git a/src/test/resources/org/opentripplanner/graph_builder/module/osm/wendlingen-bahnhof.osm.pbf b/src/test/resources/org/opentripplanner/graph_builder/module/osm/wendlingen-bahnhof.osm.pbf new file mode 100644 index 0000000000000000000000000000000000000000..13bcd69211f54078b563f3e64a6c77a3dba739f3 GIT binary patch literal 60837 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 Date: Wed, 13 Sep 2023 21:40:19 +0200 Subject: [PATCH 087/118] Move code to separate line --- .../ext/gtfsgraphqlapi/GraphQLIntegrationTest.java | 3 ++- .../org/opentripplanner/transit/service/TransitModel.java | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index 56466db1e03..c57a05735c0 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -115,7 +115,8 @@ static void setup() { var stopModel = StopModel.of(); PlanTestConstants.listStops().forEach(sl -> stopModel.withRegularStop((RegularStop) sl)); - var transitModel = new TransitModel(stopModel.build(), DEDUPLICATOR); + var model = stopModel.build(); + var transitModel = new TransitModel(model, DEDUPLICATOR); final TripPattern pattern = TransitModelForTest.pattern(BUS).build(); var trip = TransitModelForTest.trip("123").withHeadsign(I18NString.of("Trip Headsign")).build(); diff --git a/src/main/java/org/opentripplanner/transit/service/TransitModel.java b/src/main/java/org/opentripplanner/transit/service/TransitModel.java index e845aa27dfb..c2262fafbc4 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitModel.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitModel.java @@ -375,13 +375,6 @@ public Map getTripOnServiceDates() { return tripOnServiceDates; } - /** - * Return a transit stop, a flex stop location or flex stop location group. - */ - public StopLocation getStopLocationById(FeedScopedId id) { - return stopModel.getStopLocation(id); - } - /** * Map from GTFS ServiceIds to integers close to 0. Allows using BitSets instead of * {@code Set}. An empty Map is created before the Graph is built to allow registering IDs From 15e8fb48568f99115e052a31fe8daed3d2e22a19 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 22:00:41 +0200 Subject: [PATCH 088/118] Move OsmFilter and make it package-private --- .../graph_builder/module/osm/OsmDatabase.java | 11 ++--------- .../graph_builder/module/osm/OsmModule.java | 2 +- .../openstreetmap/model/OSMRelation.java | 5 +++++ .../openstreetmap/model/OSMWay.java | 5 +++++ .../model/OsmChecks.java} | 16 ++++++---------- .../openstreetmap/wayproperty/WayProperties.java | 4 ++-- .../wayproperty/WayPropertiesBuilder.java | 4 ++-- 7 files changed, 23 insertions(+), 24 deletions(-) rename src/main/java/org/opentripplanner/{graph_builder/module/osm/OsmFilter.java => openstreetmap/model/OsmChecks.java} (82%) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index a1a795ef112..f6756b218da 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -246,12 +246,7 @@ public void addWay(OSMWay way) { /* filter out ways that are not relevant for routing */ if ( - !( - OsmFilter.isWayRoutable(way) || - way.isParkAndRide() || - way.isBikeParking() || - way.isBoardingLocation() - ) + !(way.isRoutable() || way.isParkAndRide() || way.isBikeParking() || way.isBoardingLocation()) ) { return; } @@ -296,9 +291,7 @@ public void addRelation(OSMRelation relation) { // without reference to the ways that compose them. Accordingly, we will merely // mark the ways for preservation here, and deal with the details once we have // the ways loaded. - if ( - !OsmFilter.isWayRoutable(relation) && !relation.isParkAndRide() && !relation.isBikeParking() - ) { + if (!relation.isRoutable() && !relation.isParkAndRide() && !relation.isBikeParking()) { return; } for (OSMRelationMember member : relation.getMembers()) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 4ef3451da60..1f54820a912 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -250,7 +250,7 @@ private void buildBasicGraph() { var permissions = wayData.getPermission(); - if (!OsmFilter.isWayRoutable(way) || permissions.allowsNothing()) { + if (!way.isRoutable() || permissions.allowsNothing()) { continue; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java index 45f89d6ccab..e8d0d7418ab 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java @@ -56,6 +56,11 @@ public boolean isStopArea() { return isPublicTransport() && isTag("public_transport", "stop_area"); } + @Override + public boolean isRoutable() { + return super.isRoutable() && OsmChecks.isRoutable(this); + } + private boolean isType(String type) { return isTag("type", type); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index f8510ed037c..2621b7146de 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -83,6 +83,11 @@ public String toString() { return "osm way " + id; } + @Override + public boolean isRoutable() { + return super.isRoutable() && OsmChecks.isRoutable(this); + } + /** * Returns true if way geometry is a closed loop */ diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java b/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java similarity index 82% rename from src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java rename to src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java index 9367b6f27d5..8965e1eb1f4 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmFilter.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java @@ -1,11 +1,11 @@ -package org.opentripplanner.graph_builder.module.osm; - -import org.opentripplanner.openstreetmap.model.OSMWithTags; +package org.opentripplanner.openstreetmap.model; /** - * + * This class contains methods that are shared between {@link OSMWay} and {@link OSMRelation}. + *

+ * These two classes have some things in common but not enough to create their own inheritance chain. */ -public class OsmFilter { +class OsmChecks { /** * Determine whether any mode can or should ever traverse the given way. If not, we leave the way @@ -18,11 +18,7 @@ public class OsmFilter { *

* A whitelist for highway tags is an alternative to a blacklist. */ - static boolean isWayRoutable(OSMWithTags way) { - if (!(way.isRoutable())) { - return false; - } - + protected static boolean isRoutable(OSMWithTags way) { String highway = way.getTag("highway"); if (highway != null) { if ( diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java index 0e0b6d87b31..f9e1361f763 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java @@ -48,12 +48,12 @@ public WayPropertiesBuilder mutate() { } @Nonnull - public Optional walkSafetyOpt() { + protected Optional walkSafetyOpt() { return Optional.ofNullable(walkSafetyFeatures); } @Nonnull - public Optional bicycleSafetyOpt() { + protected Optional bicycleSafetyOpt() { return Optional.ofNullable(bicycleSafetyFeatures); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java index fb5f4724c0c..1a974b73d5f 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java @@ -73,12 +73,12 @@ public StreetTraversalPermission getPermission() { } @Nullable - public SafetyFeatures getBicycleSafetyFeatures() { + protected SafetyFeatures getBicycleSafetyFeatures() { return bicycleSafetyFeatures; } @Nullable - public SafetyFeatures getWalkSafetyFeatures() { + protected SafetyFeatures getWalkSafetyFeatures() { return walkSafetyFeatures; } From 1a5a5ed098a2a97baa6df19f94aec88e30a27c0a Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 08:52:07 +0300 Subject: [PATCH 089/118] Add tests for ice/winter road tag mapping --- .../tagmapping/FinlandMapperTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java index 088acbb3b23..83c8445f80b 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/FinlandMapperTest.java @@ -1,9 +1,12 @@ package org.opentripplanner.openstreetmap.tagmapping; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; import org.junit.jupiter.api.Test; +import org.opentripplanner.openstreetmap.model.OSMWay; import org.opentripplanner.openstreetmap.model.OSMWithTags; +import org.opentripplanner.openstreetmap.wayproperty.WayProperties; import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; public class FinlandMapperTest { @@ -190,4 +193,28 @@ public void testSafetyWithMixins() { epsilon ); } + + @Test + public void testTagMapping() { + OSMWithTags way; + WayProperties wayData; + + way = new OSMWay(); + way.addTag("highway", "unclassified"); + way.addTag("seasonal", "winter"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), NONE); + + way = new OSMWay(); + way.addTag("highway", "trunk"); + way.addTag("ice_road", "yes"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), NONE); + + way = new OSMWay(); + way.addTag("highway", "track"); + way.addTag("winter_road", "yes"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), NONE); + } } From c441d8be09c96c3f98af6358487a703d24209e09 Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 14 Sep 2023 09:58:06 +0300 Subject: [PATCH 090/118] Consider unknown fares within feeds --- .../fares/impl/DefaultFareServiceTest.java | 41 ++++++++++++++++--- .../ext/fares/impl/DefaultFareService.java | 16 +++++--- 2 files changed, 46 insertions(+), 11 deletions(-) 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 db80c8976dc..27b39aeade4 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 @@ -112,17 +112,22 @@ void multipleFeeds() { .walk(10, Place.forStop(OTHER_FEED_STOP)) .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP)) .build(); - var result = service - .calculateFares(itin) + var result = service.calculateFares(itin); + + var resultComponents = result .getComponents(FareType.regular) .stream() .map(r -> r.fareId()) .toList(); + var resultPrice = result.getFare(FareType.regular); + assertEquals( List.of(AIRPORT_TO_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()), - result + resultComponents ); + + assertEquals(resultPrice, Money.usDollars(20)); } @Test @@ -136,16 +141,40 @@ void multipleFeedsWithTransfersWithinFeed() { .walk(10, Place.forStop(OTHER_FEED_STOP)) .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP)) .build(); - var result = service - .calculateFares(itin) + var result = service.calculateFares(itin); + + var resultComponents = result .getComponents(FareType.regular) .stream() .map(r -> r.fareId()) .toList(); + var resultPrice = result.getFare(FareType.regular); assertEquals( List.of(INSIDE_CITY_CENTER_SET.getFareAttribute().getId(), OTHER_FEED_ATTRIBUTE.getId()), - result + resultComponents ); + + assertEquals(resultPrice, Money.usDollars(20)); + } + + @Test + void multipleFeedsWithUnknownFareLegs() { + var service = new DefaultFareService(); + service.addFareRules(FareType.regular, List.of(AIRPORT_TO_CITY_CENTER_SET, OTHER_FEED_SET)); + var itin = newItinerary(Place.forStop(AIRPORT_STOP)) + .bus(1, T11_00, T11_05, Place.forStop(OTHER_FEED_STOP)) + .walk(10, Place.forStop(OTHER_FEED_STOP)) + .bus(OTHER_FEED_ROUTE, 2, T11_20, T11_32, Place.forStop(OTHER_FEED_STOP)) + .build(); + var result = service.calculateFares(itin); + var resultComponents = result + .getComponents(FareType.regular) + .stream() + .map(r -> r.fareId()) + .toList(); + var resultPrice = result.getFare(FareType.regular); + assertEquals(List.of(OTHER_FEED_ATTRIBUTE.getId()), resultComponents); + assertEquals(Money.usDollars(-0.01f), resultPrice); } } 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 828e6d01b51..3b582042a9c 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -72,6 +72,8 @@ 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; @@ -126,6 +128,7 @@ public ItineraryFares calculateFares(Itinerary itinerary) { List fares = new ArrayList<>(); ItineraryFares currentFare = ItineraryFares.empty(); boolean legWithoutRulesFound = false; + boolean legsWithoutMatchingRulesFound = false; for (String feedId : fareLegsByFeed.keySet()) { var fareRules = fareRulesByTypeAndFeed.get(fareType).get(feedId); @@ -134,9 +137,12 @@ public ItineraryFares calculateFares(Itinerary itinerary) { Currency currency = Currency.getInstance( fareRules.iterator().next().getFareAttribute().getCurrencyType() ); - hasFare = - populateFare(currentFare, currency, fareType, fareLegsByFeed.get(feedId), fareRules) || - hasFare; // Other feeds might still have fare for some legs + boolean feedHasFare = false; + feedHasFare = + populateFare(currentFare, currency, fareType, fareLegsByFeed.get(feedId), fareRules); + + if (!feedHasFare) legsWithoutMatchingRulesFound = true; + hasFare = feedHasFare || hasFare; // Other feeds might still have fare for some legs components.addAll(currentFare.getComponents(fareType)); fare.addFare(fareType, currentFare.getFare(fareType)); @@ -159,10 +165,10 @@ public ItineraryFares calculateFares(Itinerary itinerary) { } // Accumulate the final price of the fare or indicate that no final fare could be found - if (legWithoutRulesFound) { + if (legWithoutRulesFound || legsWithoutMatchingRulesFound) { fare.addFare( fareType, - Money.ofFractionalAmount(fares.get(0).currency(), Float.POSITIVE_INFINITY) + Money.ofFractionalAmount(fares.get(0).currency(), UNKNOWN_FARE_PRICE) ); } else { fare.addFare( From 36d93cd786f436b9fdee3f8961ecb8d9d74cbef3 Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 14 Sep 2023 10:47:20 +0300 Subject: [PATCH 091/118] Return null fares only if no fare type has fare --- .../ext/fares/impl/DefaultFareServiceTest.java | 4 ++-- .../opentripplanner/ext/fares/impl/DefaultFareService.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 27b39aeade4..779ea7bd4b7 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 @@ -127,7 +127,7 @@ void multipleFeeds() { resultComponents ); - assertEquals(resultPrice, Money.usDollars(20)); + assertEquals(Money.usDollars(20), resultPrice); } @Test @@ -155,7 +155,7 @@ void multipleFeedsWithTransfersWithinFeed() { resultComponents ); - assertEquals(resultPrice, Money.usDollars(20)); + assertEquals(Money.usDollars(20), resultPrice); } @Test 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 3b582042a9c..0fcb7e399c1 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -129,6 +129,7 @@ public ItineraryFares calculateFares(Itinerary itinerary) { ItineraryFares currentFare = ItineraryFares.empty(); boolean legWithoutRulesFound = false; boolean legsWithoutMatchingRulesFound = false; + boolean fareTypeHasFare = false; for (String feedId : fareLegsByFeed.keySet()) { var fareRules = fareRulesByTypeAndFeed.get(fareType).get(feedId); @@ -161,7 +162,7 @@ public ItineraryFares calculateFares(Itinerary itinerary) { // No fares will be discovered after this point if (!hasFare) { - return null; + continue; } // Accumulate the final price of the fare or indicate that no final fare could be found @@ -182,7 +183,7 @@ public ItineraryFares calculateFares(Itinerary itinerary) { ); } } - return fare; + return hasFare ? fare : null; } /** From 894a5c71e1eb78fe617210944343fe9597573fb0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 09:54:18 +0200 Subject: [PATCH 092/118] Clean up code, document --- .../issues/ConflictingBikeTags.java | 2 +- .../issues/DisconnectedOsmNode.java | 6 +- .../issues/InvalidOsmGeometry.java | 2 +- .../issues/InvalidVehicleParkingCapacity.java | 2 +- .../graph_builder/issues/LevelAmbiguous.java | 2 +- .../issues/ParkAndRideUnlinked.java | 2 +- .../issues/StreetCarSpeedZero.java | 2 +- .../issues/TurnRestrictionUnknown.java | 2 +- .../module/osm/AreaTooComplicated.java | 8 +- .../graph_builder/module/osm/OsmDatabase.java | 2 +- .../module/osm/ParkingProcessor.java | 4 +- .../module/osm/UnconnectedArea.java | 2 +- .../module/osm/VertexGenerator.java | 2 +- .../module/osm/WalkableAreaBuilder.java | 4 +- .../openstreetmap/model/OSMNode.java | 14 +- .../openstreetmap/model/OSMRelation.java | 2 +- .../openstreetmap/model/OSMWay.java | 112 +++++------ .../openstreetmap/model/OSMWithTags.java | 174 +++++++++--------- .../openstreetmap/model/OsmChecks.java | 39 ++-- .../wayproperty/WayProperties.java | 24 ++- .../wayproperty/WayPropertiesBuilder.java | 4 +- .../wayproperty/WayPropertySet.java | 4 +- .../openstreetmap/model/OSMNodeTest.java | 21 --- .../model/vertex/BarrierVertexTest.java | 18 +- 24 files changed, 216 insertions(+), 238 deletions(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java b/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java index b546db63509..f55437fd226 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java @@ -16,6 +16,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), entity.getId()); + return String.format(HTMLFMT, entity.url(), entity.getId()); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/DisconnectedOsmNode.java b/src/main/java/org/opentripplanner/graph_builder/issues/DisconnectedOsmNode.java index afd247c85d7..f1c341a46fe 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/DisconnectedOsmNode.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/DisconnectedOsmNode.java @@ -21,11 +21,11 @@ public String getMessage() { public String getHTMLMessage() { return String.format( HTMLFMT, - node.getOpenStreetMapLink(), + node.url(), node.getId(), - way.getOpenStreetMapLink(), + way.url(), way.getId(), - area.getOpenStreetMapLink(), + area.url(), area.getId() ); } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/InvalidOsmGeometry.java b/src/main/java/org/opentripplanner/graph_builder/issues/InvalidOsmGeometry.java index 72dea76fe95..7f84453a6d8 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/InvalidOsmGeometry.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/InvalidOsmGeometry.java @@ -14,6 +14,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), entity.getId()); + return String.format(HTMLFMT, entity.url(), entity.getId()); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/InvalidVehicleParkingCapacity.java b/src/main/java/org/opentripplanner/graph_builder/issues/InvalidVehicleParkingCapacity.java index f50f0390009..c4f5498a769 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/InvalidVehicleParkingCapacity.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/InvalidVehicleParkingCapacity.java @@ -17,6 +17,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), entity.getId(), capacityValue); + return String.format(HTMLFMT, entity.url(), entity.getId(), capacityValue); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/LevelAmbiguous.java b/src/main/java/org/opentripplanner/graph_builder/issues/LevelAmbiguous.java index 0497f17797b..650c8b299ac 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/LevelAmbiguous.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/LevelAmbiguous.java @@ -20,6 +20,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), layerName, entity.getId()); + return String.format(HTMLFMT, entity.url(), layerName, entity.getId()); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/ParkAndRideUnlinked.java b/src/main/java/org/opentripplanner/graph_builder/issues/ParkAndRideUnlinked.java index aed7a9456e8..7da16a2891f 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/ParkAndRideUnlinked.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/ParkAndRideUnlinked.java @@ -16,6 +16,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), name, entity); + return String.format(HTMLFMT, entity.url(), name, entity); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java b/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java index f0888821f0e..1107486af97 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/StreetCarSpeedZero.java @@ -14,6 +14,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, entity.getOpenStreetMapLink(), entity.getId()); + return String.format(HTMLFMT, entity.url(), entity.getId()); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/TurnRestrictionUnknown.java b/src/main/java/org/opentripplanner/graph_builder/issues/TurnRestrictionUnknown.java index c0f2b749558..36e913cba6a 100644 --- a/src/main/java/org/opentripplanner/graph_builder/issues/TurnRestrictionUnknown.java +++ b/src/main/java/org/opentripplanner/graph_builder/issues/TurnRestrictionUnknown.java @@ -14,6 +14,6 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, tagval, entity.getOpenStreetMapLink(), entity.getId()); + return String.format(HTMLFMT, tagval, entity.url(), entity.getId()); } } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaTooComplicated.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaTooComplicated.java index c27c5bed732..65d03ba84ce 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaTooComplicated.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaTooComplicated.java @@ -17,13 +17,7 @@ public String getMessage() { @Override public String getHTMLMessage() { OSMWithTags entity = areaGroup.getSomeOSMObject(); - return String.format( - HTMLFMT, - entity.getOpenStreetMapLink(), - entity.getId(), - nbNodes, - maxAreaNodes - ); + return String.format(HTMLFMT, entity.url(), entity.getId(), nbNodes, maxAreaNodes); } @Override diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index f6756b218da..599c66ef585 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -789,7 +789,7 @@ private void processMultipolygonRelations() { * Handler for a new Area (single way area or multipolygon relations) */ private void newArea(Area area) { - StreetTraversalPermission permissions = area.parent.reducePermissions( + StreetTraversalPermission permissions = area.parent.overridePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) { diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java index a78e735d587..940ffa68198 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java @@ -135,7 +135,7 @@ private OHCalendar parseOpeningHours(OSMWithTags entity) { if (openingHoursTag != null) { final ZoneId zoneId = entity.getOsmProvider().getZoneId(); final var id = entity.getId(); - final var link = entity.getOpenStreetMapLink(); + final var link = entity.url(); try { return osmOpeningHoursParser.parseOpeningHours( openingHoursTag, @@ -293,7 +293,7 @@ private List createArtificialEntra ) { LOG.debug( "Creating an artificial entrance for {} as it's not linked to the street network", - entity.getOpenStreetMapLink() + entity.url() ); return List.of(builder -> builder diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/UnconnectedArea.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/UnconnectedArea.java index 07999b0c72d..5dd5f851ac2 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/UnconnectedArea.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/UnconnectedArea.java @@ -16,7 +16,7 @@ public String getMessage() { @Override public String getHTMLMessage() { - return String.format(HTMLFMT, areaGroup.getSomeOSMObject().getOpenStreetMapLink(), idList()); + return String.format(HTMLFMT, areaGroup.getSomeOSMObject().url(), idList()); } @Override diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index d38a469bdac..14489777dd4 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -91,7 +91,7 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { if (node.isBarrier()) { BarrierVertex bv = vertexFactory.barrier(nid, coordinate); - bv.setBarrierPermissions(node.reducePermissions(BarrierVertex.defaultBarrierPermissions)); + bv.setBarrierPermissions(node.overridePermissions(BarrierVertex.defaultBarrierPermissions)); iv = bv; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 13bd43d7076..af17593d36a 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -499,7 +499,7 @@ private Set createSegments( Area area = intersects.get(0); OSMWithTags areaEntity = area.parent; - StreetTraversalPermission areaPermissions = areaEntity.reducePermissions( + StreetTraversalPermission areaPermissions = areaEntity.overridePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); @@ -666,7 +666,7 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection namedArea.setOriginalEdges(intersection); - StreetTraversalPermission permission = areaEntity.reducePermissions( + StreetTraversalPermission permission = areaEntity.overridePermissions( StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE ); namedArea.setPermission(permission); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java index b394dc71858..53a47298dae 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java @@ -15,18 +15,6 @@ public Coordinate getCoordinate() { return new Coordinate(this.lon, this.lat); } - /** - * Returns the capacity of this node if defined, or 0. - */ - public int getCapacity() throws NumberFormatException { - String capacity = getTag("capacity"); - if (capacity == null) { - return 0; - } - - return Integer.parseInt(getTag("capacity")); - } - /** * Is this a multi-level node that should be decomposed to multiple coincident nodes? Currently * returns true only for elevators. @@ -72,7 +60,7 @@ public boolean isBarrier() { } @Override - public String getOpenStreetMapLink() { + public String url() { return String.format("https://www.openstreetmap.org/node/%d", getId()); } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java index e8d0d7418ab..415f2aa153a 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java @@ -20,7 +20,7 @@ public String toString() { } @Override - public String getOpenStreetMapLink() { + public String url() { return String.format("https://www.openstreetmap.org/relation/%d", getId()); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index 2621b7146de..0f9a86f9439 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -16,57 +16,6 @@ public class OSMWay extends OSMWithTags { ); private final TLongList nodes = new TLongArrayList(); - /** - * Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions - * for travel along and against the way. - */ - public StreetTraversalPermissionPair splitPermissions(StreetTraversalPermission permissions) { - StreetTraversalPermission permissionsFront = permissions; - StreetTraversalPermission permissionsBack = permissions; - - // Check driving direction restrictions. - if (isOneWayForwardDriving() || isRoundabout()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } - if (isOneWayReverseDriving()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } - - // Check bike direction restrictions. - if (isOneWayForwardBicycle()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); - } - if (isOneWayReverseBicycle()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); - } - - // TODO(flamholz): figure out what this is for. - String oneWayBicycle = getTag("oneway:bicycle"); - if (isFalse(oneWayBicycle) || isTagTrue("bicycle:backwards")) { - if (permissions.allows(StreetTraversalPermission.BICYCLE)) { - permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE); - permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); - } - } - - //This needs to be after adding permissions for oneway:bicycle=no - //removes bicycle permission when bicycles need to use sidepath - //TAG: bicycle:forward=use_sidepath - if (isForwardDirectionSidepath()) { - permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); - } - - //TAG bicycle:backward=use_sidepath - if (isReverseDirectionSidepath()) { - permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); - } - - if (isOpposableCycleway()) { - permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); - } - return new StreetTraversalPermissionPair(permissionsFront, permissionsBack); - } - public void addNodeRef(long nodeRef) { nodes.add(nodeRef); } @@ -203,12 +152,63 @@ public boolean isBackwardEscalator() { return isEscalator() && "backward".equals(this.getTag("conveying")); } - @Override - public String getOpenStreetMapLink() { - return String.format("https://www.openstreetmap.org/way/%d", getId()); - } - public boolean isArea() { return isTag("area", "yes"); } + + /** + * Given a set of {@code permissions} check if it can really be applied to both directions + * of the way and return the permissions for both cases. + */ + public StreetTraversalPermissionPair splitPermissions(StreetTraversalPermission permissions) { + StreetTraversalPermission permissionsFront = permissions; + StreetTraversalPermission permissionsBack = permissions; + + // Check driving direction restrictions. + if (isOneWayForwardDriving() || isRoundabout()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } + if (isOneWayReverseDriving()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } + + // Check bike direction restrictions. + if (isOneWayForwardBicycle()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); + } + if (isOneWayReverseBicycle()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); + } + + // TODO(flamholz): figure out what this is for. + String oneWayBicycle = getTag("oneway:bicycle"); + if (isFalse(oneWayBicycle) || isTagTrue("bicycle:backwards")) { + if (permissions.allows(StreetTraversalPermission.BICYCLE)) { + permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE); + permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); + } + } + + //This needs to be after adding permissions for oneway:bicycle=no + //removes bicycle permission when bicycles need to use sidepath + //TAG: bicycle:forward=use_sidepath + if (isForwardDirectionSidepath()) { + permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE); + } + + //TAG bicycle:backward=use_sidepath + if (isReverseDirectionSidepath()) { + permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE); + } + + if (isOpposableCycleway()) { + permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE); + } + return new StreetTraversalPermissionPair(permissionsFront, permissionsBack); + } + + @Override + public String url() { + return String.format("https://www.openstreetmap.org/way/%d", getId()); + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index b28ab186600..6080eab0fb9 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -47,90 +47,6 @@ public static boolean isTrue(String tagValue) { return ("yes".equals(tagValue) || "1".equals(tagValue) || "true".equals(tagValue)); } - public StreetTraversalPermission reducePermissions(StreetTraversalPermission def) { - StreetTraversalPermission permission; - - /* - * Only a few tags are examined here, because we only care about modes supported by OTP - * (wheelchairs are not of concern here) - * - * Only a few values are checked for, all other values are presumed to be permissive (=> - * This may not be perfect, but is closer to reality, since most people don't follow the - * rules perfectly ;-) - */ - if (isGeneralAccessDenied()) { - // this can actually be overridden - permission = StreetTraversalPermission.NONE; - } else { - permission = def; - } - - if (isVehicleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.BICYCLE_AND_CAR); - } else if (isVehicleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.BICYCLE_AND_CAR); - } - - if (isMotorcarExplicitlyDenied() || isMotorVehicleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.CAR); - } else if (isMotorcarExplicitlyAllowed() || isMotorVehicleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.CAR); - } - - if (isBicycleExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.BICYCLE); - } else if (isBicycleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.BICYCLE); - } - - if (isPedestrianExplicitlyDenied()) { - permission = permission.remove(StreetTraversalPermission.PEDESTRIAN); - } else if (isPedestrianExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.PEDESTRIAN); - } - - if (isUnderConstruction()) { - permission = StreetTraversalPermission.NONE; - } - - if (permission == null) { - return def; - } - - /* - * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle - * rules: default: permissions; - * - * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes - * any time it is forbidden to ride them, so the only thing to do here is to remove bike - * permissions - * - * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these - * permissions for bikes only - * - * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set - * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle - * - * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like - * oneway:bicycle=yes - */ - - // Compute bike permissions, check consistency. - boolean forceBikes = false; - if (isBicycleExplicitlyAllowed()) { - permission = permission.add(StreetTraversalPermission.BICYCLE); - forceBikes = true; - } - - if (isBicycleDismountForced()) { - permission = permission.remove(StreetTraversalPermission.BICYCLE); - if (forceBikes) { - //issueStore.add(new ConflictingBikeTags(way)); - } - } - return permission; - } - /** * Gets the id. */ @@ -557,7 +473,7 @@ public void setCreativeName(I18NString creativeName) { this.creativeName = creativeName; } - public String getOpenStreetMapLink() { + public String url() { return null; } @@ -643,6 +559,94 @@ public Set getLevels() { return levels; } + /** + * Given an assumed traversal permissions, check if there are explicit additional tags, like bicycle=no + * or bicycle=yes that override them. + */ + public StreetTraversalPermission overridePermissions(StreetTraversalPermission def) { + StreetTraversalPermission permission; + + /* + * Only a few tags are examined here, because we only care about modes supported by OTP + * (wheelchairs are not of concern here) + * + * Only a few values are checked for, all other values are presumed to be permissive (=> + * This may not be perfect, but is closer to reality, since most people don't follow the + * rules perfectly ;-) + */ + if (isGeneralAccessDenied()) { + // this can actually be overridden + permission = StreetTraversalPermission.NONE; + } else { + permission = def; + } + + if (isVehicleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE_AND_CAR); + } else if (isVehicleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE_AND_CAR); + } + + if (isMotorcarExplicitlyDenied() || isMotorVehicleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.CAR); + } else if (isMotorcarExplicitlyAllowed() || isMotorVehicleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.CAR); + } + + if (isBicycleExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE); + } else if (isBicycleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE); + } + + if (isPedestrianExplicitlyDenied()) { + permission = permission.remove(StreetTraversalPermission.PEDESTRIAN); + } else if (isPedestrianExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.PEDESTRIAN); + } + + if (isUnderConstruction()) { + permission = StreetTraversalPermission.NONE; + } + + if (permission == null) { + return def; + } + + /* + * pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle + * rules: default: permissions; + * + * cycleway=dismount means walk your bike -- the engine will automatically try walking bikes + * any time it is forbidden to ride them, so the only thing to do here is to remove bike + * permissions + * + * oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these + * permissions for bikes only + * + * now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set + * by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle + * + * bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like + * oneway:bicycle=yes + */ + + // Compute bike permissions, check consistency. + boolean forceBikes = false; + if (isBicycleExplicitlyAllowed()) { + permission = permission.add(StreetTraversalPermission.BICYCLE); + forceBikes = true; + } + + if (isBicycleDismountForced()) { + permission = permission.remove(StreetTraversalPermission.BICYCLE); + if (forceBikes) { + //issueStore.add(new ConflictingBikeTags(way)); + } + } + return permission; + } + @Override public String toString() { return ToStringBuilder.of(this.getClass()).addObj("tags", tags).toString(); diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java b/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java index 8965e1eb1f4..839ae20c3ac 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java @@ -1,5 +1,7 @@ package org.opentripplanner.openstreetmap.model; +import java.util.Set; + /** * This class contains methods that are shared between {@link OSMWay} and {@link OSMRelation}. *

@@ -7,6 +9,22 @@ */ class OsmChecks { + private static final Set NON_ROUTABLE_HIGHWAYS = Set.of( + "proposed", + "planned", + "construction", + "razed", + "raceway", + "abandoned", + "historic", + "no", + "emergency_bay", + "rest_area", + "services", + "bus_guideway", + "escape" + ); + /** * Determine whether any mode can or should ever traverse the given way. If not, we leave the way * out of the OTP graph. Potentially routable ways are those that have the tags : highway=* @@ -19,25 +37,8 @@ class OsmChecks { * A whitelist for highway tags is an alternative to a blacklist. */ protected static boolean isRoutable(OSMWithTags way) { - String highway = way.getTag("highway"); - if (highway != null) { - if ( - highway.equals("proposed") || - highway.equals("planned") || - highway.equals("construction") || - highway.equals("razed") || - highway.equals("raceway") || - highway.equals("abandoned") || - highway.equals("historic") || - highway.equals("no") || - highway.equals("emergency_bay") || - highway.equals("rest_area") || - highway.equals("services") || - highway.equals("bus_guideway") || - highway.equals("escape") - ) { - return false; - } + if (way.isOneOfTags("highway", NON_ROUTABLE_HIGHWAYS)) { + return false; } if (way.isGeneralAccessDenied()) { diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java index f9e1361f763..4facfd041c8 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayProperties.java @@ -24,15 +24,21 @@ public class WayProperties { WayProperties(WayPropertiesBuilder wayPropertiesBuilder) { permission = Objects.requireNonNull(wayPropertiesBuilder.getPermission()); - bicycleSafetyFeatures = wayPropertiesBuilder.getBicycleSafetyFeatures(); - walkSafetyFeatures = wayPropertiesBuilder.getWalkSafetyFeatures(); + bicycleSafetyFeatures = wayPropertiesBuilder.bicycleSafety(); + walkSafetyFeatures = wayPropertiesBuilder.walkSafety(); } + /** + * The value for the bicycle safety. If none has been set a default value of 1 is returned. + */ @Nonnull public SafetyFeatures bicycleSafety() { return Objects.requireNonNullElse(bicycleSafetyFeatures, SafetyFeatures.DEFAULT); } + /** + * The value for the walk safety. If none has been set a default value of 1 is returned. + */ @Nonnull public SafetyFeatures walkSafety() { return Objects.requireNonNullElse(walkSafetyFeatures, SafetyFeatures.DEFAULT); @@ -43,15 +49,17 @@ public StreetTraversalPermission getPermission() { return permission; } - public WayPropertiesBuilder mutate() { - return new WayPropertiesBuilder(this); - } - + /** + * An optional value for the walk safety. If none has been set an empty Optional is returned. + */ @Nonnull protected Optional walkSafetyOpt() { return Optional.ofNullable(walkSafetyFeatures); } + /** + * An optional value for the bicycle safety. If none has been set an empty Optional is returned. + */ @Nonnull protected Optional bicycleSafetyOpt() { return Optional.ofNullable(bicycleSafetyFeatures); @@ -73,4 +81,8 @@ public boolean equals(Object o) { } return false; } + + public WayPropertiesBuilder mutate() { + return new WayPropertiesBuilder(this); + } } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java index 1a974b73d5f..b91d1040a7d 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertiesBuilder.java @@ -73,12 +73,12 @@ public StreetTraversalPermission getPermission() { } @Nullable - protected SafetyFeatures getBicycleSafetyFeatures() { + protected SafetyFeatures bicycleSafety() { return bicycleSafetyFeatures; } @Nullable - protected SafetyFeatures getWalkSafetyFeatures() { + protected SafetyFeatures walkSafety() { return walkSafetyFeatures; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java index ee4af226ed5..06d5f895777 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java +++ b/src/main/java/org/opentripplanner/openstreetmap/wayproperty/WayPropertySet.java @@ -119,9 +119,9 @@ public WayProperties getDataForWay(OSMWithTags way) { float forwardSpeed = getCarSpeedForWay(way, false); float backSpeed = getCarSpeedForWay(way, true); - var permission = way.reducePermissions(forwardResult.getPermission()); + var permission = way.overridePermissions(forwardResult.getPermission()); - var backwardPermission = way.reducePermissions(backwardResult.getPermission()); + var backwardPermission = way.overridePermissions(backwardResult.getPermission()); WayProperties result = forwardResult .mutate() diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMNodeTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMNodeTest.java index 26dadab5c7d..a4579dec71e 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMNodeTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMNodeTest.java @@ -1,9 +1,7 @@ package org.opentripplanner.openstreetmap.model; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; @@ -20,23 +18,4 @@ public void testIsMultiLevel() { node.addTag("highway", "elevator"); assertTrue(node.isMultiLevel()); } - - @Test - public void testGetCapacity() { - OSMNode node = new OSMNode(); - assertFalse(node.hasTag("capacity")); - assertEquals(0, node.getCapacity()); - - try { - node.addTag("capacity", "foobie"); - node.getCapacity(); - - // Above should fail. - fail(); - } catch (NumberFormatException e) {} - - node.addTag("capacity", "10"); - assertTrue(node.hasTag("capacity")); - assertEquals(10, node.getCapacity()); - } } diff --git a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java index a748e263d7f..127f34e17ca 100644 --- a/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java +++ b/src/test/java/org/opentripplanner/street/model/vertex/BarrierVertexTest.java @@ -31,35 +31,35 @@ public void testBarrierPermissions() { String label = "simpleBarrier"; BarrierVertex bv = new BarrierVertex(simpleBarier.lon, simpleBarier.lat, 0); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("foot", "yes"); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "yes"); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("access", "no"); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("motor_vehicle", "no"); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE, bv.getBarrierPermissions()); simpleBarier.addTag("bicycle", "no"); bv.setBarrierPermissions( - simpleBarier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + simpleBarier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -68,7 +68,7 @@ public void testBarrierPermissions() { complexBarrier.addTag("access", "no"); bv.setBarrierPermissions( - complexBarrier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + complexBarrier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); @@ -77,7 +77,7 @@ public void testBarrierPermissions() { noBikeBollard.addTag("bicycle", "no"); bv.setBarrierPermissions( - noBikeBollard.reducePermissions(BarrierVertex.defaultBarrierPermissions) + noBikeBollard.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.PEDESTRIAN, bv.getBarrierPermissions()); @@ -86,7 +86,7 @@ public void testBarrierPermissions() { accessBarrier.addTag("access", "no"); bv.setBarrierPermissions( - accessBarrier.reducePermissions(BarrierVertex.defaultBarrierPermissions) + accessBarrier.overridePermissions(BarrierVertex.defaultBarrierPermissions) ); assertEquals(StreetTraversalPermission.NONE, bv.getBarrierPermissions()); } From 34bd978fd17e0e31213b657284038c502a55fe71 Mon Sep 17 00:00:00 2001 From: Viljami N Date: Thu, 14 Sep 2023 12:33:59 +0300 Subject: [PATCH 093/118] Apply suggestions from code review Co-authored-by: Joel Lappalainen --- .../ext/fares/impl/DefaultFareService.java | 15 ++++++++------- .../ext/fares/impl/HSLFareServiceImpl.java | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) 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 0fcb7e399c1..e62bcefa355 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -138,11 +138,12 @@ public ItineraryFares calculateFares(Itinerary itinerary) { Currency currency = Currency.getInstance( fareRules.iterator().next().getFareAttribute().getCurrencyType() ); - boolean feedHasFare = false; - feedHasFare = + boolean feedHasFare = populateFare(currentFare, currency, fareType, fareLegsByFeed.get(feedId), fareRules); - if (!feedHasFare) legsWithoutMatchingRulesFound = true; + if (!feedHasFare) { + legsWithoutMatchingRulesFound = true; + } hasFare = feedHasFare || hasFare; // Other feeds might still have fare for some legs components.addAll(currentFare.getComponents(fareType)); @@ -150,9 +151,9 @@ public ItineraryFares calculateFares(Itinerary itinerary) { fares.add(currentFare.getFare(fareType)); // If all the legs are from one feed, consider itinerary products - if (fareLegs.equals(fareLegsByFeed.get(feedId))) fare.addItineraryProducts( - currentFare.getItineraryProducts() - ); + if (fareLegs.equals(fareLegsByFeed.get(feedId))) { + fare.addItineraryProducts(currentFare.getItineraryProducts()); + } } else { legWithoutRulesFound = true; } @@ -269,7 +270,7 @@ protected Optional getBestFareAndId( String startZone = firstRide.getFrom().stop.getFirstZoneAsString(); String endZone = null; // stops don't really have an agency id, they have the per-feed default id - String feedId = firstRide.getAgency().getId().getFeedId(); //getTrip().getId().getFeedId(); + String feedId = firstRide.getAgency().getId().getFeedId(); ZonedDateTime lastRideStartTime = null; ZonedDateTime lastRideEndTime = null; for (var leg : legs) { diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java index 98a6b02ab55..ba62e899f70 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/HSLFareServiceImpl.java @@ -67,7 +67,9 @@ protected Optional getBestFareAndId( .stream() .map(leg -> leg.getAgency().getId().getFeedId()) .collect(Collectors.toSet()); - if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) return Optional.empty(); + if (!Sets.difference(legFeedIds, fareRuleFeedIds).isEmpty()) { + return Optional.empty(); + } for (Leg leg : legs) { lastRideStartTime = leg.getStartTime(); From a9caa84ec0f8fa332930113ee7e6ef36b4053f3d Mon Sep 17 00:00:00 2001 From: Viljami Nurminen Date: Thu, 14 Sep 2023 12:48:32 +0300 Subject: [PATCH 094/118] Fix formatting --- .../ext/fares/impl/DefaultFareService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 e62bcefa355..744782f7b56 100644 --- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java +++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareService.java @@ -138,8 +138,13 @@ public ItineraryFares calculateFares(Itinerary itinerary) { Currency currency = Currency.getInstance( fareRules.iterator().next().getFareAttribute().getCurrencyType() ); - boolean feedHasFare = - populateFare(currentFare, currency, fareType, fareLegsByFeed.get(feedId), fareRules); + boolean feedHasFare = populateFare( + currentFare, + currency, + fareType, + fareLegsByFeed.get(feedId), + fareRules + ); if (!feedHasFare) { legsWithoutMatchingRulesFound = true; From 80f58d1eaf82a38ec672c6c7be03b8ac1c0a8afc Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 12:34:24 +0200 Subject: [PATCH 095/118] Fix grammar and formatting [ci skip] --- docs/RoutingModes.md | 9 +++++---- .../opentripplanner/routing/api/request/StreetMode.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/RoutingModes.md b/docs/RoutingModes.md index 9b96e65e97d..88148d370e5 100644 --- a/docs/RoutingModes.md +++ b/docs/RoutingModes.md @@ -17,7 +17,7 @@ Cycling for the entirety of the route or taking a bicycle onto the public transp Taking a rented, shared-mobility bike for part or the entirety of the route. -_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds. +_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one. @@ -25,6 +25,7 @@ See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to a Leaving the bicycle at the departure station and walking from the arrival station to the destination. This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. @@ -51,7 +52,7 @@ Walking to a pickup point along the road, driving to a drop-off point along the Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way. This can include car rental at fixed locations or free-floating services. -_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds. +_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one. @@ -64,14 +65,14 @@ _Prerequisite:_ Park-and-ride areas near the stations need to be present in the

FLEXIBLE

-Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTex Flexible Stop Places. +Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places.

SCOOTER_RENTAL

Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way. This can include scooter rental at fixed locations or free-floating services. -_Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds. +_Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one. diff --git a/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java b/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java index fcdefa35de8..0f5d65305b3 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java +++ b/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java @@ -137,7 +137,7 @@ public String typeDescription() { private static String GBFS_PREREQ = """ - _Prerequisite:_ Vehicle or station location need to be added to OTP from dynamic data feeds. + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. See [Configuring GBFS](UpdaterConfig.md#gbfs-vehicle-rental-systems) on how to add one. """; @@ -148,8 +148,9 @@ public String enumValueDescription() { case WALK -> "Walking some or all of the way of the route."; case BIKE -> "Cycling for the entirety of the route or taking a bicycle onto the public transport and cycling from the arrival station to the destination."; case BIKE_TO_PARK -> """ - Leaving the bicycle at the departure station and walking from the arrival station to the destination. - This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + Leaving the bicycle at the departure station and walking from the arrival station to the destination. + This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. """; case BIKE_RENTAL -> """ @@ -182,7 +183,7 @@ This means that the car is not parked in a permanent parking area but rather the See [the sandbox documentation](sandbox/RideHailing.md) on how to configure it. """; - case FLEXIBLE -> "Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTex Flexible Stop Places."; + case FLEXIBLE -> "Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places."; }; } } From 2aa2120c9522717503df40073ae7957fbe42e3a1 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:02:40 +0300 Subject: [PATCH 096/118] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java Co-authored-by: Thomas Gran --- .../filterchain/ItineraryListFilterChainBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index e81e82664ed..d31f33ef282 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -202,8 +202,8 @@ public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOn } /** - * An itinerary which has transit legs and more walking than the plain walk itinerary are silly. - * This filter removes such itineraries. + * A transit itinerary with higher generalized-cost than a walk-only itinerary is silly. This filter removes such + * itineraries. *

* This filter only have an effect, if an walk-all-the-way itinerary exist. */ From 775cd16e3a6f2476b42eef53f00f782be21d5c19 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:03:19 +0300 Subject: [PATCH 097/118] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java Co-authored-by: Thomas Gran --- .../deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java index 0199f0e3b66..376e5d62af8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java @@ -11,7 +11,7 @@ import org.opentripplanner.routing.api.request.framework.CostLinearFunction; /** - * Filter itineraries which contain more walking than a pure walk itinerary + * Filter itineraries which have a higher generalized-cost than a pure walk itinerary. */ public class RemoveTransitIfWalkingIsBetterFilter implements ItineraryDeletionFlagger { From 38ac2bdadcfd9884d6b0386827a2b55974ac219d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:05:17 +0300 Subject: [PATCH 098/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 569cb21e5f5..01c8b5c91ca 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -31,7 +31,7 @@ public final class ItineraryFilterPreferences { private final boolean removeItinerariesWithSameRoutesAndStops; private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private final CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private final boolean removeTransitWithMoreWalking; + private final boolean removeTransitIfWalkingIsBetter; private ItineraryFilterPreferences() { this.accessibilityScore = false; From 79c907ed00b3cc4870ea3885b637025a52736767 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:05:26 +0300 Subject: [PATCH 099/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 01c8b5c91ca..f446e8c00d5 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -52,7 +52,7 @@ private ItineraryFilterPreferences() { ); this.removeTransitWithHigherCostThanBestOnStreetOnly = CostLinearFunction.of(Duration.ofMinutes(1), 1.3); - this.removeTransitWithMoreWalking = false; + this.removeTransitIfWalkingIsBetter = true; } private ItineraryFilterPreferences(Builder builder) { From 2bfd928f45c518f510631d7d5d846e523c95de38 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:06:10 +0300 Subject: [PATCH 100/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index f446e8c00d5..db9cb90b0e7 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -200,7 +200,7 @@ public boolean equals(Object o) { accessibilityScore == that.accessibilityScore && Double.compare(that.bikeRentalDistanceRatio, bikeRentalDistanceRatio) == 0 && debug == that.debug && - removeTransitWithMoreWalking == that.removeTransitWithMoreWalking && + removeTransitIfWalkingIsBetter == that.removeTransitIfWalkingIsBetter && filterItinerariesWithSameFirstOrLastTrip == that.filterItinerariesWithSameFirstOrLastTrip && Double.compare( that.groupedOtherThanSameLegsMaxCostMultiplier, From a2d109f1edf67cc18d9016976b0a2944312bf43b Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:06:18 +0300 Subject: [PATCH 101/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index db9cb90b0e7..ffee46f2614 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -257,7 +257,7 @@ public static class Builder { private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private boolean removeTransitWithMoreWalking; + private boolean removeTransitIfWalkingIsBetter; public ItineraryFilterPreferences original() { return original; From 5a3b13fc04e9e357969c0d7c8c676ea0c2081cc0 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:06:30 +0300 Subject: [PATCH 102/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index ffee46f2614..f731f372855 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -365,7 +365,7 @@ public Builder(ItineraryFilterPreferences original) { this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; this.removeTransitWithHigherCostThanBestOnStreetOnly = original.removeTransitWithHigherCostThanBestOnStreetOnly; - this.removeTransitWithMoreWalking = original.removeTransitWithMoreWalking; + this.removeTransitIfWalkingIsBetter = original.removeTransitIfWalkingIsBetter; } public Builder apply(Consumer body) { From 4bab50a7ed87161c26ce02c5b15f86021e56c4dd Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:07:05 +0300 Subject: [PATCH 103/118] Update src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java Co-authored-by: Thomas Gran --- .../api/request/preference/ItineraryFilterPreferences.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index f731f372855..2061e6891ce 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -341,8 +341,8 @@ public Builder withRemoveTransitWithHigherCostThanBestOnStreetOnly( return this; } - public Builder withRemoveTransitWithMoreWalking(boolean removeTransitWithMoreWalking) { - this.removeTransitWithMoreWalking = removeTransitWithMoreWalking; + public Builder withRemoveTransitIfWalkingIsBetter(boolean removeTransitIfWalkingIsBetter) { + this.removeTransitIfWalkingIsBetter = removeTransitIfWalkingIsBetter; return this; } From 061c0bb26e71bf1b26a181e0cbddf83ca309b1e2 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:07:45 +0300 Subject: [PATCH 104/118] Update src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java Co-authored-by: Thomas Gran --- .../standalone/config/routerequest/ItineraryFiltersConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 418d46f9cf4..82e2c0839f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -159,7 +159,7 @@ public static void mapItineraryFilterParams( .of("removeTransitWithHigherCostThanBestOnStreetOnly") .since(V2_4) .summary( - "Limit function for generalized-cost computed from non-transit itineries for transit itineraries." + "Limit function for generalized-cost computed from street-only itineries applied to transit itineraries." ) .description( """ From 45b459db16cefd0230f32081ab631ff055a796ab Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 14 Sep 2023 13:43:43 +0200 Subject: [PATCH 105/118] Fix race condition in GtfsModuleTest --- .../graph_builder/module/GtfsModuleTest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java index c120b63c08a..253e9882fc4 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/GtfsModuleTest.java @@ -11,12 +11,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.gtfs.graphbuilder.GtfsBundle; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; import org.opentripplanner.model.calendar.ServiceDateInterval; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -73,18 +73,20 @@ static GtfsBundle bundle(String feedId) { return b; } - static Stream interliningCases = Stream.of( - Arguments.of(List.of(bundle("A")), 2), - Arguments.of(List.of(bundle("A"), bundle("B")), 4), - Arguments.of(List.of(bundle("A"), bundle("B"), bundle("C")), 6) - ); + static List interliningCases() { + return List.of( + Arguments.of(List.of(bundle("A")), 2), + Arguments.of(List.of(bundle("A"), bundle("B")), 4), + Arguments.of(List.of(bundle("A"), bundle("B"), bundle("C")), 6) + ); + } /** * We test that the number of stay-seated transfers grows linearly (not exponentially) with the * number of GTFS input feeds. */ @ParameterizedTest(name = "Bundles {0} should generate {1} stay-seated transfers") - @VariableSource("interliningCases") + @MethodSource("interliningCases") public void interline(List bundles, int expectedTransfers) { var model = buildTestModel(); From 46f5291455141165c7b8854e9f15611e021db686 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 15:22:13 +0300 Subject: [PATCH 106/118] Fix naming issues pointed out in review --- docs/RouteRequest.md | 4 ++-- .../ItineraryListFilterChainBuilder.java | 4 ++-- .../preference/ItineraryFilterPreferences.java | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index e9b4da3eacf..bff42168c1e 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -95,7 +95,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 | |    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | |    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from non-transit itineries for transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 | +|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from street-only itineries applied to transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 | |    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | |       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | |       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | @@ -634,7 +634,7 @@ Itineraries visiting the same set of stops and riding the exact same routes, dep **Since version:** `2.4` ∙ **Type:** `cost-linear-function` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"1m + 1.30 t"` **Path:** /routingDefaults/itineraryFilters -Limit function for generalized-cost computed from non-transit itineries for transit itineraries. +Limit function for generalized-cost computed from street-only itineries applied to transit itineraries. The max-limit is applied to itineraries with transit *legs*, and only itineraries without transit legs are considered when calculating the minimum cost. The smallest diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index d31f33ef282..a0f03427e64 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -186,9 +186,9 @@ public ItineraryListFilterChainBuilder withParkAndRideDurationRatio(double value /** * The direct street search(walk, bicycle, car) is not pruning the transit search, so in some * cases we get "silly" transit itineraries that is marginally better on travel-duration compared - * with a on-street-all-the-way itinerary. Use this method to turn filter worse enough itineraries. + * with a on-street-all-the-way itinerary. Use this method to filter worse enough itineraries. *

- * The filter remove all itineraries with a generalized-cost that is higher than the best + * The filter removes all itineraries with a generalized-cost that is higher than the best * on-street-all-the-way itinerary. *

* This filter only have an effect, if an on-street-all-the-way(WALK, BICYCLE, CAR) itinerary diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 2061e6891ce..91f3071d4ed 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -52,7 +52,7 @@ private ItineraryFilterPreferences() { ); this.removeTransitWithHigherCostThanBestOnStreetOnly = CostLinearFunction.of(Duration.ofMinutes(1), 1.3); - this.removeTransitIfWalkingIsBetter = true; + this.removeTransitIfWalkingIsBetter = false; } private ItineraryFilterPreferences(Builder builder) { @@ -73,7 +73,7 @@ private ItineraryFilterPreferences(Builder builder) { this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit); this.removeTransitWithHigherCostThanBestOnStreetOnly = Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly); - this.removeTransitWithMoreWalking = builder.removeTransitWithMoreWalking; + this.removeTransitIfWalkingIsBetter = builder.removeTransitIfWalkingIsBetter; } public static Builder of() { @@ -136,8 +136,8 @@ public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() { return removeTransitWithHigherCostThanBestOnStreetOnly; } - public boolean removeTransitWithMoreWalking() { - return removeTransitWithMoreWalking; + public boolean removeTransitIfWalkingIsBetter() { + return removeTransitIfWalkingIsBetter; } @Override @@ -187,7 +187,7 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) - .addBoolIfTrue("removeTransitWithMoreWalking", removeTransitWithMoreWalking) + .addBoolIfTrue("removeTransitIfWalkingIsBetter", removeTransitIfWalkingIsBetter) .toString(); } @@ -200,7 +200,7 @@ public boolean equals(Object o) { accessibilityScore == that.accessibilityScore && Double.compare(that.bikeRentalDistanceRatio, bikeRentalDistanceRatio) == 0 && debug == that.debug && - removeTransitIfWalkingIsBetter == that.removeTransitIfWalkingIsBetter && + removeTransitIfWalkingIsBetter == that.removeTransitIfWalkingIsBetter && filterItinerariesWithSameFirstOrLastTrip == that.filterItinerariesWithSameFirstOrLastTrip && Double.compare( that.groupedOtherThanSameLegsMaxCostMultiplier, @@ -237,7 +237,7 @@ public int hashCode() { removeItinerariesWithSameRoutesAndStops, transitGeneralizedCostLimit, removeTransitWithHigherCostThanBestOnStreetOnly, - removeTransitWithMoreWalking + removeTransitIfWalkingIsBetter ); } From a49c89046be1df665b32caadfcf5bca20e359ce9 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 16:02:23 +0300 Subject: [PATCH 107/118] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java Co-authored-by: Thomas Gran --- .../deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java index 376e5d62af8..9d7aaa45e46 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java @@ -28,8 +28,6 @@ public String name() { @Override public List flagForRemoval(List itineraries) { - // Filter the most common silly itinerary case: transit itinerary has more walking than plain walk itinerary - // This never makes sense OptionalInt minWalkCost = itineraries .stream() From 200ed4ed450ec6ae4259dd39b632671427a40758 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 14 Sep 2023 16:11:21 +0300 Subject: [PATCH 108/118] Fix formatting --- .../deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java index 9d7aaa45e46..4b645e45a79 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/deletionflagger/RemoveTransitIfWalkingIsBetterFilter.java @@ -28,7 +28,6 @@ public String name() { @Override public List flagForRemoval(List itineraries) { - OptionalInt minWalkCost = itineraries .stream() .filter(Itinerary::isWalkingAllTheWay) From f8cfa4db84692d1d97cdc22350a137e2d760a133 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 14 Sep 2023 13:39:18 +0000 Subject: [PATCH 109/118] Add changelog entry for #5222 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 7f82c090888..b45d6a03617 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -6,6 +6,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle ## 2.5.0 (under development) - Gracefully handle nullable fields in TransitAlert [#5349](https://github.com/opentripplanner/OpenTripPlanner/pull/5349) +- Remove transit with higher cost than best on-street itinerary filter [#5222](https://github.com/opentripplanner/OpenTripPlanner/pull/5222) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 7e410771fe94a65e513bbeb87db184eff538a71f Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 14 Sep 2023 15:56:05 +0000 Subject: [PATCH 110/118] Add changelog entry for #5341 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index b45d6a03617..7d6755adf01 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -7,6 +7,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Gracefully handle nullable fields in TransitAlert [#5349](https://github.com/opentripplanner/OpenTripPlanner/pull/5349) - Remove transit with higher cost than best on-street itinerary filter [#5222](https://github.com/opentripplanner/OpenTripPlanner/pull/5222) +- Remove banDiscouragedCycling and banDiscouragedWalking [#5341](https://github.com/opentripplanner/OpenTripPlanner/pull/5341) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.4.0 (2023-09-13) From 72dcf519d6146787d9f5d881fdacf37c74fbe0aa Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 14 Sep 2023 15:56:26 +0000 Subject: [PATCH 111/118] Bump serialization version id for #5341 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8021f2a218a..4f1eb88707d 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 119 + 120 29.2 2.48 From d6707cc7b744b24e93eb4d6919c0606eb47201fc Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 19:23:54 +0200 Subject: [PATCH 112/118] Resolve merge artifacts --- .../openstreetmap/tagmapping/DefaultMapperTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java index f30d07458d0..cbaccb87f84 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/DefaultMapperTest.java @@ -122,12 +122,12 @@ void footDiscouraged() { var regular = WayTestData.pedestrianTunnel(); var props = wps.getDataForWay(regular); assertEquals(PEDESTRIAN_AND_BICYCLE, props.getPermission()); - assertEquals(1, props.getWalkSafetyFeatures().forward()); + assertEquals(1, props.walkSafety().forward()); var discouraged = WayTestData.pedestrianTunnel().addTag("foot", "discouraged"); var discouragedProps = wps.getDataForWay(discouraged); assertEquals(PEDESTRIAN_AND_BICYCLE, discouragedProps.getPermission()); - assertEquals(3, discouragedProps.getWalkSafetyFeatures().forward()); + assertEquals(3, discouragedProps.walkSafety().forward()); } @Test @@ -135,12 +135,12 @@ void bicycleDiscouraged() { var regular = WayTestData.southeastLaBonitaWay(); var props = wps.getDataForWay(regular); assertEquals(ALL, props.getPermission()); - assertEquals(.98, props.getBicycleSafetyFeatures().forward()); + assertEquals(.98, props.bicycleSafety().forward()); var discouraged = WayTestData.southeastLaBonitaWay().addTag("bicycle", "discouraged"); var discouragedProps = wps.getDataForWay(discouraged); assertEquals(ALL, discouragedProps.getPermission()); - assertEquals(2.94, discouragedProps.getBicycleSafetyFeatures().forward(), epsilon); + assertEquals(2.94, discouragedProps.bicycleSafety().forward(), epsilon); } /** From 19da6136e1566db1d29bc5e68099e3d125e977a4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 20:15:52 +0200 Subject: [PATCH 113/118] Remove unnecessary modifier --- .../graph_builder/module/osm/WalkableAreaBuilderTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java index 245d919d9cb..8786fbdf304 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java @@ -76,7 +76,7 @@ public Graph buildGraph(final TestInfo testInfo) { @OsmFile("lund-station-sweden.osm.pbf") @Visibility(true) @MaxAreaNodes(5) - public void testCalculateVerticesArea(TestInfo testInfo) { + void testCalculateVerticesArea(TestInfo testInfo) { var graph = buildGraph(testInfo); var areas = graph .getEdgesOfType(AreaEdge.class) @@ -93,7 +93,7 @@ public void testCalculateVerticesArea(TestInfo testInfo) { @OsmFile("lund-station-sweden.osm.pbf") @Visibility(false) @MaxAreaNodes(5) - public void testSetupCalculateVerticesAreaWithoutVisibility(TestInfo testInfo) { + void testSetupCalculateVerticesAreaWithoutVisibility(TestInfo testInfo) { var graph = buildGraph(testInfo); var areas = graph .getEdgesOfType(AreaEdge.class) @@ -112,7 +112,7 @@ public void testSetupCalculateVerticesAreaWithoutVisibility(TestInfo testInfo) { @OsmFile("stopareas.pbf") @Visibility(true) @MaxAreaNodes(50) - public void testEntranceStopAreaLinking(TestInfo testInfo) { + void testEntranceStopAreaLinking(TestInfo testInfo) { var graph = buildGraph(testInfo); // first platform contains isolated node tagged as highway=bus_stop. Those are linked if level matches. var busStopConnection = graph @@ -183,7 +183,7 @@ public void testEntranceStopAreaLinking(TestInfo testInfo) { @OsmFile("wendlingen-bahnhof.osm.pbf") @Visibility(true) @MaxAreaNodes(50) - public void testSeveralIntersections(TestInfo testInfo) { + void testSeveralIntersections(TestInfo testInfo) { var graph = buildGraph(testInfo); var areas = graph .getEdgesOfType(AreaEdge.class) From 0446321fcb6f0bd02965f16dcd6f4a6e17996712 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 15 Sep 2023 22:03:59 +0200 Subject: [PATCH 114/118] Apply review feedback --- .../opentripplanner/openstreetmap/model/OSMWithTags.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index d0a84b42c80..9913b09b238 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -142,8 +142,7 @@ public boolean isTagTrue(String tag) { * Returns true if bicycle dismounts are forced. */ public boolean isBicycleDismountForced() { - String bicycle = getTag("bicycle"); - return isTag("cycleway", "dismount") || "dismount".equals(bicycle); + return isTag("bicycle", "dismount"); } protected boolean doesTagAllowAccess(String tag) { @@ -637,17 +636,12 @@ public StreetTraversalPermission overridePermissions(StreetTraversalPermission d */ // Compute bike permissions, check consistency. - boolean forceBikes = false; if (isBicycleExplicitlyAllowed()) { permission = permission.add(StreetTraversalPermission.BICYCLE); - forceBikes = true; } if (isBicycleDismountForced()) { permission = permission.remove(StreetTraversalPermission.BICYCLE); - if (forceBikes) { - //issueStore.add(new ConflictingBikeTags(way)); - } } return permission; } From f664f770402e1006f491b9cbd18e3d9724019c80 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 15 Sep 2023 22:30:29 +0200 Subject: [PATCH 115/118] Untangle OsmChecks and inline isRoutable --- .../issues/ConflictingBikeTags.java | 21 ------- .../graph_builder/module/osm/Area.java | 2 +- .../openstreetmap/model/OSMRelation.java | 5 -- .../openstreetmap/model/OSMWay.java | 5 -- .../openstreetmap/model/OSMWithTags.java | 54 ++++++++++++++---- .../openstreetmap/model/OsmChecks.java | 56 ------------------- .../openstreetmap/model/OSMWithTagsTest.java | 5 ++ 7 files changed, 50 insertions(+), 98 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java delete mode 100644 src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java diff --git a/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java b/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java deleted file mode 100644 index f55437fd226..00000000000 --- a/src/main/java/org/opentripplanner/graph_builder/issues/ConflictingBikeTags.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.opentripplanner.graph_builder.issues; - -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.openstreetmap.model.OSMWithTags; - -public record ConflictingBikeTags(OSMWithTags entity) implements DataImportIssue { - private static final String FMT = - "Conflicting tags bicycle:[yes|designated] and cycleway:dismount, assuming dismount"; - private static final String HTMLFMT = - "Conflicting tags bicycle:[yes|designated] and cycleway:dismount on way '%s', assuming dismount"; - - @Override - public String getMessage() { - return String.format(FMT); - } - - @Override - public String getHTMLMessage() { - return String.format(HTMLFMT, entity.url(), entity.getId()); - } -} diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java index 5fb736b5ad6..f0a61e94c5a 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java @@ -25,7 +25,7 @@ class Area { final List outermostRings; // This is the way or relation that has the relevant tags for the area - OSMWithTags parent; + final OSMWithTags parent; public MultiPolygon jtsMultiPolygon; Area( diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java index 415f2aa153a..e181cf97713 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMRelation.java @@ -56,11 +56,6 @@ public boolean isStopArea() { return isPublicTransport() && isTag("public_transport", "stop_area"); } - @Override - public boolean isRoutable() { - return super.isRoutable() && OsmChecks.isRoutable(this); - } - private boolean isType(String type) { return isTag("type", type); } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java index 0f9a86f9439..1ce6c698f22 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWay.java @@ -32,11 +32,6 @@ public String toString() { return "osm way " + id; } - @Override - public boolean isRoutable() { - return super.isRoutable() && OsmChecks.isRoutable(this); - } - /** * Returns true if way geometry is a closed loop */ diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java index 9913b09b238..ed8819303d3 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMWithTags.java @@ -25,9 +25,27 @@ /** * A base class for OSM entities containing common methods. */ - public class OSMWithTags { + /** + * highway=* values that we don't want to even consider when building the graph. + */ + public static final Set NON_ROUTABLE_HIGHWAYS = Set.of( + "proposed", + "planned", + "construction", + "razed", + "raceway", + "abandoned", + "historic", + "no", + "emergency_bay", + "rest_area", + "services", + "bus_guideway", + "escape" + ); + /* To save memory this is only created when an entity actually has tags. */ private Map tags; @@ -90,7 +108,7 @@ public OSMWithTags addTag(String key, String value) { * The tags of an entity. */ public Map getTags() { - return tags; + return Objects.requireNonNullElse(tags, Map.of()); } /** @@ -440,8 +458,16 @@ public boolean isBoardingLocation() { ); } + /** + * Determines if an entity is a platform. + *

+ * However, they are filtered out if they are tagged usage=tourism. This prevents miniature tourist + * railways like the one in Portland's Zoo (https://www.openstreetmap.org/way/119108622) + * from being linked to transit stops that are underneath it. + **/ public boolean isPlatform() { - return isTag("public_transport", "platform") || isRailwayPlatform(); + var isPlatform = isTag("public_transport", "platform") || isRailwayPlatform(); + return isPlatform && !isTag("usage", "tourism"); } public boolean isRailwayPlatform() { @@ -510,17 +536,25 @@ public void setOsmProvider(OsmProvider provider) { * Determines whether this OSM way is considered routable. The majority of routable ways are those * with a highway= tag (which includes everything from motorways to hiking trails). Anything with * a public_transport=platform or railway=platform tag is also considered routable even if it - * doesn't have a highway tag. Platforms are however filtered out if they are marked - * usage=tourism. This prevents miniature tourist railways like the one in Portland's Zoo from - * receiving a better score and pulling search endpoints away from real transit stops. + * doesn't have a highway tag. */ public boolean isRoutable() { - if (hasTag("highway")) { + if (isOneOfTags("highway", NON_ROUTABLE_HIGHWAYS)) { + return false; + } else if (hasTag("highway") || isPlatform()) { + if (isGeneralAccessDenied()) { + // There are exceptions. + return ( + isMotorcarExplicitlyAllowed() || + isBicycleExplicitlyAllowed() || + isPedestrianExplicitlyAllowed() || + isMotorVehicleExplicitlyAllowed() || + isVehicleExplicitlyAllowed() + ); + } return true; } - if (isPlatform()) { - return !isTag("usage", "tourism"); - } + return false; } diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java b/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java deleted file mode 100644 index 839ae20c3ac..00000000000 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OsmChecks.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.opentripplanner.openstreetmap.model; - -import java.util.Set; - -/** - * This class contains methods that are shared between {@link OSMWay} and {@link OSMRelation}. - *

- * These two classes have some things in common but not enough to create their own inheritance chain. - */ -class OsmChecks { - - private static final Set NON_ROUTABLE_HIGHWAYS = Set.of( - "proposed", - "planned", - "construction", - "razed", - "raceway", - "abandoned", - "historic", - "no", - "emergency_bay", - "rest_area", - "services", - "bus_guideway", - "escape" - ); - - /** - * Determine whether any mode can or should ever traverse the given way. If not, we leave the way - * out of the OTP graph. Potentially routable ways are those that have the tags : highway=* - * public_transport=platform railway=platform - *

- * But not conveyers, proposed highways/roads or those still under construction, and raceways (as - * well as ways where all access is specifically forbidden to the public). - * http://wiki.openstreetmap.org/wiki/Tag:highway%3Dproposed - *

- * A whitelist for highway tags is an alternative to a blacklist. - */ - protected static boolean isRoutable(OSMWithTags way) { - if (way.isOneOfTags("highway", NON_ROUTABLE_HIGHWAYS)) { - return false; - } - - if (way.isGeneralAccessDenied()) { - // There are exceptions. - return ( - way.isMotorcarExplicitlyAllowed() || - way.isBicycleExplicitlyAllowed() || - way.isPedestrianExplicitlyAllowed() || - way.isMotorVehicleExplicitlyAllowed() || - way.isVehicleExplicitlyAllowed() - ); - } - return true; - } -} diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java index c4b52b6e67a..ca5db77df12 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWithTagsTest.java @@ -214,6 +214,11 @@ void isRoutable() { assertFalse(WayTestData.zooPlatform().isRoutable()); } + @Test + void isPlatform() { + assertFalse(WayTestData.zooPlatform().isPlatform()); + } + @Test void testGenerateI18NForPattern() { OSMWithTags osmTags = new OSMWithTags(); From 2b1117a6531cd79e97e7ab0f699412e24e55988b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 05:14:53 +0000 Subject: [PATCH 116/118] Update dependency org.entur.gbfs:gbfs-java-model to v3.0.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f1eb88707d..66e8f9dfedb 100644 --- a/pom.xml +++ b/pom.xml @@ -714,7 +714,7 @@ org.entur.gbfs gbfs-java-model - 3.0.1 + 3.0.9 From 0f4588ad8d3ad06912b4f96f9a9345a653699a0e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 18 Sep 2023 16:13:38 +0200 Subject: [PATCH 117/118] Add ID to mapper error messages --- .../gtfs/mapping/StationMapper.java | 39 +++++++------------ .../gtfs/mapping/StopMapper.java | 12 +++--- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StationMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StationMapper.java index 546894f5728..1308994c414 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/StationMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/StationMapper.java @@ -5,6 +5,7 @@ import java.time.ZoneId; import java.util.HashMap; import java.util.Map; +import org.onebusaway.gtfs.model.Stop; import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.site.StationBuilder; import org.opentripplanner.transit.model.site.StopTransferPriority; @@ -21,7 +22,7 @@ class StationMapper { /** @see StationMapper (this class JavaDoc) for way we need this. */ - private final Map mappedStops = new HashMap<>(); + private final Map mappedStops = new HashMap<>(); private final TranslationHelper translationHelper; private final StopTransferPriority stationTransferPreference; @@ -35,17 +36,18 @@ class StationMapper { } /** Map from GTFS to OTP model, {@code null} safe. */ - Station map(org.onebusaway.gtfs.model.Stop orginal) { + Station map(Stop orginal) { return orginal == null ? null : mappedStops.computeIfAbsent(orginal, this::doMap); } - private Station doMap(org.onebusaway.gtfs.model.Stop rhs) { - if (rhs.getLocationType() != org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STATION) { + private Station doMap(Stop rhs) { + if (rhs.getLocationType() != Stop.LOCATION_TYPE_STATION) { throw new IllegalArgumentException( - "Expected type " + - org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STATION + - ", but got " + - rhs.getLocationType() + "Expected location_type %s, but got %s for stops.txt entry %s".formatted( + Stop.LOCATION_TYPE_STATION, + rhs.getLocationType(), + rhs + ) ); } StationBuilder builder = Station @@ -54,30 +56,15 @@ private Station doMap(org.onebusaway.gtfs.model.Stop rhs) { .withCode(rhs.getCode()); builder.withName( - translationHelper.getTranslation( - org.onebusaway.gtfs.model.Stop.class, - "name", - rhs.getId().getId(), - rhs.getName() - ) + translationHelper.getTranslation(Stop.class, "name", rhs.getId().getId(), rhs.getName()) ); builder.withDescription( - translationHelper.getTranslation( - org.onebusaway.gtfs.model.Stop.class, - "desc", - rhs.getId().getId(), - rhs.getDesc() - ) + translationHelper.getTranslation(Stop.class, "desc", rhs.getId().getId(), rhs.getDesc()) ); builder.withUrl( - translationHelper.getTranslation( - org.onebusaway.gtfs.model.Stop.class, - "url", - rhs.getId().getId(), - rhs.getUrl() - ) + translationHelper.getTranslation(Stop.class, "url", rhs.getId().getId(), rhs.getUrl()) ); builder.withPriority(stationTransferPreference); diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopMapper.java index 5fa2a6f3d1c..a779a3631df 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/StopMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/StopMapper.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import org.onebusaway.gtfs.model.Stop; import org.opentripplanner.framework.collection.MapUtils; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.FareZone; @@ -37,12 +38,11 @@ RegularStop map(org.onebusaway.gtfs.model.Stop orginal) { private RegularStop doMap(org.onebusaway.gtfs.model.Stop gtfsStop) { if (gtfsStop.getLocationType() != org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STOP) { throw new IllegalArgumentException( - "Expected type " + - org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STOP + - ", but got " + - gtfsStop.getLocationType() + - " from stop " + - gtfsStop + "Expected location_type %s, but got %s for stops.txt entry %s".formatted( + Stop.LOCATION_TYPE_STOP, + gtfsStop.getLocationType(), + gtfsStop + ) ); } From 457764ce0add7b1b2f0481c0e83ab8d6f3ba88cb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 21 Sep 2023 10:34:03 +0200 Subject: [PATCH 118/118] Improve test setup --- .../org/opentripplanner/openstreetmap/model/OSMWayTest.java | 4 ---- .../openstreetmap/tagmapping/OsmTagMapperTest.java | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java index 7d17924fe73..c316793ad8c 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/model/OSMWayTest.java @@ -4,11 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair; -import org.opentripplanner.openstreetmap.wayproperty.WayProperties; -import org.opentripplanner.openstreetmap.wayproperty.WayPropertySet; import org.opentripplanner.openstreetmap.wayproperty.specifier.WayTestData; -import org.opentripplanner.street.model.StreetTraversalPermission; public class OSMWayTest { diff --git a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java index 23f35623bd5..164faa644c2 100644 --- a/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java +++ b/src/test/java/org/opentripplanner/openstreetmap/tagmapping/OsmTagMapperTest.java @@ -91,6 +91,7 @@ public void mixin() { assertEquals(1, wps.getDataForWay(withFoo).bicycleSafety().back()); } + @Test public void testAccessNo() { OSMWithTags tags = new OSMWithTags(); OsmTagMapper osmTagMapper = new DefaultMapper();