From 4989fead7ce94b86262659b19642a71588d40a42 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 27 Jun 2024 18:18:42 +0200 Subject: [PATCH 01/86] Map pass through points --- .../LegacyRouteRequestMapper.java | 12 +++++ .../PassThroughLocationMapper.java | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 050127d9a27..c6279a13ea9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -32,6 +32,7 @@ import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.service.TransitService; public class LegacyRouteRequestMapper { @@ -56,6 +57,8 @@ public static RouteRequest toRouteRequest( callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v))); callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v))); + mapPassThroughPoints(request, callWith, context.transitService()); + request.setDateTime( environment.getArgument("date"), environment.getArgument("time"), @@ -257,6 +260,15 @@ public static RouteRequest toRouteRequest( return request; } + static void mapPassThroughPoints(RouteRequest request, CallerWithEnvironment callWith, TransitService transitService) { + callWith.argument( + "passThroughPoints", + (List> v) -> { + request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)); + } + ); + } + private static boolean hasArgument(Map m, String name) { return m.containsKey(name) && m.get(name) != null; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java new file mode 100644 index 00000000000..9215f019b81 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -0,0 +1,46 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Map; +import org.opentripplanner.routing.api.request.PassThroughPoint; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitService; + +class PassThroughLocationMapper { + + static List toLocations( + final TransitService transitService, + final List> passThroughPoints + ) { + return passThroughPoints + .stream() + .map(p -> handlePoint(transitService, p)) + .toList(); + } + + private static PassThroughPoint handlePoint( + final TransitService transitService, + Map map + ) { + final List stops = (List) map.get("placeIds"); + final String name = (String) map.get("name"); + if (stops == null) { + throw new IllegalArgumentException("No stops in pass-through point"); + } + + return stops + .stream() + .map(FeedScopedId::parse) + .flatMap(id -> { + var stopLocations = transitService.getStopOrChildStops(id); + if (stopLocations.isEmpty()) { + throw new IllegalArgumentException("No match for %s.".formatted(id)); + } + return stopLocations.stream(); + }) + .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(sls, name))); + } +} From c16be7b24a3b2fadcb5f86fbb1f787a74bf5f769 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 9 Jul 2024 12:27:44 +0200 Subject: [PATCH 02/86] Update implementation after rebase --- .../gtfs/generated/GraphQLDataFetchers.java | 9 ++++ .../apis/gtfs/generated/GraphQLTypes.java | 42 +++++++++++++++++++ .../LegacyRouteRequestMapper.java | 6 ++- .../PassThroughLocationMapper.java | 5 +-- .../opentripplanner/apis/gtfs/schema.graphqls | 14 +++++++ 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 67944543580..9df2684b7bf 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -1,9 +1,11 @@ //THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; +import graphql.relay.Connection; import graphql.relay.Connection; import graphql.relay.DefaultEdge; import graphql.relay.Edge; +import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; @@ -24,8 +26,12 @@ import org.opentripplanner.apis.gtfs.model.FeedPublisher; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; +import org.opentripplanner.apis.gtfs.model.RouteTypeModel; +import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; +import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; +import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; @@ -48,6 +54,8 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; @@ -58,6 +66,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index ed3e9afefc9..cbd93838cce 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1421,6 +1421,35 @@ public void setGraphQLTags(List tags) { } } + public static class GraphQLPassThroughPointInput { + + private String name; + private List placeIds; + + public GraphQLPassThroughPointInput(Map args) { + if (args != null) { + this.name = (String) args.get("name"); + this.placeIds = (List) args.get("placeIds"); + } + } + + public String getGraphQLName() { + return this.name; + } + + public List getGraphQLPlaceIds() { + return this.placeIds; + } + + public void setGraphQLName(String name) { + this.name = name; + } + + public void setGraphQLPlaceIds(List placeIds) { + this.placeIds = placeIds; + } + } + public static class GraphQLPatternAlertsArgs { private List types; @@ -2661,6 +2690,7 @@ public static class GraphQLQueryTypePlanArgs { private GraphQLOptimizeType optimize; private String pageCursor; private GraphQLVehicleParkingInput parking; + private List passThroughPoints; private GraphQLInputPreferredInput preferred; private Boolean reverseOptimizeOnTheFly; private Long searchWindow; @@ -2738,6 +2768,10 @@ public GraphQLQueryTypePlanArgs(Map args) { } this.pageCursor = (String) args.get("pageCursor"); this.parking = new GraphQLVehicleParkingInput((Map) args.get("parking")); + if (args.get("passThroughPoints") != null) { + this.passThroughPoints = + (List) args.get("passThroughPoints"); + } this.preferred = new GraphQLInputPreferredInput((Map) args.get("preferred")); this.reverseOptimizeOnTheFly = (Boolean) args.get("reverseOptimizeOnTheFly"); @@ -2937,6 +2971,10 @@ public GraphQLVehicleParkingInput getGraphQLParking() { return this.parking; } + public List getGraphQLPassThroughPoints() { + return this.passThroughPoints; + } + public GraphQLInputPreferredInput getGraphQLPreferred() { return this.preferred; } @@ -3195,6 +3233,10 @@ public void setGraphQLParking(GraphQLVehicleParkingInput parking) { this.parking = parking; } + public void setGraphQLPassThroughPoints(List passThroughPoints) { + this.passThroughPoints = passThroughPoints; + } + public void setGraphQLPreferred(GraphQLInputPreferredInput preferred) { this.preferred = preferred; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index c6279a13ea9..8018f10c325 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -260,7 +260,11 @@ public static RouteRequest toRouteRequest( return request; } - static void mapPassThroughPoints(RouteRequest request, CallerWithEnvironment callWith, TransitService transitService) { + static void mapPassThroughPoints( + RouteRequest request, + CallerWithEnvironment callWith, + TransitService transitService + ) { callWith.argument( "passThroughPoints", (List> v) -> { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 9215f019b81..08fa9584704 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -15,10 +15,7 @@ static List toLocations( final TransitService transitService, final List> passThroughPoints ) { - return passThroughPoints - .stream() - .map(p -> handlePoint(transitService, p)) - .toList(); + return passThroughPoints.stream().map(p -> handlePoint(transitService, p)).toList(); } private static PassThroughPoint handlePoint( diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 78f18f4e654..946b4574dbb 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1380,6 +1380,8 @@ type QueryType { pageCursor: String, "Preferences for vehicle parking" parking: VehicleParkingInput, + "The list of points the journey is required to pass through." + passThroughPoints: [PassThroughPoint!], "List of routes and agencies which are given higher preference when planning the itinerary" preferred: InputPreferred, """ @@ -3943,6 +3945,18 @@ input ParkingFilterOperation { tags: [String] } +"Defines one point which the journey must pass through." +input PassThroughPoint { + "Optional name of the pass-through point for debugging and logging. It is not used in routing." + name: String + """ + The list of *stop location ids* which define the pass-through point. At least one id is required. + Stop and Station are supported location types. + The journey must pass through at least one of these entities - not all of them. + """ + placeIds: [String!] +} + "A coordinate used for a location in a plan query." input PlanCoordinateInput { "Latitude as a WGS84 format number." From c81a38b45ae96c6817bb8881b2b6d6f209eb73cc Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 9 Jul 2024 12:51:37 +0200 Subject: [PATCH 03/86] Add test for pass-through points --- .../gtfs/generated/GraphQLDataFetchers.java | 9 ------ .../LegacyRouteRequestMapperTest.java | 28 ++++++++++++++++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 9df2684b7bf..67944543580 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -1,11 +1,9 @@ //THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; -import graphql.relay.Connection; import graphql.relay.Connection; import graphql.relay.DefaultEdge; import graphql.relay.Edge; -import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; @@ -26,12 +24,8 @@ import org.opentripplanner.apis.gtfs.model.FeedPublisher; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; -import org.opentripplanner.apis.gtfs.model.RouteTypeModel; -import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; -import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; -import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; @@ -54,8 +48,6 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; @@ -66,7 +58,6 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; -import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index f2992a30d92..cbd48e1fb92 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -37,6 +37,8 @@ import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.street.search.TraverseMode; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -46,7 +48,11 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { static { Graph graph = new Graph(); - var transitModel = new TransitModel(); + var testModel = TransitModelForTest.of(); + var stopModelBuilder = testModel + .stopModelBuilder() + .withRegularStop(testModel.stop("stop1").build()); + var transitModel = new TransitModel(stopModelBuilder.build(), new Deduplicator()); transitModel.initTimeZone(ZoneIds.BERLIN); final DefaultTransitService transitService = new DefaultTransitService(transitModel); context = @@ -255,6 +261,26 @@ void transferSlack() { assertEquals(TransferPreferences.DEFAULT.slack(), noParamsReq.preferences().transfer().slack()); } + @Test + void passThroughPoints() { + Map arguments = Map.of( + "passThroughPoints", + List.of(Map.of("placeIds", List.of("F:stop1"))) + ); + + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); + assertEquals( + "[PassThroughPoint[stopLocations=[RegularStop{F:stop1 stop1}], name=null]]", + routeRequest.getPassThroughPoints().toString() + ); + + var noParamsReq = LegacyRouteRequestMapper.toRouteRequest(executionContext(Map.of()), context); + assertEquals(List.of(), noParamsReq.getPassThroughPoints()); + } + private DataFetchingEnvironment executionContext(Map arguments) { ExecutionInput executionInput = ExecutionInput .newExecutionInput() From d8e288a97a3dc0a0d521a55484fde4b9b661cf1d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 9 Jul 2024 14:35:57 +0200 Subject: [PATCH 04/86] Add test for failure cases --- .../PassThroughLocationMapper.java | 2 +- .../PassThroughLocationMapperTest.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 08fa9584704..a2b3dc3a1f9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -24,7 +24,7 @@ private static PassThroughPoint handlePoint( ) { final List stops = (List) map.get("placeIds"); final String name = (String) map.get("name"); - if (stops == null) { + if (stops == null || stops.isEmpty()) { throw new IllegalArgumentException("No stops in pass-through point"); } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java new file mode 100644 index 00000000000..6cf1e74f310 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java @@ -0,0 +1,35 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +/** + * A test for the successful case is at {@link LegacyRouteRequestMapperTest#passThroughPoints()} + */ +class PassThroughLocationMapperTest { + + public static List>> failureCases() { + return List.of( + List.of(Map.of()), + List.of(Map.of("placeIds", List.of("fantasy:id"))), + List.of(Map.of("placeIds", List.of())), + List.of(Map.of("placeIds", List.of()), Map.of("placeIds", List.of())) + ); + } + + @ParameterizedTest + @MethodSource("failureCases") + void throwException(List> params) { + var service = new DefaultTransitService(new TransitModel()); + assertThrows( + IllegalArgumentException.class, + () -> PassThroughLocationMapper.toLocations(service, params) + ); + } +} From 905f1e5bab08caf0a31f8843469b3096bdc74248 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 12 Jul 2024 07:35:25 +0200 Subject: [PATCH 05/86] Make general input for via and pass-through routing --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 946b4574dbb..4d7d7d6c519 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1381,7 +1381,7 @@ type QueryType { "Preferences for vehicle parking" parking: VehicleParkingInput, "The list of points the journey is required to pass through." - passThroughPoints: [PassThroughPoint!], + viaPoints: [ViaPoint!], "List of routes and agencies which are given higher preference when planning the itinerary" preferred: InputPreferred, """ @@ -3946,9 +3946,16 @@ input ParkingFilterOperation { } "Defines one point which the journey must pass through." -input PassThroughPoint { - "Optional name of the pass-through point for debugging and logging. It is not used in routing." +input ViaPoint { + "Optional name of the via point for debugging and logging. It is not used in routing." name: String + element: ViaElement! +} + +input ViaElement { + # we want to keep a space here for adding a coordinate rather than a list of stop location ids + # once we do that the API has to detect this case and switch from the pass-through routing + # to the via routing, which uses a different, less efficient algorithm. """ The list of *stop location ids* which define the pass-through point. At least one id is required. Stop and Station are supported location types. From a5b26741ea5b7e81277c8e9b25b418c313ee1e12 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 12 Jul 2024 13:21:23 +0200 Subject: [PATCH 06/86] Re-order schema elements --- .../opentripplanner/apis/gtfs/schema.graphqls | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 4d7d7d6c519..f6449925283 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1380,8 +1380,6 @@ type QueryType { pageCursor: String, "Preferences for vehicle parking" parking: VehicleParkingInput, - "The list of points the journey is required to pass through." - viaPoints: [ViaPoint!], "List of routes and agencies which are given higher preference when planning the itinerary" preferred: InputPreferred, """ @@ -1474,6 +1472,8 @@ type QueryType { triangle: InputTriangle, "List of routes and agencies which are given lower preference when planning the itinerary" unpreferred: InputUnpreferred, + "The list of points the journey is required to pass through." + viaPoints: [ViaPointInput!], """ How much less bad is waiting at the beginning of the trip (replaces `waitReluctance` on the first boarding). Default value: 0.4 @@ -3945,25 +3945,6 @@ input ParkingFilterOperation { tags: [String] } -"Defines one point which the journey must pass through." -input ViaPoint { - "Optional name of the via point for debugging and logging. It is not used in routing." - name: String - element: ViaElement! -} - -input ViaElement { - # we want to keep a space here for adding a coordinate rather than a list of stop location ids - # once we do that the API has to detect this case and switch from the pass-through routing - # to the via routing, which uses a different, less efficient algorithm. - """ - The list of *stop location ids* which define the pass-through point. At least one id is required. - Stop and Station are supported location types. - The journey must pass through at least one of these entities - not all of them. - """ - placeIds: [String!] -} - "A coordinate used for a location in a plan query." input PlanCoordinateInput { "Latitude as a WGS84 format number." @@ -4336,6 +4317,22 @@ input VehicleParkingInput { unpreferredCost: Int } +input ViaElementInput { + """ + The list of *stop location ids* which define the pass-through point. At least one id is required. + Stop and Station are supported location types. + The journey must pass through at least one of these entities - not all of them. + """ + placeIds: [String!] +} + +"Defines one point which the journey must pass through." +input ViaPointInput { + element: ViaElementInput! + "Optional name of the via point for debugging and logging. It is not used in routing." + name: String +} + "Preferences related to walking (excluding walking a bicycle or a scooter)." input WalkPreferencesInput { "The cost of boarding a vehicle while walking." From 2d5ecc8564d7a80fad8b1733076231a99d8b74c1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 13:50:13 +0200 Subject: [PATCH 07/86] Regenerate code --- .../apis/gtfs/generated/GraphQLTypes.java | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index cbd93838cce..bd8a6cc465b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1421,35 +1421,6 @@ public void setGraphQLTags(List tags) { } } - public static class GraphQLPassThroughPointInput { - - private String name; - private List placeIds; - - public GraphQLPassThroughPointInput(Map args) { - if (args != null) { - this.name = (String) args.get("name"); - this.placeIds = (List) args.get("placeIds"); - } - } - - public String getGraphQLName() { - return this.name; - } - - public List getGraphQLPlaceIds() { - return this.placeIds; - } - - public void setGraphQLName(String name) { - this.name = name; - } - - public void setGraphQLPlaceIds(List placeIds) { - this.placeIds = placeIds; - } - } - public static class GraphQLPatternAlertsArgs { private List types; @@ -2690,7 +2661,6 @@ public static class GraphQLQueryTypePlanArgs { private GraphQLOptimizeType optimize; private String pageCursor; private GraphQLVehicleParkingInput parking; - private List passThroughPoints; private GraphQLInputPreferredInput preferred; private Boolean reverseOptimizeOnTheFly; private Long searchWindow; @@ -2703,6 +2673,7 @@ public static class GraphQLQueryTypePlanArgs { private List transportModes; private GraphQLInputTriangleInput triangle; private GraphQLInputUnpreferredInput unpreferred; + private List viaPoints; private Double waitAtBeginningFactor; private Double waitReluctance; private Integer walkBoardCost; @@ -2768,10 +2739,6 @@ public GraphQLQueryTypePlanArgs(Map args) { } this.pageCursor = (String) args.get("pageCursor"); this.parking = new GraphQLVehicleParkingInput((Map) args.get("parking")); - if (args.get("passThroughPoints") != null) { - this.passThroughPoints = - (List) args.get("passThroughPoints"); - } this.preferred = new GraphQLInputPreferredInput((Map) args.get("preferred")); this.reverseOptimizeOnTheFly = (Boolean) args.get("reverseOptimizeOnTheFly"); @@ -2788,6 +2755,9 @@ public GraphQLQueryTypePlanArgs(Map args) { this.triangle = new GraphQLInputTriangleInput((Map) args.get("triangle")); this.unpreferred = new GraphQLInputUnpreferredInput((Map) args.get("unpreferred")); + if (args.get("viaPoints") != null) { + this.viaPoints = (List) args.get("viaPoints"); + } this.waitAtBeginningFactor = (Double) args.get("waitAtBeginningFactor"); this.waitReluctance = (Double) args.get("waitReluctance"); this.walkBoardCost = (Integer) args.get("walkBoardCost"); @@ -2971,10 +2941,6 @@ public GraphQLVehicleParkingInput getGraphQLParking() { return this.parking; } - public List getGraphQLPassThroughPoints() { - return this.passThroughPoints; - } - public GraphQLInputPreferredInput getGraphQLPreferred() { return this.preferred; } @@ -3023,6 +2989,10 @@ public GraphQLInputUnpreferredInput getGraphQLUnpreferred() { return this.unpreferred; } + public List getGraphQLViaPoints() { + return this.viaPoints; + } + public Double getGraphQLWaitAtBeginningFactor() { return this.waitAtBeginningFactor; } @@ -3233,10 +3203,6 @@ public void setGraphQLParking(GraphQLVehicleParkingInput parking) { this.parking = parking; } - public void setGraphQLPassThroughPoints(List passThroughPoints) { - this.passThroughPoints = passThroughPoints; - } - public void setGraphQLPreferred(GraphQLInputPreferredInput preferred) { this.preferred = preferred; } @@ -3285,6 +3251,10 @@ public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) { this.unpreferred = unpreferred; } + public void setGraphQLViaPoints(List viaPoints) { + this.viaPoints = viaPoints; + } + public void setGraphQLWaitAtBeginningFactor(Double waitAtBeginningFactor) { this.waitAtBeginningFactor = waitAtBeginningFactor; } @@ -5103,6 +5073,54 @@ public enum GraphQLVertexType { TRANSIT, } + public static class GraphQLViaElementInput { + + private List placeIds; + + public GraphQLViaElementInput(Map args) { + if (args != null) { + this.placeIds = (List) args.get("placeIds"); + } + } + + public List getGraphQLPlaceIds() { + return this.placeIds; + } + + public void setGraphQLPlaceIds(List placeIds) { + this.placeIds = placeIds; + } + } + + public static class GraphQLViaPointInput { + + private GraphQLViaElementInput element; + private String name; + + public GraphQLViaPointInput(Map args) { + if (args != null) { + this.element = new GraphQLViaElementInput((Map) args.get("element")); + this.name = (String) args.get("name"); + } + } + + public GraphQLViaElementInput getGraphQLElement() { + return this.element; + } + + public String getGraphQLName() { + return this.name; + } + + public void setGraphQLElement(GraphQLViaElementInput element) { + this.element = element; + } + + public void setGraphQLName(String name) { + this.name = name; + } + } + public static class GraphQLWalkPreferencesInput { private org.opentripplanner.framework.model.Cost boardCost; From ab706e7454240e0a25761d9d280d25e4a1430883 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 14:06:00 +0200 Subject: [PATCH 08/86] Update to newest schema --- .../routerequest/LegacyRouteRequestMapper.java | 10 ++++------ .../routerequest/PassThroughLocationMapper.java | 6 ++++-- .../org/opentripplanner/apis/gtfs/schema.graphqls | 12 +++++++++++- .../apis/gtfs/GraphQLIntegrationTest.java | 4 ++-- .../routerequest/LegacyRouteRequestMapperTest.java | 4 ++-- .../apis/gtfs/queries/plan-extended.graphql | 4 ++++ 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 8018f10c325..b91e97f194c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -57,7 +57,7 @@ public static RouteRequest toRouteRequest( callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v))); callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v))); - mapPassThroughPoints(request, callWith, context.transitService()); + mapViaPoints(request, callWith, context.transitService()); request.setDateTime( environment.getArgument("date"), @@ -260,16 +260,14 @@ public static RouteRequest toRouteRequest( return request; } - static void mapPassThroughPoints( + static void mapViaPoints( RouteRequest request, CallerWithEnvironment callWith, TransitService transitService ) { callWith.argument( - "passThroughPoints", - (List> v) -> { - request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)); - } + "viaPoints", + (List> v) -> request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)) ); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index a2b3dc3a1f9..358f8a516b7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -22,8 +22,10 @@ private static PassThroughPoint handlePoint( final TransitService transitService, Map map ) { - final List stops = (List) map.get("placeIds"); - final String name = (String) map.get("name"); + Map element= (Map) map.get("element"); + List stops = (List) element.get("placeIds"); + + final String name = (String) element.get("name"); if (stops == null || stops.isEmpty()) { throw new IllegalArgumentException("No stops in pass-through point"); } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f6449925283..329fe8a9dae 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4317,6 +4317,12 @@ input VehicleParkingInput { unpreferredCost: Int } +""" +Represents an element that must be visited by the routing result. + +Right now only stop or station IDs are supported but this will be extended to support via point +coordinates as well. +""" input ViaElementInput { """ The list of *stop location ids* which define the pass-through point. At least one id is required. @@ -4326,8 +4332,12 @@ input ViaElementInput { placeIds: [String!] } -"Defines one point which the journey must pass through." +"Defines a point which the routing result must visit." input ViaPointInput { + """ + The place that must be visited by the via routing result. Right now only stops and stations are + supported but this will be extended to cover coordinates as well. + """ element: ViaElementInput! "Optional name of the via point for debugging and logging. It is not used in routing." name: String diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 7cd9a0910b1..cca3fd0fc00 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -118,7 +118,7 @@ class GraphQLIntegrationTest { .toList(); private static final Route ROUTE = TransitModelForTest.route("a-route").build(); - private static VehicleRentalStation VEHICLE_RENTAL_STATION = new TestVehicleRentalStationBuilder() + private static final VehicleRentalStation VEHICLE_RENTAL_STATION = new TestVehicleRentalStationBuilder() .withVehicles(10) .withSpaces(10) .withVehicleTypeBicycle(5, 7) @@ -126,7 +126,7 @@ class GraphQLIntegrationTest { .withSystem("Network-1", "https://foo.bar") .build(); - private static VehicleRentalVehicle RENTAL_VEHICLE = new TestFreeFloatingRentalVehicleBuilder() + private static final VehicleRentalVehicle RENTAL_VEHICLE = new TestFreeFloatingRentalVehicleBuilder() .withSystem("Network-1", "https://foo.bar") .build(); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index cbd48e1fb92..52df026a3f8 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -264,8 +264,8 @@ void transferSlack() { @Test void passThroughPoints() { Map arguments = Map.of( - "passThroughPoints", - List.of(Map.of("placeIds", List.of("F:stop1"))) + "viaPoints", + List.of(Map.of("element", Map.of("placeIds", List.of("F:stop1")))) ); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 76bf8aa84e0..149c023821d 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -10,6 +10,10 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] + viaPoints: [{ + name: "Flower shop" + element: { placeIds: ["trimet:1123", "trimet:30284"] } + }] ) { itineraries { start From a877bd05464e7748b691bdec6964a2f164715b0a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 14:19:55 +0200 Subject: [PATCH 09/86] Fix tests --- .../mapping/routerequest/LegacyRouteRequestMapper.java | 3 ++- .../routerequest/PassThroughLocationMapper.java | 2 +- .../routerequest/PassThroughLocationMapperTest.java | 10 ++++++---- .../apis/gtfs/queries/plan-extended.graphql | 10 ++++++---- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index b91e97f194c..930176f45ee 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -267,7 +267,8 @@ static void mapViaPoints( ) { callWith.argument( "viaPoints", - (List> v) -> request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)) + (List> v) -> + request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)) ); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 358f8a516b7..5eb6a8b2e2c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -22,7 +22,7 @@ private static PassThroughPoint handlePoint( final TransitService transitService, Map map ) { - Map element= (Map) map.get("element"); + Map element = (Map) map.get("element"); List stops = (List) element.get("placeIds"); final String name = (String) element.get("name"); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java index 6cf1e74f310..e26c728d4d0 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java @@ -16,10 +16,12 @@ class PassThroughLocationMapperTest { public static List>> failureCases() { return List.of( - List.of(Map.of()), - List.of(Map.of("placeIds", List.of("fantasy:id"))), - List.of(Map.of("placeIds", List.of())), - List.of(Map.of("placeIds", List.of()), Map.of("placeIds", List.of())) + List.of(Map.of("element", Map.of("placeIds", List.of("fantasy:id")))), + List.of(Map.of("element", Map.of("placeIds", List.of()))), + List.of( + Map.of("element", Map.of("placeIds", List.of())), + Map.of("element", Map.of("placeIds", List.of())) + ) ); } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 149c023821d..f4c42bc228e 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -10,10 +10,12 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] - viaPoints: [{ - name: "Flower shop" - element: { placeIds: ["trimet:1123", "trimet:30284"] } - }] + viaPoints: [ + { + name: "Flower shop" + element: { placeIds: ["trimet:1123", "trimet:30284"] } + } + ] ) { itineraries { start From 8556f554cdcf80d88e24807b5ed789a27eb209fb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 14:26:41 +0200 Subject: [PATCH 10/86] Use correct stop ids --- .../org/opentripplanner/apis/gtfs/queries/plan-extended.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index f4c42bc228e..a5a01562e4d 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -13,7 +13,7 @@ viaPoints: [ { name: "Flower shop" - element: { placeIds: ["trimet:1123", "trimet:30284"] } + element: { placeIds: ["F:A", "F:B"] } } ] ) { From 3ea752463ecc3c31b19d1e18e60bdf48a9ffc47a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 14:36:45 +0200 Subject: [PATCH 11/86] Update formatting --- .../apis/gtfs/queries/plan-extended.graphql | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index a5a01562e4d..bbb1d5f4a56 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -10,12 +10,7 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] - viaPoints: [ - { - name: "Flower shop" - element: { placeIds: ["F:A", "F:B"] } - } - ] + viaPoints: [{ name: "Flower shop", element: { placeIds: ["F:A", "F:B"] } }] ) { itineraries { start From bb83e0c623945b2309712d28b472a6f19e29a0c6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 9 Sep 2024 10:44:19 +0200 Subject: [PATCH 12/86] Reformat schema --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 329fe8a9dae..aab9ca93fbf 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4323,24 +4323,24 @@ Represents an element that must be visited by the routing result. Right now only stop or station IDs are supported but this will be extended to support via point coordinates as well. """ -input ViaElementInput { +input ViaLocationInput { """ The list of *stop location ids* which define the pass-through point. At least one id is required. Stop and Station are supported location types. The journey must pass through at least one of these entities - not all of them. """ - placeIds: [String!] + locationIds: [String!] } "Defines a point which the routing result must visit." input ViaPointInput { + "Optional label of the via point for debugging and logging. It is not used in routing." + label: String """ The place that must be visited by the via routing result. Right now only stops and stations are supported but this will be extended to cover coordinates as well. """ - element: ViaElementInput! - "Optional name of the via point for debugging and logging. It is not used in routing." - name: String + place: ViaLocationInput! } "Preferences related to walking (excluding walking a bicycle or a scooter)." From a09793bd0f45381a8b1b464d514af4774c1ac791 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 9 Sep 2024 10:52:45 +0200 Subject: [PATCH 13/86] Regenerate APIs, update tests --- .../apis/gtfs/generated/GraphQLTypes.java | 40 +++++++++---------- .../PassThroughLocationMapper.java | 4 +- .../LegacyRouteRequestMapperTest.java | 2 +- .../PassThroughLocationMapperTest.java | 8 ++-- .../apis/gtfs/queries/plan-extended.graphql | 4 +- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index bd8a6cc465b..3744868d275 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -5073,51 +5073,51 @@ public enum GraphQLVertexType { TRANSIT, } - public static class GraphQLViaElementInput { + public static class GraphQLViaLocationInput { - private List placeIds; + private List locationIds; - public GraphQLViaElementInput(Map args) { + public GraphQLViaLocationInput(Map args) { if (args != null) { - this.placeIds = (List) args.get("placeIds"); + this.locationIds = (List) args.get("locationIds"); } } - public List getGraphQLPlaceIds() { - return this.placeIds; + public List getGraphQLLocationIds() { + return this.locationIds; } - public void setGraphQLPlaceIds(List placeIds) { - this.placeIds = placeIds; + public void setGraphQLLocationIds(List locationIds) { + this.locationIds = locationIds; } } public static class GraphQLViaPointInput { - private GraphQLViaElementInput element; - private String name; + private String label; + private GraphQLViaLocationInput place; public GraphQLViaPointInput(Map args) { if (args != null) { - this.element = new GraphQLViaElementInput((Map) args.get("element")); - this.name = (String) args.get("name"); + this.label = (String) args.get("label"); + this.place = new GraphQLViaLocationInput((Map) args.get("place")); } } - public GraphQLViaElementInput getGraphQLElement() { - return this.element; + public String getGraphQLLabel() { + return this.label; } - public String getGraphQLName() { - return this.name; + public GraphQLViaLocationInput getGraphQLPlace() { + return this.place; } - public void setGraphQLElement(GraphQLViaElementInput element) { - this.element = element; + public void setGraphQLLabel(String label) { + this.label = label; } - public void setGraphQLName(String name) { - this.name = name; + public void setGraphQLPlace(GraphQLViaLocationInput place) { + this.place = place; } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 5eb6a8b2e2c..370bc2c3917 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -22,8 +22,8 @@ private static PassThroughPoint handlePoint( final TransitService transitService, Map map ) { - Map element = (Map) map.get("element"); - List stops = (List) element.get("placeIds"); + Map element = (Map) map.get("place"); + List stops = (List) element.get("locationIds"); final String name = (String) element.get("name"); if (stops == null || stops.isEmpty()) { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 52df026a3f8..385242a49d0 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -265,7 +265,7 @@ void transferSlack() { void passThroughPoints() { Map arguments = Map.of( "viaPoints", - List.of(Map.of("element", Map.of("placeIds", List.of("F:stop1")))) + List.of(Map.of("place", Map.of("locationIds", List.of("F:stop1")))) ); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java index e26c728d4d0..fd2126496ac 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java @@ -16,11 +16,11 @@ class PassThroughLocationMapperTest { public static List>> failureCases() { return List.of( - List.of(Map.of("element", Map.of("placeIds", List.of("fantasy:id")))), - List.of(Map.of("element", Map.of("placeIds", List.of()))), + List.of(Map.of("place", Map.of("locationIds", List.of("fantasy:id")))), + List.of(Map.of("place", Map.of("locationIds", List.of()))), List.of( - Map.of("element", Map.of("placeIds", List.of())), - Map.of("element", Map.of("placeIds", List.of())) + Map.of("place", Map.of("locationIds", List.of())), + Map.of("place", Map.of("locationIds", List.of())) ) ); } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index bbb1d5f4a56..6970b59140c 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -10,7 +10,9 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] - viaPoints: [{ name: "Flower shop", element: { placeIds: ["F:A", "F:B"] } }] + viaPoints: [ + { label: "Flower shop", place: { locationIds: ["F:A", "F:B"] } } + ] ) { itineraries { start From dab0b599781d4b10007704ee2221555ef0e9ec40 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 10 Sep 2024 13:58:29 +0200 Subject: [PATCH 14/86] Take into account the newest schema --- .../apis/gtfs/generated/GraphQLTypes.java | 87 ++++++++++--------- .../LegacyRouteRequestMapper.java | 2 +- .../PassThroughLocationMapper.java | 27 +++--- .../opentripplanner/apis/gtfs/schema.graphqls | 28 +++--- .../LegacyRouteRequestMapperTest.java | 4 +- .../PassThroughLocationMapperTest.java | 9 +- .../apis/gtfs/queries/plan-extended.graphql | 9 +- 7 files changed, 78 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 3744868d275..1b3b2ceced0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1421,6 +1421,35 @@ public void setGraphQLTags(List tags) { } } + public static class GraphQLPassThroughLocationInput { + + private String label; + private String stopLocationId; + + public GraphQLPassThroughLocationInput(Map args) { + if (args != null) { + this.label = (String) args.get("label"); + this.stopLocationId = (String) args.get("stopLocationId"); + } + } + + public String getGraphQLLabel() { + return this.label; + } + + public String getGraphQLStopLocationId() { + return this.stopLocationId; + } + + public void setGraphQLLabel(String label) { + this.label = label; + } + + public void setGraphQLStopLocationId(String stopLocationId) { + this.stopLocationId = stopLocationId; + } + } + public static class GraphQLPatternAlertsArgs { private List types; @@ -2673,7 +2702,7 @@ public static class GraphQLQueryTypePlanArgs { private List transportModes; private GraphQLInputTriangleInput triangle; private GraphQLInputUnpreferredInput unpreferred; - private List viaPoints; + private List via; private Double waitAtBeginningFactor; private Double waitReluctance; private Integer walkBoardCost; @@ -2755,8 +2784,8 @@ public GraphQLQueryTypePlanArgs(Map args) { this.triangle = new GraphQLInputTriangleInput((Map) args.get("triangle")); this.unpreferred = new GraphQLInputUnpreferredInput((Map) args.get("unpreferred")); - if (args.get("viaPoints") != null) { - this.viaPoints = (List) args.get("viaPoints"); + if (args.get("via") != null) { + this.via = (List) args.get("via"); } this.waitAtBeginningFactor = (Double) args.get("waitAtBeginningFactor"); this.waitReluctance = (Double) args.get("waitReluctance"); @@ -2989,8 +3018,8 @@ public GraphQLInputUnpreferredInput getGraphQLUnpreferred() { return this.unpreferred; } - public List getGraphQLViaPoints() { - return this.viaPoints; + public List getGraphQLVia() { + return this.via; } public Double getGraphQLWaitAtBeginningFactor() { @@ -3251,8 +3280,8 @@ public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) { this.unpreferred = unpreferred; } - public void setGraphQLViaPoints(List viaPoints) { - this.viaPoints = viaPoints; + public void setGraphQLVia(List via) { + this.via = via; } public void setGraphQLWaitAtBeginningFactor(Double waitAtBeginningFactor) { @@ -5075,49 +5104,23 @@ public enum GraphQLVertexType { public static class GraphQLViaLocationInput { - private List locationIds; + private GraphQLPassThroughLocationInput passThroughLocation; public GraphQLViaLocationInput(Map args) { if (args != null) { - this.locationIds = (List) args.get("locationIds"); - } - } - - public List getGraphQLLocationIds() { - return this.locationIds; - } - - public void setGraphQLLocationIds(List locationIds) { - this.locationIds = locationIds; - } - } - - public static class GraphQLViaPointInput { - - private String label; - private GraphQLViaLocationInput place; - - public GraphQLViaPointInput(Map args) { - if (args != null) { - this.label = (String) args.get("label"); - this.place = new GraphQLViaLocationInput((Map) args.get("place")); + this.passThroughLocation = + new GraphQLPassThroughLocationInput( + (Map) args.get("passThroughLocation") + ); } } - public String getGraphQLLabel() { - return this.label; - } - - public GraphQLViaLocationInput getGraphQLPlace() { - return this.place; - } - - public void setGraphQLLabel(String label) { - this.label = label; + public GraphQLPassThroughLocationInput getGraphQLPassThroughLocation() { + return this.passThroughLocation; } - public void setGraphQLPlace(GraphQLViaLocationInput place) { - this.place = place; + public void setGraphQLPassThroughLocation(GraphQLPassThroughLocationInput passThroughLocation) { + this.passThroughLocation = passThroughLocation; } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 930176f45ee..a107f1f1aad 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -266,7 +266,7 @@ static void mapViaPoints( TransitService transitService ) { callWith.argument( - "viaPoints", + "via", (List> v) -> request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)) ); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 370bc2c3917..6cdcc234bcd 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -1,10 +1,8 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; - import java.util.List; import java.util.Map; +import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.routing.api.request.PassThroughPoint; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TransitService; @@ -22,24 +20,19 @@ private static PassThroughPoint handlePoint( final TransitService transitService, Map map ) { - Map element = (Map) map.get("place"); - List stops = (List) element.get("locationIds"); + Map element = (Map) map.get("passThroughLocation"); + String id = (String) element.get("stopLocationId"); final String name = (String) element.get("name"); - if (stops == null || stops.isEmpty()) { + if (StringUtils.hasNoValue(id)) { throw new IllegalArgumentException("No stops in pass-through point"); } - return stops - .stream() - .map(FeedScopedId::parse) - .flatMap(id -> { - var stopLocations = transitService.getStopOrChildStops(id); - if (stopLocations.isEmpty()) { - throw new IllegalArgumentException("No match for %s.".formatted(id)); - } - return stopLocations.stream(); - }) - .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(sls, name))); + var stopLocationId = FeedScopedId.parse(id); + var stopLocations = List.copyOf(transitService.getStopOrChildStops(stopLocationId)); + if (stopLocations.isEmpty()) { + throw new IllegalArgumentException("No match for %s.".formatted(id)); + } + return new PassThroughPoint(stopLocations, name); } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index aab9ca93fbf..a2ad9a7bf71 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1473,7 +1473,7 @@ type QueryType { "List of routes and agencies which are given lower preference when planning the itinerary" unpreferred: InputUnpreferred, "The list of points the journey is required to pass through." - viaPoints: [ViaPointInput!], + via: [ViaLocationInput!], """ How much less bad is waiting at the beginning of the trip (replaces `waitReluctance` on the first boarding). Default value: 0.4 @@ -3945,6 +3945,14 @@ input ParkingFilterOperation { tags: [String] } +"A stop that must be visited by the routing result." +input PassThroughLocationInput { + "Optional label of the via point for debugging and logging. It is not used in routing." + label: String + "The ID of the stop or station to visit. Should be in the format :." + stopLocationId: String! +} + "A coordinate used for a location in a plan query." input PlanCoordinateInput { "Latitude as a WGS84 format number." @@ -4324,23 +4332,7 @@ Right now only stop or station IDs are supported but this will be extended to su coordinates as well. """ input ViaLocationInput { - """ - The list of *stop location ids* which define the pass-through point. At least one id is required. - Stop and Station are supported location types. - The journey must pass through at least one of these entities - not all of them. - """ - locationIds: [String!] -} - -"Defines a point which the routing result must visit." -input ViaPointInput { - "Optional label of the via point for debugging and logging. It is not used in routing." - label: String - """ - The place that must be visited by the via routing result. Right now only stops and stations are - supported but this will be extended to cover coordinates as well. - """ - place: ViaLocationInput! + passThroughLocation: PassThroughLocationInput } "Preferences related to walking (excluding walking a bicycle or a scooter)." diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 385242a49d0..e7b879f0501 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -264,8 +264,8 @@ void transferSlack() { @Test void passThroughPoints() { Map arguments = Map.of( - "viaPoints", - List.of(Map.of("place", Map.of("locationIds", List.of("F:stop1")))) + "via", + List.of(Map.of("passThroughLocation", Map.of("stopLocationId", "F:stop1"))) ); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java index fd2126496ac..ec34df90378 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java @@ -16,12 +16,9 @@ class PassThroughLocationMapperTest { public static List>> failureCases() { return List.of( - List.of(Map.of("place", Map.of("locationIds", List.of("fantasy:id")))), - List.of(Map.of("place", Map.of("locationIds", List.of()))), - List.of( - Map.of("place", Map.of("locationIds", List.of())), - Map.of("place", Map.of("locationIds", List.of())) - ) + List.of(Map.of("passThroughLocation", Map.of("stopLocationId", "fantasy:id"))), + List.of(Map.of("passThroughLocation", Map.of())), + List.of(Map.of("passThroughLocation", Map.of()), Map.of("passThroughLocation", Map.of())) ); } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 6970b59140c..4bbcec39d06 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -10,8 +10,13 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] - viaPoints: [ - { label: "Flower shop", place: { locationIds: ["F:A", "F:B"] } } + via: [ + { + passThroughLocation: { + label: "A stop that you want to visit along the route" + stopLocationId: "F:A" + } + } ] ) { itineraries { From 66ccaa6fe8eea87845b37e8f06d1e8046f0c348a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 26 Sep 2024 13:21:48 +0200 Subject: [PATCH 15/86] Copy latest API change from via point PR --- .../apis/gtfs/generated/GraphQLTypes.java | 157 ++++++++++++------ .../PassThroughLocationMapper.java | 17 +- .../opentripplanner/apis/gtfs/schema.graphqls | 72 +++++--- .../LegacyRouteRequestMapperTest.java | 2 +- .../PassThroughLocationMapperTest.java | 6 +- .../apis/gtfs/queries/plan-extended.graphql | 4 +- 6 files changed, 171 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 1b3b2ceced0..db12589e418 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1421,35 +1421,6 @@ public void setGraphQLTags(List tags) { } } - public static class GraphQLPassThroughLocationInput { - - private String label; - private String stopLocationId; - - public GraphQLPassThroughLocationInput(Map args) { - if (args != null) { - this.label = (String) args.get("label"); - this.stopLocationId = (String) args.get("stopLocationId"); - } - } - - public String getGraphQLLabel() { - return this.label; - } - - public String getGraphQLStopLocationId() { - return this.stopLocationId; - } - - public void setGraphQLLabel(String label) { - this.label = label; - } - - public void setGraphQLStopLocationId(String stopLocationId) { - this.stopLocationId = stopLocationId; - } - } - public static class GraphQLPatternAlertsArgs { private List types; @@ -1792,6 +1763,35 @@ public void setGraphQLTransitOnly(Boolean transitOnly) { } } + public static class GraphQLPlanPassThroughViaLocationInput { + + private String label; + private List stopLocationIds; + + public GraphQLPlanPassThroughViaLocationInput(Map args) { + if (args != null) { + this.label = (String) args.get("label"); + this.stopLocationIds = (List) args.get("stopLocationIds"); + } + } + + public String getGraphQLLabel() { + return this.label; + } + + public List getGraphQLStopLocationIds() { + return this.stopLocationIds; + } + + public void setGraphQLLabel(String label) { + this.label = label; + } + + public void setGraphQLStopLocationIds(List stopLocationIds) { + this.stopLocationIds = stopLocationIds; + } + } + public static class GraphQLPlanPreferencesInput { private GraphQLAccessibilityPreferencesInput accessibility; @@ -2024,6 +2024,75 @@ public void setGraphQLTransit(List transi } } + public static class GraphQLPlanViaLocationInput { + + private GraphQLPlanPassThroughViaLocationInput passThrough; + private GraphQLPlanVisitViaLocationInput visit; + + public GraphQLPlanViaLocationInput(Map args) { + if (args != null) { + this.passThrough = + new GraphQLPlanPassThroughViaLocationInput((Map) args.get("passThrough")); + this.visit = new GraphQLPlanVisitViaLocationInput((Map) args.get("visit")); + } + } + + public GraphQLPlanPassThroughViaLocationInput getGraphQLPassThrough() { + return this.passThrough; + } + + public GraphQLPlanVisitViaLocationInput getGraphQLVisit() { + return this.visit; + } + + public void setGraphQLPassThrough(GraphQLPlanPassThroughViaLocationInput passThrough) { + this.passThrough = passThrough; + } + + public void setGraphQLVisit(GraphQLPlanVisitViaLocationInput visit) { + this.visit = visit; + } + } + + public static class GraphQLPlanVisitViaLocationInput { + + private String label; + private java.time.Duration minimumWaitTime; + private List stopLocationIds; + + public GraphQLPlanVisitViaLocationInput(Map args) { + if (args != null) { + this.label = (String) args.get("label"); + this.minimumWaitTime = (java.time.Duration) args.get("minimumWaitTime"); + this.stopLocationIds = (List) args.get("stopLocationIds"); + } + } + + public String getGraphQLLabel() { + return this.label; + } + + public java.time.Duration getGraphQLMinimumWaitTime() { + return this.minimumWaitTime; + } + + public List getGraphQLStopLocationIds() { + return this.stopLocationIds; + } + + public void setGraphQLLabel(String label) { + this.label = label; + } + + public void setGraphQLMinimumWaitTime(java.time.Duration minimumWaitTime) { + this.minimumWaitTime = minimumWaitTime; + } + + public void setGraphQLStopLocationIds(List stopLocationIds) { + this.stopLocationIds = stopLocationIds; + } + } + public enum GraphQLPropulsionType { COMBUSTION, COMBUSTION_DIESEL, @@ -2702,7 +2771,7 @@ public static class GraphQLQueryTypePlanArgs { private List transportModes; private GraphQLInputTriangleInput triangle; private GraphQLInputUnpreferredInput unpreferred; - private List via; + private List via; private Double waitAtBeginningFactor; private Double waitReluctance; private Integer walkBoardCost; @@ -2785,7 +2854,7 @@ public GraphQLQueryTypePlanArgs(Map args) { this.unpreferred = new GraphQLInputUnpreferredInput((Map) args.get("unpreferred")); if (args.get("via") != null) { - this.via = (List) args.get("via"); + this.via = (List) args.get("via"); } this.waitAtBeginningFactor = (Double) args.get("waitAtBeginningFactor"); this.waitReluctance = (Double) args.get("waitReluctance"); @@ -3018,7 +3087,7 @@ public GraphQLInputUnpreferredInput getGraphQLUnpreferred() { return this.unpreferred; } - public List getGraphQLVia() { + public List getGraphQLVia() { return this.via; } @@ -3280,7 +3349,7 @@ public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) { this.unpreferred = unpreferred; } - public void setGraphQLVia(List via) { + public void setGraphQLVia(List via) { this.via = via; } @@ -5102,28 +5171,6 @@ public enum GraphQLVertexType { TRANSIT, } - public static class GraphQLViaLocationInput { - - private GraphQLPassThroughLocationInput passThroughLocation; - - public GraphQLViaLocationInput(Map args) { - if (args != null) { - this.passThroughLocation = - new GraphQLPassThroughLocationInput( - (Map) args.get("passThroughLocation") - ); - } - } - - public GraphQLPassThroughLocationInput getGraphQLPassThroughLocation() { - return this.passThroughLocation; - } - - public void setGraphQLPassThroughLocation(GraphQLPassThroughLocationInput passThroughLocation) { - this.passThroughLocation = passThroughLocation; - } - } - public static class GraphQLWalkPreferencesInput { private org.opentripplanner.framework.model.Cost boardCost; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java index 6cdcc234bcd..5c126531390 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.Map; -import org.opentripplanner.framework.lang.StringUtils; +import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.routing.api.request.PassThroughPoint; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TransitService; @@ -20,18 +20,21 @@ private static PassThroughPoint handlePoint( final TransitService transitService, Map map ) { - Map element = (Map) map.get("passThroughLocation"); - String id = (String) element.get("stopLocationId"); + Map element = (Map) map.get("passThrough"); + List ids = (List) element.get("stopLocationIds"); final String name = (String) element.get("name"); - if (StringUtils.hasNoValue(id)) { + if (CollectionUtils.isEmpty(ids)) { throw new IllegalArgumentException("No stops in pass-through point"); } - var stopLocationId = FeedScopedId.parse(id); - var stopLocations = List.copyOf(transitService.getStopOrChildStops(stopLocationId)); + var stopLocations = ids + .stream() + .map(FeedScopedId::parse) + .flatMap(id -> transitService.getStopOrChildStops(id).stream()) + .toList(); if (stopLocations.isEmpty()) { - throw new IllegalArgumentException("No match for %s.".formatted(id)); + throw new IllegalArgumentException("No stop locations found for %s.".formatted(ids)); } return new PassThroughPoint(stopLocations, name); } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a2ad9a7bf71..645f0f836bf 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1473,7 +1473,7 @@ type QueryType { "List of routes and agencies which are given lower preference when planning the itinerary" unpreferred: InputUnpreferred, "The list of points the journey is required to pass through." - via: [ViaLocationInput!], + via: [PlanViaLocationInput!], """ How much less bad is waiting at the beginning of the trip (replaces `waitReluctance` on the first boarding). Default value: 0.4 @@ -3945,14 +3945,6 @@ input ParkingFilterOperation { tags: [String] } -"A stop that must be visited by the routing result." -input PassThroughLocationInput { - "Optional label of the via point for debugging and logging. It is not used in routing." - label: String - "The ID of the stop or station to visit. Should be in the format :." - stopLocationId: String! -} - "A coordinate used for a location in a plan query." input PlanCoordinateInput { "Latitude as a WGS84 format number." @@ -4063,6 +4055,21 @@ input PlanModesInput { transitOnly: Boolean = false } +""" +One of the listed stop locations must be visited on-board a transit vehicle or the journey must +alight or board at the location. +""" +input PlanPassThroughViaLocationInput { + "The label/name of the location. This is pass-through information and is not used in routing." + label: String + """ + A list of stop locations. A stop location can be a quay, a stop place, a multimodal + stop place or a group of stop places. It is enough to visit ONE of the locations + listed. + """ + stopLocationIds: [String] +} + "Wrapper type for different types of preferences related to plan query." input PlanPreferencesInput { "Accessibility preferences that affect both the street and transit routing." @@ -4156,6 +4163,43 @@ input PlanTransitModesInput { transit: [PlanTransitModePreferenceInput!] } +""" +A via-location is used to specifying a location as an intermediate place the router must +route through. The via-location must be either a pass-through-location or a +visit-via-location. An on-board "visit" is only allowed for pass-through-via-locations, while +the visit-via-location can visit a stop-location or a coordinate and specify a +minimum-wait-time. +""" +input PlanViaLocationInput @oneOf { + passThrough: PlanPassThroughViaLocationInput + visit: PlanVisitViaLocationInput +} + +""" +A visit-via-location is a physical visit to one of the stops or coordinates listed. An +on-board visit does not count, the traveler must alight or board at the given stop for +it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to +the closest point in the street network from a stop and back to another stop to join +the transit network. + +NOTE! Coordinates are NOT supported jet. +""" +input PlanVisitViaLocationInput { + "The label/name of the location. This is pass-through information and is not used in routing." + label: String + """ + The minimum wait time is used to force the trip to stay the given duration at the + via-location before the trip is continued. + """ + minimumWaitTime: Duration = "PT0S" + """ + A list of stop locations. A stop location can be a quay, a stop place, a multimodal + stop place or a group of stop places. It is enough to visit ONE of the locations + listed. + """ + stopLocationIds: [String] +} + "What criteria should be used when optimizing a scooter route." input ScooterOptimizationInput @oneOf { "Define optimization by weighing three criteria." @@ -4325,16 +4369,6 @@ input VehicleParkingInput { unpreferredCost: Int } -""" -Represents an element that must be visited by the routing result. - -Right now only stop or station IDs are supported but this will be extended to support via point -coordinates as well. -""" -input ViaLocationInput { - passThroughLocation: PassThroughLocationInput -} - "Preferences related to walking (excluding walking a bicycle or a scooter)." input WalkPreferencesInput { "The cost of boarding a vehicle while walking." diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index e7b879f0501..a8bff772863 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -265,7 +265,7 @@ void transferSlack() { void passThroughPoints() { Map arguments = Map.of( "via", - List.of(Map.of("passThroughLocation", Map.of("stopLocationId", "F:stop1"))) + List.of(Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1")))) ); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java index ec34df90378..391f3bf9aee 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java @@ -16,9 +16,9 @@ class PassThroughLocationMapperTest { public static List>> failureCases() { return List.of( - List.of(Map.of("passThroughLocation", Map.of("stopLocationId", "fantasy:id"))), - List.of(Map.of("passThroughLocation", Map.of())), - List.of(Map.of("passThroughLocation", Map.of()), Map.of("passThroughLocation", Map.of())) + List.of(Map.of("passThrough", Map.of("stopLocationIds", List.of("fantasy:id")))), + List.of(Map.of("passThrough", Map.of())), + List.of(Map.of("passThrough", Map.of()), Map.of("passThroughLocation", Map.of())) ); } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql index 4bbcec39d06..1e59a74d82f 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan-extended.graphql @@ -12,9 +12,9 @@ transportModes: [{ mode: CAR, qualifier: HAIL }] via: [ { - passThroughLocation: { + passThrough: { label: "A stop that you want to visit along the route" - stopLocationId: "F:A" + stopLocationIds: ["F:A"] } } ] From 47e49c1f501bbd082e2464bbdac24d2e75baea6f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 26 Sep 2024 14:08:25 +0200 Subject: [PATCH 16/86] Adapt to new API --- .../LegacyRouteRequestMapper.java | 11 +--- .../PassThroughLocationMapper.java | 41 ------------- .../routerequest/ViaLocationMapper.java | 58 +++++++++++++++++++ .../LegacyRouteRequestMapperTest.java | 10 ++-- .../PassThroughLocationMapperTest.java | 34 ----------- .../collection/CollectionUtilsTest.java | 2 +- 6 files changed, 68 insertions(+), 88 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java delete mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index a107f1f1aad..42468eafe02 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -32,7 +32,6 @@ import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.TransitMode; -import org.opentripplanner.transit.service.TransitService; public class LegacyRouteRequestMapper { @@ -57,7 +56,7 @@ public static RouteRequest toRouteRequest( callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v))); callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v))); - mapViaPoints(request, callWith, context.transitService()); + mapViaPoints(request, callWith); request.setDateTime( environment.getArgument("date"), @@ -260,15 +259,11 @@ public static RouteRequest toRouteRequest( return request; } - static void mapViaPoints( - RouteRequest request, - CallerWithEnvironment callWith, - TransitService transitService - ) { + static void mapViaPoints(RouteRequest request, CallerWithEnvironment callWith) { callWith.argument( "via", (List> v) -> - request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)) + request.setViaLocations(ViaLocationMapper.mapToViaLocations(v)) ); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java deleted file mode 100644 index 5c126531390..00000000000 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapper.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.opentripplanner.apis.gtfs.mapping.routerequest; - -import java.util.List; -import java.util.Map; -import org.opentripplanner.framework.collection.CollectionUtils; -import org.opentripplanner.routing.api.request.PassThroughPoint; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.service.TransitService; - -class PassThroughLocationMapper { - - static List toLocations( - final TransitService transitService, - final List> passThroughPoints - ) { - return passThroughPoints.stream().map(p -> handlePoint(transitService, p)).toList(); - } - - private static PassThroughPoint handlePoint( - final TransitService transitService, - Map map - ) { - Map element = (Map) map.get("passThrough"); - List ids = (List) element.get("stopLocationIds"); - - final String name = (String) element.get("name"); - if (CollectionUtils.isEmpty(ids)) { - throw new IllegalArgumentException("No stops in pass-through point"); - } - - var stopLocations = ids - .stream() - .map(FeedScopedId::parse) - .flatMap(id -> transitService.getStopOrChildStops(id).stream()) - .toList(); - if (stopLocations.isEmpty()) { - throw new IllegalArgumentException("No stop locations found for %s.".formatted(ids)); - } - return new PassThroughPoint(stopLocations, name); - } -} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java new file mode 100644 index 00000000000..e2fbac8979e --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -0,0 +1,58 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.stream.Collectors.toList; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.apis.transmodel.model.plan.TripQuery; +import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; +import org.opentripplanner.apis.transmodel.support.OneOfInputValidator; +import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; +import org.opentripplanner.routing.api.request.via.ViaLocation; +import org.opentripplanner.routing.api.request.via.VisitViaLocation; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +class ViaLocationMapper { + + static List mapToViaLocations(final List> via) { + return via.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); + } + + private static ViaLocation mapViaLocation(Map inputMap) { + var fieldName = OneOfInputValidator.validateOneOf( + inputMap, + TripQuery.FIELD_VIA, + ViaLocationInputType.FIELD_VISIT, + ViaLocationInputType.FIELD_PASS_THROUGH + ); + + Map value = (Map) inputMap.get(fieldName); + + return switch (fieldName) { + case ViaLocationInputType.FIELD_VISIT -> mapVisitViaLocation(value); + case ViaLocationInputType.FIELD_PASS_THROUGH -> mapPassThroughViaLocation(value); + default -> throw new IllegalArgumentException("Unknown field: " + fieldName); + }; + } + + private static VisitViaLocation mapVisitViaLocation(Map inputMap) { + var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); + var minimumWaitTime = (Duration) inputMap.get(ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME); + var stopLocationIds = mapStopLocationIds(inputMap); + return new VisitViaLocation(label, minimumWaitTime, stopLocationIds, List.of()); + } + + private static PassThroughViaLocation mapPassThroughViaLocation(Map inputMap) { + var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); + var stopLocationIds = mapStopLocationIds(inputMap); + return new PassThroughViaLocation(label, stopLocationIds); + } + + private static List mapStopLocationIds(Map map) { + var c = (Collection) map.get(ViaLocationInputType.FIELD_STOP_LOCATION_IDS); + return c.stream().map(TransitIdMapper::mapIDToDomain).toList(); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index a8bff772863..97b5dff62e9 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -265,7 +265,9 @@ void transferSlack() { void passThroughPoints() { Map arguments = Map.of( "via", - List.of(Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1")))) + List.of( + Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) + ) ); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( @@ -273,12 +275,12 @@ void passThroughPoints() { context ); assertEquals( - "[PassThroughPoint[stopLocations=[RegularStop{F:stop1 stop1}], name=null]]", - routeRequest.getPassThroughPoints().toString() + "[PassThroughViaLocation{label: a label, stopLocationIds: [F:stop1]}]", + routeRequest.getViaLocations().toString() ); var noParamsReq = LegacyRouteRequestMapper.toRouteRequest(executionContext(Map.of()), context); - assertEquals(List.of(), noParamsReq.getPassThroughPoints()); + assertEquals(List.of(), noParamsReq.getViaLocations()); } private DataFetchingEnvironment executionContext(Map arguments) { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java deleted file mode 100644 index 391f3bf9aee..00000000000 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/PassThroughLocationMapperTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.opentripplanner.apis.gtfs.mapping.routerequest; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.List; -import java.util.Map; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; - -/** - * A test for the successful case is at {@link LegacyRouteRequestMapperTest#passThroughPoints()} - */ -class PassThroughLocationMapperTest { - - public static List>> failureCases() { - return List.of( - List.of(Map.of("passThrough", Map.of("stopLocationIds", List.of("fantasy:id")))), - List.of(Map.of("passThrough", Map.of())), - List.of(Map.of("passThrough", Map.of()), Map.of("passThroughLocation", Map.of())) - ); - } - - @ParameterizedTest - @MethodSource("failureCases") - void throwException(List> params) { - var service = new DefaultTransitService(new TransitModel()); - assertThrows( - IllegalArgumentException.class, - () -> PassThroughLocationMapper.toLocations(service, params) - ); - } -} diff --git a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java index 5b89811e9fe..6686ac8e0d9 100644 --- a/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/collection/CollectionUtilsTest.java @@ -21,7 +21,7 @@ class CollectionUtilsTest { @Test void testIsEmpty() { - assertTrue(CollectionUtils.isEmpty(null)); + assertTrue(CollectionUtils.isEmpty((List) null)); assertTrue(CollectionUtils.isEmpty(List.of())); assertFalse(CollectionUtils.isEmpty(List.of(1))); } From f2eaf3c46920f92fc6985d0b231beca4dd746609 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 27 Sep 2024 08:55:23 +0200 Subject: [PATCH 17/86] Add test for via location mapper --- .../routerequest/ViaLocationMapper.java | 38 +++--- .../routerequest/ViaLocationMapperTest.java | 108 ++++++++++++++++++ 2 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index e2fbac8979e..5221e077d59 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -1,58 +1,60 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; -import static java.util.stream.Collectors.toList; - import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Map; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; -import org.opentripplanner.apis.transmodel.model.plan.TripQuery; -import org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType; -import org.opentripplanner.apis.transmodel.support.OneOfInputValidator; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.api.request.via.VisitViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; +/** + * Maps the input data to the data structure needed for via routing. + */ class ViaLocationMapper { + private static final String FIELD_LABEL = "label"; + private static final String FIELD_MINIMUM_WAIT_TIME = "minimumWaitTime"; + private static final String FIELD_STOP_LOCATION_IDS = "stopLocationIds"; + private static final String FIELD_VISIT = "visit"; + private static final String FIELD_PASS_THROUGH = "passThrough"; + static List mapToViaLocations(final List> via) { - return via.stream().map(ViaLocationMapper::mapViaLocation).collect(toList()); + return via.stream().map(ViaLocationMapper::mapViaLocation).toList(); } private static ViaLocation mapViaLocation(Map inputMap) { - var fieldName = OneOfInputValidator.validateOneOf( - inputMap, - TripQuery.FIELD_VIA, - ViaLocationInputType.FIELD_VISIT, - ViaLocationInputType.FIELD_PASS_THROUGH - ); + var fieldName = FIELD_PASS_THROUGH; + if (inputMap.containsKey(FIELD_VISIT)) { + fieldName = FIELD_VISIT; + } Map value = (Map) inputMap.get(fieldName); return switch (fieldName) { - case ViaLocationInputType.FIELD_VISIT -> mapVisitViaLocation(value); - case ViaLocationInputType.FIELD_PASS_THROUGH -> mapPassThroughViaLocation(value); + case FIELD_VISIT -> mapVisitViaLocation(value); + case FIELD_PASS_THROUGH -> mapPassThroughViaLocation(value); default -> throw new IllegalArgumentException("Unknown field: " + fieldName); }; } private static VisitViaLocation mapVisitViaLocation(Map inputMap) { - var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); - var minimumWaitTime = (Duration) inputMap.get(ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME); + var label = (String) inputMap.get(FIELD_LABEL); + var minimumWaitTime = (Duration) inputMap.get(FIELD_MINIMUM_WAIT_TIME); var stopLocationIds = mapStopLocationIds(inputMap); return new VisitViaLocation(label, minimumWaitTime, stopLocationIds, List.of()); } private static PassThroughViaLocation mapPassThroughViaLocation(Map inputMap) { - var label = (String) inputMap.get(ViaLocationInputType.FIELD_LABEL); + var label = (String) inputMap.get(FIELD_LABEL); var stopLocationIds = mapStopLocationIds(inputMap); return new PassThroughViaLocation(label, stopLocationIds); } private static List mapStopLocationIds(Map map) { - var c = (Collection) map.get(ViaLocationInputType.FIELD_STOP_LOCATION_IDS); + var c = (Collection) map.get(FIELD_STOP_LOCATION_IDS); return c.stream().map(TransitIdMapper::mapIDToDomain).toList(); } } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java new file mode 100644 index 00000000000..9f0c333eb7b --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java @@ -0,0 +1,108 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_LABEL; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_PASS_THROUGH; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_STOP_LOCATION_IDS; +import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_VISIT; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ViaLocationMapperTest { + + public static final String LABEL = "TestLabel"; + public static final Duration MIN_WAIT_TIME = Duration.ofMinutes(5); + public static final List LIST_IDS_INPUT = List.of("F:ID1", "F:ID2"); + public static final String EXPECTED_IDS_AS_STRING = "[F:ID1, F:ID2]"; + + @Test + void testMapToVisitViaLocations() { + Map input = Map.ofEntries( + entry(FIELD_VISIT, visitInput(LABEL, MIN_WAIT_TIME, LIST_IDS_INPUT)) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + + var via = result.getFirst(); + + assertEquals(LABEL, via.label()); + assertEquals(MIN_WAIT_TIME, via.minimumWaitTime()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertFalse(via.isPassThroughLocation()); + assertEquals( + "[VisitViaLocation{label: TestLabel, minimumWaitTime: 5m, stopLocationIds: [F:ID1, F:ID2], coordinates: []}]", + result.toString() + ); + } + + @Test + void testMapToVisitViaLocationsWithBareMinimum() { + Map input = Map.of( + FIELD_VISIT, + Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + + var via = result.getFirst(); + + assertNull(via.label()); + assertEquals(Duration.ZERO, via.minimumWaitTime()); + assertEquals("[F:1]", via.stopLocationIds().toString()); + assertFalse(via.isPassThroughLocation()); + } + + @Test + void mapToPassThrough() { + Map input = Map.of(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var via = result.getFirst(); + + assertEquals(LABEL, via.label()); + assertEquals(EXPECTED_IDS_AS_STRING, via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + assertEquals( + "PassThroughViaLocation{label: TestLabel, stopLocationIds: [F:ID1, F:ID2]}", + via.toString() + ); + } + + @Test + void mapToPassThroughWithBareMinimum() { + Map input = Map.of( + FIELD_PASS_THROUGH, + Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + ); + var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var via = result.getFirst(); + + assertNull(via.label()); + assertEquals("[F:1]", via.stopLocationIds().toString()); + assertTrue(via.isPassThroughLocation()); + } + + private Map visitInput(String label, Duration minWaitTime, List ids) { + var map = new HashMap(); + if (label != null) { + map.put(FIELD_LABEL, label); + } + if (minWaitTime != null) { + map.put(FIELD_MINIMUM_WAIT_TIME, minWaitTime); + } + if (ids != null) { + map.put(FIELD_STOP_LOCATION_IDS, ids); + } + return map; + } + + private Map passThroughInput(String label, List ids) { + return visitInput(label, null, ids); + } +} From bf7c368d6e7a4123af5078325363640f76cb01fb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 11 Oct 2024 23:03:49 +0200 Subject: [PATCH 18/86] Fix path for performance tests --- .../opentripplanner/apis/gtfs/schema.graphqls | 22 +++++++++---------- .../apis/gtfs/queries/plan.graphql | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 2bc5c229836..a5f5c0236f6 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4077,7 +4077,7 @@ input PlanPassThroughViaLocationInput { stop place or a group of stop places. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String] + stopLocationIds: [String!] } "Wrapper type for different types of preferences related to plan query." @@ -4175,24 +4175,22 @@ input PlanTransitModesInput { """ A via-location is used to specifying a location as an intermediate place the router must -route through. The via-location must be either a pass-through-location or a -visit-via-location. An on-board "visit" is only allowed for pass-through-via-locations, while -the visit-via-location can visit a stop-location or a coordinate and specify a -minimum-wait-time. +route through. The via-location is either a pass-through-location or a visit-via-location. """ input PlanViaLocationInput @oneOf { + "Board, alight or pass-through(on-board) at the stop location." passThrough: PlanPassThroughViaLocationInput + "Board or alight at a stop location or visit a coordinate." visit: PlanVisitViaLocationInput } """ -A visit-via-location is a physical visit to one of the stops or coordinates listed. An -on-board visit does not count, the traveler must alight or board at the given stop for -it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to -the closest point in the street network from a stop and back to another stop to join -the transit network. +A visit-via-location is a physical visit to one of the stop locations or coordinates listed. An +on-board visit does not count, the traveler must alight or board at the given stop for it to to +be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the closest point +in the street network from a stop and back to another stop to join the transit network. -NOTE! Coordinates are NOT supported jet. +NOTE! Coordinates are NOT supported yet. """ input PlanVisitViaLocationInput { "The label/name of the location. This is pass-through information and is not used in routing." @@ -4207,7 +4205,7 @@ input PlanVisitViaLocationInput { stop place or a group of stop places. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String] + stopLocationIds: [String!] } "What criteria should be used when optimizing a scooter route." diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql index 6a51cc10d2c..b965ff11eff 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql @@ -10,6 +10,7 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] + via: { passThrough: { label: "via", stopLocationIds: ["F:BUS"] } } ) { itineraries { start From f143bd8a10c947683f00215b69f563fd8f70312c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 13 Oct 2024 08:41:49 +0200 Subject: [PATCH 19/86] Move to use proper GraphQL types --- .../apis/gtfs/generated/GraphQLTypes.java | 12 ++++ .../LegacyRouteRequestMapper.java | 12 ++-- .../routerequest/RouteRequestMapper.java | 9 +++ .../routerequest/ViaLocationMapper.java | 60 +++++++------------ .../opentripplanner/apis/gtfs/schema.graphqls | 2 + .../routerequest/ViaLocationMapperTest.java | 29 ++++++--- 6 files changed, 71 insertions(+), 53 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index bc0fd0df634..c0669905029 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -3419,6 +3419,7 @@ public static class GraphQLQueryTypePlanConnectionArgs { private GraphQLPlanLabeledLocationInput origin; private GraphQLPlanPreferencesInput preferences; private java.time.Duration searchWindow; + private List via; public GraphQLQueryTypePlanConnectionArgs(Map args) { if (args != null) { @@ -3437,6 +3438,9 @@ public GraphQLQueryTypePlanConnectionArgs(Map args) { this.preferences = new GraphQLPlanPreferencesInput((Map) args.get("preferences")); this.searchWindow = (java.time.Duration) args.get("searchWindow"); + if (args.get("via") != null) { + this.via = (List) args.get("via"); + } } } @@ -3488,6 +3492,10 @@ public java.time.Duration getGraphQLSearchWindow() { return this.searchWindow; } + public List getGraphQLVia() { + return this.via; + } + public void setGraphQLAfter(String after) { this.after = after; } @@ -3535,6 +3543,10 @@ public void setGraphQLPreferences(GraphQLPlanPreferencesInput preferences) { public void setGraphQLSearchWindow(java.time.Duration searchWindow) { this.searchWindow = searchWindow; } + + public void setGraphQLVia(List via) { + this.via = via; + } } public static class GraphQLQueryTypeRentalVehicleArgs { diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 803ebea37c2..0ca48f76f83 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -54,7 +54,7 @@ public static RouteRequest toRouteRequest( callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v))); callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v))); - mapViaPoints(request, callWith); + mapViaPoints(request, environment); request.setDateTime( environment.getArgument("date"), @@ -257,12 +257,10 @@ public static RouteRequest toRouteRequest( return request; } - static void mapViaPoints(RouteRequest request, CallerWithEnvironment callWith) { - callWith.argument( - "via", - (List> v) -> - request.setViaLocations(ViaLocationMapper.mapToViaLocations(v)) - ); + static void mapViaPoints(RouteRequest request, DataFetchingEnvironment env) { + var args = new GraphQLTypes.GraphQLQueryTypePlanArgs(env.getArguments()); + var locs = ViaLocationMapper.mapToViaLocations(args.getGraphQLVia()); + request.setViaLocations(locs); } private static boolean hasArgument(Map m, String name) { diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index 22834b200f6..ef3436bafba 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -9,6 +9,7 @@ import graphql.schema.DataFetchingEnvironment; import java.time.Instant; +import java.util.List; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; @@ -63,6 +64,7 @@ public static RouteRequest toRouteRequest( setModes(request.journey(), args.getGraphQLModes(), environment); + mapViaPoints(request, args.getGraphQLVia()); return request; } @@ -178,4 +180,11 @@ private static GenericLocation parseGenericLocation( coordinate.getGraphQLLongitude() ); } + + static void mapViaPoints( + RouteRequest request, + List locations + ) { + request.setViaLocations(ViaLocationMapper.mapToViaLocations(locations)); + } } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index 5221e077d59..6fe4a43dd44 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -1,9 +1,7 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; -import java.time.Duration; -import java.util.Collection; import java.util.List; -import java.util.Map; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; @@ -15,46 +13,32 @@ */ class ViaLocationMapper { - private static final String FIELD_LABEL = "label"; - private static final String FIELD_MINIMUM_WAIT_TIME = "minimumWaitTime"; - private static final String FIELD_STOP_LOCATION_IDS = "stopLocationIds"; - private static final String FIELD_VISIT = "visit"; - private static final String FIELD_PASS_THROUGH = "passThrough"; - - static List mapToViaLocations(final List> via) { + static List mapToViaLocations(List via) { return via.stream().map(ViaLocationMapper::mapViaLocation).toList(); } - private static ViaLocation mapViaLocation(Map inputMap) { - var fieldName = FIELD_PASS_THROUGH; - if (inputMap.containsKey(FIELD_VISIT)) { - fieldName = FIELD_VISIT; + private static ViaLocation mapViaLocation(GraphQLPlanViaLocationInput via) { + var passThrough = via.getGraphQLPassThrough(); + var visit = via.getGraphQLVisit(); + + if (passThrough != null) { + return new PassThroughViaLocation( + passThrough.getGraphQLLabel(), + mapStopLocationIds(passThrough.getGraphQLStopLocationIds()) + ); + } else if (visit != null) { + return new VisitViaLocation( + visit.getGraphQLLabel(), + visit.getGraphQLMinimumWaitTime(), + mapStopLocationIds(visit.getGraphQLStopLocationIds()), + List.of() + ); + } else { + throw new IllegalArgumentException("ViaLocation must define either pass-through or visit."); } - - Map value = (Map) inputMap.get(fieldName); - - return switch (fieldName) { - case FIELD_VISIT -> mapVisitViaLocation(value); - case FIELD_PASS_THROUGH -> mapPassThroughViaLocation(value); - default -> throw new IllegalArgumentException("Unknown field: " + fieldName); - }; - } - - private static VisitViaLocation mapVisitViaLocation(Map inputMap) { - var label = (String) inputMap.get(FIELD_LABEL); - var minimumWaitTime = (Duration) inputMap.get(FIELD_MINIMUM_WAIT_TIME); - var stopLocationIds = mapStopLocationIds(inputMap); - return new VisitViaLocation(label, minimumWaitTime, stopLocationIds, List.of()); - } - - private static PassThroughViaLocation mapPassThroughViaLocation(Map inputMap) { - var label = (String) inputMap.get(FIELD_LABEL); - var stopLocationIds = mapStopLocationIds(inputMap); - return new PassThroughViaLocation(label, stopLocationIds); } - private static List mapStopLocationIds(Map map) { - var c = (Collection) map.get(FIELD_STOP_LOCATION_IDS); - return c.stream().map(TransitIdMapper::mapIDToDomain).toList(); + private static List mapStopLocationIds(List ids) { + return ids.stream().map(TransitIdMapper::mapIDToDomain).toList(); } } diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a5f5c0236f6..514b1746798 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1609,6 +1609,8 @@ type QueryType { number of itineraries in each search. """ searchWindow: Duration + "The list of points the journey is required to pass through." + via: [PlanViaLocationInput!], ): PlanConnection @async "Get a single rental vehicle based on its ID, i.e. value of field `vehicleId`" rentalVehicle(id: String!): RentalVehicle diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java index 9f0c333eb7b..9485175bb00 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; class ViaLocationMapperTest { @@ -29,7 +30,7 @@ void testMapToVisitViaLocations() { Map input = Map.ofEntries( entry(FIELD_VISIT, visitInput(LABEL, MIN_WAIT_TIME, LIST_IDS_INPUT)) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = ViaLocationMapper.mapToViaLocations(List.of()); var via = result.getFirst(); @@ -43,13 +44,17 @@ void testMapToVisitViaLocations() { ); } + private List toArgs(Map input) { + return new GraphQLTypes.GraphQLQueryTypePlanArgs(Map.of("via", List.of(input))).getGraphQLVia(); + } + @Test void testMapToVisitViaLocationsWithBareMinimum() { Map input = Map.of( FIELD_VISIT, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = ViaLocationMapper.mapToViaLocations(toArgs(input)); var via = result.getFirst(); @@ -61,8 +66,12 @@ void testMapToVisitViaLocationsWithBareMinimum() { @Test void mapToPassThrough() { - Map input = Map.of(FIELD_PASS_THROUGH, passThroughInput(LABEL, LIST_IDS_INPUT)); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + final List es = List.of( + new GraphQLTypes.GraphQLPlanViaLocationInput( + Map.of("passThrough", passThroughInput(LABEL, LIST_IDS_INPUT)) + ) + ); + var result = ViaLocationMapper.mapToViaLocations(es); var via = result.getFirst(); assertEquals(LABEL, via.label()); @@ -80,7 +89,7 @@ void mapToPassThroughWithBareMinimum() { FIELD_PASS_THROUGH, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(List.of(input)); + var result = ViaLocationMapper.mapToViaLocations(toArgs(input)); var via = result.getFirst(); assertNull(via.label()); @@ -88,7 +97,11 @@ void mapToPassThroughWithBareMinimum() { assertTrue(via.isPassThroughLocation()); } - private Map visitInput(String label, Duration minWaitTime, List ids) { + private GraphQLTypes.GraphQLPlanVisitViaLocationInput visitInput( + String label, + Duration minWaitTime, + List ids + ) { var map = new HashMap(); if (label != null) { map.put(FIELD_LABEL, label); @@ -99,10 +112,10 @@ private Map visitInput(String label, Duration minWaitTime, List< if (ids != null) { map.put(FIELD_STOP_LOCATION_IDS, ids); } - return map; + return new GraphQLTypes.GraphQLPlanVisitViaLocationInput(map); } private Map passThroughInput(String label, List ids) { - return visitInput(label, null, ids); + return Map.ofEntries(entry(FIELD_LABEL, label), entry(FIELD_STOP_LOCATION_IDS, ids)); } } From da8a0e415c6a95e1ee487abc46a9cd0aa3cc6e16 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 13 Oct 2024 11:29:08 +0200 Subject: [PATCH 20/86] Improve testing --- .../LegacyRouteRequestMapper.java | 4 +- .../routerequest/ViaLocationMapper.java | 11 +-- .../framework/collection/ListUtils.java | 3 +- .../LegacyRouteRequestMapperTest.java | 5 +- .../routerequest/ViaLocationMapperTest.java | 67 +++++++------------ 5 files changed, 38 insertions(+), 52 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 0ca48f76f83..b014713067a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -54,7 +54,7 @@ public static RouteRequest toRouteRequest( callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v))); callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v))); - mapViaPoints(request, environment); + mapViaLocations(request, environment); request.setDateTime( environment.getArgument("date"), @@ -257,7 +257,7 @@ public static RouteRequest toRouteRequest( return request; } - static void mapViaPoints(RouteRequest request, DataFetchingEnvironment env) { + static void mapViaLocations(RouteRequest request, DataFetchingEnvironment env) { var args = new GraphQLTypes.GraphQLQueryTypePlanArgs(env.getArguments()); var locs = ViaLocationMapper.mapToViaLocations(args.getGraphQLVia()); request.setViaLocations(locs); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index 6fe4a43dd44..b1177589f8d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -1,8 +1,9 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; -import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; +import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.api.request.via.VisitViaLocation; @@ -13,15 +14,15 @@ */ class ViaLocationMapper { - static List mapToViaLocations(List via) { - return via.stream().map(ViaLocationMapper::mapViaLocation).toList(); + static List mapToViaLocations(@Nullable List via) { + return ListUtils.nullSafeImmutableList(via).stream().map(ViaLocationMapper::mapViaLocation).toList(); } private static ViaLocation mapViaLocation(GraphQLPlanViaLocationInput via) { var passThrough = via.getGraphQLPassThrough(); var visit = via.getGraphQLVisit(); - if (passThrough != null) { + if (passThrough != null && passThrough.getGraphQLStopLocationIds() != null) { return new PassThroughViaLocation( passThrough.getGraphQLLabel(), mapStopLocationIds(passThrough.getGraphQLStopLocationIds()) @@ -39,6 +40,6 @@ private static ViaLocation mapViaLocation(GraphQLPlanViaLocationInput via) { } private static List mapStopLocationIds(List ids) { - return ids.stream().map(TransitIdMapper::mapIDToDomain).toList(); + return ids.stream().map(FeedScopedId::parse).toList(); } } diff --git a/application/src/main/java/org/opentripplanner/framework/collection/ListUtils.java b/application/src/main/java/org/opentripplanner/framework/collection/ListUtils.java index 35b7e083695..2d1492246f7 100644 --- a/application/src/main/java/org/opentripplanner/framework/collection/ListUtils.java +++ b/application/src/main/java/org/opentripplanner/framework/collection/ListUtils.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.function.Function; +import javax.annotation.Nullable; public class ListUtils { @@ -75,7 +76,7 @@ public static List ofNullable(T input) { * {@code null} an empty collection is returned. If not the {@link List#copyOf(Collection)} is * called. */ - public static List nullSafeImmutableList(Collection c) { + public static List nullSafeImmutableList(@Nullable Collection c) { return (c == null) ? List.of() : List.copyOf(c); } } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 97b5dff62e9..cc8267c52b6 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -26,6 +26,7 @@ import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; @@ -248,7 +249,7 @@ void walkReluctance() { @Test void transferSlack() { - var seconds = 119L; + var seconds = 119; Map arguments = Map.of("minTransferTime", seconds); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( @@ -266,7 +267,7 @@ void passThroughPoints() { Map arguments = Map.of( "via", List.of( - Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) + new GraphQLPlanViaLocationInput(Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label"))) ) ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java index 9485175bb00..f332adacdcb 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.mapToViaLocations; import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_LABEL; import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME; import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_PASS_THROUGH; @@ -12,11 +13,10 @@ import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_VISIT; import java.time.Duration; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; class ViaLocationMapperTest { @@ -26,11 +26,18 @@ class ViaLocationMapperTest { public static final String EXPECTED_IDS_AS_STRING = "[F:ID1, F:ID2]"; @Test - void testMapToVisitViaLocations() { - Map input = Map.ofEntries( - entry(FIELD_VISIT, visitInput(LABEL, MIN_WAIT_TIME, LIST_IDS_INPUT)) + void mapToVisitViaLocations() { + Map args = Map.of( + FIELD_VISIT, + Map.ofEntries( + entry(FIELD_LABEL, LABEL), + entry(FIELD_MINIMUM_WAIT_TIME, MIN_WAIT_TIME), + entry(FIELD_STOP_LOCATION_IDS, LIST_IDS_INPUT) + ) ); - var result = ViaLocationMapper.mapToViaLocations(List.of()); + + var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -44,17 +51,14 @@ void testMapToVisitViaLocations() { ); } - private List toArgs(Map input) { - return new GraphQLTypes.GraphQLQueryTypePlanArgs(Map.of("via", List.of(input))).getGraphQLVia(); - } - @Test - void testMapToVisitViaLocationsWithBareMinimum() { - Map input = Map.of( + void mapToVisitViaLocationsWithBareMinimum() { + Map args = Map.of( FIELD_VISIT, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(toArgs(input)); + var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -66,12 +70,12 @@ void testMapToVisitViaLocationsWithBareMinimum() { @Test void mapToPassThrough() { - final List es = List.of( - new GraphQLTypes.GraphQLPlanViaLocationInput( - Map.of("passThrough", passThroughInput(LABEL, LIST_IDS_INPUT)) - ) + final Map args = Map.of( + FIELD_PASS_THROUGH, + Map.ofEntries(entry(FIELD_LABEL, LABEL), entry(FIELD_STOP_LOCATION_IDS, LIST_IDS_INPUT)) ); - var result = ViaLocationMapper.mapToViaLocations(es); + var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var result = mapToViaLocations(inputs); var via = result.getFirst(); assertEquals(LABEL, via.label()); @@ -85,37 +89,16 @@ void mapToPassThrough() { @Test void mapToPassThroughWithBareMinimum() { - Map input = Map.of( + Map args = Map.of( FIELD_PASS_THROUGH, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var result = ViaLocationMapper.mapToViaLocations(toArgs(input)); + var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var result = mapToViaLocations(inputs); var via = result.getFirst(); assertNull(via.label()); assertEquals("[F:1]", via.stopLocationIds().toString()); assertTrue(via.isPassThroughLocation()); } - - private GraphQLTypes.GraphQLPlanVisitViaLocationInput visitInput( - String label, - Duration minWaitTime, - List ids - ) { - var map = new HashMap(); - if (label != null) { - map.put(FIELD_LABEL, label); - } - if (minWaitTime != null) { - map.put(FIELD_MINIMUM_WAIT_TIME, minWaitTime); - } - if (ids != null) { - map.put(FIELD_STOP_LOCATION_IDS, ids); - } - return new GraphQLTypes.GraphQLPlanVisitViaLocationInput(map); - } - - private Map passThroughInput(String label, List ids) { - return Map.ofEntries(entry(FIELD_LABEL, label), entry(FIELD_STOP_LOCATION_IDS, ids)); - } } From 9c92b2e6489d8b9cb6133de75155e57fa4994464 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 11 Oct 2024 18:01:20 +0200 Subject: [PATCH 21/86] Obey configured tag mapping in walkable area processing Walkable area builder used hardcoded bike+pedestrial default permissions for area edges. Now default permissions take tag mapping into account. --- .../graph_builder/module/osm/OsmDatabase.java | 8 +-- .../module/osm/WalkableAreaBuilder.java | 51 ++++++++----------- 2 files changed, 26 insertions(+), 33 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 88b1fe9f7da..c1c70f17fac 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 @@ -791,9 +791,11 @@ private void processMultipolygonRelations() { * Handler for a new Area (single way area or multipolygon relations) */ private void newArea(Area area) { - StreetTraversalPermission permissions = area.parent.overridePermissions( - StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE - ); + StreetTraversalPermission permissions = area.parent + .getOsmProvider() + .getWayPropertySet() + .getDataForWay(area.parent) + .getPermission(); if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) { walkableAreas.add(area); } 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 f27d80d5617..81f59287939 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 @@ -49,6 +49,8 @@ import org.opentripplanner.street.search.StreetSearchBuilder; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Theoretically, it is not correct to build the visibility graph on the joined polygon of areas @@ -70,6 +72,8 @@ */ class WalkableAreaBuilder { + private static final Logger LOG = LoggerFactory.getLogger(WalkableAreaBuilder.class); + private final DataImportIssueStore issueStore; private final int maxAreaNodes; @@ -499,9 +503,14 @@ private Set createSegments( Area area = intersects.getFirst(); OsmWithTags areaEntity = area.parent; - StreetTraversalPermission areaPermissions = areaEntity.overridePermissions( - StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE - ); + WayProperties wayData; + if (!wayPropertiesCache.containsKey(areaEntity)) { + wayData = areaEntity.getOsmProvider().getWayPropertySet().getDataForWay(areaEntity); + wayPropertiesCache.put(areaEntity, wayData); + } else { + wayData = wayPropertiesCache.get(areaEntity); + } + StreetTraversalPermission areaPermissions = wayData.getPermission(); float carSpeed = areaEntity .getOsmProvider() @@ -520,8 +529,8 @@ private Set createSegments( startEndpoint.getLabel() + " to " + endEndpoint.getLabel(); - I18NString name = namer.getNameForWay(areaEntity, label); + I18NString name = namer.getNameForWay(areaEntity, label); AreaEdgeBuilder streetEdgeBuilder = new AreaEdgeBuilder() .withFromVertex(startEndpoint) .withToVertex(endEndpoint) @@ -543,8 +552,8 @@ private Set createSegments( endEndpoint.getLabel() + " to " + startEndpoint.getLabel(); - name = namer.getNameForWay(areaEntity, label); + name = namer.getNameForWay(areaEntity, label); AreaEdgeBuilder backStreetEdgeBuilder = new AreaEdgeBuilder() .withFromVertex(endEndpoint) .withToVertex(startEndpoint) @@ -559,22 +568,10 @@ private Set createSegments( .withWheelchairAccessible(areaEntity.isWheelchairAccessible()) .withLink(areaEntity.isLink()); - if (!wayPropertiesCache.containsKey(areaEntity)) { - WayProperties wayData = areaEntity - .getOsmProvider() - .getWayPropertySet() - .getDataForWay(areaEntity); - wayPropertiesCache.put(areaEntity, wayData); - } - AreaEdge street = streetEdgeBuilder.buildAndConnect(); + AreaEdge backStreet = backStreetEdgeBuilder.buildAndConnect(); - normalizer.applyWayProperties( - street, - backStreet, - wayPropertiesCache.get(areaEntity), - areaEntity - ); + normalizer.applyWayProperties(street, backStreet, wayData, areaEntity); return Set.of(street, backStreet); } else { // take the part that intersects with the start vertex @@ -640,12 +637,12 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection I18NString name = namer.getNameForWay(areaEntity, id); namedArea.setName(name); + WayProperties wayData; if (!wayPropertiesCache.containsKey(areaEntity)) { - WayProperties wayData = areaEntity - .getOsmProvider() - .getWayPropertySet() - .getDataForWay(areaEntity); + wayData = areaEntity.getOsmProvider().getWayPropertySet().getDataForWay(areaEntity); wayPropertiesCache.put(areaEntity, wayData); + } else { + wayData = wayPropertiesCache.get(areaEntity); } double bicycleSafety = wayPropertiesCache.get(areaEntity).bicycleSafety().forward(); @@ -653,14 +650,8 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection double walkSafety = wayPropertiesCache.get(areaEntity).walkSafety().forward(); namedArea.setWalkSafetyMultiplier(walkSafety); - namedArea.setOriginalEdges(intersection); - - StreetTraversalPermission permission = areaEntity.overridePermissions( - StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE - ); - namedArea.setPermission(permission); - + namedArea.setPermission(wayData.getPermission()); edgeList.addArea(namedArea); } } From aaf304d44da2a03b49f8ce5d1b5da5babd843922 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 14 Oct 2024 11:52:25 +0300 Subject: [PATCH 22/86] Do not allow cycling on public transit platforms Rule whicgh assigns pedestrian permissions to transit platforms is not enough, because another rule which contains more tags overrules it. --- .../java/org/opentripplanner/osm/tagmapping/DefaultMapper.java | 1 + .../java/org/opentripplanner/osm/tagmapping/FinlandMapper.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java index faa666c750a..c0b786d02e8 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java @@ -65,6 +65,7 @@ public void populateProperties(WayPropertySet props) { props.setProperties("highway=crossing", pedestrianWayProperties); props.setProperties("highway=platform", pedestrianWayProperties); props.setProperties("public_transport=platform", pedestrianWayProperties); + props.setProperties("public_transport=platform;area=yes", pedestrianWayProperties); props.setProperties("railway=platform", pedestrianWayProperties); props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties); props.setProperties("mtb:scale=1", pedestrianWayProperties); diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java index e796ebff17f..86d80605ca6 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java @@ -91,6 +91,8 @@ else if (speedLimit <= 16.65f) { // No biking on designated footways/sidewalks props.setProperties("highway=footway", withModes(PEDESTRIAN)); + props.setProperties("highway=footway;area=yes", withModes(PEDESTRIAN)); + //props.setProperties("public_transport=platform;area=yes", withModes(PEDESTRIAN)); props.setProperties("footway=sidewalk;highway=footway", withModes(PEDESTRIAN)); // Walking on segregated ways is safer than when cycling and walking happens on the same lane From 2fde06af6beaec097491bfe449c20e7877ad811f Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 14 Oct 2024 11:57:41 +0300 Subject: [PATCH 23/86] Update permission docs --- doc/user/osm/Default.md | 1 + doc/user/osm/Finland.md | 2 ++ doc/user/osm/Germany.md | 1 + doc/user/osm/UK.md | 1 + 4 files changed, 5 insertions(+) diff --git a/doc/user/osm/Default.md b/doc/user/osm/Default.md index 814420b791f..a77ebb41b82 100644 --- a/doc/user/osm/Default.md +++ b/doc/user/osm/Default.md @@ -33,6 +33,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | +| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | diff --git a/doc/user/osm/Finland.md b/doc/user/osm/Finland.md index 8a60b5f0b13..cdf56dab057 100644 --- a/doc/user/osm/Finland.md +++ b/doc/user/osm/Finland.md @@ -49,6 +49,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `present(highway); ice_road=yes` | `NONE` | | | | `present(highway); winter_road=yes` | `NONE` | | | | `highway=footway` | `PEDESTRIAN` | | | +| `highway=footway; area=yes` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `highway=cycleway; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | 1.1 | | `highway=footway; bridge=yes` | `PEDESTRIAN` | | | @@ -77,6 +78,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | +| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | diff --git a/doc/user/osm/Germany.md b/doc/user/osm/Germany.md index 922aa3af836..fb6581a8d3b 100644 --- a/doc/user/osm/Germany.md +++ b/doc/user/osm/Germany.md @@ -42,6 +42,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | +| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | diff --git a/doc/user/osm/UK.md b/doc/user/osm/UK.md index 4a640caf95c..1eebd08c880 100644 --- a/doc/user/osm/UK.md +++ b/doc/user/osm/UK.md @@ -47,6 +47,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | +| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | From 1dc168adbd9f55f4b02840e550df7584151f1f36 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 14 Oct 2024 15:48:04 +0300 Subject: [PATCH 24/86] Extend mappers from default mapper OSM tag mappers now use normal OOP inheritance. It is usually a bad idea to implement classes directly from an inteface. --- .../org/opentripplanner/osm/tagmapping/AtlantaMapper.java | 5 ++--- .../opentripplanner/osm/tagmapping/ConstantSpeedMapper.java | 5 ++--- .../org/opentripplanner/osm/tagmapping/FinlandMapper.java | 5 ++--- .../org/opentripplanner/osm/tagmapping/GermanyMapper.java | 5 ++--- .../org/opentripplanner/osm/tagmapping/HoustonMapper.java | 6 ++---- .../org/opentripplanner/osm/tagmapping/NorwayMapper.java | 4 ++-- .../org/opentripplanner/osm/tagmapping/PortlandMapper.java | 5 ++--- .../java/org/opentripplanner/osm/tagmapping/UKMapper.java | 5 ++--- 8 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java index 4d190c0b667..7ae6d985785 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java @@ -16,7 +16,7 @@ * @see DefaultMapper */ -class AtlantaMapper implements OsmTagMapper { +class AtlantaMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -27,7 +27,6 @@ public void populateProperties(WayPropertySet props) { // Max speed limit in Georgia is 70 mph ~= 113kmh ~= 31.3m/s props.maxPossibleCarSpeed = 31.4f; - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } } diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java index 9f1b1ac0ade..e2ecb998159 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java @@ -6,7 +6,7 @@ /** * OSM way properties for optimizing distance (not traveling time) in routing. */ -class ConstantSpeedFinlandMapper implements OsmTagMapper { +class ConstantSpeedFinlandMapper extends FinlandMapper { private float speed; @@ -23,8 +23,7 @@ public ConstantSpeedFinlandMapper(float speed) { @Override public void populateProperties(WayPropertySet props) { props.setCarSpeed("highway=*", speed); - // Read the rest from the default set - new FinlandMapper().populateProperties(props); + super.populateProperties(props); props.maxPossibleCarSpeed = speed; } diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java index 86d80605ca6..1d9479e8fc3 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java @@ -25,7 +25,7 @@ * @see OsmTagMapper * @see DefaultMapper */ -class FinlandMapper implements OsmTagMapper { +class FinlandMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -205,8 +205,7 @@ else if (speedLimit <= 16.65f) { // ~= 16 kph props.setCarSpeed("highway=track", 4.5f); - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } @Override diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java index 70a5bd593aa..bad6f8f651e 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java @@ -17,7 +17,7 @@ * @see OsmTagMapper * @see DefaultMapper */ -class GermanyMapper implements OsmTagMapper { +class GermanyMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -88,7 +88,6 @@ public void populateProperties(WayPropertySet props) { props.setProperties("highway=unclassified;cycleway=lane", withModes(ALL).bicycleSafety(0.87)); - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } } diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java index 7e4aba9da4e..6d03e7aea65 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java @@ -14,7 +14,7 @@ * 1. In Houston we want to disallow usage of downtown pedestrian tunnel system. */ -class HoustonMapper implements OsmTagMapper { +class HoustonMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -26,11 +26,9 @@ public void populateProperties(WayPropertySet props) { new ExactMatchSpecifier("highway=footway;layer=-1;tunnel=yes;indoor=yes"), withModes(NONE) ); - // Max speed limit in Texas is 38 m/s ~= 85 mph ~= 137 kph props.maxPossibleCarSpeed = 38f; - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } } diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java index 9e06c0aa591..21b2ecdab65 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java @@ -26,7 +26,7 @@ * @see OsmTagMapper * @see DefaultMapper */ -class NorwayMapper implements OsmTagMapper { +class NorwayMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -621,7 +621,7 @@ else if (speedLimit >= 11.1f) { props.defaultCarSpeed = 22.22f; // 80 km/h props.maxPossibleCarSpeed = 30.56f; // 110 km/h - new DefaultMapper().populateNotesAndNames(props); + super.populateNotesAndNames(props); props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true); props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true); diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java index 98379852689..7353c186564 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java @@ -9,7 +9,7 @@ import org.opentripplanner.osm.wayproperty.specifier.Condition.GreaterThan; import org.opentripplanner.osm.wayproperty.specifier.ExactMatchSpecifier; -class PortlandMapper implements OsmTagMapper { +class PortlandMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -57,7 +57,6 @@ public void populateProperties(WayPropertySet props) { // Max speed limit in Oregon is 70 mph ~= 113kmh ~= 31.3m/s props.maxPossibleCarSpeed = 31.4f; - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } } diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java index bef0be4101b..4f93425cc2b 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java @@ -21,7 +21,7 @@ * @see OsmTagMapper * @see DefaultMapper */ -class UKMapper implements OsmTagMapper { +class UKMapper extends DefaultMapper { @Override public void populateProperties(WayPropertySet props) { @@ -73,7 +73,6 @@ public void populateProperties(WayPropertySet props) { props.setCarSpeed("highway=secondary_link", 13.4f); // ~= 30mph props.setCarSpeed("highway=tertiary", 15.7f); // ~= 35mph - // Read the rest from the default set - new DefaultMapper().populateProperties(props); + super.populateProperties(props); } } From 7c452db2fd1708da941a60f3347a41504d6d9df2 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 15 Oct 2024 11:02:34 +0300 Subject: [PATCH 25/86] Remove unnecessary interface --- .../osm/tagmapping/AtlantaMapper.java | 3 +- .../osm/tagmapping/FinlandMapper.java | 3 +- .../osm/tagmapping/GermanyMapper.java | 3 +- .../osm/tagmapping/HamburgMapper.java | 1 - .../osm/tagmapping/HoustonMapper.java | 2 +- .../osm/tagmapping/NorwayMapper.java | 4 +- .../osm/tagmapping/OsmTagMapper.java | 736 +++++++++++++++++- .../osm/tagmapping/OsmTagMapperSource.java | 2 +- .../osm/tagmapping/PortlandMapper.java | 2 +- .../osm/tagmapping/UKMapper.java | 4 +- 10 files changed, 733 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java index 7ae6d985785..34ba62a1daa 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java @@ -13,10 +13,9 @@ * * @author demory * @see OsmTagMapper - * @see DefaultMapper */ -class AtlantaMapper extends DefaultMapper { +class AtlantaMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java index 1d9479e8fc3..e4267ce9454 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java @@ -23,9 +23,8 @@ * * @author juusokor * @see OsmTagMapper - * @see DefaultMapper */ -class FinlandMapper extends DefaultMapper { +class FinlandMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java index bad6f8f651e..af56b572bd8 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java @@ -15,9 +15,8 @@ * networks. * * @see OsmTagMapper - * @see DefaultMapper */ -class GermanyMapper extends DefaultMapper { +class GermanyMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java index 47bd5164d1f..755f5864ba2 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java @@ -7,7 +7,6 @@ * * @see GermanyMapper * @see OsmTagMapper - * @see DefaultMapper * * @author Maintained by HBT (geofox-team@hbt.de) */ diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java index 6d03e7aea65..1d24dbafffb 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java @@ -14,7 +14,7 @@ * 1. In Houston we want to disallow usage of downtown pedestrian tunnel system. */ -class HoustonMapper extends DefaultMapper { +class HoustonMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java index 21b2ecdab65..c37de8533c6 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java @@ -24,9 +24,9 @@ * * @author seime * @see OsmTagMapper - * @see DefaultMapper + * @see OsmTagMapper */ -class NorwayMapper extends DefaultMapper { +class NorwayMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java index 9bf0ed2d20d..6bfb9367111 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java @@ -1,18 +1,728 @@ package org.opentripplanner.osm.tagmapping; +import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; +import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofWalkSafety; +import static org.opentripplanner.osm.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; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE; + import org.opentripplanner.osm.model.OsmWithTags; +import org.opentripplanner.osm.wayproperty.WayProperties; import org.opentripplanner.osm.wayproperty.WayPropertySet; +import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier; +import org.opentripplanner.osm.wayproperty.specifier.LogicalOrSpecifier; +import org.opentripplanner.routing.services.notes.StreetNotesService; /** - * Interface for populating a {@link WayPropertySet} that determine how OSM streets can be traversed - * in various modes and named. + * This factory class provides a default collection of {@link WayProperties} that determine how OSM + * streets can be traversed in various modes. + *

+ * Circa January 2011, Grant and Mele at TriMet undertook proper testing of bike (and transit) + * routing, and worked with David Turner on assigning proper weights to different facility types. + * The weights in this file grew organically from trial and error, and are the result of months of + * testing and tweaking the routes that OTP returned, as well as actually walking/biking these + * routes and making changes based on those experiences. This set of weights should be a great + * starting point for others to use, but they are to some extent tailored to the situation in + * Portland and people shouldn't hesitate to adjust them to for their own instance. + *

+ * The rules for assigning WayProperties to OSM ways are explained in. The final tie breaker if two + * Pickers both match is the sequence that the properties are added in this file: if all else is + * equal the 'props.setProperties' statement that is closer to the top of the page will prevail over + * those lower down the page. + *

+ * Foot and bicycle permissions are also addressed in OpenStreetMapGraphBuilderImpl.Handler#getPermissionsForEntity(). + * For instance, if a way that normally does not permit walking based on its tag matches (the + * prevailing 'props.setProperties' statement) has a 'foot=yes' tag the permissions are overridden + * and walking is allowed on that way. + *

+ * TODO clarify why this needs a separate factory interface. * - * @author bdferris, novalis, seime + * @author bdferris, novalis + * @see OsmTagMapper */ -public interface OsmTagMapper { - void populateProperties(WayPropertySet wayPropertySet); - default boolean doesTagValueDisallowThroughTraffic(String tagValue) { +public class OsmTagMapper { + + /* Populate properties on existing WayPropertySet */ + public void populateProperties(WayPropertySet props) { + WayProperties allWayProperties = withModes(ALL).build(); + WayProperties noneWayProperties = withModes(NONE).build(); + WayProperties pedestrianWayProperties = withModes(PEDESTRIAN).build(); + WayProperties pedestrianAndBicycleWayProperties = withModes(PEDESTRIAN_AND_BICYCLE).build(); + /* no bicycle tags */ + + /* NONE */ + props.setProperties("mtb:scale=3", noneWayProperties); + props.setProperties("mtb:scale=4", noneWayProperties); + props.setProperties("mtb:scale=5", noneWayProperties); + props.setProperties("mtb:scale=6", noneWayProperties); + + /* PEDESTRIAN */ + props.setProperties("highway=corridor", pedestrianWayProperties); + props.setProperties("highway=steps", pedestrianWayProperties); + props.setProperties("highway=crossing", pedestrianWayProperties); + props.setProperties("highway=platform", pedestrianWayProperties); + props.setProperties("public_transport=platform", pedestrianWayProperties); + props.setProperties("public_transport=platform;area=yes", pedestrianWayProperties); + props.setProperties("railway=platform", pedestrianWayProperties); + props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties); + props.setProperties("mtb:scale=1", pedestrianWayProperties); + props.setProperties("mtb:scale=2", pedestrianWayProperties); + + /* PEDESTRIAN_AND_BICYCLE */ + props.setProperties("mtb:scale=0", pedestrianAndBicycleWayProperties); + props.setProperties("highway=cycleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.6)); + props.setProperties("highway=path", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)); + props.setProperties("highway=pedestrian", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9)); + props.setProperties("highway=footway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)); + props.setProperties("highway=bridleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3)); + + /* ALL */ + props.setProperties("highway=living_street", withModes(ALL).bicycleSafety(0.9)); + props.setProperties("highway=unclassified", allWayProperties); + props.setProperties("highway=road", allWayProperties); + props.setProperties("highway=byway", withModes(ALL).bicycleSafety(1.3)); + props.setProperties("highway=track", withModes(ALL).bicycleSafety(1.3)); + props.setProperties("highway=service", withModes(ALL).bicycleSafety(1.1)); + props.setProperties("highway=residential", withModes(ALL).bicycleSafety(0.98)); + props.setProperties("highway=residential_link", withModes(ALL).bicycleSafety(0.98)); + props.setProperties("highway=tertiary", allWayProperties); + props.setProperties("highway=tertiary_link", allWayProperties); + props.setProperties("highway=secondary", withModes(ALL).bicycleSafety(1.5)); + props.setProperties("highway=secondary_link", withModes(ALL).bicycleSafety(1.5)); + props.setProperties("highway=primary", withModes(ALL).bicycleSafety(2.06)); + props.setProperties("highway=primary_link", withModes(ALL).bicycleSafety(2.06)); + + /* DRIVING ONLY */ + // trunk and motorway links are often short distances and necessary connections + props.setProperties("highway=trunk_link", withModes(CAR).bicycleSafety(2.06)); + props.setProperties("highway=motorway_link", withModes(CAR).bicycleSafety(2.06)); + + props.setProperties("highway=trunk", withModes(CAR).bicycleSafety(7.47)); + props.setProperties("highway=motorway", withModes(CAR).bicycleSafety(8)); + + /* cycleway=lane */ + props.setProperties( + "highway=*;cycleway=lane", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.87) + ); + props.setProperties("highway=service;cycleway=lane", withModes(ALL).bicycleSafety(0.77)); + props.setProperties("highway=residential;cycleway=lane", withModes(ALL).bicycleSafety(0.77)); + props.setProperties( + "highway=residential_link;cycleway=lane", + withModes(ALL).bicycleSafety(0.77) + ); + props.setProperties("highway=tertiary;cycleway=lane", withModes(ALL).bicycleSafety(0.87)); + props.setProperties("highway=tertiary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.87)); + props.setProperties("highway=secondary;cycleway=lane", withModes(ALL).bicycleSafety(0.96)); + props.setProperties("highway=secondary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.96)); + props.setProperties("highway=primary;cycleway=lane", withModes(ALL).bicycleSafety(1.15)); + props.setProperties("highway=primary_link;cycleway=lane", withModes(ALL).bicycleSafety(1.15)); + + /* BICYCLE_AND_CAR */ + props.setProperties( + "highway=trunk;cycleway=lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.5) + ); + props.setProperties( + "highway=trunk_link;cycleway=lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.15) + ); + props.setProperties( + "highway=motorway;cycleway=lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(2) + ); + props.setProperties( + "highway=motorway_link;cycleway=lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.15) + ); + + /* cycleway=share_busway */ + props.setProperties( + "highway=*;cycleway=share_busway", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.92) + ); + props.setProperties( + "highway=service;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.85) + ); + props.setProperties( + "highway=residential;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.85) + ); + props.setProperties( + "highway=residential_link;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.85) + ); + props.setProperties( + "highway=tertiary;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.92) + ); + props.setProperties( + "highway=tertiary_link;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.92) + ); + props.setProperties( + "highway=secondary;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.99) + ); + props.setProperties( + "highway=secondary_link;cycleway=share_busway", + withModes(ALL).bicycleSafety(0.99) + ); + props.setProperties( + "highway=primary;cycleway=share_busway", + withModes(ALL).bicycleSafety(1.25) + ); + props.setProperties( + "highway=primary_link;cycleway=share_busway", + withModes(ALL).bicycleSafety(1.25) + ); + props.setProperties( + "highway=trunk;cycleway=share_busway", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.75) + ); + props.setProperties( + "highway=trunk_link;cycleway=share_busway", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.25) + ); + props.setProperties( + "highway=motorway;cycleway=share_busway", + withModes(BICYCLE_AND_CAR).bicycleSafety(2.5) + ); + props.setProperties( + "highway=motorway_link;cycleway=share_busway", + withModes(BICYCLE_AND_CAR).bicycleSafety(1.25) + ); + + /* cycleway=opposite_lane */ + props.setProperties( + "highway=*;cycleway=opposite_lane", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 0.87) + ); + props.setProperties( + "highway=service;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(1.1, 0.77) + ); + props.setProperties( + "highway=residential;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(0.98, 0.77) + ); + props.setProperties( + "highway=residential_link;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(0.98, 0.77) + ); + props.setProperties( + "highway=tertiary;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(1, 0.87) + ); + props.setProperties( + "highway=tertiary_link;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(1, 0.87) + ); + props.setProperties( + "highway=secondary;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(1.5, 0.96) + ); + props.setProperties( + "highway=secondary_link;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(1.5, 0.96) + ); + props.setProperties( + "highway=primary;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(2.06, 1.15) + ); + props.setProperties( + "highway=primary_link;cycleway=opposite_lane", + withModes(ALL).bicycleSafety(2.06, 1.15) + ); + props.setProperties( + "highway=trunk;cycleway=opposite_lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 1.5) + ); + props.setProperties( + "highway=trunk_link;cycleway=opposite_lane", + withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 1.15) + ); + + /* cycleway=track */ + props.setProperties( + "highway=*;cycleway=track", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) + ); + props.setProperties("highway=service;cycleway=track", withModes(ALL).bicycleSafety(0.65)); + props.setProperties("highway=residential;cycleway=track", withModes(ALL).bicycleSafety(0.65)); + props.setProperties( + "highway=residential_link;cycleway=track", + withModes(ALL).bicycleSafety(0.65) + ); + props.setProperties("highway=tertiary;cycleway=track", withModes(ALL).bicycleSafety(0.75)); + props.setProperties("highway=tertiary_link;cycleway=track", withModes(ALL).bicycleSafety(0.75)); + props.setProperties("highway=secondary;cycleway=track", withModes(ALL).bicycleSafety(0.8)); + props.setProperties("highway=secondary_link;cycleway=track", withModes(ALL).bicycleSafety(0.8)); + props.setProperties("highway=primary;cycleway=track", withModes(ALL).bicycleSafety(0.85)); + props.setProperties("highway=primary_link;cycleway=track", withModes(ALL).bicycleSafety(0.85)); + props.setProperties( + "highway=trunk;cycleway=track", + withModes(BICYCLE_AND_CAR).bicycleSafety(0.95) + ); + props.setProperties( + "highway=trunk_link;cycleway=track", + withModes(BICYCLE_AND_CAR).bicycleSafety(0.85) + ); + + /* cycleway=opposite_track */ + props.setProperties( + "highway=*;cycleway=opposite_track", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.0, 0.75) + ); + props.setProperties( + "highway=service;cycleway=opposite_track", + withModes(ALL).bicycleSafety(1.1, 0.65) + ); + props.setProperties( + "highway=residential;cycleway=opposite_track", + withModes(ALL).bicycleSafety(0.98, 0.65) + ); + props.setProperties( + "highway=residential_link;cycleway=opposite_track", + withModes(ALL).bicycleSafety(0.98, 0.65) + ); + props.setProperties( + "highway=tertiary;cycleway=opposite_track", + withModes(ALL).bicycleSafety(1, 0.75) + ); + props.setProperties( + "highway=tertiary_link;cycleway=opposite_track", + withModes(ALL).bicycleSafety(1, 0.75) + ); + props.setProperties( + "highway=secondary;cycleway=opposite_track", + withModes(ALL).bicycleSafety(1.5, 0.8) + ); + props.setProperties( + "highway=secondary_link;cycleway=opposite_track", + withModes(ALL).bicycleSafety(1.5, 0.8) + ); + props.setProperties( + "highway=primary;cycleway=opposite_track", + withModes(ALL).bicycleSafety(2.06, 0.85) + ); + props.setProperties( + "highway=primary_link;cycleway=opposite_track", + withModes(ALL).bicycleSafety(2.06, 0.85) + ); + props.setProperties( + "highway=trunk;cycleway=opposite_track", + withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 0.95) + ); + props.setProperties( + "highway=trunk_link;cycleway=opposite_track", + withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 0.85) + ); + + /* cycleway=shared_lane a.k.a. bike boulevards or neighborhood greenways */ + props.setProperties( + "highway=*;cycleway=shared_lane", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.77) + ); + props.setProperties("highway=service;cycleway=shared_lane", withModes(ALL).bicycleSafety(0.73)); + props.setProperties( + "highway=residential;cycleway=shared_lane", + withModes(ALL).bicycleSafety(0.77) + ); + props.setProperties( + "highway=residential_link;cycleway=shared_lane", + withModes(ALL).bicycleSafety(0.77) + ); + props.setProperties( + "highway=tertiary;cycleway=shared_lane", + withModes(ALL).bicycleSafety(0.83) + ); + props.setProperties( + "highway=tertiary_link;cycleway=shared_lane", + withModes(ALL).bicycleSafety(0.83) + ); + props.setProperties( + "highway=secondary;cycleway=shared_lane", + withModes(ALL).bicycleSafety(1.25) + ); + props.setProperties( + "highway=secondary_link;cycleway=shared_lane", + withModes(ALL).bicycleSafety(1.25) + ); + props.setProperties("highway=primary;cycleway=shared_lane", withModes(ALL).bicycleSafety(1.75)); + props.setProperties( + "highway=primary_link;cycleway=shared_lane", + withModes(ALL).bicycleSafety(1.75) + ); + + /* cycleway=opposite */ + props.setProperties( + "highway=*;cycleway=opposite", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 1.4) + ); + props.setProperties("highway=service;cycleway=opposite", withModes(ALL).bicycleSafety(1.1)); + props.setProperties( + "highway=residential;cycleway=opposite", + withModes(ALL).bicycleSafety(0.98) + ); + props.setProperties( + "highway=residential_link;cycleway=opposite", + withModes(ALL).bicycleSafety(0.98) + ); + props.setProperties("highway=tertiary;cycleway=opposite", allWayProperties); + props.setProperties("highway=tertiary_link;cycleway=opposite", allWayProperties); + props.setProperties( + "highway=secondary;cycleway=opposite", + withModes(ALL).bicycleSafety(1.5, 1.71) + ); + props.setProperties( + "highway=secondary_link;cycleway=opposite", + withModes(ALL).bicycleSafety(1.5, 1.71) + ); + props.setProperties( + "highway=primary;cycleway=opposite", + withModes(ALL).bicycleSafety(2.06, 2.99) + ); + props.setProperties( + "highway=primary_link;cycleway=opposite", + withModes(ALL).bicycleSafety(2.06, 2.99) + ); + + /* + * path designed for bicycles (should be treated exactly as a cycleway is), this is a multi-use path (MUP) + */ + props.setProperties( + "highway=path;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.60) + ); + + /* special cases for footway, pedestrian and bicycles */ + props.setProperties( + "highway=footway;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) + ); + props.setProperties( + "highway=footway;bicycle=yes;area=yes", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9) + ); + props.setProperties( + "highway=pedestrian;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) + ); + + /* sidewalk and crosswalk */ + props.setProperties( + "footway=sidewalk;highway=footway;bicycle=yes", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5) + ); + props.setProperties( + "footway=sidewalk;highway=footway;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1) + ); + props.setProperties( + "highway=footway;footway=crossing", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5) + ); + props.setProperties( + "highway=footway;footway=crossing;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1) + ); + + /* + * bicycles on tracks (tracks are defined in OSM as: Roads for agricultural use, gravel roads in the forest etc.; usually unpaved/unsealed but + * may occasionally apply to paved tracks as well.) + */ + props.setProperties( + "highway=track;bicycle=yes", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18) + ); + props.setProperties( + "highway=track;bicycle=designated", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99) + ); + props.setProperties( + "highway=track;bicycle=yes;surface=*", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18) + ); + props.setProperties( + "highway=track;bicycle=designated;surface=*", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99) + ); + /* this is to avoid double counting since tracks are almost of surface type that is penalized */ + props.setProperties( + "highway=track;surface=*", + withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3) + ); + + /* bicycle=designated, but no bike infrastructure is present */ + props.setProperties("highway=*;bicycle=designated", withModes(ALL).bicycleSafety(0.97)); + props.setProperties("highway=service;bicycle=designated", withModes(ALL).bicycleSafety(0.84)); + props.setProperties( + "highway=residential;bicycle=designated", + withModes(ALL).bicycleSafety(0.95) + ); + props.setProperties( + "highway=unclassified;bicycle=designated", + withModes(ALL).bicycleSafety(0.95) + ); + props.setProperties( + "highway=residential_link;bicycle=designated", + withModes(ALL).bicycleSafety(0.95) + ); + props.setProperties("highway=tertiary;bicycle=designated", withModes(ALL).bicycleSafety(0.97)); + props.setProperties( + "highway=tertiary_link;bicycle=designated", + withModes(ALL).bicycleSafety(0.97) + ); + props.setProperties("highway=secondary;bicycle=designated", withModes(ALL).bicycleSafety(1.46)); + props.setProperties( + "highway=secondary_link;bicycle=designated", + withModes(ALL).bicycleSafety(1.46) + ); + props.setProperties("highway=primary;bicycle=designated", withModes(ALL).bicycleSafety(2)); + props.setProperties("highway=primary_link;bicycle=designated", withModes(ALL).bicycleSafety(2)); + props.setProperties( + "highway=trunk;bicycle=designated", + withModes(BICYCLE_AND_CAR).bicycleSafety(7.25) + ); + props.setProperties( + "highway=trunk_link;bicycle=designated", + withModes(BICYCLE_AND_CAR).bicycleSafety(2) + ); + props.setProperties( + "highway=motorway;bicycle=designated", + withModes(BICYCLE_AND_CAR).bicycleSafety(7.76) + ); + props.setProperties( + "highway=motorway_link;bicycle=designated", + withModes(BICYCLE_AND_CAR).bicycleSafety(2) + ); + + // We assume highway/cycleway of a cycle network to be safer (for bicycle network relations, their network is copied to way in postLoad) + // this uses a OR since you don't want to apply the safety multiplier more than once. + // Signed bicycle_roads and cyclestreets exist in traffic codes of some european countries. + // Tagging in OSM and on-the-ground use is varied, so just assume they are "somehow safer", too. + // In my test area ways often, but not always, have both tags. + // For simplicity these two concepts are handled together. + props.setMixinProperties( + new LogicalOrSpecifier( + "lcn=yes", + "rcn=yes", + "ncn=yes", + "bicycle_road=yes", + "cyclestreet=yes" + ), + ofBicycleSafety(0.7) + ); + + /* + * Automobile speeds in the United States: Based on my (mattwigway) personal experience, primarily in California + */ + props.setCarSpeed("highway=motorway", 29); // 29 m/s ~= 65 mph + props.setCarSpeed("highway=motorway_link", 15); // ~= 35 mph + props.setCarSpeed("highway=trunk", 24.6f); // ~= 55 mph + props.setCarSpeed("highway=trunk_link", 15); // ~= 35 mph + props.setCarSpeed("highway=primary", 20); // ~= 45 mph + props.setCarSpeed("highway=primary_link", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=secondary", 15); // ~= 35 mph + props.setCarSpeed("highway=secondary_link", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=tertiary", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=tertiary_link", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=living_street", 2.2f); // ~= 5 mph + + // generally, these will not allow cars at all, but the docs say + // "For roads used mainly/exclusively for pedestrians . . . which may allow access by + // motorised vehicles only for very limited periods of the day." + // http://wiki.openstreetmap.org/wiki/Key:highway + // This of course makes the street network time-dependent + props.setCarSpeed("highway=pedestrian", 2.2f); // ~= 5 mph + + props.setCarSpeed("highway=residential", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=unclassified", 11.2f); // ~= 25 mph + props.setCarSpeed("highway=service", 6.7f); // ~= 15 mph + props.setCarSpeed("highway=track", 4.5f); // ~= 10 mph + props.setCarSpeed("highway=road", 11.2f); // ~= 25 mph + + // default ~= 25 mph + props.defaultCarSpeed = 11.2f; + // 38 m/s ~= 85 mph ~= 137 kph + props.maxPossibleCarSpeed = 38f; + + /* special situations */ + + /* + * cycleway:left/right=lane/track/shared_lane permutations - no longer needed because left/right matching algorithm does this + */ + + /* cycleway:left=lane */ + /* cycleway:right=track */ + /* cycleway:left=track */ + /* cycleway:right=shared_lane */ + /* cycleway:left=shared_lane */ + /* cycleway:right=lane, cycleway:left=track */ + /* cycleway:right=lane, cycleway:left=shared_lane */ + /* cycleway:right=track, cycleway:left=lane */ + /* cycleway:right=track, cycleway:left=shared_lane */ + /* cycleway:right=shared_lane, cycleway:left=lane */ + /* cycleway:right=shared_lane, cycleway:left=track */ + + /* surface=* mixins */ + + /* + * The following tags have been removed from surface weights because they are no more of an impedence to bicycling than a paved surface + * surface=paving_stones surface=fine_gravel (sounds counter-intuitive but see the definition on the OSM Wiki) surface=tartan (this what + * running tracks are usually made of) + */ + + props.setMixinProperties("surface=unpaved", ofBicycleSafety(1.18)); + props.setMixinProperties("surface=compacted", ofBicycleSafety(1.18)); + props.setMixinProperties("surface=wood", ofBicycleSafety(1.18)); + + props.setMixinProperties("surface=cobblestone", ofBicycleSafety(1.3)); + props.setMixinProperties("surface=sett", ofBicycleSafety(1.3)); + props.setMixinProperties("surface=unhewn_cobblestone", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=grass_paver", ofBicycleSafety(1.3)); + props.setMixinProperties("surface=pebblestone", ofBicycleSafety(1.3)); + // Can be slick if wet, but otherwise not unfavorable to bikes + props.setMixinProperties("surface=metal", ofBicycleSafety(1.3)); + props.setMixinProperties("surface=ground", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=dirt", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=earth", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=grass", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=mud", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=woodchip", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=gravel", ofBicycleSafety(1.5)); + props.setMixinProperties("surface=artifical_turf", ofBicycleSafety(1.5)); + + /* sand is deadly for bikes */ + props.setMixinProperties("surface=sand", ofBicycleSafety(100)); + + /* Portland-local mixins */ + + props.setMixinProperties("foot=discouraged", ofWalkSafety(3)); + props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3)); + + props.setMixinProperties("foot=use_sidepath", ofWalkSafety(5)); + props.setMixinProperties("bicycle=use_sidepath", ofBicycleSafety(5)); + + populateNotesAndNames(props); + + // slope overrides + props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true); + props.setSlopeOverride(new BestMatchSpecifier("embankment=*"), true); + props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true); + props.setSlopeOverride(new BestMatchSpecifier("tunnel=*"), true); + props.setSlopeOverride(new BestMatchSpecifier("location=underground"), true); + props.setSlopeOverride(new BestMatchSpecifier("indoor=yes"), true); + } + + public void populateNotesAndNames(WayPropertySet props) { + /* and the notes */ + // TODO: The curly brackets in the string below mean that the CreativeNamer should substitute in OSM tag values. + // However they are not taken into account when passed to the translation function. + // props.createNotes("wheelchair:description=*", "{wheelchair:description}", StreetNotesService.WHEELCHAIR_MATCHER); + // TODO: The two entries below produce lots of spurious notes (because of OSM mapper comments) + // props.createNotes("note=*", "{note}", StreetNotesService.ALWAYS_MATCHER); + // props.createNotes("notes=*", "{notes}", StreetNotesService.ALWAYS_MATCHER); + props.createNotes( + "RLIS:bicycle=caution_area", + "note.caution", + StreetNotesService.BICYCLE_MATCHER + ); + props.createNotes( + "CCGIS:bicycle=caution_area", + "note.caution", + StreetNotesService.BICYCLE_MATCHER + ); + // TODO: Maybe we should apply the following notes only for car/bike + props.createNotes("surface=unpaved", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes( + "surface=compacted", + "note.unpaved_surface", + StreetNotesService.ALWAYS_MATCHER + ); + props.createNotes("surface=ground", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes("surface=dirt", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes("surface=earth", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes("surface=grass", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes("surface=mud", "note.muddy_surface", StreetNotesService.ALWAYS_MATCHER); + props.createNotes("toll=yes", "note.toll", StreetNotesService.DRIVING_MATCHER); + props.createNotes("toll:motorcar=yes", "note.toll", StreetNotesService.DRIVING_MATCHER); + + /* and some names */ + // Basics + props.createNames("highway=cycleway", "name.bike_path"); + props.createNames("cycleway=track", "name.bike_path"); + props.createNames("highway=pedestrian", "name.pedestrian_path"); + props.createNames("highway=pedestrian;area=yes", "name.pedestrian_area"); + props.createNames("highway=path", "name.path"); + props.createNames("highway=footway", "name.pedestrian_path"); + props.createNames("highway=bridleway", "name.bridleway"); + props.createNames("highway=footway;bicycle=no", "name.pedestrian_path"); + + // Platforms + props.createNames("otp:route_ref=*", "name.otp_route_ref"); + props.createNames("highway=platform;ref=*", "name.platform_ref"); + props.createNames("railway=platform;ref=*", "name.platform_ref"); + props.createNames("railway=platform;highway=footway;footway=sidewalk", "name.platform"); + props.createNames("railway=platform;highway=path;path=sidewalk", "name.platform"); + props.createNames("railway=platform;highway=pedestrian", "name.platform"); + props.createNames("railway=platform;highway=path", "name.platform"); + props.createNames("railway=platform;highway=footway", "name.platform"); + props.createNames("highway=platform", "name.platform"); + props.createNames("railway=platform", "name.platform"); + props.createNames("railway=platform;highway=footway;bicycle=no", "name.platform"); + + // Bridges/Tunnels + props.createNames("highway=pedestrian;bridge=*", "name.footbridge"); + props.createNames("highway=path;bridge=*", "name.footbridge"); + props.createNames("highway=footway;bridge=*", "name.footbridge"); + + props.createNames("highway=pedestrian;tunnel=*", "name.underpass"); + props.createNames("highway=path;tunnel=*", "name.underpass"); + props.createNames("highway=footway;tunnel=*", "name.underpass"); + + // Basic Mappings + props.createNames("highway=motorway", "name.road"); + props.createNames("highway=motorway_link", "name.ramp"); + props.createNames("highway=trunk", "name.road"); + props.createNames("highway=trunk_link", "name.ramp"); + + props.createNames("highway=primary", "name.road"); + props.createNames("highway=primary_link", "name.link"); + props.createNames("highway=secondary", "name.road"); + props.createNames("highway=secondary_link", "name.link"); + props.createNames("highway=tertiary", "name.road"); + props.createNames("highway=tertiary_link", "name.link"); + props.createNames("highway=unclassified", "name.road"); + props.createNames("highway=residential", "name.road"); + props.createNames("highway=living_street", "name.road"); + props.createNames("highway=road", "name.road"); + props.createNames("highway=service", "name.service_road"); + props.createNames("highway=service;service=alley", "name.alley"); + props.createNames("highway=service;service=parking_aisle", "name.parking_aisle"); + props.createNames("highway=byway", "name.byway"); + props.createNames("highway=track", "name.track"); + + props.createNames("highway=footway;footway=sidewalk", "name.sidewalk"); + props.createNames("highway=path;path=sidewalk", "name.sidewalk"); + + props.createNames("highway=steps", "name.steps"); + + props.createNames("amenity=bicycle_parking;name=*", "name.bicycle_parking_name"); + props.createNames("amenity=bicycle_parking", "name.bicycle_parking"); + + props.createNames("amenity=parking;name=*", "name.park_and_ride_name"); + props.createNames("amenity=parking", "name.park_and_ride_station"); + } + + public boolean doesTagValueDisallowThroughTraffic(String tagValue) { return ( "no".equals(tagValue) || "destination".equals(tagValue) || @@ -22,20 +732,20 @@ default boolean doesTagValueDisallowThroughTraffic(String tagValue) { ); } - default float getCarSpeedForWay(OsmWithTags way, boolean backward) { + public float getCarSpeedForWay(OsmWithTags way, boolean backward) { return way.getOsmProvider().getWayPropertySet().getCarSpeedForWay(way, backward); } - default Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) { + public Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) { return wayPropertySet.maxUsedCarSpeed; } - default boolean isGeneralNoThroughTraffic(OsmWithTags way) { + public boolean isGeneralNoThroughTraffic(OsmWithTags way) { String access = way.getTag("access"); return doesTagValueDisallowThroughTraffic(access); } - default boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) { + public boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) { String vehicle = way.getTag("vehicle"); if (vehicle != null) { return doesTagValueDisallowThroughTraffic(vehicle); @@ -47,7 +757,7 @@ default boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) { /** * Returns true if through traffic for motor vehicles is not allowed. */ - default boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) { + public boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) { String motorVehicle = way.getTag("motor_vehicle"); if (motorVehicle != null) { return doesTagValueDisallowThroughTraffic(motorVehicle); @@ -59,7 +769,7 @@ default boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way /** * Returns true if through traffic for bicycle is not allowed. */ - default boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) { + public boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) { String bicycle = way.getTag("bicycle"); if (bicycle != null) { return doesTagValueDisallowThroughTraffic(bicycle); @@ -71,7 +781,7 @@ default boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) { /** * Returns true if through traffic for walk is not allowed. */ - default boolean isWalkNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) { + public boolean isWalkNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) { String foot = way.getTag("foot"); if (foot != null) { return doesTagValueDisallowThroughTraffic(foot); diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java b/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java index b40c2e7f75a..98593ef70d0 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java @@ -18,7 +18,7 @@ public enum OsmTagMapperSource { public OsmTagMapper getInstance() { return switch (this) { - case DEFAULT -> new DefaultMapper(); + case DEFAULT -> new OsmTagMapper(); case NORWAY -> new NorwayMapper(); case UK -> new UKMapper(); case FINLAND -> new FinlandMapper(); diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java index 7353c186564..7da8f8ca886 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java @@ -9,7 +9,7 @@ import org.opentripplanner.osm.wayproperty.specifier.Condition.GreaterThan; import org.opentripplanner.osm.wayproperty.specifier.ExactMatchSpecifier; -class PortlandMapper extends DefaultMapper { +class PortlandMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java index 4f93425cc2b..954fd84417a 100644 --- a/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java +++ b/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java @@ -19,9 +19,9 @@ * * @author marcusyoung * @see OsmTagMapper - * @see DefaultMapper + * @see OsmTagMapper */ -class UKMapper extends DefaultMapper { +class UKMapper extends OsmTagMapper { @Override public void populateProperties(WayPropertySet props) { From 7ffca9b3a21fa0e4b0f459f7881f55dd353c9dc2 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 15 Oct 2024 11:08:57 +0300 Subject: [PATCH 26/86] Update OSM tag mapping related tests --- .../osm/tagmapping/OsmTagMapperTest.java | 48 +++++-------------- .../MapperTest.java} | 28 +++++++++-- 2 files changed, 35 insertions(+), 41 deletions(-) rename src/test/java/org/opentripplanner/osm/{tagmapping/DefaultMapperTest.java => wayproperty/MapperTest.java} (89%) diff --git a/src/test/java/org/opentripplanner/osm/tagmapping/OsmTagMapperTest.java b/src/test/java/org/opentripplanner/osm/tagmapping/OsmTagMapperTest.java index e0c70690e91..aaaf5178d5b 100644 --- a/src/test/java/org/opentripplanner/osm/tagmapping/OsmTagMapperTest.java +++ b/src/test/java/org/opentripplanner/osm/tagmapping/OsmTagMapperTest.java @@ -3,20 +3,17 @@ 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.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; -import static org.opentripplanner.osm.wayproperty.WayPropertiesBuilder.withModes; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import org.junit.jupiter.api.Test; import org.opentripplanner.osm.model.OsmWithTags; -import org.opentripplanner.osm.wayproperty.WayPropertySet; public class OsmTagMapperTest { @Test public void isMotorThroughTrafficExplicitlyDisallowed() { OsmWithTags o = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); assertFalse(osmTagMapper.isMotorVehicleThroughTrafficExplicitlyDisallowed(o)); @@ -52,7 +49,7 @@ public void constantSpeedCarRouting() { @Test public void isBicycleNoThroughTrafficExplicitlyDisallowed() { - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); assertTrue( osmTagMapper.isBicycleNoThroughTrafficExplicitlyDisallowed(way("bicycle", "destination")) ); @@ -63,38 +60,17 @@ public void isBicycleNoThroughTrafficExplicitlyDisallowed() { @Test public void isWalkNoThroughTrafficExplicitlyDisallowed() { - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); assertTrue(osmTagMapper.isWalkNoThroughTrafficExplicitlyDisallowed(way("foot", "destination"))); assertTrue( osmTagMapper.isWalkNoThroughTrafficExplicitlyDisallowed(way("access", "destination")) ); } - @Test - public void mixin() { - var source = new DefaultMapper(); - var wps = new WayPropertySet(); - - wps.setProperties("tag=imaginary", withModes(CAR).bicycleSafety(2)); - - wps.setMixinProperties("foo=bar", ofBicycleSafety(0.5)); - source.populateProperties(wps); - - var withoutFoo = new OsmWithTags(); - withoutFoo.addTag("tag", "imaginary"); - 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).bicycleSafety().back()); - } - @Test public void testAccessNo() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "no"); @@ -106,7 +82,7 @@ public void testAccessNo() { @Test public void testAccessPrivate() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "private"); @@ -118,7 +94,7 @@ public void testAccessPrivate() { @Test public void testFootModifier() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "private"); tags.addTag("foot", "yes"); @@ -131,7 +107,7 @@ public void testFootModifier() { @Test public void testVehicleDenied() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("vehicle", "destination"); @@ -143,7 +119,7 @@ public void testVehicleDenied() { @Test public void testVehicleDeniedMotorVehiclePermissive() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("vehicle", "destination"); tags.addTag("motor_vehicle", "designated"); @@ -156,7 +132,7 @@ public void testVehicleDeniedMotorVehiclePermissive() { @Test public void testVehicleDeniedBicyclePermissive() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("vehicle", "destination"); tags.addTag("bicycle", "designated"); @@ -169,7 +145,7 @@ public void testVehicleDeniedBicyclePermissive() { @Test public void testMotorcycleModifier() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "private"); tags.addTag("motor_vehicle", "yes"); @@ -182,7 +158,7 @@ public void testMotorcycleModifier() { @Test public void testBicycleModifier() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "private"); tags.addTag("bicycle", "yes"); @@ -195,7 +171,7 @@ public void testBicycleModifier() { @Test public void testBicyclePermissive() { OsmWithTags tags = new OsmWithTags(); - OsmTagMapper osmTagMapper = new DefaultMapper(); + OsmTagMapper osmTagMapper = new OsmTagMapper(); tags.addTag("access", "private"); tags.addTag("bicycle", "permissive"); diff --git a/src/test/java/org/opentripplanner/osm/tagmapping/DefaultMapperTest.java b/src/test/java/org/opentripplanner/osm/wayproperty/MapperTest.java similarity index 89% rename from src/test/java/org/opentripplanner/osm/tagmapping/DefaultMapperTest.java rename to src/test/java/org/opentripplanner/osm/wayproperty/MapperTest.java index 87e23acbf12..2cd9a74f06b 100644 --- a/src/test/java/org/opentripplanner/osm/tagmapping/DefaultMapperTest.java +++ b/src/test/java/org/opentripplanner/osm/wayproperty/MapperTest.java @@ -1,21 +1,23 @@ -package org.opentripplanner.osm.tagmapping; +package org.opentripplanner.osm.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.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; +import static org.opentripplanner.osm.wayproperty.WayPropertiesBuilder.withModes; import static org.opentripplanner.street.model.StreetTraversalPermission.ALL; +import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; 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.osm.model.OsmWithTags; -import org.opentripplanner.osm.wayproperty.SpeedPicker; -import org.opentripplanner.osm.wayproperty.WayPropertySet; +import org.opentripplanner.osm.tagmapping.OsmTagMapper; import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier; import org.opentripplanner.osm.wayproperty.specifier.WayTestData; -public class DefaultMapperTest { +public class MapperTest { private WayPropertySet wps; private OsmTagMapper mapper; @@ -24,7 +26,7 @@ public class DefaultMapperTest { @BeforeEach public void setup() { var wps = new WayPropertySet(); - DefaultMapper source = new DefaultMapper(); + var source = new OsmTagMapper(); source.populateProperties(wps); this.wps = wps; this.mapper = source; @@ -199,6 +201,22 @@ void slopeOverrides() { assertTrue(wps.getSlopeOverride(indoor)); } + @Test + public void mixin() { + wps.setProperties("tag=imaginary", withModes(CAR).bicycleSafety(2)); + wps.setMixinProperties("foo=bar", ofBicycleSafety(0.5)); + + var withoutFoo = new OsmWithTags(); + withoutFoo.addTag("tag", "imaginary"); + 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).bicycleSafety().back()); + } + /** * Test that two values are within epsilon of each other. */ From 3417952b000cc58e6012eddc9812932178ed7881 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 15 Oct 2024 11:09:31 +0300 Subject: [PATCH 27/86] Update OSM tag mapping docs --- doc/user/osm/{Default.md => OsmTag.md} | 0 mkdocs.yml | 2 +- .../osm/tagmapping/DefaultMapper.java | 722 ------------------ 3 files changed, 1 insertion(+), 723 deletions(-) rename doc/user/osm/{Default.md => OsmTag.md} (100%) delete mode 100644 src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java diff --git a/doc/user/osm/Default.md b/doc/user/osm/OsmTag.md similarity index 100% rename from doc/user/osm/Default.md rename to doc/user/osm/OsmTag.md diff --git a/mkdocs.yml b/mkdocs.yml index 1364be7be1f..bce905f337a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,7 +74,7 @@ nav: - Introduction: 'Configuration.md' - Build: 'BuildConfiguration.md' - OSM Tag Mapping: - - Default: 'osm/Default.md' + - Default: 'osm/OsmTag.md' - Finland: 'osm/Finland.md' - Germany: 'osm/Germany.md' - Norway: 'osm/Norway.md' diff --git a/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java b/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java deleted file mode 100644 index c0b786d02e8..00000000000 --- a/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java +++ /dev/null @@ -1,722 +0,0 @@ -package org.opentripplanner.osm.tagmapping; - -import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety; -import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofWalkSafety; -import static org.opentripplanner.osm.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; -import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE; - -import org.opentripplanner.osm.wayproperty.WayProperties; -import org.opentripplanner.osm.wayproperty.WayPropertySet; -import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier; -import org.opentripplanner.osm.wayproperty.specifier.LogicalOrSpecifier; -import org.opentripplanner.routing.services.notes.StreetNotesService; - -/** - * This factory class provides a default collection of {@link WayProperties} that determine how OSM - * streets can be traversed in various modes. - *

- * Circa January 2011, Grant and Mele at TriMet undertook proper testing of bike (and transit) - * routing, and worked with David Turner on assigning proper weights to different facility types. - * The weights in this file grew organically from trial and error, and are the result of months of - * testing and tweaking the routes that OTP returned, as well as actually walking/biking these - * routes and making changes based on those experiences. This set of weights should be a great - * starting point for others to use, but they are to some extent tailored to the situation in - * Portland and people shouldn't hesitate to adjust them to for their own instance. - *

- * The rules for assigning WayProperties to OSM ways are explained in. The final tie breaker if two - * Pickers both match is the sequence that the properties are added in this file: if all else is - * equal the 'props.setProperties' statement that is closer to the top of the page will prevail over - * those lower down the page. - *

- * Foot and bicycle permissions are also addressed in OpenStreetMapGraphBuilderImpl.Handler#getPermissionsForEntity(). - * For instance, if a way that normally does not permit walking based on its tag matches (the - * prevailing 'props.setProperties' statement) has a 'foot=yes' tag the permissions are overridden - * and walking is allowed on that way. - *

- * TODO clarify why this needs a separate factory interface. - * - * @author bdferris, novalis - * @see OsmTagMapper - */ -class DefaultMapper implements OsmTagMapper { - - /* Populate properties on existing WayPropertySet */ - public void populateProperties(WayPropertySet props) { - WayProperties allWayProperties = withModes(ALL).build(); - WayProperties noneWayProperties = withModes(NONE).build(); - WayProperties pedestrianWayProperties = withModes(PEDESTRIAN).build(); - WayProperties pedestrianAndBicycleWayProperties = withModes(PEDESTRIAN_AND_BICYCLE).build(); - /* no bicycle tags */ - - /* NONE */ - props.setProperties("mtb:scale=3", noneWayProperties); - props.setProperties("mtb:scale=4", noneWayProperties); - props.setProperties("mtb:scale=5", noneWayProperties); - props.setProperties("mtb:scale=6", noneWayProperties); - - /* PEDESTRIAN */ - props.setProperties("highway=corridor", pedestrianWayProperties); - props.setProperties("highway=steps", pedestrianWayProperties); - props.setProperties("highway=crossing", pedestrianWayProperties); - props.setProperties("highway=platform", pedestrianWayProperties); - props.setProperties("public_transport=platform", pedestrianWayProperties); - props.setProperties("public_transport=platform;area=yes", pedestrianWayProperties); - props.setProperties("railway=platform", pedestrianWayProperties); - props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties); - props.setProperties("mtb:scale=1", pedestrianWayProperties); - props.setProperties("mtb:scale=2", pedestrianWayProperties); - - /* PEDESTRIAN_AND_BICYCLE */ - props.setProperties("mtb:scale=0", pedestrianAndBicycleWayProperties); - props.setProperties("highway=cycleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.6)); - props.setProperties("highway=path", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)); - props.setProperties("highway=pedestrian", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9)); - props.setProperties("highway=footway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)); - props.setProperties("highway=bridleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3)); - - /* ALL */ - props.setProperties("highway=living_street", withModes(ALL).bicycleSafety(0.9)); - props.setProperties("highway=unclassified", allWayProperties); - props.setProperties("highway=road", allWayProperties); - props.setProperties("highway=byway", withModes(ALL).bicycleSafety(1.3)); - props.setProperties("highway=track", withModes(ALL).bicycleSafety(1.3)); - props.setProperties("highway=service", withModes(ALL).bicycleSafety(1.1)); - props.setProperties("highway=residential", withModes(ALL).bicycleSafety(0.98)); - props.setProperties("highway=residential_link", withModes(ALL).bicycleSafety(0.98)); - props.setProperties("highway=tertiary", allWayProperties); - props.setProperties("highway=tertiary_link", allWayProperties); - props.setProperties("highway=secondary", withModes(ALL).bicycleSafety(1.5)); - props.setProperties("highway=secondary_link", withModes(ALL).bicycleSafety(1.5)); - props.setProperties("highway=primary", withModes(ALL).bicycleSafety(2.06)); - props.setProperties("highway=primary_link", withModes(ALL).bicycleSafety(2.06)); - - /* DRIVING ONLY */ - // trunk and motorway links are often short distances and necessary connections - props.setProperties("highway=trunk_link", withModes(CAR).bicycleSafety(2.06)); - props.setProperties("highway=motorway_link", withModes(CAR).bicycleSafety(2.06)); - - props.setProperties("highway=trunk", withModes(CAR).bicycleSafety(7.47)); - props.setProperties("highway=motorway", withModes(CAR).bicycleSafety(8)); - - /* cycleway=lane */ - props.setProperties( - "highway=*;cycleway=lane", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.87) - ); - props.setProperties("highway=service;cycleway=lane", withModes(ALL).bicycleSafety(0.77)); - props.setProperties("highway=residential;cycleway=lane", withModes(ALL).bicycleSafety(0.77)); - props.setProperties( - "highway=residential_link;cycleway=lane", - withModes(ALL).bicycleSafety(0.77) - ); - props.setProperties("highway=tertiary;cycleway=lane", withModes(ALL).bicycleSafety(0.87)); - props.setProperties("highway=tertiary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.87)); - props.setProperties("highway=secondary;cycleway=lane", withModes(ALL).bicycleSafety(0.96)); - props.setProperties("highway=secondary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.96)); - props.setProperties("highway=primary;cycleway=lane", withModes(ALL).bicycleSafety(1.15)); - props.setProperties("highway=primary_link;cycleway=lane", withModes(ALL).bicycleSafety(1.15)); - - /* BICYCLE_AND_CAR */ - props.setProperties( - "highway=trunk;cycleway=lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.5) - ); - props.setProperties( - "highway=trunk_link;cycleway=lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.15) - ); - props.setProperties( - "highway=motorway;cycleway=lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(2) - ); - props.setProperties( - "highway=motorway_link;cycleway=lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.15) - ); - - /* cycleway=share_busway */ - props.setProperties( - "highway=*;cycleway=share_busway", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.92) - ); - props.setProperties( - "highway=service;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.85) - ); - props.setProperties( - "highway=residential;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.85) - ); - props.setProperties( - "highway=residential_link;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.85) - ); - props.setProperties( - "highway=tertiary;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.92) - ); - props.setProperties( - "highway=tertiary_link;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.92) - ); - props.setProperties( - "highway=secondary;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.99) - ); - props.setProperties( - "highway=secondary_link;cycleway=share_busway", - withModes(ALL).bicycleSafety(0.99) - ); - props.setProperties( - "highway=primary;cycleway=share_busway", - withModes(ALL).bicycleSafety(1.25) - ); - props.setProperties( - "highway=primary_link;cycleway=share_busway", - withModes(ALL).bicycleSafety(1.25) - ); - props.setProperties( - "highway=trunk;cycleway=share_busway", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.75) - ); - props.setProperties( - "highway=trunk_link;cycleway=share_busway", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.25) - ); - props.setProperties( - "highway=motorway;cycleway=share_busway", - withModes(BICYCLE_AND_CAR).bicycleSafety(2.5) - ); - props.setProperties( - "highway=motorway_link;cycleway=share_busway", - withModes(BICYCLE_AND_CAR).bicycleSafety(1.25) - ); - - /* cycleway=opposite_lane */ - props.setProperties( - "highway=*;cycleway=opposite_lane", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 0.87) - ); - props.setProperties( - "highway=service;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(1.1, 0.77) - ); - props.setProperties( - "highway=residential;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(0.98, 0.77) - ); - props.setProperties( - "highway=residential_link;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(0.98, 0.77) - ); - props.setProperties( - "highway=tertiary;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(1, 0.87) - ); - props.setProperties( - "highway=tertiary_link;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(1, 0.87) - ); - props.setProperties( - "highway=secondary;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(1.5, 0.96) - ); - props.setProperties( - "highway=secondary_link;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(1.5, 0.96) - ); - props.setProperties( - "highway=primary;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(2.06, 1.15) - ); - props.setProperties( - "highway=primary_link;cycleway=opposite_lane", - withModes(ALL).bicycleSafety(2.06, 1.15) - ); - props.setProperties( - "highway=trunk;cycleway=opposite_lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 1.5) - ); - props.setProperties( - "highway=trunk_link;cycleway=opposite_lane", - withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 1.15) - ); - - /* cycleway=track */ - props.setProperties( - "highway=*;cycleway=track", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) - ); - props.setProperties("highway=service;cycleway=track", withModes(ALL).bicycleSafety(0.65)); - props.setProperties("highway=residential;cycleway=track", withModes(ALL).bicycleSafety(0.65)); - props.setProperties( - "highway=residential_link;cycleway=track", - withModes(ALL).bicycleSafety(0.65) - ); - props.setProperties("highway=tertiary;cycleway=track", withModes(ALL).bicycleSafety(0.75)); - props.setProperties("highway=tertiary_link;cycleway=track", withModes(ALL).bicycleSafety(0.75)); - props.setProperties("highway=secondary;cycleway=track", withModes(ALL).bicycleSafety(0.8)); - props.setProperties("highway=secondary_link;cycleway=track", withModes(ALL).bicycleSafety(0.8)); - props.setProperties("highway=primary;cycleway=track", withModes(ALL).bicycleSafety(0.85)); - props.setProperties("highway=primary_link;cycleway=track", withModes(ALL).bicycleSafety(0.85)); - props.setProperties( - "highway=trunk;cycleway=track", - withModes(BICYCLE_AND_CAR).bicycleSafety(0.95) - ); - props.setProperties( - "highway=trunk_link;cycleway=track", - withModes(BICYCLE_AND_CAR).bicycleSafety(0.85) - ); - - /* cycleway=opposite_track */ - props.setProperties( - "highway=*;cycleway=opposite_track", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.0, 0.75) - ); - props.setProperties( - "highway=service;cycleway=opposite_track", - withModes(ALL).bicycleSafety(1.1, 0.65) - ); - props.setProperties( - "highway=residential;cycleway=opposite_track", - withModes(ALL).bicycleSafety(0.98, 0.65) - ); - props.setProperties( - "highway=residential_link;cycleway=opposite_track", - withModes(ALL).bicycleSafety(0.98, 0.65) - ); - props.setProperties( - "highway=tertiary;cycleway=opposite_track", - withModes(ALL).bicycleSafety(1, 0.75) - ); - props.setProperties( - "highway=tertiary_link;cycleway=opposite_track", - withModes(ALL).bicycleSafety(1, 0.75) - ); - props.setProperties( - "highway=secondary;cycleway=opposite_track", - withModes(ALL).bicycleSafety(1.5, 0.8) - ); - props.setProperties( - "highway=secondary_link;cycleway=opposite_track", - withModes(ALL).bicycleSafety(1.5, 0.8) - ); - props.setProperties( - "highway=primary;cycleway=opposite_track", - withModes(ALL).bicycleSafety(2.06, 0.85) - ); - props.setProperties( - "highway=primary_link;cycleway=opposite_track", - withModes(ALL).bicycleSafety(2.06, 0.85) - ); - props.setProperties( - "highway=trunk;cycleway=opposite_track", - withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 0.95) - ); - props.setProperties( - "highway=trunk_link;cycleway=opposite_track", - withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 0.85) - ); - - /* cycleway=shared_lane a.k.a. bike boulevards or neighborhood greenways */ - props.setProperties( - "highway=*;cycleway=shared_lane", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.77) - ); - props.setProperties("highway=service;cycleway=shared_lane", withModes(ALL).bicycleSafety(0.73)); - props.setProperties( - "highway=residential;cycleway=shared_lane", - withModes(ALL).bicycleSafety(0.77) - ); - props.setProperties( - "highway=residential_link;cycleway=shared_lane", - withModes(ALL).bicycleSafety(0.77) - ); - props.setProperties( - "highway=tertiary;cycleway=shared_lane", - withModes(ALL).bicycleSafety(0.83) - ); - props.setProperties( - "highway=tertiary_link;cycleway=shared_lane", - withModes(ALL).bicycleSafety(0.83) - ); - props.setProperties( - "highway=secondary;cycleway=shared_lane", - withModes(ALL).bicycleSafety(1.25) - ); - props.setProperties( - "highway=secondary_link;cycleway=shared_lane", - withModes(ALL).bicycleSafety(1.25) - ); - props.setProperties("highway=primary;cycleway=shared_lane", withModes(ALL).bicycleSafety(1.75)); - props.setProperties( - "highway=primary_link;cycleway=shared_lane", - withModes(ALL).bicycleSafety(1.75) - ); - - /* cycleway=opposite */ - props.setProperties( - "highway=*;cycleway=opposite", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 1.4) - ); - props.setProperties("highway=service;cycleway=opposite", withModes(ALL).bicycleSafety(1.1)); - props.setProperties( - "highway=residential;cycleway=opposite", - withModes(ALL).bicycleSafety(0.98) - ); - props.setProperties( - "highway=residential_link;cycleway=opposite", - withModes(ALL).bicycleSafety(0.98) - ); - props.setProperties("highway=tertiary;cycleway=opposite", allWayProperties); - props.setProperties("highway=tertiary_link;cycleway=opposite", allWayProperties); - props.setProperties( - "highway=secondary;cycleway=opposite", - withModes(ALL).bicycleSafety(1.5, 1.71) - ); - props.setProperties( - "highway=secondary_link;cycleway=opposite", - withModes(ALL).bicycleSafety(1.5, 1.71) - ); - props.setProperties( - "highway=primary;cycleway=opposite", - withModes(ALL).bicycleSafety(2.06, 2.99) - ); - props.setProperties( - "highway=primary_link;cycleway=opposite", - withModes(ALL).bicycleSafety(2.06, 2.99) - ); - - /* - * path designed for bicycles (should be treated exactly as a cycleway is), this is a multi-use path (MUP) - */ - props.setProperties( - "highway=path;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.60) - ); - - /* special cases for footway, pedestrian and bicycles */ - props.setProperties( - "highway=footway;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) - ); - props.setProperties( - "highway=footway;bicycle=yes;area=yes", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9) - ); - props.setProperties( - "highway=pedestrian;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) - ); - - /* sidewalk and crosswalk */ - props.setProperties( - "footway=sidewalk;highway=footway;bicycle=yes", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5) - ); - props.setProperties( - "footway=sidewalk;highway=footway;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1) - ); - props.setProperties( - "highway=footway;footway=crossing", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5) - ); - props.setProperties( - "highway=footway;footway=crossing;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1) - ); - - /* - * bicycles on tracks (tracks are defined in OSM as: Roads for agricultural use, gravel roads in the forest etc.; usually unpaved/unsealed but - * may occasionally apply to paved tracks as well.) - */ - props.setProperties( - "highway=track;bicycle=yes", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18) - ); - props.setProperties( - "highway=track;bicycle=designated", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99) - ); - props.setProperties( - "highway=track;bicycle=yes;surface=*", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18) - ); - props.setProperties( - "highway=track;bicycle=designated;surface=*", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99) - ); - /* this is to avoid double counting since tracks are almost of surface type that is penalized */ - props.setProperties( - "highway=track;surface=*", - withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3) - ); - - /* bicycle=designated, but no bike infrastructure is present */ - props.setProperties("highway=*;bicycle=designated", withModes(ALL).bicycleSafety(0.97)); - props.setProperties("highway=service;bicycle=designated", withModes(ALL).bicycleSafety(0.84)); - props.setProperties( - "highway=residential;bicycle=designated", - withModes(ALL).bicycleSafety(0.95) - ); - props.setProperties( - "highway=unclassified;bicycle=designated", - withModes(ALL).bicycleSafety(0.95) - ); - props.setProperties( - "highway=residential_link;bicycle=designated", - withModes(ALL).bicycleSafety(0.95) - ); - props.setProperties("highway=tertiary;bicycle=designated", withModes(ALL).bicycleSafety(0.97)); - props.setProperties( - "highway=tertiary_link;bicycle=designated", - withModes(ALL).bicycleSafety(0.97) - ); - props.setProperties("highway=secondary;bicycle=designated", withModes(ALL).bicycleSafety(1.46)); - props.setProperties( - "highway=secondary_link;bicycle=designated", - withModes(ALL).bicycleSafety(1.46) - ); - props.setProperties("highway=primary;bicycle=designated", withModes(ALL).bicycleSafety(2)); - props.setProperties("highway=primary_link;bicycle=designated", withModes(ALL).bicycleSafety(2)); - props.setProperties( - "highway=trunk;bicycle=designated", - withModes(BICYCLE_AND_CAR).bicycleSafety(7.25) - ); - props.setProperties( - "highway=trunk_link;bicycle=designated", - withModes(BICYCLE_AND_CAR).bicycleSafety(2) - ); - props.setProperties( - "highway=motorway;bicycle=designated", - withModes(BICYCLE_AND_CAR).bicycleSafety(7.76) - ); - props.setProperties( - "highway=motorway_link;bicycle=designated", - withModes(BICYCLE_AND_CAR).bicycleSafety(2) - ); - - // We assume highway/cycleway of a cycle network to be safer (for bicycle network relations, their network is copied to way in postLoad) - // this uses a OR since you don't want to apply the safety multiplier more than once. - // Signed bicycle_roads and cyclestreets exist in traffic codes of some european countries. - // Tagging in OSM and on-the-ground use is varied, so just assume they are "somehow safer", too. - // In my test area ways often, but not always, have both tags. - // For simplicity these two concepts are handled together. - props.setMixinProperties( - new LogicalOrSpecifier( - "lcn=yes", - "rcn=yes", - "ncn=yes", - "bicycle_road=yes", - "cyclestreet=yes" - ), - ofBicycleSafety(0.7) - ); - - /* - * Automobile speeds in the United States: Based on my (mattwigway) personal experience, primarily in California - */ - props.setCarSpeed("highway=motorway", 29); // 29 m/s ~= 65 mph - props.setCarSpeed("highway=motorway_link", 15); // ~= 35 mph - props.setCarSpeed("highway=trunk", 24.6f); // ~= 55 mph - props.setCarSpeed("highway=trunk_link", 15); // ~= 35 mph - props.setCarSpeed("highway=primary", 20); // ~= 45 mph - props.setCarSpeed("highway=primary_link", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=secondary", 15); // ~= 35 mph - props.setCarSpeed("highway=secondary_link", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=tertiary", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=tertiary_link", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=living_street", 2.2f); // ~= 5 mph - - // generally, these will not allow cars at all, but the docs say - // "For roads used mainly/exclusively for pedestrians . . . which may allow access by - // motorised vehicles only for very limited periods of the day." - // http://wiki.openstreetmap.org/wiki/Key:highway - // This of course makes the street network time-dependent - props.setCarSpeed("highway=pedestrian", 2.2f); // ~= 5 mph - - props.setCarSpeed("highway=residential", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=unclassified", 11.2f); // ~= 25 mph - props.setCarSpeed("highway=service", 6.7f); // ~= 15 mph - props.setCarSpeed("highway=track", 4.5f); // ~= 10 mph - props.setCarSpeed("highway=road", 11.2f); // ~= 25 mph - - // default ~= 25 mph - props.defaultCarSpeed = 11.2f; - // 38 m/s ~= 85 mph ~= 137 kph - props.maxPossibleCarSpeed = 38f; - - /* special situations */ - - /* - * cycleway:left/right=lane/track/shared_lane permutations - no longer needed because left/right matching algorithm does this - */ - - /* cycleway:left=lane */ - /* cycleway:right=track */ - /* cycleway:left=track */ - /* cycleway:right=shared_lane */ - /* cycleway:left=shared_lane */ - /* cycleway:right=lane, cycleway:left=track */ - /* cycleway:right=lane, cycleway:left=shared_lane */ - /* cycleway:right=track, cycleway:left=lane */ - /* cycleway:right=track, cycleway:left=shared_lane */ - /* cycleway:right=shared_lane, cycleway:left=lane */ - /* cycleway:right=shared_lane, cycleway:left=track */ - - /* surface=* mixins */ - - /* - * The following tags have been removed from surface weights because they are no more of an impedence to bicycling than a paved surface - * surface=paving_stones surface=fine_gravel (sounds counter-intuitive but see the definition on the OSM Wiki) surface=tartan (this what - * running tracks are usually made of) - */ - - props.setMixinProperties("surface=unpaved", ofBicycleSafety(1.18)); - props.setMixinProperties("surface=compacted", ofBicycleSafety(1.18)); - props.setMixinProperties("surface=wood", ofBicycleSafety(1.18)); - - props.setMixinProperties("surface=cobblestone", ofBicycleSafety(1.3)); - props.setMixinProperties("surface=sett", ofBicycleSafety(1.3)); - props.setMixinProperties("surface=unhewn_cobblestone", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=grass_paver", ofBicycleSafety(1.3)); - props.setMixinProperties("surface=pebblestone", ofBicycleSafety(1.3)); - // Can be slick if wet, but otherwise not unfavorable to bikes - props.setMixinProperties("surface=metal", ofBicycleSafety(1.3)); - props.setMixinProperties("surface=ground", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=dirt", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=earth", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=grass", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=mud", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=woodchip", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=gravel", ofBicycleSafety(1.5)); - props.setMixinProperties("surface=artifical_turf", ofBicycleSafety(1.5)); - - /* sand is deadly for bikes */ - props.setMixinProperties("surface=sand", ofBicycleSafety(100)); - - /* Portland-local mixins */ - - props.setMixinProperties("foot=discouraged", ofWalkSafety(3)); - props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3)); - - props.setMixinProperties("foot=use_sidepath", ofWalkSafety(5)); - props.setMixinProperties("bicycle=use_sidepath", ofBicycleSafety(5)); - - populateNotesAndNames(props); - - // slope overrides - props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true); - props.setSlopeOverride(new BestMatchSpecifier("embankment=*"), true); - props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true); - props.setSlopeOverride(new BestMatchSpecifier("tunnel=*"), true); - props.setSlopeOverride(new BestMatchSpecifier("location=underground"), true); - props.setSlopeOverride(new BestMatchSpecifier("indoor=yes"), true); - } - - public void populateNotesAndNames(WayPropertySet props) { - /* and the notes */ - // TODO: The curly brackets in the string below mean that the CreativeNamer should substitute in OSM tag values. - // However they are not taken into account when passed to the translation function. - // props.createNotes("wheelchair:description=*", "{wheelchair:description}", StreetNotesService.WHEELCHAIR_MATCHER); - // TODO: The two entries below produce lots of spurious notes (because of OSM mapper comments) - // props.createNotes("note=*", "{note}", StreetNotesService.ALWAYS_MATCHER); - // props.createNotes("notes=*", "{notes}", StreetNotesService.ALWAYS_MATCHER); - props.createNotes( - "RLIS:bicycle=caution_area", - "note.caution", - StreetNotesService.BICYCLE_MATCHER - ); - props.createNotes( - "CCGIS:bicycle=caution_area", - "note.caution", - StreetNotesService.BICYCLE_MATCHER - ); - // TODO: Maybe we should apply the following notes only for car/bike - props.createNotes("surface=unpaved", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes( - "surface=compacted", - "note.unpaved_surface", - StreetNotesService.ALWAYS_MATCHER - ); - props.createNotes("surface=ground", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes("surface=dirt", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes("surface=earth", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes("surface=grass", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes("surface=mud", "note.muddy_surface", StreetNotesService.ALWAYS_MATCHER); - props.createNotes("toll=yes", "note.toll", StreetNotesService.DRIVING_MATCHER); - props.createNotes("toll:motorcar=yes", "note.toll", StreetNotesService.DRIVING_MATCHER); - - /* and some names */ - // Basics - props.createNames("highway=cycleway", "name.bike_path"); - props.createNames("cycleway=track", "name.bike_path"); - props.createNames("highway=pedestrian", "name.pedestrian_path"); - props.createNames("highway=pedestrian;area=yes", "name.pedestrian_area"); - props.createNames("highway=path", "name.path"); - props.createNames("highway=footway", "name.pedestrian_path"); - props.createNames("highway=bridleway", "name.bridleway"); - props.createNames("highway=footway;bicycle=no", "name.pedestrian_path"); - - // Platforms - props.createNames("otp:route_ref=*", "name.otp_route_ref"); - props.createNames("highway=platform;ref=*", "name.platform_ref"); - props.createNames("railway=platform;ref=*", "name.platform_ref"); - props.createNames("railway=platform;highway=footway;footway=sidewalk", "name.platform"); - props.createNames("railway=platform;highway=path;path=sidewalk", "name.platform"); - props.createNames("railway=platform;highway=pedestrian", "name.platform"); - props.createNames("railway=platform;highway=path", "name.platform"); - props.createNames("railway=platform;highway=footway", "name.platform"); - props.createNames("highway=platform", "name.platform"); - props.createNames("railway=platform", "name.platform"); - props.createNames("railway=platform;highway=footway;bicycle=no", "name.platform"); - - // Bridges/Tunnels - props.createNames("highway=pedestrian;bridge=*", "name.footbridge"); - props.createNames("highway=path;bridge=*", "name.footbridge"); - props.createNames("highway=footway;bridge=*", "name.footbridge"); - - props.createNames("highway=pedestrian;tunnel=*", "name.underpass"); - props.createNames("highway=path;tunnel=*", "name.underpass"); - props.createNames("highway=footway;tunnel=*", "name.underpass"); - - // Basic Mappings - props.createNames("highway=motorway", "name.road"); - props.createNames("highway=motorway_link", "name.ramp"); - props.createNames("highway=trunk", "name.road"); - props.createNames("highway=trunk_link", "name.ramp"); - - props.createNames("highway=primary", "name.road"); - props.createNames("highway=primary_link", "name.link"); - props.createNames("highway=secondary", "name.road"); - props.createNames("highway=secondary_link", "name.link"); - props.createNames("highway=tertiary", "name.road"); - props.createNames("highway=tertiary_link", "name.link"); - props.createNames("highway=unclassified", "name.road"); - props.createNames("highway=residential", "name.road"); - props.createNames("highway=living_street", "name.road"); - props.createNames("highway=road", "name.road"); - props.createNames("highway=service", "name.service_road"); - props.createNames("highway=service;service=alley", "name.alley"); - props.createNames("highway=service;service=parking_aisle", "name.parking_aisle"); - props.createNames("highway=byway", "name.byway"); - props.createNames("highway=track", "name.track"); - - props.createNames("highway=footway;footway=sidewalk", "name.sidewalk"); - props.createNames("highway=path;path=sidewalk", "name.sidewalk"); - - props.createNames("highway=steps", "name.steps"); - - props.createNames("amenity=bicycle_parking;name=*", "name.bicycle_parking_name"); - props.createNames("amenity=bicycle_parking", "name.bicycle_parking"); - - props.createNames("amenity=parking;name=*", "name.park_and_ride_name"); - props.createNames("amenity=parking", "name.park_and_ride_station"); - } -} From 94403bffcb03f398bf2fe6524620beb294b8827c Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 26 Sep 2024 16:39:24 +0200 Subject: [PATCH 28/86] Fix max-stop limit in StreetNearbyStopFinder --- .../strategy/MaxCountSkipEdgeStrategy.java | 34 --------- .../strategy/MaxCountTerminationStrategy.java | 36 ++++++++++ .../nearbystops/StreetNearbyStopFinder.java | 41 +++++------ .../MaxCountSkipEdgeStrategyTest.java | 38 ---------- .../MaxCountTerminationStrategyTest.java | 29 ++++++++ ...reetNearbyStopFinderMultipleLinksTest.java | 71 +++++++++++++++++++ .../StreetNearbyStopFinderTest.java | 8 +-- 7 files changed, 156 insertions(+), 101 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java create mode 100644 application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java delete mode 100644 application/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java create mode 100644 application/src/test/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategyTest.java create mode 100644 application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java diff --git a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java deleted file mode 100644 index 0369e3e29db..00000000000 --- a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.opentripplanner.astar.strategy; - -import java.util.function.Predicate; -import org.opentripplanner.astar.spi.AStarEdge; -import org.opentripplanner.astar.spi.AStarState; -import org.opentripplanner.astar.spi.SkipEdgeStrategy; - -/** - * Skips edges when the specified number of desired vertices have been visited. - */ -public class MaxCountSkipEdgeStrategy< - State extends AStarState, Edge extends AStarEdge -> - implements SkipEdgeStrategy { - - private final int maxCount; - private final Predicate shouldIncreaseCount; - - private int visited; - - public MaxCountSkipEdgeStrategy(int count, Predicate shouldIncreaseCount) { - this.maxCount = count; - this.shouldIncreaseCount = shouldIncreaseCount; - this.visited = 0; - } - - @Override - public boolean shouldSkipEdge(State current, Edge edge) { - if (shouldIncreaseCount.test(current)) { - visited++; - } - return visited > maxCount; - } -} diff --git a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java new file mode 100644 index 00000000000..66c5496c923 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java @@ -0,0 +1,36 @@ +package org.opentripplanner.astar.strategy; + +import java.util.function.Predicate; +import org.opentripplanner.astar.spi.AStarState; +import org.opentripplanner.astar.spi.SearchTerminationStrategy; + +/** + * This termination strategy is used to terminate an a-star search after a number of states matching + * some criteria has been found. For example it can be used to limit a search to a maximum number of + * stops. + */ +public class MaxCountTerminationStrategy> + implements SearchTerminationStrategy { + + private final int maxCount; + private final Predicate shouldIncreaseCount; + private int count; + + /** + * @param maxCount Terminate the search after this many matching states have been reached. + * @param shouldIncreaseCount A predicate to check if a state should increase the count or not. + */ + public MaxCountTerminationStrategy(int maxCount, Predicate shouldIncreaseCount) { + this.maxCount = maxCount; + this.shouldIncreaseCount = shouldIncreaseCount; + this.count = 0; + } + + @Override + public boolean shouldSearchTerminate(State current) { + if (shouldIncreaseCount.test(current)) { + count++; + } + return count >= maxCount; + } +} diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java index e54c27249e1..8277bd47e4c 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java @@ -11,10 +11,8 @@ import java.util.List; import java.util.Set; import org.opentripplanner.astar.model.ShortestPathTree; -import org.opentripplanner.astar.spi.SkipEdgeStrategy; -import org.opentripplanner.astar.strategy.ComposingSkipEdgeStrategy; import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy; -import org.opentripplanner.astar.strategy.MaxCountSkipEdgeStrategy; +import org.opentripplanner.astar.strategy.MaxCountTerminationStrategy; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.application.OTPRequestTimeoutException; @@ -30,8 +28,6 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.StreetSearchBuilder; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.street.search.request.StreetSearchRequest; -import org.opentripplanner.street.search.request.StreetSearchRequestMapper; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.transit.model.site.AreaStop; @@ -46,8 +42,8 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { /** * Construct a NearbyStopFinder for the given graph and search radius. * - * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount - * we will always return all the directly connected stops. + * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the + * maxStopCount we will always return all the directly connected stops. */ public StreetNearbyStopFinder( Duration durationLimit, @@ -117,23 +113,30 @@ public Collection findNearbyStops( // Return only the origin vertices if there are no valid street modes if ( streetRequest.mode() == StreetMode.NOT_SET || - (maxStopCount != 0 && stopsFound.size() >= maxStopCount) + (maxStopCount > 0 && stopsFound.size() >= maxStopCount) ) { return stopsFound; } stopsFound = new ArrayList<>(stopsFound); - ShortestPathTree spt = StreetSearchBuilder + var streetSearch = StreetSearchBuilder .of() - .setSkipEdgeStrategy(getSkipEdgeStrategy()) + .setSkipEdgeStrategy(new DurationSkipEdgeStrategy<>(durationLimit)) .setDominanceFunction(new DominanceFunctions.MinimumWeight()) .setRequest(request) .setArriveBy(reverseDirection) .setStreetRequest(streetRequest) .setFrom(reverseDirection ? null : originVertices) .setTo(reverseDirection ? originVertices : null) - .setDataOverlayContext(dataOverlayContext) - .getShortestPathTree(); + .setDataOverlayContext(dataOverlayContext); + + if (maxStopCount > 0) { + streetSearch.setTerminationStrategy( + new MaxCountTerminationStrategy<>(maxStopCount, this::hasReachedStop) + ); + } + + ShortestPathTree spt = streetSearch.getShortestPathTree(); // Only used if OTPFeature.FlexRouting.isOn() Multimap locationsMap = ArrayListMultimap.create(); @@ -186,16 +189,6 @@ public Collection findNearbyStops( return stopsFound; } - private SkipEdgeStrategy getSkipEdgeStrategy() { - var durationSkipEdgeStrategy = new DurationSkipEdgeStrategy(durationLimit); - - if (maxStopCount > 0) { - var strategy = new MaxCountSkipEdgeStrategy<>(maxStopCount, this::hasReachedStop); - return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy); - } - return durationSkipEdgeStrategy; - } - private boolean canBoardFlex(State state, boolean reverse) { Collection edges = reverse ? state.getVertex().getIncoming() @@ -212,13 +205,13 @@ private boolean canBoardFlex(State state, boolean reverse) { *

* This is important because there can be cases where states that cannot actually board the vehicle * can dominate those that can thereby leading to zero found stops when this predicate is used with - * the {@link MaxCountSkipEdgeStrategy}. + * the {@link MaxCountTerminationStrategy}. *

* An example of this would be an egress/reverse search with a very high walk reluctance where the * states that speculatively rent a vehicle move the walk states down the A* priority queue until * the required number of stops are reached to abort the search, leading to zero egress results. */ - public boolean hasReachedStop(State state) { + private boolean hasReachedStop(State state) { var vertex = state.getVertex(); return ( vertex instanceof TransitStopVertex && state.isFinal() && !ignoreVertices.contains(vertex) diff --git a/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java b/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java deleted file mode 100644 index c190ff1abac..00000000000 --- a/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategyTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.opentripplanner.astar.strategy; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; -import org.opentripplanner.street.search.state.TestStateBuilder; - -class MaxCountSkipEdgeStrategyTest { - - private final StreetNearbyStopFinder finder = new StreetNearbyStopFinder(null, 0, null); - - @Test - void countStops() { - var state = TestStateBuilder.ofWalking().stop().build(); - var strategy = new MaxCountSkipEdgeStrategy<>(1, finder::hasReachedStop); - assertFalse(strategy.shouldSkipEdge(state, null)); - assertTrue(strategy.shouldSkipEdge(state, null)); - } - - @Test - void doNotCountStop() { - var state = TestStateBuilder.ofWalking().build(); - var strategy = new MaxCountSkipEdgeStrategy<>(1, finder::hasReachedStop); - assertFalse(strategy.shouldSkipEdge(state, null)); - assertFalse(strategy.shouldSkipEdge(state, null)); - assertFalse(strategy.shouldSkipEdge(state, null)); - } - - @Test - void nonFinalState() { - var state = TestStateBuilder.ofScooterRentalArriveBy().stop().build(); - assertFalse(state.isFinal()); - var strategy = new MaxCountSkipEdgeStrategy<>(1, finder::hasReachedStop); - assertFalse(strategy.shouldSkipEdge(state, null)); - } -} diff --git a/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategyTest.java b/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategyTest.java new file mode 100644 index 00000000000..50a920f1252 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategyTest.java @@ -0,0 +1,29 @@ +package org.opentripplanner.astar.strategy; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class MaxCountTerminationStrategyTest { + + @Test + void countStates() { + var countAllStatesStrategy = new MaxCountTerminationStrategy<>(3, state -> true); + + assertFalse(countAllStatesStrategy.shouldSearchTerminate(null)); + assertFalse(countAllStatesStrategy.shouldSearchTerminate(null)); + assertTrue(countAllStatesStrategy.shouldSearchTerminate(null)); + assertTrue(countAllStatesStrategy.shouldSearchTerminate(null)); + } + + @Test + void countNoStates() { + var countNoStatesStrategy = new MaxCountTerminationStrategy<>(1, state -> false); + + assertFalse(countNoStatesStrategy.shouldSearchTerminate(null)); + assertFalse(countNoStatesStrategy.shouldSearchTerminate(null)); + assertFalse(countNoStatesStrategy.shouldSearchTerminate(null)); + assertFalse(countNoStatesStrategy.shouldSearchTerminate(null)); + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java new file mode 100644 index 00000000000..3466c1bfd8a --- /dev/null +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java @@ -0,0 +1,71 @@ +package org.opentripplanner.graph_builder.module.nearbystops; + +import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinderTest.assertStopAtDistance; +import static org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinderTest.assertZeroDistanceStop; +import static org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinderTest.sort; + +import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.routing.algorithm.GraphRoutingTest; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.street.model.vertex.TransitStopVertex; + +class StreetNearbyStopFinderMultipleLinksTest extends GraphRoutingTest { + + private static final WgsCoordinate origin = new WgsCoordinate(0.0, 0.0); + private TransitStopVertex stopA; + private TransitStopVertex stopB; + private TransitStopVertex stopC; + + @BeforeEach + protected void setUp() throws Exception { + modelOf( + new Builder() { + @Override + public void build() { + var A = intersection("A", origin); + var B = intersection("B", origin.moveEastMeters(100)); + var C = intersection("C", origin.moveEastMeters(200)); + + biStreet(A, B, 100); + biStreet(B, C, 100); + + stopA = stop("StopA", A.toWgsCoordinate()); + stopB = stop("StopB", B.toWgsCoordinate()); + stopC = stop("StopC", C.toWgsCoordinate()); + + biLink(A, stopA); + + // B has many links + biLink(B, stopB); + biLink(B, stopB); + biLink(B, stopB); + biLink(B, stopB); + + biLink(C, stopC); + } + } + ); + } + + @Test + void testMaxStopCountRegression() { + // Max-stop-count should work correctly even though there are multiple links B <-> stopB + var durationLimit = Duration.ofMinutes(10); + var maxStopCount = 3; + var finder = new StreetNearbyStopFinder(durationLimit, maxStopCount, null); + + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(3); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); + assertStopAtDistance(stopC, 200, sortedNearbyStops.get(2)); + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java index 261a40454f0..aae02451b33 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java @@ -99,7 +99,6 @@ void testMultipleStops() { } @Test - @Disabled("Currently disabled because of a bug in stop counting") void testMaxStopCount() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 2; @@ -150,7 +149,6 @@ void testIgnoreStops() { } @Test - @Disabled("Currently disabled because of a bug in stop counting") void testIgnoreStopsWithMaxStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 1; @@ -165,14 +163,14 @@ void testIgnoreStopsWithMaxStops() { assertStopAtDistance(stopC, 200, sortedNearbyStops.get(0)); } - private List sort(Collection stops) { + static List sort(Collection stops) { return stops.stream().sorted(Comparator.comparing(x -> x.distance)).toList(); } /** * Verify that the nearby stop is zero distance and corresponds to the expected vertex */ - private void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { + static void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { assertEquals(expected.getStop(), nearbyStop.stop); assertEquals(0, nearbyStop.distance); assertEquals(0, nearbyStop.edges.size()); @@ -183,7 +181,7 @@ private void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearb /** * Verify that the nearby stop is at a specific distance and corresponds to the expected vertex */ - private void assertStopAtDistance( + static void assertStopAtDistance( TransitStopVertex expected, double expectedDistance, NearbyStop nearbyStop From c6e74f3c007ea8eae39733cb6425eb411423c502 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 16 Oct 2024 09:08:14 +0300 Subject: [PATCH 29/86] Add unit tests for area permissions of Finland mapper --- .../osm/tagmapping/FinlandMapperTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/application/src/test/java/org/opentripplanner/osm/tagmapping/FinlandMapperTest.java b/application/src/test/java/org/opentripplanner/osm/tagmapping/FinlandMapperTest.java index e3f24f64c6c..8e931e1096a 100644 --- a/application/src/test/java/org/opentripplanner/osm/tagmapping/FinlandMapperTest.java +++ b/application/src/test/java/org/opentripplanner/osm/tagmapping/FinlandMapperTest.java @@ -2,6 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE; import org.junit.jupiter.api.Test; import org.opentripplanner.osm.model.OsmWay; @@ -194,4 +196,28 @@ public void testTagMapping() { wayData = wps.getDataForWay(way); assertEquals(wayData.getPermission(), NONE); } + + /** + * Test that biking is not allowed in footway areas and transit platforms + */ + @Test + public void testArea() { + OsmWithTags way; + WayProperties wayData; + + way = new OsmWay(); + way.addTag("highway", "footway"); + way.addTag("area", "yes"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), PEDESTRIAN); + + way = new OsmWay(); + way.addTag("public_transport", "platform"); + way.addTag("area", "yes"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), PEDESTRIAN); + way.addTag("bicycle", "yes"); + wayData = wps.getDataForWay(way); + assertEquals(wayData.getPermission(), PEDESTRIAN_AND_BICYCLE); + } } From c5bb551ad8d3b4f348f44b9fe5b3edd07f5bd234 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 16 Oct 2024 09:36:55 +0300 Subject: [PATCH 30/86] Test biking on platforms in Germany --- .../osm/tagmapping/GermanyMapperTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/application/src/test/java/org/opentripplanner/osm/tagmapping/GermanyMapperTest.java b/application/src/test/java/org/opentripplanner/osm/tagmapping/GermanyMapperTest.java index 28f482a3388..c0901f4fcf5 100644 --- a/application/src/test/java/org/opentripplanner/osm/tagmapping/GermanyMapperTest.java +++ b/application/src/test/java/org/opentripplanner/osm/tagmapping/GermanyMapperTest.java @@ -1,6 +1,8 @@ package org.opentripplanner.osm.tagmapping; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; +import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -279,4 +281,19 @@ void testGermanAutobahnSpeed() { autobahn.addTag("maxspeed", "none"); assertEquals(33.33000183105469, wps.getCarSpeedForWay(autobahn, false), epsilon); } + + /** + * Test that biking is not allowed in transit platforms + */ + @Test + public void testArea() { + OsmWithTags way; + + way = new OsmWithTags(); + way.addTag("public_transport", "platform"); + way.addTag("area", "yes"); + assertEquals(wps.getDataForWay(way).getPermission(), PEDESTRIAN); + way.addTag("bicycle", "yes"); + assertEquals(wps.getDataForWay(way).getPermission(), PEDESTRIAN_AND_BICYCLE); + } } From a247756f7bd9793999e391a91d8a2283b0903055 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 16 Oct 2024 10:55:12 +0300 Subject: [PATCH 31/86] Remove unused logger --- .../graph_builder/module/osm/WalkableAreaBuilder.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 81f59287939..92c4cf3dad1 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -49,8 +49,6 @@ import org.opentripplanner.street.search.StreetSearchBuilder; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Theoretically, it is not correct to build the visibility graph on the joined polygon of areas @@ -71,9 +69,6 @@ * to an excessive number of edges, or to no edges at all if maxAreaNodes is surpassed. */ class WalkableAreaBuilder { - - private static final Logger LOG = LoggerFactory.getLogger(WalkableAreaBuilder.class); - private final DataImportIssueStore issueStore; private final int maxAreaNodes; From 03ef955b5d2149a4b4400eed84c687d31a9074b9 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 16 Oct 2024 11:43:44 +0300 Subject: [PATCH 32/86] Fix formatting --- .../graph_builder/module/osm/WalkableAreaBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java index 92c4cf3dad1..2802ee70a89 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java @@ -69,6 +69,7 @@ * to an excessive number of edges, or to no edges at all if maxAreaNodes is surpassed. */ class WalkableAreaBuilder { + private final DataImportIssueStore issueStore; private final int maxAreaNodes; From 338b1efeb00ee7d826fefc5ec3712dd07a15f9b5 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 23 Sep 2024 14:59:44 +0100 Subject: [PATCH 33/86] fix GTFS coach service --- .../opentripplanner/gtfs/mapping/TransitModeMapper.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java index 608ff6ba2d3..203dd5693e3 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java @@ -19,7 +19,7 @@ public static TransitMode mapMode(int routeType) { if (routeType >= 100 && routeType < 200) { // Railway Service return TransitMode.RAIL; } else if (routeType >= 200 && routeType < 300) { //Coach Service - return TransitMode.BUS; + return TransitMode.COACH; } else if (routeType >= 300 && routeType < 500) { //Suburban Railway Service and Urban Railway service if (routeType >= 401 && routeType <= 402) { return TransitMode.SUBWAY; @@ -30,11 +30,10 @@ public static TransitMode mapMode(int routeType) { return TransitMode.RAIL; } else if (routeType >= 500 && routeType < 700) { //Metro Service and Underground Service return TransitMode.SUBWAY; - } else if (routeType >= 700 && routeType < 900) { //Bus Service and Trolleybus service - if (routeType == 800) { - return TransitMode.TROLLEYBUS; - } + } else if (routeType >= 700 && routeType < 800) { //Bus Service return TransitMode.BUS; + } else if (routeType >= 800 && routeType < 900) { //Trolleybus Service + return TransitMode.TROLLEYBUS; } else if (routeType >= 900 && routeType < 1000) { //Tram service return TransitMode.TRAM; } else if (routeType >= 1000 && routeType < 1100) { //Water Transport Service From f4244a5d17327de8b125ca835375f89edcff1070 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 17 Oct 2024 12:26:14 +0100 Subject: [PATCH 34/86] add a switch to configure bus / coach for 200 - 299 route types --- .../framework/application/OTPFeature.java | 8 ++++++++ .../gtfs/mapping/TransitModeMapper.java | 10 ++++++---- .../gtfs/mapping/TransitModeMapperTest.java | 16 ++++++++++++++++ doc/user/Configuration.md | 1 + 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index 324f5397673..c8816981974 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,6 +38,14 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), FloatingBike(true, false, "Enable floating bike routing."), + GtfsCoach( + false, + false, + """ + When parsing GTFS data, treat GTFS route type 200 to 299 as coach routes instead of bus routes. + When using the GTFS GraphQL API, do not return coach routes when only BUS is specified as a mode. + """ + ), GtfsGraphQlApi(true, false, "Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md)."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java index 203dd5693e3..a96c42a5c12 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java @@ -1,5 +1,6 @@ package org.opentripplanner.gtfs.mapping; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; public class TransitModeMapper { @@ -19,7 +20,7 @@ public static TransitMode mapMode(int routeType) { if (routeType >= 100 && routeType < 200) { // Railway Service return TransitMode.RAIL; } else if (routeType >= 200 && routeType < 300) { //Coach Service - return TransitMode.COACH; + return OTPFeature.GtfsCoach.isOn() ? TransitMode.COACH : TransitMode.BUS; } else if (routeType >= 300 && routeType < 500) { //Suburban Railway Service and Urban Railway service if (routeType >= 401 && routeType <= 402) { return TransitMode.SUBWAY; @@ -30,10 +31,11 @@ public static TransitMode mapMode(int routeType) { return TransitMode.RAIL; } else if (routeType >= 500 && routeType < 700) { //Metro Service and Underground Service return TransitMode.SUBWAY; - } else if (routeType >= 700 && routeType < 800) { //Bus Service + } else if (routeType >= 700 && routeType < 900) { //Bus Service and Trolleybus service + if (routeType == 800) { + return TransitMode.TROLLEYBUS; + } return TransitMode.BUS; - } else if (routeType >= 800 && routeType < 900) { //Trolleybus Service - return TransitMode.TROLLEYBUS; } else if (routeType >= 900 && routeType < 1000) { //Tram service return TransitMode.TRAM; } else if (routeType >= 1000 && routeType < 1100) { //Water Transport Service diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java index b2e5b1a8a4d..f99dd8cd773 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java @@ -1,13 +1,17 @@ package org.opentripplanner.gtfs.mapping; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.CARPOOL; +import static org.opentripplanner.transit.model.basic.TransitMode.COACH; import static org.opentripplanner.transit.model.basic.TransitMode.TAXI; import java.util.stream.Stream; +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.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; class TransitModeMapperTest { @@ -29,4 +33,16 @@ static Stream testCases() { void map(int mode, TransitMode expectedMode) { assertEquals(expectedMode, TransitModeMapper.mapMode(mode)); } + + @Test + void testCoachFeatureFlag() { + OTPFeature.GtfsCoach.testOff(() -> { + assertEquals(BUS, TransitModeMapper.mapMode(200)); + assertEquals(BUS, TransitModeMapper.mapMode(299)); + }); + OTPFeature.GtfsCoach.testOn(() -> { + assertEquals(COACH, TransitModeMapper.mapMode(200)); + assertEquals(COACH, TransitModeMapper.mapMode(299)); + }); + } } diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index bca974f8617..1ae152d331c 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,6 +228,7 @@ Here is a list of all features which can be toggled on/off and their default val | `DebugUi` | Enable the debug GraphQL client and web UI and located at the root of the web server as well as the debug map tiles it uses. Be aware that the map tiles are not a stable API and can change without notice. Use the [vector tiles feature if](sandbox/MapboxVectorTilesApi.md) you want a stable map tiles API. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | +| `GtfsCoach` | When parsing GTFS data, treat GTFS route type 200 to 299 as coach routes instead of bus routes. When using the GTFS GraphQL API, do not return coach routes when only BUS is specified as a mode. | | | | `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | | `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | From 5cee1405fe49b3bf2cf80c71d7039896452e84a7 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 17 Oct 2024 13:04:39 +0100 Subject: [PATCH 35/86] separate bus and coach API --- .../api/parameter/ApiRequestMode.java | 8 +++++- .../LegacyRouteRequestMapperTest.java | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java index 6e19b106033..c78e5dfb66f 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.List; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; public enum ApiRequestMode { @@ -12,7 +13,8 @@ public enum ApiRequestMode { TRAM(TransitMode.TRAM), SUBWAY(TransitMode.SUBWAY), RAIL(TransitMode.RAIL), - BUS(TransitMode.BUS, TransitMode.COACH), + BUS(TransitMode.BUS), + COACH(TransitMode.COACH), FERRY(TransitMode.FERRY), CABLE_CAR(TransitMode.CABLE_CAR), GONDOLA(TransitMode.GONDOLA), @@ -40,6 +42,10 @@ public enum ApiRequestMode { } public Collection getTransitModes() { + if (this == BUS && OTPFeature.GtfsCoach.isOff()) { + return List.of(TransitMode.BUS, TransitMode.COACH); + } + return transitModes; } } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 3ca29f7c531..b42ea828462 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -27,6 +27,7 @@ import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.ext.fares.impl.DefaultFareService; +import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; @@ -144,9 +145,36 @@ static Stream transportModesCases() { ); } + static Stream transportModesCasesWithCoach() { + return Stream.of( + of( + List.of(mode("BUS")), + "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS]}]}]" + ), + of( + List.of(mode("BUS"), mode("COACH")), + "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH]}]}]" + ), + of( + List.of(mode("BUS"), mode("MONORAIL")), + "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, MONORAIL]}]}]" + ) + ); + } + @ParameterizedTest @MethodSource("transportModesCases") void modes(List> modes, String expectedFilters) { + OTPFeature.GtfsCoach.testOff(() -> testModes(modes, expectedFilters)); + } + + @ParameterizedTest + @MethodSource("transportModesCasesWithCoach") + void modesWithCoach(List> modes, String expectedFilters) { + OTPFeature.GtfsCoach.testOn(() -> testModes(modes, expectedFilters)); + } + + private void testModes(List> modes, String expectedFilters) { Map arguments = Map.of("transportModes", modes); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( From 336fb13e956e1eef5a40b75e9bba4cd026fc1a69 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Fri, 11 Oct 2024 13:14:13 +0200 Subject: [PATCH 36/86] vector tiles: add no-thru traffic layers + group no-thru traffic and permission layers by mode --- .../apis/vectortiles/DebugStyleSpec.java | 102 ++- .../apis/vectortiles/model/StyleBuilder.java | 38 +- .../vector/edge/EdgePropertyMapper.java | 28 +- .../apis/vectortiles/style.json | 579 +++++++++++------- 4 files changed, 490 insertions(+), 257 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index 1fc87176af8..beb8a087866 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -1,5 +1,7 @@ package org.opentripplanner.apis.vectortiles; +import static org.opentripplanner.inspector.vector.edge.EdgePropertyMapper.streetPermissionAsString; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -13,6 +15,7 @@ import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; +import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.AreaEdge; import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; @@ -30,8 +33,8 @@ import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; /** - * A Mapbox/Mapblibre style specification for rendering debug information about transit and - * street data. + * A Mapbox/Mapblibre style specification for rendering debug information about transit and street + * data. */ public class DebugStyleSpec { @@ -47,13 +50,18 @@ public class DebugStyleSpec { private static final String DARK_GREEN = "#136b04"; private static final String PURPLE = "#BC55F2"; private static final String BLACK = "#140d0e"; + private static final String GRAY = "#DDDDDD"; private static final int MAX_ZOOM = 23; + private static final int LINE_DETAIL_ZOOM = 13; private static final ZoomDependentNumber LINE_OFFSET = new ZoomDependentNumber( - List.of(new ZoomStop(13, 0.3f), new ZoomStop(MAX_ZOOM, 6)) + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.4f), new ZoomStop(MAX_ZOOM, 7)) ); private static final ZoomDependentNumber LINE_WIDTH = new ZoomDependentNumber( - List.of(new ZoomStop(13, 0.2f), new ZoomStop(MAX_ZOOM, 8)) + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.2f), new ZoomStop(MAX_ZOOM, 8)) + ); + private static final ZoomDependentNumber LINE_HALF_WIDTH = new ZoomDependentNumber( + List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.1f), new ZoomStop(MAX_ZOOM, 6)) ); private static final ZoomDependentNumber CIRCLE_STROKE = new ZoomDependentNumber( List.of(new ZoomStop(15, 0.2f), new ZoomStop(MAX_ZOOM, 3)) @@ -70,7 +78,14 @@ public class DebugStyleSpec { private static final String EDGES_GROUP = "Edges"; private static final String STOPS_GROUP = "Stops"; private static final String VERTICES_GROUP = "Vertices"; - private static final String TRAVERSAL_PERMISSIONS_GROUP = "Traversal permissions"; + private static final String PERMISSIONS_GROUP = "Permissions"; + private static final String NO_THRU_TRAFFIC_GROUP = "No-thru traffic"; + + private static final StreetTraversalPermission[] streetModes = new StreetTraversalPermission[] { + StreetTraversalPermission.PEDESTRIAN, + StreetTraversalPermission.BICYCLE, + StreetTraversalPermission.CAR, + }; static StyleSpec build( VectorSourceLayer regularStops, @@ -90,8 +105,9 @@ static StyleSpec build( allSources, ListUtils.combine( List.of(StyleBuilder.ofId("background").typeRaster().source(BACKGROUND_SOURCE).minZoom(0)), - traversalPermissions(edges), edges(edges), + traversalPermissions(edges), + noThruTraffic(edges), vertices(vertices), stops(regularStops, areaStops, groupStops) ) @@ -181,9 +197,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(MAGENTA) - .edgeFilter(EDGES_TO_DISPLAY) - .lineWidth(LINE_WIDTH) + .lineColor(GRAY) + .lineWidth(LINE_HALF_WIDTH) .lineOffset(LINE_OFFSET) .minZoom(6) .maxZoom(MAX_ZOOM) @@ -194,7 +209,6 @@ private static List edges(VectorSourceLayer edges) { .typeSymbol() .lineText("name") .vectorSourceLayer(edges) - .edgeFilter(EDGES_TO_DISPLAY) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(), @@ -203,7 +217,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(BRIGHT_GREEN) + .lineColor(GRAY) + .lineOpacity(0.2f) .edgeFilter( StreetTransitStopLink.class, StreetTransitEntranceLink.class, @@ -222,26 +237,28 @@ private static List edges(VectorSourceLayer edges) { private static List traversalPermissions(VectorSourceLayer edges) { var permissionStyles = Arrays - .stream(StreetTraversalPermission.values()) - .map(p -> + .stream(streetModes) + .map(streetTraversalPermission -> StyleBuilder - .ofId(p.name()) + .ofId("permission " + streetTraversalPermission) .vectorSourceLayer(edges) - .group(TRAVERSAL_PERMISSIONS_GROUP) + .group(PERMISSIONS_GROUP) .typeLine() - .lineColor(permissionColor(p)) - .permissionsFilter(p) + .filterValueInProperty(streetTraversalPermission.name(), "permission") + .lineCap("butt") + .lineColorMatch("permission", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) .lineOffset(LINE_OFFSET) - .minZoom(6) + .minZoom(LINE_DETAIL_ZOOM) .maxZoom(MAX_ZOOM) .intiallyHidden() ) .toList(); + var textStyle = StyleBuilder .ofId("permission-text") .vectorSourceLayer(edges) - .group(TRAVERSAL_PERMISSIONS_GROUP) + .group(PERMISSIONS_GROUP) .typeSymbol() .lineText("permission") .textOffset(1) @@ -249,12 +266,55 @@ private static List traversalPermissions(VectorSourceLayer edges) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(); + return ListUtils.combine(permissionStyles, List.of(textStyle)); } - private static String permissionColor(StreetTraversalPermission p) { + private static List noThruTraffic(VectorSourceLayer edges) { + var noThruTrafficStyles = Arrays + .stream(streetModes) + .map(streetTraversalPermission -> + StyleBuilder + .ofId("no-thru-traffic " + streetTraversalPermission) + .vectorSourceLayer(edges) + .group(NO_THRU_TRAFFIC_GROUP) + .typeLine() + .filterValueInProperty(streetTraversalPermission.name(), "noThruTraffic") + .lineCap("butt") + .lineColorMatch("noThruTraffic", permissionColors(), BLACK) + .lineWidth(LINE_WIDTH) + .lineOffset(LINE_OFFSET) + .minZoom(LINE_DETAIL_ZOOM) + .maxZoom(MAX_ZOOM) + .intiallyHidden() + ) + .toList(); + + var textStyle = StyleBuilder + .ofId("no-thru-traffic-text") + .vectorSourceLayer(edges) + .group(NO_THRU_TRAFFIC_GROUP) + .typeSymbol() + .lineText("noThruTraffic") + .textOffset(1) + .edgeFilter(EDGES_TO_DISPLAY) + .minZoom(17) + .maxZoom(MAX_ZOOM) + .intiallyHidden(); + + return ListUtils.combine(noThruTrafficStyles, List.of(textStyle)); + } + + private static List permissionColors() { + return Arrays + .stream(StreetTraversalPermission.values()) + .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColors(p))) + .toList(); + } + + private static String permissionColors(StreetTraversalPermission p) { return switch (p) { - case NONE -> "#000"; + case NONE -> BLACK; case PEDESTRIAN -> "#2ba812"; case BICYCLE, PEDESTRIAN_AND_BICYCLE -> "#10d3b6"; case CAR -> "#f92e13"; diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index 93b7ea91e7c..316fa56d4eb 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -29,7 +30,7 @@ public class StyleBuilder { private final Map layout = new LinkedHashMap<>(); private final Map metadata = new LinkedHashMap<>(); private final Map line = new LinkedHashMap<>(); - private List filter = List.of(); + private List filter = List.of(); public static StyleBuilder ofId(String id) { return new StyleBuilder(id); @@ -167,12 +168,42 @@ public StyleBuilder circleRadius(ZoomDependentNumber radius) { } // Line styling + public StyleBuilder lineCap(String lineCap) { + layout.put("line-cap", lineCap); + return this; + } public StyleBuilder lineColor(String color) { paint.put("line-color", validateColor(color)); return this; } + public StyleBuilder lineColorMatch( + String propertyName, + Collection values, + String defaultValue + ) { + paint.put( + "line-color", + ListUtils.combine( + List.of("match", List.of("get", propertyName)), + (Collection) values, + List.of(defaultValue) + ) + ); + return this; + } + + public StyleBuilder lineOpacity(float lineOpacity) { + paint.put("line-opacity", lineOpacity); + return this; + } + + public StyleBuilder lineDasharray(float... dashArray) { + paint.put("line-dasharray", dashArray); + return this; + } + public StyleBuilder lineWidth(float width) { paint.put("line-width", width); return this; @@ -235,6 +266,11 @@ public final StyleBuilder vertexFilter(Class... classToFilter) return filterClasses(classToFilter); } + public StyleBuilder filterValueInProperty(String value, String propertyName) { + filter = List.of("in", value, List.of("string", List.of("get", propertyName))); + return this; + } + public JsonNode toJson() { validate(); diff --git a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java index d43a91d384d..ae7e4969428 100644 --- a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java +++ b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java @@ -9,6 +9,7 @@ import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.EscalatorEdge; import org.opentripplanner.street.model.edge.StreetEdge; @@ -29,8 +30,9 @@ protected Collection map(Edge input) { private static List mapStreetEdge(StreetEdge se) { var props = Lists.newArrayList( - kv("permission", se.getPermission().toString()), - kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor())) + kv("permission", streetPermissionAsString(se.getPermission())), + kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor())), + kv("noThruTraffic", noThruTrafficAsString(se)) ); if (se.hasBogusName()) { props.addFirst(kv("name", "%s (generated)".formatted(se.getName().toString()))); @@ -39,4 +41,26 @@ private static List mapStreetEdge(StreetEdge se) { } return props; } + + public static String streetPermissionAsString(StreetTraversalPermission permission) { + return ( + permission == StreetTraversalPermission.ALL + ? "PEDESTRIAN_AND_BICYCLE_AND_CAR" + : permission.toString() + ).replace("_AND_", " "); + } + + private static String noThruTrafficAsString(StreetEdge se) { + var noThruPermission = StreetTraversalPermission.NONE; + if (se.isWalkNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.PEDESTRIAN); + } + if (se.isBicycleNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.BICYCLE); + } + if (se.isMotorVehicleNoThruTraffic()) { + noThruPermission = noThruPermission.add(StreetTraversalPermission.CAR); + } + return streetPermissionAsString(noThruPermission); + } } diff --git a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index 8a0e457396e..1aad369a557 100644 --- a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -28,14 +28,14 @@ } }, { - "id" : "NONE", + "id" : "edge", + "type" : "line", "source" : "vectorSource", "source-layer" : "edges", - "type" : "line", "minzoom" : 6, "maxzoom" : 23, "paint" : { - "line-color" : "#000", + "line-color" : "#DDDDDD", "line-width" : [ "interpolate", [ @@ -45,9 +45,9 @@ "zoom" ], 13, - 0.2, + 0.1, 23, - 8.0 + 6.0 ], "line-offset" : [ "interpolate", @@ -58,33 +58,72 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, - "filter" : [ - "==", - "permission", - "NONE" - ], "layout" : { "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Edges" } }, { - "id" : "PEDESTRIAN", + "id" : "edge-name", + "type" : "symbol", "source" : "vectorSource", "source-layer" : "edges", + "minzoom" : 17, + "maxzoom" : 23, + "paint" : { + "text-color" : "#000", + "text-halo-color" : "#fff", + "text-halo-blur" : 4, + "text-halo-width" : 3 + }, + "layout" : { + "symbol-placement" : "line-center", + "symbol-spacing" : 1000, + "text-field" : "{name}", + "text-font" : [ + "KlokanTech Noto Sans Regular" + ], + "text-size" : [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 6.0, + 24, + 12.0 + ], + "text-max-width" : 100, + "text-keep-upright" : true, + "text-rotation-alignment" : "map", + "text-overlap" : "never", + "visibility" : "none" + }, + "metadata" : { + "group" : "Edges" + } + }, + { + "id" : "link", "type" : "line", - "minzoom" : 6, + "source" : "vectorSource", + "source-layer" : "edges", + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#2ba812", + "line-color" : "#DDDDDD", + "line-opacity" : 0.2, "line-width" : [ "interpolate", [ @@ -107,33 +146,61 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "PEDESTRIAN" + "in", + "class", + "StreetTransitStopLink", + "StreetTransitEntranceLink", + "BoardingLocationToStopLink", + "StreetVehicleRentalLink", + "StreetVehicleParkingLink", + "StreetStationCentroidLink" ], "layout" : { "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Edges" } }, { - "id" : "BICYCLE", + "id" : "permission PEDESTRIAN", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#10d3b6", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -156,33 +223,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE" + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "PEDESTRIAN_AND_BICYCLE", + "id" : "permission BICYCLE", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#10d3b6", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -205,33 +301,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "PEDESTRIAN_AND_BICYCLE" + "in", + "BICYCLE", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "CAR", + "id" : "permission CAR", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#f92e13", + "line-color" : [ + "match", + [ + "get", + "permission" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -254,47 +379,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "CAR" + "in", + "CAR", + [ + "string", + [ + "get", + "permission" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "PEDESTRIAN_AND_CAR", + "id" : "permission-text", "source" : "vectorSource", "source-layer" : "edges", - "type" : "line", - "minzoom" : 6, + "type" : "symbol", + "minzoom" : 17, "maxzoom" : 23, "paint" : { - "line-color" : "#e25f8f", - "line-width" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.2, - 23, - 8.0 + "text-color" : "#000", + "text-halo-color" : "#fff", + "text-halo-blur" : 4, + "text-halo-width" : 3 + }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], + "layout" : { + "symbol-placement" : "line-center", + "symbol-spacing" : 1000, + "text-field" : "{permission}", + "text-font" : [ + "KlokanTech Noto Sans Regular" ], - "line-offset" : [ + "text-size" : [ "interpolate", [ "linear" @@ -302,34 +442,57 @@ [ "zoom" ], - 13, - 0.3, - 23, - 6.0 - ] - }, - "filter" : [ - "==", - "permission", - "PEDESTRIAN_AND_CAR" - ], - "layout" : { - "line-cap" : "round", + 10, + 6.0, + 24, + 12.0 + ], + "text-max-width" : 100, + "text-keep-upright" : true, + "text-rotation-alignment" : "map", + "text-overlap" : "never", + "text-offset" : [ + 0, + 1.0 + ], "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "Permissions" } }, { - "id" : "BICYCLE_AND_CAR", + "id" : "no-thru-traffic PEDESTRIAN", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#e25f8f", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -352,33 +515,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ - "==", - "permission", - "BICYCLE_AND_CAR" + "in", + "PEDESTRIAN", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "No-thru traffic" } }, { - "id" : "ALL", + "id" : "no-thru-traffic BICYCLE", "source" : "vectorSource", "source-layer" : "edges", "type" : "line", - "minzoom" : 6, + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#adb2b0", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -401,91 +593,62 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, - "filter" : [ - "==", - "permission", - "ALL" - ], - "layout" : { - "line-cap" : "round", - "visibility" : "none" - }, - "metadata" : { - "group" : "Traversal permissions" - } - }, - { - "id" : "permission-text", - "source" : "vectorSource", - "source-layer" : "edges", - "type" : "symbol", - "minzoom" : 17, - "maxzoom" : 23, - "paint" : { - "text-color" : "#000", - "text-halo-color" : "#fff", - "text-halo-blur" : 4, - "text-halo-width" : 3 - }, "filter" : [ "in", - "class", - "StreetEdge", - "AreaEdge", - "EscalatorEdge", - "PathwayEdge", - "ElevatorHopEdge", - "TemporaryPartialStreetEdge", - "TemporaryFreeEdge" + "BICYCLE", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "symbol-placement" : "line-center", - "symbol-spacing" : 1000, - "text-field" : "{permission}", - "text-font" : [ - "KlokanTech Noto Sans Regular" - ], - "text-size" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 10, - 6.0, - 24, - 12.0 - ], - "text-max-width" : 100, - "text-keep-upright" : true, - "text-rotation-alignment" : "map", - "text-overlap" : "never", - "text-offset" : [ - 0, - 1.0 - ], + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Traversal permissions" + "group" : "No-thru traffic" } }, { - "id" : "edge", - "type" : "line", + "id" : "no-thru-traffic CAR", "source" : "vectorSource", "source-layer" : "edges", - "minzoom" : 6, + "type" : "line", + "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#f21d52", + "line-color" : [ + "match", + [ + "get", + "noThruTraffic" + ], + "NONE", + "#140d0e", + "PEDESTRIAN", + "#2ba812", + "BICYCLE", + "#10d3b6", + "PEDESTRIAN BICYCLE", + "#10d3b6", + "CAR", + "#f92e13", + "PEDESTRIAN CAR", + "#e25f8f", + "BICYCLE CAR", + "#e25f8f", + "PEDESTRIAN BICYCLE CAR", + "#adb2b0", + "#140d0e" + ], "line-width" : [ "interpolate", [ @@ -508,35 +671,35 @@ "zoom" ], 13, - 0.3, + 0.4, 23, - 6.0 + 7.0 ] }, "filter" : [ "in", - "class", - "StreetEdge", - "AreaEdge", - "EscalatorEdge", - "PathwayEdge", - "ElevatorHopEdge", - "TemporaryPartialStreetEdge", - "TemporaryFreeEdge" + "CAR", + [ + "string", + [ + "get", + "noThruTraffic" + ] + ] ], "layout" : { - "line-cap" : "round", + "line-cap" : "butt", "visibility" : "none" }, "metadata" : { - "group" : "Edges" + "group" : "No-thru traffic" } }, { - "id" : "edge-name", - "type" : "symbol", + "id" : "no-thru-traffic-text", "source" : "vectorSource", "source-layer" : "edges", + "type" : "symbol", "minzoom" : 17, "maxzoom" : 23, "paint" : { @@ -559,7 +722,7 @@ "layout" : { "symbol-placement" : "line-center", "symbol-spacing" : 1000, - "text-field" : "{name}", + "text-field" : "{noThruTraffic}", "text-font" : [ "KlokanTech Noto Sans Regular" ], @@ -580,64 +743,14 @@ "text-keep-upright" : true, "text-rotation-alignment" : "map", "text-overlap" : "never", - "visibility" : "none" - }, - "metadata" : { - "group" : "Edges" - } - }, - { - "id" : "link", - "type" : "line", - "source" : "vectorSource", - "source-layer" : "edges", - "minzoom" : 13, - "maxzoom" : 23, - "paint" : { - "line-color" : "#22DD9E", - "line-width" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.2, - 23, - 8.0 + "text-offset" : [ + 0, + 1.0 ], - "line-offset" : [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.3, - 23, - 6.0 - ] - }, - "filter" : [ - "in", - "class", - "StreetTransitStopLink", - "StreetTransitEntranceLink", - "BoardingLocationToStopLink", - "StreetVehicleRentalLink", - "StreetVehicleParkingLink", - "StreetStationCentroidLink" - ], - "layout" : { - "line-cap" : "round", "visibility" : "none" }, "metadata" : { - "group" : "Edges" + "group" : "No-thru traffic" } }, { From 0bde65520557b5e0917be7283ad758ef1a56e34d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Tue, 22 Oct 2024 13:31:54 +0300 Subject: [PATCH 37/86] Mark BestMatchSpecifier as deprecated --- .../osm/wayproperty/specifier/BestMatchSpecifier.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java b/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java index 1d03d74956e..5365937ad56 100644 --- a/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java +++ b/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java @@ -26,6 +26,10 @@ public class BestMatchSpecifier implements OsmSpecifier { public static final int NO_MATCH_SCORE = 0; private final Condition[] conditions; + /** + * @deprecated Logic is fuzzy and unpredictable, use ExactMatchSpecifier instead + */ + @Deprecated public BestMatchSpecifier(String spec) { conditions = OsmSpecifier.parseConditions(spec, ";"); } From 36c1006df4095d4a577873b6383ef792a69d7b79 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 23 Oct 2024 15:48:12 +0300 Subject: [PATCH 38/86] Use ExactMatchSpecifier for the problematic footway area biking rule Instead of adding competing best match rules, start using the well designed exact match specifier. This may cause some unexpected changes but as the change concerns only one specific rule, we should be able to deal with them, --- .../opentripplanner/osm/tagmapping/FinlandMapper.java | 2 -- .../opentripplanner/osm/tagmapping/OsmTagMapper.java | 11 +++++++---- doc/user/osm/Finland.md | 4 +--- doc/user/osm/Germany.md | 3 +-- doc/user/osm/OsmTag.md | 3 +-- doc/user/osm/UK.md | 3 +-- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java index e4267ce9454..5e413af510a 100644 --- a/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java +++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java @@ -90,8 +90,6 @@ else if (speedLimit <= 16.65f) { // No biking on designated footways/sidewalks props.setProperties("highway=footway", withModes(PEDESTRIAN)); - props.setProperties("highway=footway;area=yes", withModes(PEDESTRIAN)); - //props.setProperties("public_transport=platform;area=yes", withModes(PEDESTRIAN)); props.setProperties("footway=sidewalk;highway=footway", withModes(PEDESTRIAN)); // Walking on segregated ways is safer than when cycling and walking happens on the same lane diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java index 6bfb9367111..6b8511cc4c8 100644 --- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java +++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java @@ -14,6 +14,8 @@ import org.opentripplanner.osm.wayproperty.WayProperties; import org.opentripplanner.osm.wayproperty.WayPropertySet; import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier; +import org.opentripplanner.osm.wayproperty.specifier.Condition; +import org.opentripplanner.osm.wayproperty.specifier.ExactMatchSpecifier; import org.opentripplanner.osm.wayproperty.specifier.LogicalOrSpecifier; import org.opentripplanner.routing.services.notes.StreetNotesService; @@ -39,10 +41,8 @@ * prevailing 'props.setProperties' statement) has a 'foot=yes' tag the permissions are overridden * and walking is allowed on that way. *

- * TODO clarify why this needs a separate factory interface. * * @author bdferris, novalis - * @see OsmTagMapper */ public class OsmTagMapper { @@ -67,7 +67,6 @@ public void populateProperties(WayPropertySet props) { props.setProperties("highway=crossing", pedestrianWayProperties); props.setProperties("highway=platform", pedestrianWayProperties); props.setProperties("public_transport=platform", pedestrianWayProperties); - props.setProperties("public_transport=platform;area=yes", pedestrianWayProperties); props.setProperties("railway=platform", pedestrianWayProperties); props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties); props.setProperties("mtb:scale=1", pedestrianWayProperties); @@ -408,7 +407,11 @@ public void populateProperties(WayPropertySet props) { withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) ); props.setProperties( - "highway=footway;bicycle=yes;area=yes", + new ExactMatchSpecifier( + new Condition.Equals("highway", "footway"), + new Condition.Equals("bicycle", "yes"), + new Condition.Equals("area", " yes") + ), withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9) ); props.setProperties( diff --git a/doc/user/osm/Finland.md b/doc/user/osm/Finland.md index cdf56dab057..df0d4856014 100644 --- a/doc/user/osm/Finland.md +++ b/doc/user/osm/Finland.md @@ -49,7 +49,6 @@ Lower safety values make an OSM way more desirable and higher values less desira | `present(highway); ice_road=yes` | `NONE` | | | | `present(highway); winter_road=yes` | `NONE` | | | | `highway=footway` | `PEDESTRIAN` | | | -| `highway=footway; area=yes` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `highway=cycleway; segregated=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | 1.1 | | `highway=footway; bridge=yes` | `PEDESTRIAN` | | | @@ -78,7 +77,6 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | -| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | @@ -193,7 +191,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/Germany.md b/doc/user/osm/Germany.md index fb6581a8d3b..527e5f5a3e7 100644 --- a/doc/user/osm/Germany.md +++ b/doc/user/osm/Germany.md @@ -42,7 +42,6 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | -| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | @@ -157,7 +156,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/OsmTag.md b/doc/user/osm/OsmTag.md index a77ebb41b82..e78467ce8bd 100644 --- a/doc/user/osm/OsmTag.md +++ b/doc/user/osm/OsmTag.md @@ -33,7 +33,6 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | -| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | @@ -148,7 +147,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/UK.md b/doc/user/osm/UK.md index 1eebd08c880..1dcae13e145 100644 --- a/doc/user/osm/UK.md +++ b/doc/user/osm/UK.md @@ -47,7 +47,6 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=crossing` | `PEDESTRIAN` | | | | `highway=platform` | `PEDESTRIAN` | | | | `public_transport=platform` | `PEDESTRIAN` | | | -| `public_transport=platform; area=yes` | `PEDESTRIAN` | | | | `railway=platform` | `PEDESTRIAN` | | | | `footway=sidewalk; highway=footway` | `PEDESTRIAN` | | | | `mtb:scale=1` | `PEDESTRIAN` | | | @@ -162,7 +161,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | From 372e4f0e2f161f8c891acff2957534fc82a52d60 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Fri, 25 Oct 2024 10:57:22 +0100 Subject: [PATCH 39/86] add failing test for #6072 --- .../model/network/TripPatternTest.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java index bb0ec765331..32563545694 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java @@ -24,7 +24,9 @@ class TripPatternTest { private static final Route ROUTE = TimetableRepositoryForTest.route("routeId").build(); public static final RegularStop STOP_A = TEST_MODEL.stop("A").build(); + public static final RegularStop STOP_X = TEST_MODEL.stop("X").build(); public static final RegularStop STOP_B = TEST_MODEL.stop("B").build(); + public static final RegularStop STOP_Y = TEST_MODEL.stop("Y").build(); public static final RegularStop STOP_C = TEST_MODEL.stop("C").build(); private static final StopPattern STOP_PATTERN = TimetableRepositoryForTest.stopPattern( STOP_A, @@ -33,8 +35,8 @@ class TripPatternTest { ); private static final List HOP_GEOMETRIES = List.of( - makeLineString(STOP_A.getCoordinate(), STOP_B.getCoordinate()), - makeLineString(STOP_B.getCoordinate(), STOP_C.getCoordinate()) + makeLineString(STOP_A.getCoordinate(), STOP_X.getCoordinate(), STOP_B.getCoordinate()), + makeLineString(STOP_B.getCoordinate(), STOP_Y.getCoordinate(), STOP_C.getCoordinate()) ); private static final TripPattern subject = TripPattern @@ -70,6 +72,27 @@ void copy() { assertEquals(HOP_GEOMETRIES.get(1), copy.getHopGeometry(1)); } + @Test + void hopGeometryForReplacementPattern() { + var pattern = TripPattern + .of(id("replacement")) + .withName("replacement") + .withRoute(ROUTE) + .withStopPattern(TimetableRepositoryForTest.stopPattern(STOP_A, STOP_B, STOP_X, STOP_Y)) + .withOriginalTripPattern(subject) + .build(); + + assertEquals(HOP_GEOMETRIES.get(0), pattern.getHopGeometry(0)); + assertEquals( + makeLineString(STOP_B.getCoordinate(), STOP_X.getCoordinate()), + pattern.getHopGeometry(1) + ); + assertEquals( + makeLineString(STOP_X.getCoordinate(), STOP_Y.getCoordinate()), + pattern.getHopGeometry(2) + ); + } + @Test void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); From 8753f82cd160304a978fb195108b05b7e7de6876 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Fri, 25 Oct 2024 11:03:25 +0100 Subject: [PATCH 40/86] fix failing test --- .../transit/model/network/TripPatternBuilder.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java index ec2451ea66d..5f9be0da455 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java +++ b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java @@ -219,15 +219,12 @@ private List generateHopGeometriesFromOriginalTripPattern() { // being replaced having a different number of stops. In that case the geometry will be // preserved up until the first mismatching stop, and a straight line will be used for // all segments after that. - int sizeOfShortestPattern = Math.min( - stopPattern.getSize(), - originalTripPattern.numberOfStops() - ); - List hopGeometries = new ArrayList<>(); - for (int i = 0; i < sizeOfShortestPattern - 1; i++) { - LineString hopGeometry = originalTripPattern.getHopGeometry(i); + for (int i = 0; i < stopPattern.getSize() - 1; i++) { + LineString hopGeometry = i < originalTripPattern.numberOfStops() - 1 + ? originalTripPattern.getHopGeometry(i) + : null; if (hopGeometry != null && stopPattern.sameStops(originalTripPattern.getStopPattern(), i)) { // Copy hop geometry from previous pattern From 06cf46dcf43b9cb9b8b785efa8db02e98b73a7d2 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 31 Oct 2024 15:27:09 +0000 Subject: [PATCH 41/86] remove the feature flag --- .../parameter/QualifiedModeSetTest.java | 2 +- .../api/parameter/ApiRequestMode.java | 4 ---- .../framework/application/OTPFeature.java | 8 ------- .../gtfs/mapping/TransitModeMapper.java | 2 +- .../LegacyRouteRequestMapperTest.java | 24 ------------------- .../gtfs/mapping/TransitModeMapperTest.java | 14 ++--------- doc/user/Configuration.md | 1 - 7 files changed, 4 insertions(+), 51 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java b/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java index ad344713a74..7c3bf410192 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java @@ -231,7 +231,7 @@ void carHail() { @Test void carHailWithTransit() { var modeSet = new QualifiedModeSet("CAR_HAIL,BUS,RAIL"); - assertEquals(Set.of(COACH, BUS, RAIL), Set.copyOf(modeSet.getTransitModes())); + assertEquals(Set.of(BUS, RAIL), Set.copyOf(modeSet.getTransitModes())); assertEquals(WALK, modeSet.getRequestModes().directMode); assertEquals(CAR_HAILING, modeSet.getRequestModes().accessMode); diff --git a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java index c78e5dfb66f..5681bd1d76d 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java @@ -42,10 +42,6 @@ public enum ApiRequestMode { } public Collection getTransitModes() { - if (this == BUS && OTPFeature.GtfsCoach.isOff()) { - return List.of(TransitMode.BUS, TransitMode.COACH); - } - return transitModes; } } diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java index c8816981974..324f5397673 100644 --- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java +++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java @@ -38,14 +38,6 @@ public enum OTPFeature { "Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated." ), FloatingBike(true, false, "Enable floating bike routing."), - GtfsCoach( - false, - false, - """ - When parsing GTFS data, treat GTFS route type 200 to 299 as coach routes instead of bus routes. - When using the GTFS GraphQL API, do not return coach routes when only BUS is specified as a mode. - """ - ), GtfsGraphQlApi(true, false, "Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md)."), GtfsGraphQlApiRentalStationFuzzyMatching( false, diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java index 1b5044e3530..aef5399475e 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java @@ -21,7 +21,7 @@ public static TransitMode mapMode(int routeType) { // Railway Service return TransitMode.RAIL; } else if (routeType >= 200 && routeType < 300) { //Coach Service - return OTPFeature.GtfsCoach.isOn() ? TransitMode.COACH : TransitMode.BUS; + return TransitMode.COACH; } else if (routeType >= 300 && routeType < 500) { //Suburban Railway Service and Urban Railway service if (routeType >= 401 && routeType <= 402) { return TransitMode.SUBWAY; diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index b42ea828462..78f287f78ac 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -27,7 +27,6 @@ import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.ext.fares.impl.DefaultFareService; -import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle; @@ -134,19 +133,6 @@ static Stream transportModesCases() { return Stream.of( of(List.of(), "[ExcludeAllTransitFilter{}]"), of(List.of(mode("BICYCLE")), "[ExcludeAllTransitFilter{}]"), - of( - List.of(mode("BUS")), - "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH]}]}]" - ), - of( - List.of(mode("BUS"), mode("MONORAIL")), - "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS, COACH, MONORAIL]}]}]" - ) - ); - } - - static Stream transportModesCasesWithCoach() { - return Stream.of( of( List.of(mode("BUS")), "[TransitFilterRequest{select: [SelectRequest{transportModes: [BUS]}]}]" @@ -165,16 +151,6 @@ static Stream transportModesCasesWithCoach() { @ParameterizedTest @MethodSource("transportModesCases") void modes(List> modes, String expectedFilters) { - OTPFeature.GtfsCoach.testOff(() -> testModes(modes, expectedFilters)); - } - - @ParameterizedTest - @MethodSource("transportModesCasesWithCoach") - void modesWithCoach(List> modes, String expectedFilters) { - OTPFeature.GtfsCoach.testOn(() -> testModes(modes, expectedFilters)); - } - - private void testModes(List> modes, String expectedFilters) { Map arguments = Map.of("transportModes", modes); var routeRequest = LegacyRouteRequestMapper.toRouteRequest( diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java index ac1da483c86..d5086d153b6 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java @@ -45,6 +45,8 @@ static Stream testCases() { // https://groups.google.com/g/gtfs-changes/c/keT5rTPS7Y0/m/71uMz2l6ke0J?pli=1 Arguments.of(100, RAIL), Arguments.of(199, RAIL), + Arguments.of(200, COACH), + Arguments.of(299, COACH), Arguments.of(400, RAIL), Arguments.of(401, SUBWAY), Arguments.of(402, SUBWAY), @@ -86,16 +88,4 @@ static Stream testCases() { void map(int mode, TransitMode expectedMode) { assertEquals(expectedMode, TransitModeMapper.mapMode(mode)); } - - @Test - void testCoachFeatureFlag() { - OTPFeature.GtfsCoach.testOff(() -> { - assertEquals(BUS, TransitModeMapper.mapMode(200)); - assertEquals(BUS, TransitModeMapper.mapMode(299)); - }); - OTPFeature.GtfsCoach.testOn(() -> { - assertEquals(COACH, TransitModeMapper.mapMode(200)); - assertEquals(COACH, TransitModeMapper.mapMode(299)); - }); - } } diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md index 1ae152d331c..bca974f8617 100644 --- a/doc/user/Configuration.md +++ b/doc/user/Configuration.md @@ -228,7 +228,6 @@ Here is a list of all features which can be toggled on/off and their default val | `DebugUi` | Enable the debug GraphQL client and web UI and located at the root of the web server as well as the debug map tiles it uses. Be aware that the map tiles are not a stable API and can change without notice. Use the [vector tiles feature if](sandbox/MapboxVectorTilesApi.md) you want a stable map tiles API. | ✓️ | | | `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | | | `FloatingBike` | Enable floating bike routing. | ✓️ | | -| `GtfsCoach` | When parsing GTFS data, treat GTFS route type 200 to 299 as coach routes instead of bus routes. When using the GTFS GraphQL API, do not return coach routes when only BUS is specified as a mode. | | | | `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | | | `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | | | `MinimumTransferTimeIsDefinitive` | If the minimum transfer time is a lower bound (default) or the definitive time for the transfer. Set this to `true` if you want to set a transfer time lower than what OTP derives from OSM data. | | | From 36381e88d2514480455c3c30b736c0b8d14e9c50 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 3 Nov 2024 00:40:27 +0200 Subject: [PATCH 42/86] Deprecate old style translated alert fields and add language param to normal fields --- .../apis/gtfs/datafetchers/AlertImpl.java | 23 +++--- .../apis/gtfs/generated/GraphQLTypes.java | 57 +++++++++++++ .../opentripplanner/apis/gtfs/schema.graphqls | 21 +++-- .../apis/gtfs/expectations/alerts.json | 82 +++++++------------ .../apis/gtfs/queries/alerts.graphql | 19 ++--- 5 files changed, 120 insertions(+), 82 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java index 90db8ef1605..4f7a3f61a57 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java @@ -23,6 +23,7 @@ import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.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; @@ -65,11 +66,11 @@ public DataFetcher alertCause() { public DataFetcher alertDescriptionText() { return environment -> { var alert = getSource(environment); - return alert - .descriptionText() - .or(alert::headerText) - .map(t -> t.toString(environment.getLocale())) - .orElse(FALLBACK_EMPTY_STRING); + var descriptionText = GraphQLUtils.getTranslation( + alert.descriptionText().or(alert::headerText).orElse(null), + environment + ); + return descriptionText != null ? descriptionText : FALLBACK_EMPTY_STRING; }; } @@ -103,11 +104,11 @@ public DataFetcher alertHash() { public DataFetcher alertHeaderText() { return environment -> { var alert = getSource(environment); - return alert - .headerText() - .or(alert::descriptionText) - .map(h -> h.toString(environment.getLocale())) - .orElse(FALLBACK_EMPTY_STRING); + var headerText = GraphQLUtils.getTranslation( + alert.headerText().or(alert::descriptionText).orElse(null), + environment + ); + return headerText != null ? headerText : FALLBACK_EMPTY_STRING; }; } @@ -125,7 +126,7 @@ public DataFetcher alertSeverityLevel() { @Override public DataFetcher alertUrl() { return environment -> - getSource(environment).url().map(u -> u.toString(environment.getLocale())).orElse(null); + GraphQLUtils.getTranslation(getSource(environment).url().orElse(null), environment); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 48d60701a96..2d7f5d33f56 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -75,6 +75,63 @@ public enum GraphQLAgencyAlertType { ROUTE_TYPES, } + public static class GraphQLAlertAlertDescriptionTextArgs { + + private String language; + + public GraphQLAlertAlertDescriptionTextArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } + } + + public String getGraphQLLanguage() { + return this.language; + } + + public void setGraphQLLanguage(String language) { + this.language = language; + } + } + + public static class GraphQLAlertAlertHeaderTextArgs { + + private String language; + + public GraphQLAlertAlertHeaderTextArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } + } + + public String getGraphQLLanguage() { + return this.language; + } + + public void setGraphQLLanguage(String language) { + this.language = language; + } + } + + public static class GraphQLAlertAlertUrlArgs { + + private String language; + + public GraphQLAlertAlertUrlArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } + } + + public String getGraphQLLanguage() { + return this.language; + } + + public void setGraphQLLanguage(String language) { + this.language = language; + } + } + /** Cause of a alert */ public enum GraphQLAlertCauseType { ACCIDENT, diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index de1230b9eca..f8b9f1fdd6d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -117,23 +117,32 @@ type Alert implements Node { "Alert cause" alertCause: AlertCauseType "Long description of the alert" - alertDescriptionText: String! + alertDescriptionText( + "Returns description with the specified language, if found, otherwise returns the value with some default language." + language: String + ): String! "Long descriptions of the alert in all different available languages" - alertDescriptionTextTranslations: [TranslatedString!]! + alertDescriptionTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertDescriptionText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertDescriptionText` field.") "Alert effect" alertEffect: AlertEffectType "hashcode from the original GTFS-RT alert" alertHash: Int "Header of the alert, if available" - alertHeaderText: String + alertHeaderText( + "Returns header with the specified language, if found, otherwise returns the value with some default language." + language: String + ): String "Header of the alert in all different available languages" - alertHeaderTextTranslations: [TranslatedString!]! + alertHeaderTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertHeaderText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertHeaderText` field.") "Alert severity level" alertSeverityLevel: AlertSeverityLevelType "Url with more information" - alertUrl: String + alertUrl( + "Returns URL with the specified language, if found, otherwise returns the value with some default language." + language: String + ): String "Url with more information in all different available languages" - alertUrlTranslations: [TranslatedString!]! + alertUrlTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertUrl` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertUrl` field.") "Time when this alert is not in effect anymore. Format: Unix timestamp in seconds" effectiveEndDate: Long "Time when this alert comes into effect. Format: Unix timestamp in seconds" diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/alerts.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/alerts.json index eb7b3d24154..1683a37dbe9 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/alerts.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/alerts.json @@ -1,63 +1,41 @@ { - "data" : { - "alerts" : [ + "data": { + "alerts": [ { - "id" : "QWxlcnQ6RjpuZWl0aGVyLWhlYWRlci1ub3ItZGVzY3JpcHRpb24", - "alertHeaderText" : "", - "alertDescriptionText" : "", - "alertUrl" : null, - "alertDescriptionTextTranslations" : [ ], - "alertHeaderTextTranslations" : [ ] + "id": "QWxlcnQ6RjpuZWl0aGVyLWhlYWRlci1ub3ItZGVzY3JpcHRpb24", + "headerDefault": "", + "headerDe": "", + "descriptionDefault": "", + "descriptionDe": "", + "urlDefault": null, + "urlDe": null }, { - "id" : "QWxlcnQ6Rjpuby1oZWFkZXI", - "alertHeaderText" : "Second string", - "alertDescriptionText" : "Second string", - "alertUrl" : null, - "alertDescriptionTextTranslations" : [ - { - "language" : null, - "text" : "Second string" - }, - { - "language" : "de", - "text" : "Zweite Zeichenabfolge" - }, - { - "language" : "fi", - "text" : "Etkö ole varma, mitä tämä tarkoittaa" - } - ], - "alertHeaderTextTranslations" : [ ] + "id": "QWxlcnQ6Rjpuby1oZWFkZXI", + "headerDefault": "Second string", + "headerDe": "Zweite Zeichenabfolge", + "descriptionDefault": "Second string", + "descriptionDe": "Zweite Zeichenabfolge", + "urlDefault": null, + "urlDe": null }, { - "id" : "QWxlcnQ6Rjphbi1hbGVydA", - "alertHeaderText" : "A header", - "alertDescriptionText" : "A description", - "alertUrl" : "https://example.com", - "alertDescriptionTextTranslations" : [ ], - "alertHeaderTextTranslations" : [ ] + "id": "QWxlcnQ6Rjphbi1hbGVydA", + "headerDefault": "A header", + "headerDe": "A header", + "descriptionDefault": "A description", + "descriptionDe": "A description", + "urlDefault": "https://example.com", + "urlDe": "https://example.com" }, { - "id" : "QWxlcnQ6Rjpuby1kZXNjcmlwdGlvbg", - "alertHeaderText" : "First string", - "alertDescriptionText" : "First string", - "alertUrl" : null, - "alertDescriptionTextTranslations" : [ ], - "alertHeaderTextTranslations" : [ - { - "text" : "First string", - "language" : null - }, - { - "text" : "Erste Zeichenabfolge", - "language" : "de" - }, - { - "text" : "Minulla ei ole aavistustakaan kuinka puhua suomea", - "language" : "fi" - } - ] + "id": "QWxlcnQ6Rjpuby1kZXNjcmlwdGlvbg", + "headerDefault": "First string", + "headerDe": "Erste Zeichenabfolge", + "descriptionDefault": "First string", + "descriptionDe": "Erste Zeichenabfolge", + "urlDefault": null, + "urlDe": null } ] } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/alerts.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/alerts.graphql index 33ff47dd33b..3a5d438e8ef 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/alerts.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/alerts.graphql @@ -1,18 +1,11 @@ { alerts { id - 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 - } + headerDefault: alertHeaderText + headerDe: alertHeaderText(language: "de") + descriptionDefault: alertDescriptionText + descriptionDe: alertDescriptionText(language: "de") + urlDefault: alertUrl + urlDe: alertUrl(language: "de") } } From 3d754a59307a1c73441672b70a5ac5cb79d5a01d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Mon, 4 Nov 2024 09:30:52 +0200 Subject: [PATCH 43/86] Use convenient notation for cycling area ExactMatchSpecifier --- .../org/opentripplanner/osm/tagmapping/OsmTagMapper.java | 6 +----- doc/user/osm/Finland.md | 2 +- doc/user/osm/Germany.md | 2 +- doc/user/osm/OsmTag.md | 2 +- doc/user/osm/UK.md | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java index 6b8511cc4c8..e4c258ccdc4 100644 --- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java +++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java @@ -407,11 +407,7 @@ public void populateProperties(WayPropertySet props) { withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75) ); props.setProperties( - new ExactMatchSpecifier( - new Condition.Equals("highway", "footway"), - new Condition.Equals("bicycle", "yes"), - new Condition.Equals("area", " yes") - ), + new ExactMatchSpecifier("highway=footway;bicycle=yes;area=yes"), withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9) ); props.setProperties( diff --git a/doc/user/osm/Finland.md b/doc/user/osm/Finland.md index df0d4856014..8a60b5f0b13 100644 --- a/doc/user/osm/Finland.md +++ b/doc/user/osm/Finland.md @@ -191,7 +191,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/Germany.md b/doc/user/osm/Germany.md index 527e5f5a3e7..922aa3af836 100644 --- a/doc/user/osm/Germany.md +++ b/doc/user/osm/Germany.md @@ -156,7 +156,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/OsmTag.md b/doc/user/osm/OsmTag.md index e78467ce8bd..814420b791f 100644 --- a/doc/user/osm/OsmTag.md +++ b/doc/user/osm/OsmTag.md @@ -147,7 +147,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | diff --git a/doc/user/osm/UK.md b/doc/user/osm/UK.md index 0fa908745d6..34c4d1c1778 100644 --- a/doc/user/osm/UK.md +++ b/doc/user/osm/UK.md @@ -163,7 +163,7 @@ Lower safety values make an OSM way more desirable and higher values less desira | `highway=primary_link; cycleway=opposite` | `ALL` | forward: 2.06
back: 2.99 | | | `highway=path; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.6 | | | `highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | -| `highway=footway; bicycle=yes; area= yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | +| `highway=footway; bicycle=yes; area=yes` | `PEDESTRIAN_AND_BICYCLE` | 0.9 | | | `highway=pedestrian; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 0.75 | | | `footway=sidewalk; highway=footway; bicycle=yes` | `PEDESTRIAN_AND_BICYCLE` | 2.5 | | | `footway=sidewalk; highway=footway; bicycle=designated` | `PEDESTRIAN_AND_BICYCLE` | 1.1 | | From 31e0640be177ba85b62ab54d9a8b9a91163f3917 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 13:38:56 +0100 Subject: [PATCH 44/86] Use raw types --- .../LegacyRouteRequestMapper.java | 4 +-- .../routerequest/RouteRequestMapper.java | 11 +++---- .../routerequest/ViaLocationMapper.java | 33 ++++++++++++------- .../opentripplanner/apis/gtfs/schema.graphqls | 6 ++-- .../LegacyRouteRequestMapperTest.java | 10 +++--- .../routerequest/ViaLocationMapperTest.java | 28 +++------------- .../apis/gtfs/queries/plan.graphql | 11 ++++++- .../queries/planConnection-extended.graphql | 10 ++++++ 8 files changed, 61 insertions(+), 52 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index b014713067a..8e4c45a380c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -258,8 +258,8 @@ public static RouteRequest toRouteRequest( } static void mapViaLocations(RouteRequest request, DataFetchingEnvironment env) { - var args = new GraphQLTypes.GraphQLQueryTypePlanArgs(env.getArguments()); - var locs = ViaLocationMapper.mapToViaLocations(args.getGraphQLVia()); + var args = env.getArguments().get("via"); + var locs = ViaLocationMapper.mapToViaLocations((List>>) args); request.setViaLocations(locs); } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index ef3436bafba..62fb4a275af 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -10,6 +10,7 @@ import graphql.schema.DataFetchingEnvironment; import java.time.Instant; import java.util.List; +import java.util.Map; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; @@ -64,7 +65,8 @@ public static RouteRequest toRouteRequest( setModes(request.journey(), args.getGraphQLModes(), environment); - mapViaPoints(request, args.getGraphQLVia()); + // sadly we need to use the raw collection because it is cast to the wrong type + mapViaPoints(request, environment.getArgument("via")); return request; } @@ -181,10 +183,7 @@ private static GenericLocation parseGenericLocation( ); } - static void mapViaPoints( - RouteRequest request, - List locations - ) { - request.setViaLocations(ViaLocationMapper.mapToViaLocations(locations)); + static void mapViaPoints(RouteRequest request, List>> via) { + request.setViaLocations(ViaLocationMapper.mapToViaLocations(via)); } } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index b1177589f8d..e73fb9a77c2 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -1,8 +1,9 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; +import java.time.Duration; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; @@ -14,24 +15,32 @@ */ class ViaLocationMapper { - static List mapToViaLocations(@Nullable List via) { - return ListUtils.nullSafeImmutableList(via).stream().map(ViaLocationMapper::mapViaLocation).toList(); + private static final String STOP_LOCATION_IDS = "stopLocationIds"; + private static final String LABEL = "label"; + private static final String MINIMUM_WAIT_TIME = "minimumWaitTime"; + + static List mapToViaLocations(@Nullable List>> via) { + return ListUtils + .nullSafeImmutableList(via) + .stream() + .map(ViaLocationMapper::mapViaLocation) + .toList(); } - private static ViaLocation mapViaLocation(GraphQLPlanViaLocationInput via) { - var passThrough = via.getGraphQLPassThrough(); - var visit = via.getGraphQLVisit(); + private static ViaLocation mapViaLocation(Map> via) { + var passThrough = via.get("passThrough"); + var visit = via.get("visit"); - if (passThrough != null && passThrough.getGraphQLStopLocationIds() != null) { + if (passThrough != null && passThrough.get(STOP_LOCATION_IDS) != null) { return new PassThroughViaLocation( - passThrough.getGraphQLLabel(), - mapStopLocationIds(passThrough.getGraphQLStopLocationIds()) + (String) passThrough.get(LABEL), + mapStopLocationIds((List) passThrough.get(STOP_LOCATION_IDS)) ); } else if (visit != null) { return new VisitViaLocation( - visit.getGraphQLLabel(), - visit.getGraphQLMinimumWaitTime(), - mapStopLocationIds(visit.getGraphQLStopLocationIds()), + (String) visit.get(LABEL), + (Duration) visit.get(MINIMUM_WAIT_TIME), + mapStopLocationIds((List) visit.get(STOP_LOCATION_IDS)), List.of() ); } else { diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index ee3bbb2b523..31f2b025fc5 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1608,9 +1608,9 @@ type QueryType { is in combination of using paging can lead to better performance and to getting a more consistent number of itineraries in each search. """ - searchWindow: Duration + searchWindow: Duration, "The list of points the journey is required to pass through." - via: [PlanViaLocationInput!], + via: [PlanViaLocationInput!] ): PlanConnection @async "Get a single rental vehicle based on its ID, i.e. value of field `vehicleId`" rentalVehicle(id: String!): RentalVehicle @@ -4105,7 +4105,7 @@ input PlanPassThroughViaLocationInput { stop place or a group of stop places. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String!] + stopLocationIds: [String!]! } "Wrapper type for different types of preferences related to plan query." diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index baf88d77423..82a47c93b68 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -38,7 +38,7 @@ import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TimetableRepository; @@ -49,9 +49,9 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { static { Graph graph = new Graph(); - var testModel = TransitModelForTest.of(); + var testModel = TimetableRepositoryForTest.of(); var stopModelBuilder = testModel - .stopModelBuilder() + .siteRepositoryBuilder() .withRegularStop(testModel.stop("stop1").build()); var timetableRepository = new TimetableRepository(stopModelBuilder.build(), new Deduplicator()); timetableRepository.initTimeZone(ZoneIds.BERLIN); @@ -267,7 +267,9 @@ void passThroughPoints() { Map arguments = Map.of( "via", List.of( - new GraphQLPlanViaLocationInput(Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label"))) + new GraphQLPlanViaLocationInput( + Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) + ) ) ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java index f332adacdcb..22d172fb0bf 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java @@ -1,23 +1,5 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; - -import static java.util.Map.entry; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.mapToViaLocations; -import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_LABEL; -import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_MINIMUM_WAIT_TIME; -import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_PASS_THROUGH; -import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_STOP_LOCATION_IDS; -import static org.opentripplanner.apis.transmodel.model.plan.ViaLocationInputType.FIELD_VISIT; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; - +/* class ViaLocationMapperTest { public static final String LABEL = "TestLabel"; @@ -36,7 +18,7 @@ void mapToVisitViaLocations() { ) ); - var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var inputs = List.of(args); var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -53,10 +35,7 @@ void mapToVisitViaLocations() { @Test void mapToVisitViaLocationsWithBareMinimum() { - Map args = Map.of( - FIELD_VISIT, - Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) - ); + Map args = Map.of(FIELD_VISIT, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1"))); var inputs = List.of(new GraphQLPlanViaLocationInput(args)); var result = mapToViaLocations(inputs); @@ -102,3 +81,4 @@ void mapToPassThroughWithBareMinimum() { assertTrue(via.isPassThroughLocation()); } } +*/ diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql index b965ff11eff..5de2d2fdfef 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/plan.graphql @@ -10,7 +10,16 @@ filters: [{ select: [{ tags: ["e"] }] }] } transportModes: [{ mode: CAR, qualifier: HAIL }] - via: { passThrough: { label: "via", stopLocationIds: ["F:BUS"] } } + via: [ + { passThrough: { label: "via1", stopLocationIds: ["F:BUS"] } } + { + visit: { + label: "via2" + stopLocationIds: ["F:RAIL"] + minimumWaitTime: "1h" + } + } + ] ) { itineraries { start diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection-extended.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection-extended.graphql index e0266281aa4..1bbf9135321 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection-extended.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection-extended.graphql @@ -11,6 +11,16 @@ location: { coordinate: { latitude: 45.4908, longitude: -122.5519 } } label: "Work" } + via: [ + { passThrough: { label: "via1", stopLocationIds: ["F:BUS"] } } + { + visit: { + label: "via2" + stopLocationIds: ["F:RAIL"] + minimumWaitTime: "1h" + } + } + ] modes: { directOnly: false transitOnly: false From 1930b78d8bc17d24ebed493deba30751d00e5b6d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 14:06:52 +0100 Subject: [PATCH 45/86] Re-enable tests --- .../routerequest/ViaLocationMapper.java | 24 ++++++------ .../LegacyRouteRequestMapperTest.java | 5 +-- .../routerequest/ViaLocationMapperTest.java | 37 ++++++++++++++----- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index e73fb9a77c2..0b3d65e90b7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -15,9 +15,11 @@ */ class ViaLocationMapper { - private static final String STOP_LOCATION_IDS = "stopLocationIds"; - private static final String LABEL = "label"; - private static final String MINIMUM_WAIT_TIME = "minimumWaitTime"; + static final String FIELD_STOP_LOCATION_IDS = "stopLocationIds"; + static final String FIELD_LABEL = "label"; + static final String FIELD_MINIMUM_WAIT_TIME = "minimumWaitTime"; + static final String FIELD_VISIT = "visit"; + static final String FIELD_PASS_THROUGH = "passThrough"; static List mapToViaLocations(@Nullable List>> via) { return ListUtils @@ -28,19 +30,19 @@ static List mapToViaLocations(@Nullable List> via) { - var passThrough = via.get("passThrough"); - var visit = via.get("visit"); + var passThrough = via.get(FIELD_PASS_THROUGH); + var visit = via.get(FIELD_VISIT); - if (passThrough != null && passThrough.get(STOP_LOCATION_IDS) != null) { + if (passThrough != null && passThrough.get(FIELD_STOP_LOCATION_IDS) != null) { return new PassThroughViaLocation( - (String) passThrough.get(LABEL), - mapStopLocationIds((List) passThrough.get(STOP_LOCATION_IDS)) + (String) passThrough.get(FIELD_LABEL), + mapStopLocationIds((List) passThrough.get(FIELD_STOP_LOCATION_IDS)) ); } else if (visit != null) { return new VisitViaLocation( - (String) visit.get(LABEL), - (Duration) visit.get(MINIMUM_WAIT_TIME), - mapStopLocationIds((List) visit.get(STOP_LOCATION_IDS)), + (String) visit.get(FIELD_LABEL), + (Duration) visit.get(FIELD_MINIMUM_WAIT_TIME), + mapStopLocationIds((List) visit.get(FIELD_STOP_LOCATION_IDS)), List.of() ); } else { diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 82a47c93b68..aeda59f081b 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -26,7 +26,6 @@ import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPlanViaLocationInput; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.routing.api.request.RouteRequest; @@ -267,9 +266,7 @@ void passThroughPoints() { Map arguments = Map.of( "via", List.of( - new GraphQLPlanViaLocationInput( - Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) - ) + Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) ) ); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java index 22d172fb0bf..a3eb74a5d43 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapperTest.java @@ -1,5 +1,22 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; -/* + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.FIELD_LABEL; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.FIELD_MINIMUM_WAIT_TIME; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.FIELD_PASS_THROUGH; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.FIELD_STOP_LOCATION_IDS; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.FIELD_VISIT; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ViaLocationMapper.mapToViaLocations; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + class ViaLocationMapperTest { public static final String LABEL = "TestLabel"; @@ -9,7 +26,7 @@ class ViaLocationMapperTest { @Test void mapToVisitViaLocations() { - Map args = Map.of( + Map> args = Map.of( FIELD_VISIT, Map.ofEntries( entry(FIELD_LABEL, LABEL), @@ -35,8 +52,11 @@ void mapToVisitViaLocations() { @Test void mapToVisitViaLocationsWithBareMinimum() { - Map args = Map.of(FIELD_VISIT, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1"))); - var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + Map> args = Map.of( + FIELD_VISIT, + Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) + ); + var inputs = List.of(args); var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -49,11 +69,11 @@ void mapToVisitViaLocationsWithBareMinimum() { @Test void mapToPassThrough() { - final Map args = Map.of( + final Map> args = Map.of( FIELD_PASS_THROUGH, Map.ofEntries(entry(FIELD_LABEL, LABEL), entry(FIELD_STOP_LOCATION_IDS, LIST_IDS_INPUT)) ); - var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var inputs = List.of(args); var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -68,11 +88,11 @@ void mapToPassThrough() { @Test void mapToPassThroughWithBareMinimum() { - Map args = Map.of( + Map> args = Map.of( FIELD_PASS_THROUGH, Map.of(FIELD_STOP_LOCATION_IDS, List.of("F:1")) ); - var inputs = List.of(new GraphQLPlanViaLocationInput(args)); + var inputs = List.of(args); var result = mapToViaLocations(inputs); var via = result.getFirst(); @@ -81,4 +101,3 @@ void mapToPassThroughWithBareMinimum() { assertTrue(via.isPassThroughLocation()); } } -*/ From 7521c88d1e0af51c9adb221b31c4779342a64e9b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Nov 2024 22:23:48 +0100 Subject: [PATCH 46/86] Add global Maven settings # Conflicts: # .github/workflows/cibuild.yml --- .github/workflows/cibuild.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 66162ed6b3a..9e94fa17e5f 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -14,6 +14,12 @@ on: - master - dev-1.x - dev-2.x +env: + # Since version 3.9.0 of Maven it will automatically understand this environment variable. + # However, as of 2024-11 the latest versions of Ubuntu and Debian were on 3.8.8 so it will take some + # time until we can remove the $MAVEN_ARGS below. + MAVEN_ARGS: "--batch-mode --no-transfer-log" + jobs: build-linux: runs-on: ubuntu-latest @@ -61,7 +67,7 @@ jobs: if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev-1.x' || github.ref == 'refs/heads/dev-2.x') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: mvn --batch-mode deploy --settings maven-settings.xml -DskipTests -DGITHUB_REPOSITORY=$GITHUB_REPOSITORY -P prettierSkip -P deployGitHub + run: mvn $MAVEN_ARGS deploy --settings maven-settings.xml -DskipTests -DGITHUB_REPOSITORY=$GITHUB_REPOSITORY -P prettierSkip -P deployGitHub build-windows: timeout-minutes: 20 @@ -79,7 +85,7 @@ jobs: - name: Configure Windows Pagefile uses: al-cheb/configure-pagefile-action@v1.4 - name: Run tests - run: mvn --batch-mode test -P prettierSkip + run: mvn $MAVEN_ARGS test -P prettierSkip docs: if: github.repository_owner == 'opentripplanner' @@ -192,7 +198,7 @@ jobs: distribution: temurin cache: maven - name: Compile Java code - run: mvn --batch-mode compile -DskipTests -P prettierSkip + run: mvn $MAVEN_ARGS compile -DskipTests -P prettierSkip container-image: if: github.repository_owner == 'opentripplanner' && github.event_name == 'push' && (github.ref == 'refs/heads/dev-2.x' || github.ref == 'refs/heads/master') @@ -237,4 +243,4 @@ jobs: MAVEN_SKIP_ARGS="-P prettierSkip -Dmaven.test.skip=true -Dmaven.source.skip=true" - mvn --batch-mode $MAVEN_SKIP_ARGS package com.google.cloud.tools:jib-maven-plugin:build -Djib.to.tags=latest,$image_version + mvn $MAVEN_ARGS $MAVEN_SKIP_ARGS package com.google.cloud.tools:jib-maven-plugin:build -Djib.to.tags=latest,$image_version From ff433a3847a014131539ec1f1815df8251afea31 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Nov 2024 22:54:26 +0100 Subject: [PATCH 47/86] Use MAVEN_ARGS to hide download logs --- .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 9e94fa17e5f..09632ca50be 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -52,8 +52,8 @@ jobs: # https://github.com/actions/runner-images/issues/1499 # we set nodePath and npmPath to skip downloading the node binary, which frequently times out run: | - mvn --batch-mode jacoco:prepare-agent test jacoco:report -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm - mvn --batch-mode package -Dmaven.test.skip -P prettierSkip + mvn $MAVEN_ARGS jacoco:prepare-agent test jacoco:report -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm + mvn $MAVEN_ARGS package -Dmaven.test.skip -P prettierSkip - name: Send coverage data to codecov.io if: github.repository_owner == 'opentripplanner' From 34c7863634d0da7e53d302de99bc4822a4d803e0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 2 Nov 2024 22:59:11 +0100 Subject: [PATCH 48/86] Add Unicode theme --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 284153b6486..e043093c928 100644 --- a/pom.xml +++ b/pom.xml @@ -236,6 +236,7 @@ true true false + UNICODE From 2b45e355d0c8ea702d648988f281fb1af4bedde2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 08:32:05 +0100 Subject: [PATCH 49/86] Use correct command --- .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 09632ca50be..e2796d28df5 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -18,7 +18,7 @@ env: # Since version 3.9.0 of Maven it will automatically understand this environment variable. # However, as of 2024-11 the latest versions of Ubuntu and Debian were on 3.8.8 so it will take some # time until we can remove the $MAVEN_ARGS below. - MAVEN_ARGS: "--batch-mode --no-transfer-log" + MAVEN_ARGS: "--batch-mode --no-transfer-progress" jobs: build-linux: From fae5108217f5790114df11f5ed29e1d8b102d5a1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 08:35:06 +0100 Subject: [PATCH 50/86] Enable colour --- .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 e2796d28df5..1be659deaa6 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -18,7 +18,7 @@ env: # Since version 3.9.0 of Maven it will automatically understand this environment variable. # However, as of 2024-11 the latest versions of Ubuntu and Debian were on 3.8.8 so it will take some # time until we can remove the $MAVEN_ARGS below. - MAVEN_ARGS: "--batch-mode --no-transfer-progress" + MAVEN_ARGS: "--no-transfer-progress -Dstyle.color=always" jobs: build-linux: From 9c1d4329fef5b3a7e52667a70af4bfb58dab6dae Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 08:36:53 +0100 Subject: [PATCH 51/86] Use jacoco:report-aggregate --- .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 1be659deaa6..90bd2731604 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -52,7 +52,7 @@ jobs: # https://github.com/actions/runner-images/issues/1499 # we set nodePath and npmPath to skip downloading the node binary, which frequently times out run: | - mvn $MAVEN_ARGS jacoco:prepare-agent test jacoco:report -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm + mvn $MAVEN_ARGS jacoco:prepare-agent test jacoco:report-aggregate -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm mvn $MAVEN_ARGS package -Dmaven.test.skip -P prettierSkip - name: Send coverage data to codecov.io From 325cb1246faa6cc02b2a48be684e249c33db4d8b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 08:58:17 +0100 Subject: [PATCH 52/86] Update path for aggregated jacoco results --- .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 90bd2731604..2331179b832 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -59,7 +59,7 @@ jobs: if: github.repository_owner == 'opentripplanner' uses: codecov/codecov-action@v4 with: - files: target/site/jacoco/jacoco.xml + files: target/site/jacoco-aggregate/jacoco.xml token: ${{ secrets.CODECOV_TOKEN }} verbose: true From 56514dc604e2b42f86b2263b9197cd8fdfb9c9e5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 10:12:00 +0100 Subject: [PATCH 53/86] Remove hardcoded path --- .github/workflows/cibuild.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 2331179b832..a44257b7bf3 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -52,14 +52,13 @@ jobs: # https://github.com/actions/runner-images/issues/1499 # we set nodePath and npmPath to skip downloading the node binary, which frequently times out run: | - mvn $MAVEN_ARGS jacoco:prepare-agent test jacoco:report-aggregate -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm + mvn $MAVEN_ARGS jacoco:prepare-agent test jacoco:report -P prettierCheck -Dprettier.nodePath=node -Dprettier.npmPath=npm mvn $MAVEN_ARGS package -Dmaven.test.skip -P prettierSkip - name: Send coverage data to codecov.io if: github.repository_owner == 'opentripplanner' uses: codecov/codecov-action@v4 with: - files: target/site/jacoco-aggregate/jacoco.xml token: ${{ secrets.CODECOV_TOKEN }} verbose: true From 28fbcf1f63909f4a9c0950757bae94a52c46b8cb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 14:12:51 +0100 Subject: [PATCH 54/86] Add test result uploads --- .github/workflows/cibuild.yml | 7 +++++++ pom.xml | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index a44257b7bf3..84bdf2e84f0 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -62,6 +62,13 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true + - name: Upload test results to Codecov + # always upload test results, even when failed + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Deploy to Github Package Registry if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev-1.x' || github.ref == 'refs/heads/dev-2.x') env: diff --git a/pom.xml b/pom.xml index e043093c928..03b94662123 100644 --- a/pom.xml +++ b/pom.xml @@ -221,8 +221,6 @@ --add-opens java.base/sun.invoke.util=ALL-UNNAMED --add-opens java.xml/org.xml.sax.helpers=ALL-UNNAMED - - true plain true From 9063f9337a3635045dcf6aebe5eb0878a4175bf5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 21:30:11 +0100 Subject: [PATCH 55/86] Add file pattern for all tests --- .github/workflows/cibuild.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 84bdf2e84f0..212c3934dc2 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -68,6 +68,7 @@ jobs: uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} + files: "*TEST-*.xml" - name: Deploy to Github Package Registry if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev-1.x' || github.ref == 'refs/heads/dev-2.x') From c398d54a9350f634a25f30c27ae1575804ca2991 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 21:55:40 +0100 Subject: [PATCH 56/86] Add test case --- .../LegacyRouteRequestMapperTest.java | 2 +- .../routerequest/RouteRequestMapperTest.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index aeda59f081b..cd926bacf6e 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -262,7 +262,7 @@ void transferSlack() { } @Test - void passThroughPoints() { + void via() { Map arguments = Map.of( "via", List.of( diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index d4e48b63141..b60f7bede7e 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -288,6 +288,28 @@ void testItineraryFilters() { assertEquals(multiplier, itinFilter.groupedOtherThanSameLegsMaxCostMultiplier()); } + @Test + void via() { + Map arguments = createArgsCopy(ARGS); + arguments.put( + "via", + List.of( + Map.of("passThrough", Map.of("stopLocationIds", List.of("F:stop1"), "label", "a label")) + ) + ); + + var routeRequest = RouteRequestMapper.toRouteRequest( + executionContext(arguments, LOCALE, CONTEXT), CONTEXT + ); + assertEquals( + "[PassThroughViaLocation{label: a label, stopLocationIds: [F:stop1]}]", + routeRequest.getViaLocations().toString() + ); + + var noParamsReq = RouteRequestMapper.toRouteRequest(executionContext(ARGS, LOCALE, CONTEXT), CONTEXT); + assertEquals(List.of(), noParamsReq.getViaLocations()); + } + static Map createArgsCopy(Map arguments) { Map newArgs = new HashMap<>(); newArgs.putAll(arguments); From ff02dd427caa78fd91f9a6ca77771685471c9151 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 22:03:37 +0100 Subject: [PATCH 57/86] Add test case --- .../gtfs/mapping/routerequest/RouteRequestMapperTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index b60f7bede7e..cf1dc759a3e 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -299,14 +299,18 @@ void via() { ); var routeRequest = RouteRequestMapper.toRouteRequest( - executionContext(arguments, LOCALE, CONTEXT), CONTEXT + executionContext(arguments, LOCALE, CONTEXT), + CONTEXT ); assertEquals( "[PassThroughViaLocation{label: a label, stopLocationIds: [F:stop1]}]", routeRequest.getViaLocations().toString() ); - var noParamsReq = RouteRequestMapper.toRouteRequest(executionContext(ARGS, LOCALE, CONTEXT), CONTEXT); + var noParamsReq = RouteRequestMapper.toRouteRequest( + executionContext(ARGS, LOCALE, CONTEXT), + CONTEXT + ); assertEquals(List.of(), noParamsReq.getViaLocations()); } From f900796071e479f2cd92aaee911254ccbdde21da Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 5 Nov 2024 06:51:04 +0100 Subject: [PATCH 58/86] Fix imports --- .../apis/gtfs/mapping/routerequest/ViaLocationMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java index 0b3d65e90b7..984f67855e8 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.Map; import javax.annotation.Nullable; -import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.routing.api.request.via.PassThroughViaLocation; import org.opentripplanner.routing.api.request.via.ViaLocation; import org.opentripplanner.routing.api.request.via.VisitViaLocation; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.utils.collection.ListUtils; /** * Maps the input data to the data structure needed for via routing. From 15c862d11b8366cdfacd013858cae5d4d64beaf7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 4 Nov 2024 11:59:53 +0100 Subject: [PATCH 59/86] Fix conversion of coordinates --- .../transit/model/network/TripPatternBuilder.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java index 5f9be0da455..66f695521c0 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java +++ b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java @@ -1,7 +1,5 @@ package org.opentripplanner.transit.model.network; -import static java.util.Objects.requireNonNullElseGet; - import java.util.ArrayList; import java.util.List; import java.util.function.UnaryOperator; @@ -233,15 +231,8 @@ private List generateHopGeometriesFromOriginalTripPattern() { hopGeometry != null && stopPattern.sameStations(originalTripPattern.getStopPattern(), i) ) { // Use old geometry but patch first and last point with new stops - var newStart = new Coordinate( - stopPattern.getStop(i).getCoordinate().longitude(), - stopPattern.getStop(i).getCoordinate().latitude() - ); - - var newEnd = new Coordinate( - stopPattern.getStop(i + 1).getCoordinate().longitude(), - stopPattern.getStop(i + 1).getCoordinate().latitude() - ); + var newStart = stopPattern.getStop(i).getCoordinate().asJtsCoordinate(); + var newEnd = stopPattern.getStop(i + 1).getCoordinate().asJtsCoordinate(); Coordinate[] coordinates = originalTripPattern.getHopGeometry(i).getCoordinates().clone(); coordinates[0].setCoordinate(newStart); From f92ff7e2799a8e046bae7e1426d4d1b19ced288d Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 5 Nov 2024 14:44:38 +0000 Subject: [PATCH 60/86] remove unused import Co-authored-by: Leonard Ehrenfried --- .../java/org/opentripplanner/api/parameter/ApiRequestMode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java index 5681bd1d76d..7ac2c6f7d86 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java @@ -2,7 +2,6 @@ import java.util.Collection; import java.util.List; -import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; public enum ApiRequestMode { From d92a7fe8aeada39e1d64d7bbae3ec6baf78acbe5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 6 Nov 2024 10:12:48 +0100 Subject: [PATCH 61/86] fix: Return empty list if there is no siriUrls in situations[]/infoLinks. --- .../apis/transmodel/model/siri/sx/PtSituationElementType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java index 1567cd977c9..4fd9505b73e 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java @@ -241,7 +241,7 @@ public static GraphQLObjectType create( if (!siriUrls.isEmpty()) { return siriUrls; } - return null; + return emptyList(); }) .build() ) From f4924468169adb1ca188fbf6e73a85135feca7d1 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 6 Nov 2024 11:49:19 +0100 Subject: [PATCH 62/86] Ignore situation alerts without url or siri uris --- .../model/framework/InfoLinkType.java | 4 +- .../framework/i18n/I18NString.java | 22 +++++++-- .../routing/alertpatch/AlertUrl.java | 9 ++-- .../updater/siri/SiriAlertsUpdateHandler.java | 47 ++++++++++++------- .../framework/i18n/I18NStringTest.java | 32 +++++++++++++ .../siri/SiriAlertsUpdateHandlerTest.java | 4 +- 6 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 application/src/test/java/org/opentripplanner/framework/i18n/I18NStringTest.java diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java index 6541b48a44d..e0472a2ccf8 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java @@ -20,7 +20,7 @@ public static GraphQLObjectType create() { .description("URI") .dataFetcher(environment -> { AlertUrl source = environment.getSource(); - return source.uri; + return source.uri(); }) .build() ) @@ -32,7 +32,7 @@ public static GraphQLObjectType create() { .description("Label") .dataFetcher(environment -> { AlertUrl source = environment.getSource(); - return source.label; + return source.label(); }) .build() ) diff --git a/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java b/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java index 4f75d214c91..101342c7a6f 100644 --- a/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java +++ b/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java @@ -1,6 +1,7 @@ package org.opentripplanner.framework.i18n; import java.util.Locale; +import javax.annotation.Nullable; /** * This interface is used when providing translations on server side. Sources: OSM tags with @@ -9,9 +10,20 @@ * @author mabu */ public interface I18NString { - /** true if the given value is not {@code null} or has at least one none white-space character. */ - public static boolean hasValue(I18NString value) { - return value != null && !value.toString().isBlank(); + /** + * Return {@code true} if the given value is not {@code null} or has at least one none + * white-space character. + */ + static boolean hasValue(@Nullable I18NString value) { + return !hasNoValue(value); + } + + /** + * Return {@code true} if the given value has at least one none white-space character. + * Return {@code false} if the value is {@code null} or blank. + */ + static boolean hasNoValue(@Nullable I18NString value) { + return value == null || value.toString().isBlank(); } /** @@ -26,8 +38,8 @@ public static boolean hasValue(I18NString value) { */ String toString(Locale locale); - static I18NString assertHasValue(I18NString value) { - if (value == null || value.toString().isBlank()) { + static I18NString assertHasValue(@Nullable I18NString value) { + if (hasNoValue(value)) { throw new IllegalArgumentException( "Value can not be null, empty or just whitespace: " + (value == null ? "null" : "'" + value + "'") diff --git a/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java b/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java index b13fad6e97b..fcce3720538 100644 --- a/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java +++ b/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java @@ -1,7 +1,10 @@ package org.opentripplanner.routing.alertpatch; -public class AlertUrl { +import java.util.Objects; +import javax.annotation.Nullable; - public String uri; - public String label; +public record AlertUrl(String uri, @Nullable String label) { + public AlertUrl { + Objects.requireNonNull(uri); + } } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java b/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java index a5817eca41b..9656d1a0b01 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java @@ -135,9 +135,9 @@ private TransitAlert mapSituationToAlert( TransitAlertBuilder alert = createAlertWithTexts(situation); if ( - (alert.headerText() == null || alert.headerText().toString().isEmpty()) && - (alert.descriptionText() == null || alert.descriptionText().toString().isEmpty()) && - (alert.detailText() == null || alert.detailText().toString().isEmpty()) + I18NString.hasNoValue(alert.headerText()) && + I18NString.hasNoValue(alert.descriptionText()) && + I18NString.hasNoValue(alert.detailText()) ) { LOG.debug( "Empty Alert - ignoring situationNumber: {}", @@ -221,18 +221,18 @@ private long getEpochSecond(ZonedDateTime startTime) { private TransitAlertBuilder createAlertWithTexts(PtSituationElement situation) { return TransitAlert .of(new FeedScopedId(feedId, situation.getSituationNumber().getValue())) - .withDescriptionText(getTranslatedString(situation.getDescriptions())) - .withDetailText(getTranslatedString(situation.getDetails())) - .withAdviceText(getTranslatedString(situation.getAdvices())) - .withHeaderText(getTranslatedString(situation.getSummaries())) - .withUrl(getInfoLinkAsString(situation.getInfoLinks())) - .addSiriUrls(getInfoLinks(situation.getInfoLinks())); + .withDescriptionText(mapTranslatedString(situation.getDescriptions())) + .withDetailText(mapTranslatedString(situation.getDetails())) + .withAdviceText(mapTranslatedString(situation.getAdvices())) + .withHeaderText(mapTranslatedString(situation.getSummaries())) + .withUrl(mapInfoLinkToI18NString(situation.getInfoLinks())) + .addSiriUrls(mapInfoLinks(situation)); } /* * Returns first InfoLink-uri as a String */ - private I18NString getInfoLinkAsString(PtSituationElement.InfoLinks infoLinks) { + private I18NString mapInfoLinkToI18NString(PtSituationElement.InfoLinks infoLinks) { if (infoLinks != null) { if (isNotEmpty(infoLinks.getInfoLinks())) { InfoLinkStructure infoLinkStructure = infoLinks.getInfoLinks().get(0); @@ -247,21 +247,32 @@ private I18NString getInfoLinkAsString(PtSituationElement.InfoLinks infoLinks) { /* * Returns all InfoLinks */ - private List getInfoLinks(PtSituationElement.InfoLinks infoLinks) { + private List mapInfoLinks(PtSituationElement situation) { + PtSituationElement.InfoLinks infoLinks = situation.getInfoLinks(); List alertUrls = new ArrayList<>(); if (infoLinks != null) { if (isNotEmpty(infoLinks.getInfoLinks())) { for (InfoLinkStructure infoLink : infoLinks.getInfoLinks()) { - AlertUrl alertUrl = new AlertUrl(); - + String label = null; List labels = infoLink.getLabels(); if (labels != null && !labels.isEmpty()) { - NaturalLanguageStringStructure label = labels.get(0); - alertUrl.label = label.getValue(); + NaturalLanguageStringStructure lbl = labels.get(0); + label = lbl.getValue(); } - alertUrl.uri = infoLink.getUri(); - alertUrls.add(alertUrl); + var uri = infoLink.getUri(); + if (uri != null) { + alertUrls.add(new AlertUrl(uri, label)); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug( + "URI missing in info-link - ignoring info-link in situation: {}", + situation.getSituationNumber() != null + ? situation.getSituationNumber().getValue() + : null + ); + } + } } } } @@ -281,7 +292,7 @@ private boolean isNotEmpty(List list) { * * @return A TranslatedString containing the same information as the input */ - private I18NString getTranslatedString(List input) { + private I18NString mapTranslatedString(List input) { Map translations = new HashMap<>(); if (input != null && input.size() > 0) { for (DefaultedTextStructure textStructure : input) { diff --git a/application/src/test/java/org/opentripplanner/framework/i18n/I18NStringTest.java b/application/src/test/java/org/opentripplanner/framework/i18n/I18NStringTest.java new file mode 100644 index 00000000000..101da6edc91 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/framework/i18n/I18NStringTest.java @@ -0,0 +1,32 @@ +package org.opentripplanner.framework.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class I18NStringTest { + + private final I18NString noValue = I18NString.of(" \t\n\r\f"); + private final I18NString hasValue = I18NString.of("A value"); + + @Test + void hasValue() { + assertTrue(I18NString.hasValue(hasValue)); + assertFalse(I18NString.hasValue(noValue)); + } + + @Test + void hasNoValue() { + assertFalse(I18NString.hasNoValue(hasValue)); + assertTrue(I18NString.hasNoValue(noValue)); + } + + @Test + void assertHasValue() { + var ex = assertThrows(IllegalArgumentException.class, () -> I18NString.assertHasValue(noValue)); + assertEquals("Value can not be null, empty or just whitespace: ' \t\n\r\f'", ex.getMessage()); + } +} diff --git a/application/src/test/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandlerTest.java b/application/src/test/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandlerTest.java index 94c4af8d22e..3cd8e50c000 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandlerTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandlerTest.java @@ -198,8 +198,8 @@ public void testSiriSxUpdateForStop() { final List alertUrlList = transitAlert.siriUrls(); AlertUrl alertUrl = alertUrlList.get(0); - assertEquals(infoLinkUri, alertUrl.uri); - assertEquals(infoLinkLabel, alertUrl.label); + assertEquals(infoLinkUri, alertUrl.uri()); + assertEquals(infoLinkLabel, alertUrl.label()); } @Test From 7688365c59153ac49c14c2974c471e74af9786c0 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 7 Nov 2024 10:01:03 +0000 Subject: [PATCH 63/86] Add changelog entry for #6136 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 06aaf674f9e..57b54a67e1a 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -32,6 +32,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix NullPointerException when searching backwards with a frequency-based trip [#6211](https://github.com/opentripplanner/OpenTripPlanner/pull/6211) - Combine two multi-criteria searches in Raptor [#6182](https://github.com/opentripplanner/OpenTripPlanner/pull/6182) - Implement alert node query in GTFS GraphQL API [#6225](https://github.com/opentripplanner/OpenTripPlanner/pull/6225) +- Fix hop geometries when one pattern is replaced by another with different number of stops [#6136](https://github.com/opentripplanner/OpenTripPlanner/pull/6136) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 13949af020f1fd66ae450515291fffa6f1a936c0 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 7 Nov 2024 10:19:41 +0000 Subject: [PATCH 64/86] remove unused import Co-authored-by: Leonard Ehrenfried --- .../org/opentripplanner/gtfs/mapping/TransitModeMapper.java | 1 - .../org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java index aef5399475e..919d71455a2 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java @@ -1,6 +1,5 @@ package org.opentripplanner.gtfs.mapping; -import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; public class TransitModeMapper { diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java index d5086d153b6..55e6f3a0d86 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/TransitModeMapperTest.java @@ -17,11 +17,9 @@ import static org.opentripplanner.transit.model.basic.TransitMode.TROLLEYBUS; import java.util.stream.Stream; -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.framework.application.OTPFeature; import org.opentripplanner.transit.model.basic.TransitMode; class TransitModeMapperTest { From 3eb39f1b053b3f8030838464749d537704328ddf Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 7 Nov 2024 13:02:49 +0000 Subject: [PATCH 65/86] Add changelog entry for #6171 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 57b54a67e1a..adaa6ada3ac 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -33,6 +33,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Combine two multi-criteria searches in Raptor [#6182](https://github.com/opentripplanner/OpenTripPlanner/pull/6182) - Implement alert node query in GTFS GraphQL API [#6225](https://github.com/opentripplanner/OpenTripPlanner/pull/6225) - Fix hop geometries when one pattern is replaced by another with different number of stops [#6136](https://github.com/opentripplanner/OpenTripPlanner/pull/6136) +- Distinct coach from bus when reading in GTFS data and in GTFS GraphQL API [#6171](https://github.com/opentripplanner/OpenTripPlanner/pull/6171) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From a9a23fdb0dd4a7f613b3dc0b130a7a2d6311db55 Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 7 Nov 2024 13:03:36 +0000 Subject: [PATCH 66/86] Bump serialization version id for #6171 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index abc70e5dc8c..caa3b6779ba 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 170 + 171 32.0 2.52 From 5438e020be026333ec0b28f546b1e14ee1fb585c Mon Sep 17 00:00:00 2001 From: Eivind Morris Bakke Date: Thu, 7 Nov 2024 15:07:30 +0100 Subject: [PATCH 67/86] Add provider of updates as a dimension to metrics. (#6199) * Adds provider / datasource of updates as a dimension to metrics. * Changes provider to producer and addresses smaller comments in PR. * Updates how counters are incremented and kept track of in StreamingTripUpdateMetrics. Also adds more details to the new producerMetrics flag, and renames it from detailedMetrics. * Addresses comments from PR. * Addresses comments from PR. * Addresses comments from PR and regenerates autogenerated documentation. * Allows for dataSource to be null in AddedTripBuilder. --- .../model/RealTimeTripUpdate.java | 43 ++++++++-- .../model/TimetableSnapshot.java | 3 +- .../SiriETGooglePubsubUpdaterConfig.java | 6 ++ .../updaters/SiriETUpdaterConfig.java | 8 +- .../transit/model/framework/FeedScopedId.java | 2 +- .../updater/siri/AddedTripBuilder.java | 25 ++++-- .../updater/siri/ModifiedTripBuilder.java | 18 ++-- .../siri/SiriTimetableSnapshotSource.java | 16 ++-- .../updater/siri/TripUpdate.java | 26 ++++-- .../siri/updater/SiriETUpdaterParameters.java | 3 +- .../SiriETGooglePubsubUpdaterParameters.java | 3 +- .../spi/DataValidationExceptionMapper.java | 12 ++- .../updater/spi/UpdateError.java | 20 ++++- .../updater/spi/UpdateResult.java | 35 ++++++-- .../updater/spi/UpdateSuccess.java | 21 ++--- .../updater/trip/UrlUpdaterParameters.java | 4 + .../metrics/StreamingTripUpdateMetrics.java | 86 ++++++++----------- .../updater/siri/AddedTripBuilderTest.java | 27 ++++-- .../updater/siri/ModifiedTripBuilderTest.java | 24 ++++-- .../updater/siri/SiriEtBuilder.java | 1 + .../sandbox/siri/SiriGooglePubSubUpdater.md | 1 + doc/user/sandbox/siri/SiriUpdater.md | 1 + 22 files changed, 254 insertions(+), 131 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java index e5bcc6c0322..28fc16a98ac 100644 --- a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java +++ b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java @@ -9,12 +9,19 @@ /** * Represents the real-time update of a single trip. - * @param pattern the pattern to which belongs the updated trip. This can be a new pattern created in real-time. - * @param updatedTripTimes the new trip times for the updated trip. - * @param serviceDate the service date for which this update applies (updates are valid only for one service date) - * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip. - * @param tripCreation true if this update creates a new trip, not present in scheduled data. - * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created. + * + * @param pattern the pattern to which belongs the updated trip. This can be a new + * pattern created in real-time. + * @param updatedTripTimes the new trip times for the updated trip. + * @param serviceDate the service date for which this update applies (updates are valid + * only for one service date) + * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the + * TripOnServiceDate corresponding to this new trip. + * @param tripCreation true if this update creates a new trip, not present in scheduled + * data. + * @param routeCreation true if an added trip cannot be registered under an existing route + * and a new route must be created. + * @param producer the producer of the real-time update. */ public record RealTimeTripUpdate( TripPattern pattern, @@ -22,7 +29,8 @@ public record RealTimeTripUpdate( LocalDate serviceDate, @Nullable TripOnServiceDate addedTripOnServiceDate, boolean tripCreation, - boolean routeCreation + boolean routeCreation, + @Nullable String producer ) { public RealTimeTripUpdate { Objects.requireNonNull(pattern); @@ -38,6 +46,25 @@ public RealTimeTripUpdate( TripTimes updatedTripTimes, LocalDate serviceDate ) { - this(pattern, updatedTripTimes, serviceDate, null, false, false); + this(pattern, updatedTripTimes, serviceDate, null, false, false, null); + } + + public RealTimeTripUpdate( + TripPattern pattern, + TripTimes updatedTripTimes, + LocalDate serviceDate, + @Nullable TripOnServiceDate addedTripOnServiceDate, + boolean tripCreation, + boolean routeCreation + ) { + this( + pattern, + updatedTripTimes, + serviceDate, + addedTripOnServiceDate, + tripCreation, + routeCreation, + null + ); } } diff --git a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java index 0a54fc964d8..94b490c48a0 100644 --- a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java +++ b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java @@ -347,8 +347,7 @@ public Result update(RealTimeTripUpdate realTimeTrip } // The time tables are finished during the commit - - return Result.success(UpdateSuccess.noWarnings()); + return Result.success(UpdateSuccess.noWarnings(realTimeTripUpdate.producer())); } /** diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java index 65fb7bb2c11..3396e56f6e3 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.INITIAL_GET_DATA_TIMEOUT; import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.RECONNECT_PERIOD; @@ -79,6 +80,11 @@ If this parameter is set, the updater will be marked as initialized (primed) onl .of("fuzzyTripMatching") .since(V2_1) .summary("If the trips should be matched fuzzily.") + .asBoolean(false), + c + .of("producerMetrics") + .since(V2_7) + .summary("If failure, success, and warning metrics should be collected per producer.") .asBoolean(false) ); } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java index 4402ba83b7e..1b0eb2e4420 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java @@ -2,6 +2,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -43,7 +44,12 @@ public static SiriETUpdaterParameters create(String configRef, NodeAdapter c) { .since(V2_0) .summary("If the fuzzy trip matcher should be used to match trips.") .asBoolean(false), - HttpHeadersConfig.headers(c, V2_3) + HttpHeadersConfig.headers(c, V2_3), + c + .of("producerMetrics") + .since(V2_7) + .summary("If failure, success, and warning metrics should be collected per producer.") + .asBoolean(false) ); } } diff --git a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java index be12be83548..0b269220ae1 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java +++ b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java @@ -78,7 +78,7 @@ public static boolean isValidString(String value) throws IllegalArgumentExceptio /** * Concatenate feedId and id into a string. */ - public static String concatenateId(String feedId, String id) { + private static String concatenateId(String feedId, String id) { return feedId + ID_SEPARATOR + id; } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java index a1ca89f5c83..aff1659653b 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java @@ -56,6 +56,7 @@ class AddedTripBuilder { private final Function getTripPatternId; private final FeedScopedId tripId; private final Operator operator; + private final String dataSource; private final String lineRef; private final Route replacedRoute; private final LocalDate serviceDate; @@ -81,11 +82,14 @@ class AddedTripBuilder { Objects.requireNonNull(newServiceJourneyRef, "EstimatedVehicleJourneyCode is required"); tripId = entityResolver.resolveId(newServiceJourneyRef); - //OperatorRef of added trip + // OperatorRef of added trip Objects.requireNonNull(estimatedVehicleJourney.getOperatorRef(), "OperatorRef is required"); String operatorRef = estimatedVehicleJourney.getOperatorRef().getValue(); operator = entityResolver.resolveOperator(operatorRef); + // DataSource of added trip + dataSource = estimatedVehicleJourney.getDataSource(); + // LineRef of added trip Objects.requireNonNull(estimatedVehicleJourney.getLineRef(), "LineRef is required"); lineRef = estimatedVehicleJourney.getLineRef().getValue(); @@ -135,7 +139,8 @@ class AddedTripBuilder { boolean cancellation, String shortName, String headsign, - List replacedTrips + List replacedTrips, + String dataSource ) { this.transitService = transitService; this.entityResolver = entityResolver; @@ -155,20 +160,21 @@ class AddedTripBuilder { this.shortName = shortName; this.headsign = headsign; this.replacedTrips = replacedTrips; + this.dataSource = dataSource; } Result build() { if (calls.size() < 2) { - return UpdateError.result(tripId, TOO_FEW_STOPS); + return UpdateError.result(tripId, TOO_FEW_STOPS, dataSource); } if (serviceDate == null) { - return UpdateError.result(tripId, NO_START_DATE); + return UpdateError.result(tripId, NO_START_DATE, dataSource); } FeedScopedId calServiceId = transitService.getOrCreateServiceIdForDate(serviceDate); if (calServiceId == null) { - return UpdateError.result(tripId, NO_START_DATE); + return UpdateError.result(tripId, NO_START_DATE, dataSource); } boolean isAddedRoute = false; @@ -176,7 +182,7 @@ Result build() { if (route == null) { Agency agency = resolveAgency(); if (agency == null) { - return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY); + return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY, dataSource); } route = createRoute(agency); isAddedRoute = true; @@ -201,7 +207,7 @@ Result build() { // Drop this update if the call refers to an unknown stop (not present in the site repository). if (stopTime == null) { - return UpdateError.result(tripId, NO_VALID_STOPS); + return UpdateError.result(tripId, NO_VALID_STOPS, dataSource); } aimedStopTimes.add(stopTime); @@ -256,7 +262,7 @@ Result build() { try { updatedTripTimes.validateNonIncreasingTimes(); } catch (DataValidationException e) { - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, dataSource); } var tripOnServiceDate = TripOnServiceDate @@ -273,7 +279,8 @@ Result build() { serviceDate, tripOnServiceDate, pattern, - isAddedRoute + isAddedRoute, + dataSource ) ); } diff --git a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java index 0f1475f7b50..06768087c74 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java @@ -45,6 +45,7 @@ public class ModifiedTripBuilder { private final boolean cancellation; private final OccupancyEnumeration occupancy; private final boolean predictionInaccurate; + private final String dataSource; public ModifiedTripBuilder( TripTimes existingTripTimes, @@ -64,6 +65,7 @@ public ModifiedTripBuilder( cancellation = TRUE.equals(journey.isCancellation()); predictionInaccurate = TRUE.equals(journey.isPredictionInaccurate()); occupancy = journey.getOccupancy(); + dataSource = journey.getDataSource(); } /** @@ -78,7 +80,8 @@ public ModifiedTripBuilder( List calls, boolean cancellation, OccupancyEnumeration occupancy, - boolean predictionInaccurate + boolean predictionInaccurate, + String dataSource ) { this.existingTripTimes = existingTripTimes; this.pattern = pattern; @@ -89,6 +92,7 @@ public ModifiedTripBuilder( this.cancellation = cancellation; this.occupancy = occupancy; this.predictionInaccurate = predictionInaccurate; + this.dataSource = dataSource; } /** @@ -103,7 +107,9 @@ public Result build() { if (cancellation || stopPattern.isAllStopsNonRoutable()) { LOG.debug("Trip is cancelled"); newTimes.cancelTrip(); - return Result.success(new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate)); + return Result.success( + new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate, dataSource) + ); } applyUpdates(newTimes); @@ -116,7 +122,7 @@ public Result build() { newTimes.setRealTimeState(RealTimeState.MODIFIED); } - // TODO - Handle DataValidationException at the outemost level(pr trip) + // TODO - Handle DataValidationException at the outermost level (pr trip) try { newTimes.validateNonIncreasingTimes(); } catch (DataValidationException e) { @@ -125,7 +131,7 @@ public Result build() { newTimes.getTrip().getId(), e.getMessage() ); - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, dataSource); } int numStopsInUpdate = newTimes.getNumStops(); @@ -137,11 +143,11 @@ public Result build() { numStopsInUpdate, numStopsInPattern ); - return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS); + return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS, dataSource); } LOG.debug("A valid TripUpdate object was applied using the Timetable class update method."); - return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate)); + return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate, dataSource)); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java index b6aa5310d83..33ca220ca7d 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java @@ -174,7 +174,7 @@ private Result apply( /* commit */ return addTripToGraphAndBuffer(result.successValue()); } catch (DataValidationException e) { - return DataValidationExceptionMapper.toResult(e); + return DataValidationExceptionMapper.toResult(e, journey.getDataSource()); } catch (Exception e) { LOG.warn( "{} EstimatedJourney {} failed.", @@ -217,6 +217,7 @@ private Result handleModifiedTrip( EstimatedVehicleJourney estimatedVehicleJourney ) { Trip trip = entityResolver.resolveTrip(estimatedVehicleJourney); + String dataSource = estimatedVehicleJourney.getDataSource(); // Check if EstimatedVehicleJourney is reported as NOT monitored, ignore the notMonitored-flag // if the journey is NOT monitored because it has been cancelled @@ -224,13 +225,13 @@ private Result handleModifiedTrip( !TRUE.equals(estimatedVehicleJourney.isMonitored()) && !TRUE.equals(estimatedVehicleJourney.isCancellation()) ) { - return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED); + return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED, dataSource); } LocalDate serviceDate = entityResolver.resolveServiceDate(estimatedVehicleJourney); if (serviceDate == null) { - return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE); + return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE, dataSource); } TripPattern pattern; @@ -252,20 +253,20 @@ private Result handleModifiedTrip( "No trips found for EstimatedVehicleJourney. {}", DebugString.of(estimatedVehicleJourney) ); - return UpdateError.result(null, NO_FUZZY_TRIP_MATCH); + return UpdateError.result(null, NO_FUZZY_TRIP_MATCH, dataSource); } trip = tripAndPattern.trip(); pattern = tripAndPattern.tripPattern(); } else { - return UpdateError.result(null, TRIP_NOT_FOUND); + return UpdateError.result(null, TRIP_NOT_FOUND, dataSource); } Timetable currentTimetable = getCurrentTimetable(pattern, serviceDate); TripTimes existingTripTimes = currentTimetable.getTripTimes(trip); if (existingTripTimes == null) { LOG.debug("tripId {} not found in pattern.", trip.getId()); - return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN); + return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN, dataSource); } var updateResult = new ModifiedTripBuilder( existingTripTimes, @@ -315,7 +316,8 @@ private Result addTripToGraphAndBuffer(TripUpdate tr serviceDate, tripUpdate.addedTripOnServiceDate(), tripUpdate.tripCreation(), - tripUpdate.routeCreation() + tripUpdate.routeCreation(), + tripUpdate.dataSource() ); var result = snapshotManager.updateBuffer(realTimeTripUpdate); LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate); diff --git a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java index 71c521b76fb..8432ed06d54 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java @@ -11,12 +11,18 @@ /** * Represents the SIRI real-time update of a single trip. - * @param stopPattern the stop pattern to which belongs the updated trip. - * @param tripTimes the new trip times for the updated trip. - * @param serviceDate the service date for which this update applies (updates are valid only for one service date) - * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip. - * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new trip pattern to this new trip. - * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created. + * + * @param stopPattern the stop pattern to which belongs the updated trip. + * @param tripTimes the new trip times for the updated trip. + * @param serviceDate the service date for which this update applies (updates are valid + * only for one service date) + * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the + * TripOnServiceDate corresponding to this new trip. + * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new + * trip pattern to this new trip. + * @param routeCreation true if an added trip cannot be registered under an existing route + * and a new route must be created. + * @param dataSource the dataSource of the real-time update. */ record TripUpdate( StopPattern stopPattern, @@ -24,7 +30,8 @@ record TripUpdate( LocalDate serviceDate, @Nullable TripOnServiceDate addedTripOnServiceDate, @Nullable TripPattern addedTripPattern, - boolean routeCreation + boolean routeCreation, + @Nullable String dataSource ) { public TripUpdate { Objects.requireNonNull(stopPattern); @@ -38,9 +45,10 @@ record TripUpdate( public TripUpdate( StopPattern stopPattern, RealTimeTripTimes updatedTripTimes, - LocalDate serviceDate + LocalDate serviceDate, + String dataSource ) { - this(stopPattern, updatedTripTimes, serviceDate, null, null, false); + this(stopPattern, updatedTripTimes, serviceDate, null, null, false, dataSource); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java index 8bbe66559c6..dc479c034e1 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java @@ -15,7 +15,8 @@ public record SiriETUpdaterParameters( Duration timeout, Duration previewInterval, boolean fuzzyTripMatching, - HttpHeaders httpRequestHeaders + HttpHeaders httpRequestHeaders, + boolean producerMetrics ) implements PollingGraphUpdaterParameters, UrlUpdaterParameters, SiriETHttpTripUpdateSource.Parameters { diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java index 2970ac114c0..d7fb064966a 100644 --- a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java @@ -15,7 +15,8 @@ public record SiriETGooglePubsubUpdaterParameters( @Nullable String dataInitializationUrl, Duration reconnectPeriod, Duration initialGetDataTimeout, - boolean fuzzyTripMatching + boolean fuzzyTripMatching, + boolean producerMetrics ) implements UrlUpdaterParameters { public static Duration RECONNECT_PERIOD = Duration.ofSeconds(30); diff --git a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java index dee103bc385..2a650f684f9 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java @@ -1,5 +1,6 @@ package org.opentripplanner.updater.spi; +import com.beust.jcommander.internal.Nullable; import org.opentripplanner.transit.model.framework.DataValidationException; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.timetable.TimetableValidationError; @@ -15,13 +16,20 @@ public class DataValidationExceptionMapper { private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class); public static Result toResult(DataValidationException error) { + return toResult(error, null); + } + + public static Result toResult( + DataValidationException error, + @Nullable String producer + ) { if (error.error() instanceof TimetableValidationError tt) { return Result.failure( - new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex()) + new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex(), producer) ); } // The mapper should handle all possible errors - LOG.error("Unhandled error: " + error.getMessage(), error); + LOG.error("Unhandled error: {}", error.getMessage(), error); return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN)); } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java index 1f568ba99a4..4e81cf3c2a7 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java @@ -11,10 +11,18 @@ public record UpdateError( @Nullable FeedScopedId tripId, UpdateErrorType errorType, - @Nullable Integer stopIndex + @Nullable Integer stopIndex, + @Nullable String producer ) { public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType) { - this(tripId, errorType, null); + this(tripId, errorType, null, null); + } + + public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, Integer stopIndex) { + this(tripId, errorType, stopIndex, null); + } + public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, String producer) { + this(tripId, errorType, null, producer); } public String debugId() { @@ -56,6 +64,14 @@ public static Result result(FeedScopedId tripId, UpdateError return Result.failure(new UpdateError(tripId, errorType)); } + public static Result result( + FeedScopedId tripId, + UpdateErrorType errorType, + String producer + ) { + return Result.failure(new UpdateError(tripId, errorType, producer)); + } + public static UpdateError noTripId(UpdateErrorType errorType) { return new UpdateError(null, errorType); } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java index 643703f2252..0e47d8d202f 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java @@ -1,6 +1,7 @@ package org.opentripplanner.updater.spi; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.util.List; @@ -15,23 +16,43 @@ public record UpdateResult( int successful, int failed, Multimap failures, - List warnings + List warnings, + List successes, + List errors ) { /** * Create an empty result. */ public static UpdateResult empty() { - return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of()); + return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of(), List.of(), List.of()); } /** * Aggregate a list of results into an instance of {@link UpdateResult}. */ public static UpdateResult ofResults(List> results) { - var errors = results.stream().filter(Result::isFailure).map(Result::failureValue).toList(); - var successes = results.stream().filter(Result::isSuccess).map(Result::successValue).toList(); - var warnings = successes.stream().flatMap(s -> s.warnings().stream()).toList(); - var errorIndex = Multimaps.index(errors, UpdateError::errorType); - return new UpdateResult(successes.size(), errors.size(), errorIndex, warnings); + List errors = results + .stream() + .filter(Result::isFailure) + .map(Result::failureValue) + .toList(); + List successes = results + .stream() + .filter(Result::isSuccess) + .map(Result::successValue) + .toList(); + List warnings = successes + .stream() + .flatMap(s -> s.warnings().stream()) + .toList(); + ImmutableListMultimap errorIndex = Multimaps.index(errors, UpdateError::errorType); + return new UpdateResult( + successes.size(), + errors.size(), + errorIndex, + warnings, + successes, + errors + ); } } diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java index 6d797a9a650..861f4c5edff 100644 --- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java +++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java @@ -1,34 +1,35 @@ package org.opentripplanner.updater.spi; -import java.util.Arrays; import java.util.Collection; import java.util.List; import org.opentripplanner.utils.collection.ListUtils; /** * The result of a successful application of a realtime update, for example for trips or - * vehicle positions. Its only extra information is a collection of possible warnings that - * ought to be looked at but didn't prevent the application of the update. + * vehicle positions. Its extra information is a collection of possible warnings that + * ought to be looked at but didn't prevent the application of the update and the provider of the + * update. */ -public record UpdateSuccess(List warnings) { +public record UpdateSuccess(List warnings, String producer) { /** - * Create an instance with no warnings. + * Create an instance with no warnings and no provider. */ public static UpdateSuccess noWarnings() { - return new UpdateSuccess(List.of()); + return new UpdateSuccess(List.of(), null); } + /** - * Create an instance with the provided warnings. + * Create an instance with no warnings, but a provider. */ - public static UpdateSuccess ofWarnings(WarningType... warnings) { - return new UpdateSuccess(Arrays.asList(warnings)); + public static UpdateSuccess noWarnings(String producer) { + return new UpdateSuccess(List.of(), producer); } /** * Return a copy of the instance with the provided warnings added. */ public UpdateSuccess addWarnings(Collection addedWarnings) { - return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings)); + return new UpdateSuccess(ListUtils.combine(this.warnings, addedWarnings), this.producer); } public enum WarningType { diff --git a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java index 2070aedbbd6..4918be84e45 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/UrlUpdaterParameters.java @@ -4,4 +4,8 @@ public interface UrlUpdaterParameters { String url(); String configRef(); String feedId(); + + default boolean producerMetrics() { + return false; + } } diff --git a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java index c53a398f3dc..0660f8d801b 100644 --- a/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java +++ b/application/src/main/java/org/opentripplanner/updater/trip/metrics/StreamingTripUpdateMetrics.java @@ -5,9 +5,7 @@ import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.List; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; import org.opentripplanner.updater.spi.UpdateSuccess; @@ -25,66 +23,58 @@ public class StreamingTripUpdateMetrics extends TripUpdateMetrics { protected static final String METRICS_PREFIX = "streaming_trip_updates"; - private final Counter successfulCounter; - private final Counter failureCounter; - private final Counter warningsCounter; - private final Map failuresByType = new HashMap<>(); - private final Map warningsByType = new HashMap<>(); + private final boolean producerMetrics; public StreamingTripUpdateMetrics(UrlUpdaterParameters parameters) { super(parameters); - this.successfulCounter = getCounter("successful", "Total successfully applied trip updates"); - this.failureCounter = getCounter("failed", "Total failed trip updates"); - this.warningsCounter = getCounter("warnings", "Total warnings for successful trip updates"); + this.producerMetrics = parameters.producerMetrics(); } public void setCounters(UpdateResult result) { - this.successfulCounter.increment(result.successful()); - this.failureCounter.increment(result.failed()); - this.warningsCounter.increment(result.warnings().size()); - - setFailures(result); - setWarnings(result); + incrementWarningCounts(result); + incrementFailureCounts(result); + incrementSuccessCounts(result); } - private void setWarnings(UpdateResult result) { + private void incrementWarningCounts(UpdateResult result) { for (var warningType : result.warnings()) { - var counter = warningsByType.get(warningType); - if (Objects.isNull(counter)) { - counter = - getCounter( - "warning_type", - "Total warnings by type generated by successful trip updates", - Tag.of("warningType", warningType.name()) - ); - warningsByType.put(warningType, counter); - } - counter.increment(); + Tags tags = Tags.concat(baseTags, Tags.of("warningType", warningType.name())); + Counter + .builder(METRICS_PREFIX + "." + "warnings") + .description("Total warnings by type generated by successful trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); } } - private void setFailures(UpdateResult result) { - for (var errorType : result.failures().keySet()) { - var counter = failuresByType.get(errorType); - if (Objects.isNull(counter)) { - counter = - getCounter( - "failure_type", - "Total failed trip updates by type", - Tag.of("errorType", errorType.name()) - ); - failuresByType.put(errorType, counter); + private void incrementFailureCounts(UpdateResult result) { + for (UpdateError error : result.errors()) { + Tags tags = Tags.concat(baseTags, Tags.of("errorType", error.errorType().name())); + if (producerMetrics && error.producer() != null) { + tags = tags.and(Tag.of("producer", error.producer())); } - counter.increment(result.failures().get(errorType).size()); + Counter + .builder(METRICS_PREFIX + "." + "failed") + .description("Total failed trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); } } - private Counter getCounter(String name, String description, Tag... tags) { - var finalTags = Tags.concat(Arrays.stream(tags).toList(), baseTags); - return Counter - .builder(METRICS_PREFIX + "." + name) - .description(description) - .tags(finalTags) - .register(Metrics.globalRegistry); + private void incrementSuccessCounts(UpdateResult result) { + for (UpdateSuccess success : result.successes()) { + Tags tags = Tags.of(baseTags); + if (producerMetrics && success.producer() != null) { + tags = tags.and(Tag.of("producer", success.producer())); + } + Counter + .builder(METRICS_PREFIX + "." + "successful") + .description("Total successfully applied trip updates") + .tags(tags) + .register(Metrics.globalRegistry) + .increment(); + } } } diff --git a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java index 0c01e847148..abed2144982 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/AddedTripBuilderTest.java @@ -135,7 +135,8 @@ void testAddedTrip() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -247,7 +248,8 @@ void testAddedTripOnAddedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -275,7 +277,8 @@ void testAddedTripOnAddedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -317,7 +320,8 @@ void testAddedTripOnExistingRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -351,7 +355,8 @@ void testAddedTripWithoutReplacedRoute() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -395,7 +400,8 @@ void testAddedTripFailOnMissingServiceId() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -450,7 +456,8 @@ void testAddedTripFailOnNonIncreasingDwellTime() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -489,7 +496,8 @@ void testAddedTripFailOnTooFewCalls() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); @@ -536,7 +544,8 @@ void testAddedTripFailOnUnknownStop() { false, SHORT_NAME, HEADSIGN, - List.of() + List.of(), + "DATASOURCE" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java index ad3ed749fb9..6c1797a24a2 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/ModifiedTripBuilderTest.java @@ -174,7 +174,8 @@ void testUpdateNoCalls() { List.of(), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -203,7 +204,8 @@ void testUpdateCancellation() { List.of(), true, null, - false + false, + "DATASOURCE" ) .build(); @@ -247,7 +249,8 @@ void testUpdateSameStops() { ), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -297,7 +300,8 @@ void testUpdateValidationFailure() { ), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -345,7 +349,8 @@ void testUpdateSameStopsDepartEarly() { ), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -395,7 +400,8 @@ void testUpdateUpdatedStop() { ), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -451,7 +457,8 @@ void testUpdateCascading() { ), false, null, - false + false, + "DATASOURCE" ) .build(); @@ -496,7 +503,8 @@ void testUpdateCascading() { ), false, null, - false + false, + "DATASOURCE" ) .build(); diff --git a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java index b9517e987c4..3656d9da2f0 100644 --- a/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java +++ b/application/src/test/java/org/opentripplanner/updater/siri/SiriEtBuilder.java @@ -39,6 +39,7 @@ public SiriEtBuilder(DateTimeHelper dateTimeHelper) { // Set default values evj.setMonitored(true); + evj.setDataSource("DATASOURCE"); } public List buildEstimatedTimetableDeliveries() { diff --git a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md index 58d05d5490e..5232696ad9b 100644 --- a/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md +++ b/doc/user/sandbox/siri/SiriGooglePubSubUpdater.md @@ -35,6 +35,7 @@ of the `router-config.json`. | feedId | `string` | The ID of the feed to apply the updates to. | *Optional* | | 2.1 | | fuzzyTripMatching | `boolean` | If the trips should be matched fuzzily. | *Optional* | `false` | 2.1 | | [initialGetDataTimeout](#u__12__initialGetDataTimeout) | `duration` | Timeout for retrieving the recent history of SIRI-ET messages. | *Optional* | `"PT30S"` | 2.1 | +| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.7 | | [reconnectPeriod](#u__12__reconnectPeriod) | `duration` | Wait this amount of time before trying to reconnect to the PubSub subscription. | *Optional* | `"PT30S"` | 2.1 | | [subscriptionProjectName](#u__12__subscriptionProjectName) | `string` | The Google Cloud project that hosts the PubSub subscription. | *Required* | | 2.1 | | topicName | `string` | The name of the PubSub topic that publishes the updates. | *Required* | | 2.1 | diff --git a/doc/user/sandbox/siri/SiriUpdater.md b/doc/user/sandbox/siri/SiriUpdater.md index f6c4c3f999f..28f2f9a85db 100644 --- a/doc/user/sandbox/siri/SiriUpdater.md +++ b/doc/user/sandbox/siri/SiriUpdater.md @@ -35,6 +35,7 @@ To enable the SIRI updater you need to add it to the updaters section of the `ro | frequency | `duration` | How often the updates should be retrieved. | *Optional* | `"PT1M"` | 2.0 | | fuzzyTripMatching | `boolean` | If the fuzzy trip matcher should be used to match trips. | *Optional* | `false` | 2.0 | | previewInterval | `duration` | TODO | *Optional* | | 2.0 | +| producerMetrics | `boolean` | If failure, success, and warning metrics should be collected per producer. | *Optional* | `false` | 2.7 | | requestorRef | `string` | The requester reference. | *Optional* | | 2.0 | | timeout | `duration` | The HTTP timeout to download the updates. | *Optional* | `"PT15S"` | 2.0 | | [url](#u__8__url) | `string` | The URL to send the HTTP requests to. | *Required* | | 2.0 | From a3f8ea85f82666bc9ecbf79ed097d6ad87c46748 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 7 Nov 2024 14:07:47 +0000 Subject: [PATCH 68/86] Add changelog entry for #6199 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index adaa6ada3ac..8029b2d624d 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -34,6 +34,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Implement alert node query in GTFS GraphQL API [#6225](https://github.com/opentripplanner/OpenTripPlanner/pull/6225) - Fix hop geometries when one pattern is replaced by another with different number of stops [#6136](https://github.com/opentripplanner/OpenTripPlanner/pull/6136) - Distinct coach from bus when reading in GTFS data and in GTFS GraphQL API [#6171](https://github.com/opentripplanner/OpenTripPlanner/pull/6171) +- Add provider of updates as a dimension to metrics. [#6199](https://github.com/opentripplanner/OpenTripPlanner/pull/6199) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 55cce3f13584295fb2762f2f52699d55b34d3788 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 7 Nov 2024 15:58:46 +0000 Subject: [PATCH 69/86] Add changelog entry for #6232 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 8029b2d624d..2e7e5aec573 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -35,6 +35,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix hop geometries when one pattern is replaced by another with different number of stops [#6136](https://github.com/opentripplanner/OpenTripPlanner/pull/6136) - Distinct coach from bus when reading in GTFS data and in GTFS GraphQL API [#6171](https://github.com/opentripplanner/OpenTripPlanner/pull/6171) - Add provider of updates as a dimension to metrics. [#6199](https://github.com/opentripplanner/OpenTripPlanner/pull/6199) +- Return empty list if there is no siriUrls in situations/infoLinks [#6232](https://github.com/opentripplanner/OpenTripPlanner/pull/6232) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 14cef7cba71f9309a968e541586d6de08450a3f9 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 21:57:38 +0100 Subject: [PATCH 70/86] vector tile: use ALL for permissions --- .../apis/vectortiles/DebugStyleSpec.java | 12 ++++++++++-- .../apis/vectortiles/model/StyleBuilder.java | 19 ++++++++----------- .../vector/edge/EdgePropertyMapper.java | 6 +----- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index f821f7c0e82..999b90a8bbd 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -244,7 +244,11 @@ private static List traversalPermissions(VectorSourceLayer edges) .vectorSourceLayer(edges) .group(PERMISSIONS_GROUP) .typeLine() - .filterValueInProperty(streetTraversalPermission.name(), "permission") + .filterValueInProperty( + "permission", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) .lineCap("butt") .lineColorMatch("permission", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) @@ -279,7 +283,11 @@ private static List noThruTraffic(VectorSourceLayer edges) { .vectorSourceLayer(edges) .group(NO_THRU_TRAFFIC_GROUP) .typeLine() - .filterValueInProperty(streetTraversalPermission.name(), "noThruTraffic") + .filterValueInProperty( + "noThruTraffic", + streetTraversalPermission.name(), + StreetTraversalPermission.ALL.name() + ) .lineCap("butt") .lineColorMatch("noThruTraffic", permissionColors(), BLACK) .lineWidth(LINE_WIDTH) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index c2b9c2a9d22..d84ee0f533d 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -11,7 +12,6 @@ import java.util.stream.Stream; import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.framework.json.ObjectMappers; -import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.utils.collection.ListUtils; @@ -250,14 +250,6 @@ public final StyleBuilder edgeFilter(Class... classToFilter) { return filterClasses(classToFilter); } - /** - * Filter the entities by their "permission" property. - */ - public final StyleBuilder permissionsFilter(StreetTraversalPermission p) { - filter = List.of("==", "permission", p.name()); - return this; - } - /** * Only apply the style to the given vertices. */ @@ -266,8 +258,13 @@ public final StyleBuilder vertexFilter(Class... classToFilter) return filterClasses(classToFilter); } - public StyleBuilder filterValueInProperty(String value, String propertyName) { - filter = List.of("in", value, List.of("string", List.of("get", propertyName))); + public StyleBuilder filterValueInProperty(String propertyName, String... values) { + var newFilter = new ArrayList<>(); + newFilter.add("any"); + for (String value : values) { + newFilter.add(List.of("in", value, List.of("string", List.of("get", propertyName)))); + } + filter = newFilter; return this; } diff --git a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java index 70041c6f5b4..d6e2d7250ea 100644 --- a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java +++ b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java @@ -43,11 +43,7 @@ private static List mapStreetEdge(StreetEdge se) { } public static String streetPermissionAsString(StreetTraversalPermission permission) { - return ( - permission == StreetTraversalPermission.ALL - ? "PEDESTRIAN_AND_BICYCLE_AND_CAR" - : permission.toString() - ).replace("_AND_", " "); + return permission.name().replace("_AND_", " "); } private static String noThruTrafficAsString(StreetEdge se) { From 3415473bbb3012158f687086501bdb472dafa38b Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 21:57:56 +0100 Subject: [PATCH 71/86] vector tile: update/restore edge styles --- .../apis/vectortiles/DebugStyleSpec.java | 13 +- .../apis/vectortiles/style.json | 183 ++++++++++++++---- 2 files changed, 150 insertions(+), 46 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index 999b90a8bbd..289841a53d6 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -14,7 +14,6 @@ import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber; import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; -import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.AreaEdge; import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; @@ -50,7 +49,6 @@ public class DebugStyleSpec { private static final String DARK_GREEN = "#136b04"; private static final String PURPLE = "#BC55F2"; private static final String BLACK = "#140d0e"; - private static final String GRAY = "#DDDDDD"; private static final int MAX_ZOOM = 23; private static final int LINE_DETAIL_ZOOM = 13; @@ -197,7 +195,8 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(GRAY) + .lineColor(MAGENTA) + .edgeFilter(EDGES_TO_DISPLAY) .lineWidth(LINE_HALF_WIDTH) .lineOffset(LINE_OFFSET) .minZoom(6) @@ -209,6 +208,7 @@ private static List edges(VectorSourceLayer edges) { .typeSymbol() .lineText("name") .vectorSourceLayer(edges) + .edgeFilter(EDGES_TO_DISPLAY) .minZoom(17) .maxZoom(MAX_ZOOM) .intiallyHidden(), @@ -217,8 +217,7 @@ private static List edges(VectorSourceLayer edges) { .group(EDGES_GROUP) .typeLine() .vectorSourceLayer(edges) - .lineColor(GRAY) - .lineOpacity(0.2f) + .lineColor(BRIGHT_GREEN) .edgeFilter( StreetTransitStopLink.class, StreetTransitEntranceLink.class, @@ -316,11 +315,11 @@ private static List noThruTraffic(VectorSourceLayer edges) { private static List permissionColors() { return Arrays .stream(StreetTraversalPermission.values()) - .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColors(p))) + .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColor(p))) .toList(); } - private static String permissionColors(StreetTraversalPermission p) { + private static String permissionColor(StreetTraversalPermission p) { return switch (p) { case NONE -> BLACK; case PEDESTRIAN -> "#2ba812"; diff --git a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index 1aad369a557..2a25a3722f9 100644 --- a/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/application/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -35,7 +35,7 @@ "minzoom" : 6, "maxzoom" : 23, "paint" : { - "line-color" : "#DDDDDD", + "line-color" : "#f21d52", "line-width" : [ "interpolate", [ @@ -63,6 +63,17 @@ 7.0 ] }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], "layout" : { "line-cap" : "round", "visibility" : "none" @@ -84,6 +95,17 @@ "text-halo-blur" : 4, "text-halo-width" : 3 }, + "filter" : [ + "in", + "class", + "StreetEdge", + "AreaEdge", + "EscalatorEdge", + "PathwayEdge", + "ElevatorHopEdge", + "TemporaryPartialStreetEdge", + "TemporaryFreeEdge" + ], "layout" : { "symbol-placement" : "line-center", "symbol-spacing" : 1000, @@ -122,8 +144,7 @@ "minzoom" : 13, "maxzoom" : 23, "paint" : { - "line-color" : "#DDDDDD", - "line-opacity" : 0.2, + "line-color" : "#22DD9E", "line-width" : [ "interpolate", [ @@ -197,7 +218,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -229,13 +250,27 @@ ] }, "filter" : [ - "in", - "PEDESTRIAN", + "any", [ - "string", + "in", + "PEDESTRIAN", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -275,7 +310,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -307,13 +342,27 @@ ] }, "filter" : [ - "in", - "BICYCLE", + "any", [ - "string", + "in", + "BICYCLE", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -353,7 +402,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -385,13 +434,27 @@ ] }, "filter" : [ - "in", - "CAR", + "any", [ - "string", + "in", + "CAR", [ - "get", - "permission" + "string", + [ + "get", + "permission" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "permission" + ] ] ] ], @@ -489,7 +552,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -521,13 +584,27 @@ ] }, "filter" : [ - "in", - "PEDESTRIAN", + "any", [ - "string", + "in", + "PEDESTRIAN", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], @@ -567,7 +644,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -599,13 +676,27 @@ ] }, "filter" : [ - "in", - "BICYCLE", + "any", [ - "string", + "in", + "BICYCLE", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], @@ -645,7 +736,7 @@ "#e25f8f", "BICYCLE CAR", "#e25f8f", - "PEDESTRIAN BICYCLE CAR", + "ALL", "#adb2b0", "#140d0e" ], @@ -677,13 +768,27 @@ ] }, "filter" : [ - "in", - "CAR", + "any", [ - "string", + "in", + "CAR", [ - "get", - "noThruTraffic" + "string", + [ + "get", + "noThruTraffic" + ] + ] + ], + [ + "in", + "ALL", + [ + "string", + [ + "get", + "noThruTraffic" + ] ] ] ], From 8e786c016ef0fa36a0a6db6acb3f7e81b1714999 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Thu, 7 Nov 2024 22:02:12 +0100 Subject: [PATCH 72/86] debug client: dynamically update interactive layers --- .../src/components/MapView/LayerControl.tsx | 33 ++++++++++++++++--- client/src/components/MapView/MapView.tsx | 5 +-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index d6be2d641d7..e79ae95e61e 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -4,6 +4,7 @@ import { IControl, Map, TypedStyleLayer } from 'maplibre-gl'; type LayerControlProps = { position: ControlPosition; + setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; }; /** @@ -15,6 +16,12 @@ type LayerControlProps = { class LayerControl implements IControl { private readonly container: HTMLDivElement = document.createElement('div'); + private readonly setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; + + constructor(setInteractiveLayerIds: (interactiveLayerIds: string[]) => void) { + this.setInteractiveLayerIds = setInteractiveLayerIds; + } + onAdd(map: Map) { this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group layer-select'; @@ -32,10 +39,8 @@ class LayerControl implements IControl { map .getLayersOrder() .map((l) => map.getLayer(l)) - .filter((s) => s?.type !== 'raster') - // the polylines of the routing result are put in map layers called jsx-1, jsx-2... - // we don't want them to show up in the debug layer selector - .filter((s) => !s?.id.startsWith('jsx')) + .filter((layer) => !!layer) + .filter((layer) => this.layerInteractive(layer)) .reverse() .forEach((layer) => { if (layer) { @@ -62,6 +67,17 @@ class LayerControl implements IControl { return this.container; } + private updateInteractiveLayerIds(map: Map) { + const visibleInteractiveLayerIds = map + .getLayersOrder() + .map((l) => map.getLayer(l)) + .filter((layer) => !!layer) + .filter((layer) => this.layerVisible(map, layer) && this.layerInteractive(layer)) + .map((layer) => layer.id); + + this.setInteractiveLayerIds(visibleInteractiveLayerIds); + } + private buildLayerDiv(layer: TypedStyleLayer, map: Map) { const layerDiv = document.createElement('div'); layerDiv.className = 'layer'; @@ -77,6 +93,7 @@ class LayerControl implements IControl { } else { map.setLayoutProperty(layer.id, 'visibility', 'none'); } + this.updateInteractiveLayerIds(map); }; input.checked = this.layerVisible(map, layer); input.className = 'layer'; @@ -118,13 +135,19 @@ class LayerControl implements IControl { return map.getLayoutProperty(layer.id, 'visibility') !== 'none'; } + private layerInteractive(layer: { id: string; type: string }) { + // the polylines of the routing result are put in map layers called jsx-1, jsx-2... + // we don't want them to show up in the debug layer selector + return layer?.type !== 'raster' && !layer?.id.startsWith('jsx'); + } + onRemove() { this.container.parentNode?.removeChild(this.container); } } export default function DebugLayerControl(props: LayerControlProps) { - useControl(() => new LayerControl(), { + useControl(() => new LayerControl(props.setInteractiveLayerIds), { position: props.position, }); diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index 4a6080a1b45..ec008853737 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -37,6 +37,7 @@ export function MapView({ const onMapDoubleClick = useMapDoubleClick({ tripQueryVariables, setTripQueryVariables }); const [showContextPopup, setShowContextPopup] = useState(null); const [showPropsPopup, setShowPropsPopup] = useState(null); + const [interactiveLayerIds, setInteractiveLayerIds] = useState([]); const [cursor, setCursor] = useState('auto'); const onMouseEnter = useCallback(() => setCursor('pointer'), []); const onMouseLeave = useCallback(() => setCursor('auto'), []); @@ -78,7 +79,7 @@ export function MapView({ }} // it's unfortunate that you have to list these layers here. // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 - interactiveLayerIds={['regular-stop', 'area-stop', 'group-stop', 'parking-vertex', 'vertex', 'edge', 'link']} + interactiveLayerIds={interactiveLayerIds} cursor={cursor} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} @@ -97,7 +98,7 @@ export function MapView({ setTripQueryVariables={setTripQueryVariables} loading={loading} /> - + {tripQueryResult?.trip.tripPatterns.length && ( )} From 0b6fcacdbd969f100708571bee75305828da0d62 Mon Sep 17 00:00:00 2001 From: Zsombor Welker Date: Fri, 8 Nov 2024 08:36:29 +0100 Subject: [PATCH 73/86] debug client: remove obsolete comment ... since the list of interactive layer ids is updated when a layer's visibility is changed --- client/src/components/MapView/MapView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index ec008853737..8eb66f8c446 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -77,8 +77,6 @@ export function MapView({ onContextMenu={(e) => { setShowContextPopup(e.lngLat); }} - // it's unfortunate that you have to list these layers here. - // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 interactiveLayerIds={interactiveLayerIds} cursor={cursor} onMouseEnter={onMouseEnter} From 854b404e46958148c6c3d4d14027a84ae536e356 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 8 Nov 2024 11:01:21 +0100 Subject: [PATCH 74/86] Use more common method --- .../gtfs/mapping/routerequest/LegacyRouteRequestMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 8e4c45a380c..19bee5e430d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -258,7 +258,7 @@ public static RouteRequest toRouteRequest( } static void mapViaLocations(RouteRequest request, DataFetchingEnvironment env) { - var args = env.getArguments().get("via"); + var args = env.getArgument("via"); var locs = ViaLocationMapper.mapToViaLocations((List>>) args); request.setViaLocations(locs); } From 3a03f3b48346ea5dfc5bfb67181269f18a27798f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 8 Nov 2024 11:16:40 +0100 Subject: [PATCH 75/86] Apply review feedback --- .../opentripplanner/apis/gtfs/schema.graphqls | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 31f2b025fc5..0943e844e71 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1482,7 +1482,10 @@ type QueryType { triangle: InputTriangle, "List of routes and agencies which are given lower preference when planning the itinerary" unpreferred: InputUnpreferred, - "The list of points the journey is required to pass through." + """ + The list of points the itinerary required to pass through. + All locations are visited in the order they are listed. + """ via: [PlanViaLocationInput!], """ How much less bad is waiting at the beginning of the trip (replaces @@ -1609,7 +1612,7 @@ type QueryType { number of itineraries in each search. """ searchWindow: Duration, - "The list of points the journey is required to pass through." + "The list of points the itinerary is required to pass through." via: [PlanViaLocationInput!] ): PlanConnection @async "Get a single rental vehicle based on its ID, i.e. value of field `vehicleId`" @@ -4101,9 +4104,8 @@ input PlanPassThroughViaLocationInput { "The label/name of the location. This is pass-through information and is not used in routing." label: String """ - A list of stop locations. A stop location can be a quay, a stop place, a multimodal - stop place or a group of stop places. It is enough to visit ONE of the locations - listed. + A list of stop locations. A stop location can be a stop or a station. + It is enough to visit ONE of the locations listed. """ stopLocationIds: [String!]! } @@ -4229,11 +4231,10 @@ input PlanVisitViaLocationInput { """ minimumWaitTime: Duration = "PT0S" """ - A list of stop locations. A stop location can be a quay, a stop place, a multimodal - stop place or a group of stop places. It is enough to visit ONE of the locations - listed. + A list of stop locations. A stop location can be a stop or a station. + It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String!] + stopLocationIds: [String!]! } "What criteria should be used when optimizing a scooter route." From 2f0cc304580b53e6f477d7c652b5839dbf4d4c8b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 8 Nov 2024 11:18:41 +0100 Subject: [PATCH 76/86] 'itinerary' instead of 'trip' --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0943e844e71..c7c7e33cca4 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4227,7 +4227,7 @@ input PlanVisitViaLocationInput { label: String """ The minimum wait time is used to force the trip to stay the given duration at the - via-location before the trip is continued. + via-location before the itinerary is continued. """ minimumWaitTime: Duration = "PT0S" """ From d2f41088f7178a936cdd9b94fd006bc19c7833a6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 8 Nov 2024 11:39:30 +0100 Subject: [PATCH 77/86] Make stopLocationIds nullable again --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c7c7e33cca4..5d79e08bd47 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4234,7 +4234,7 @@ input PlanVisitViaLocationInput { A list of stop locations. A stop location can be a stop or a station. It is enough to visit ONE of the locations listed. """ - stopLocationIds: [String!]! + stopLocationIds: [String!] } "What criteria should be used when optimizing a scooter route." From 69b7bab6bacbca920ae3e6187d0ccc1648f1ca2c Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 8 Nov 2024 12:47:35 +0200 Subject: [PATCH 78/86] Document the need to use graphQL alias for leg id when querying id and fares together --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index de1230b9eca..592a04cad3d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -670,8 +670,10 @@ type Leg { """ headsign: String """ - An identifier for the leg, which can be used to re-fetch transit leg information. + An identifier for the leg, which can be used to re-fetch transit leg information, except leg's fare products. Re-fetching fails when the underlying transit data no longer exists. + **Note:** when both id and fare products are queried, id should be queried using a suitable graphQL alias + such as `legId`. Relay does not accept different fare product ids in otherwise identical legs. """ id: String """ @@ -1173,6 +1175,7 @@ type QueryType { """ Try refetching the current state of a transit leg using its id. This fails when the underlying transit data (mostly IDs) has changed or are no longer available. + Fare products cannot be refetched using this query. """ leg(id: String!): Leg """ From 32eae051359df0a3a65416670cff24b024cf4f64 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 8 Nov 2024 13:04:40 +0200 Subject: [PATCH 79/86] Update application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Leonard Ehrenfried --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 592a04cad3d..740b15445dd 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -672,7 +672,7 @@ type Leg { """ An identifier for the leg, which can be used to re-fetch transit leg information, except leg's fare products. Re-fetching fails when the underlying transit data no longer exists. - **Note:** when both id and fare products are queried, id should be queried using a suitable graphQL alias + **Note:** when both id and fare products are queried, id should be queried using a suitable GraphQL alias such as `legId`. Relay does not accept different fare product ids in otherwise identical legs. """ id: String From e6d383b1e544ccf0f50c065e29e3d580f333c339 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 8 Nov 2024 13:21:09 +0200 Subject: [PATCH 80/86] Rename function newArea as addArea --- .../graph_builder/module/osm/OsmDatabase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 3079f41c4d4..8b88dea93b6 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -688,7 +688,7 @@ private void processSingleWayAreas() { } } try { - newArea(new Area(way, List.of(way), Collections.emptyList(), nodesById)); + addArea(new Area(way, List.of(way), Collections.emptyList(), nodesById)); } catch (Area.AreaConstructionException | Ring.RingConstructionException e) { // this area cannot be constructed, but we already have all the // necessary nodes to construct it. So, something must be wrong with @@ -751,7 +751,7 @@ private void processMultipolygonRelations() { } processedAreas.add(relation); try { - newArea(new Area(relation, outerWays, innerWays, nodesById)); + addArea(new Area(relation, outerWays, innerWays, nodesById)); } catch (Area.AreaConstructionException | Ring.RingConstructionException e) { issueStore.add(new InvalidOsmGeometry(relation)); continue; @@ -786,7 +786,7 @@ private void processMultipolygonRelations() { /** * Handler for a new Area (single way area or multipolygon relations) */ - private void newArea(Area area) { + private void addArea(Area area) { StreetTraversalPermission permissions = area.parent .getOsmProvider() .getWayPropertySet() From bdf60e06ef41a77b42622ea23fb0edf1cbb4dee0 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 8 Nov 2024 13:39:48 +0200 Subject: [PATCH 81/86] Update application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Joel Lappalainen --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 740b15445dd..73e2020a28a 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -672,8 +672,8 @@ type Leg { """ An identifier for the leg, which can be used to re-fetch transit leg information, except leg's fare products. Re-fetching fails when the underlying transit data no longer exists. - **Note:** when both id and fare products are queried, id should be queried using a suitable GraphQL alias - such as `legId`. Relay does not accept different fare product ids in otherwise identical legs. + **Note:** when both id and fare products are queried with [Relay](https://relay.dev/), id should be queried using a suitable GraphQL alias + such as `legId: id`. Relay does not accept different fare product ids in otherwise identical legs. """ id: String """ From 25b267142a495cd2112dbf22092dee25243ee50f Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Fri, 8 Nov 2024 11:59:57 +0000 Subject: [PATCH 82/86] Add changelog entry for #6164 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 2e7e5aec573..e1706d8cfd1 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -36,6 +36,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Distinct coach from bus when reading in GTFS data and in GTFS GraphQL API [#6171](https://github.com/opentripplanner/OpenTripPlanner/pull/6171) - Add provider of updates as a dimension to metrics. [#6199](https://github.com/opentripplanner/OpenTripPlanner/pull/6199) - Return empty list if there is no siriUrls in situations/infoLinks [#6232](https://github.com/opentripplanner/OpenTripPlanner/pull/6232) +- OSM area processing obeys tag mapping [#6164](https://github.com/opentripplanner/OpenTripPlanner/pull/6164) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From da95e7a319e9dc35779f02ea1bc2c8297bbb4bba Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Fri, 8 Nov 2024 12:00:24 +0000 Subject: [PATCH 83/86] Add changelog entry for #5958 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index e1706d8cfd1..74811c9c546 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -37,6 +37,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add provider of updates as a dimension to metrics. [#6199](https://github.com/opentripplanner/OpenTripPlanner/pull/6199) - Return empty list if there is no siriUrls in situations/infoLinks [#6232](https://github.com/opentripplanner/OpenTripPlanner/pull/6232) - OSM area processing obeys tag mapping [#6164](https://github.com/opentripplanner/OpenTripPlanner/pull/6164) +- Add `via` to GTFS GraphQL API [#5958](https://github.com/opentripplanner/OpenTripPlanner/pull/5958) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 570cdb5c073afe1cbf0449640e4870e45dcaa34b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:36:29 +0000 Subject: [PATCH 84/86] Update micrometer.version to v1.13.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index afedd24e9ea..3c29e5f25eb 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 2.18.1 3.1.9 5.11.2 - 1.13.5 + 1.13.7 5.6.0 1.5.12 9.12.0 From e655812302d08c54363f6bbb54b7282c1b999032 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Tue, 12 Nov 2024 09:27:24 +0000 Subject: [PATCH 85/86] Upgrade debug client to version 2024/11/2024-11-12T09:26 --- application/src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/client/index.html b/application/src/client/index.html index b5df7bb65aa..b0a50f65e29 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +

From 237cf262715eb46d8fb49a8adaa6aacf25e36a38 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 12 Nov 2024 09:31:36 +0000 Subject: [PATCH 86/86] Add changelog entry for #6216 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 74811c9c546..c0058ab7ef4 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -38,6 +38,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Return empty list if there is no siriUrls in situations/infoLinks [#6232](https://github.com/opentripplanner/OpenTripPlanner/pull/6232) - OSM area processing obeys tag mapping [#6164](https://github.com/opentripplanner/OpenTripPlanner/pull/6164) - Add `via` to GTFS GraphQL API [#5958](https://github.com/opentripplanner/OpenTripPlanner/pull/5958) +- Deprecate old alert translations in the GTFS API and add language param to a few alert fields [#6216](https://github.com/opentripplanner/OpenTripPlanner/pull/6216) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18)