From 8fdd093db0a30ed63aa408c9e3d3009701913c13 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 6 Jan 2026 12:57:25 +0000 Subject: [PATCH 001/177] process more station entrances --- .../graph_builder/module/osm/OsmDatabase.java | 9 ++++++- .../module/osm/VertexGenerator.java | 2 +- .../opentripplanner/osm/model/OsmEntity.java | 14 ----------- .../opentripplanner/osm/model/OsmNode.java | 25 +++++++++++++++---- doc/user/StopAreas.md | 2 +- 5 files changed, 30 insertions(+), 22 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 6664ecc1f0a..87054711791 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 @@ -1026,8 +1026,15 @@ private void processPublicTransportStopArea(OsmRelation relation) { switch (member.getType()) { case NODE -> { var node = nodesById.get(member.getRef()); - if (node != null && (node.isEntrance() || node.isBoardingLocation())) { + if ( + node != null && + (node.isEntrance() || node.isBoardingLocation() || node.isTag("highway", "elevator")) + ) { platformNodes.add(node); + if (node.isEntrance()) { + // mark it as a station entrance so it can be returned in walk step descriptions. + node.addTag("public_transport", "entrance"); + } } } case WAY -> { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index c565f1c4567..15ac294c44b 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -135,7 +135,7 @@ IntersectionVertex getVertexForOsmNode( } } - if (includeOsmSubwayEntrances && node.isSubwayEntrance()) { + if (includeOsmSubwayEntrances && node.isStationEntrance()) { String ref = node.getTag("ref"); iv = vertexFactory.stationEntrance( nid, diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index 2efabfa218f..efa57a5af85 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -755,20 +755,6 @@ public boolean isRailwayPlatform() { return isTag("railway", "platform"); } - /** - * @return True if this entity provides an entrance to a platform or similar entity - */ - public boolean isEntrance() { - return ( - (isTag("railway", "subway_entrance") || - isTag("highway", "elevator") || - isTag("entrance", "yes") || - isTag("entrance", "main")) && - !isTag("access", "private") && - !isTag("access", "no") - ); - } - /** * @return True if this node / area is a bike parking. */ diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java index a9a6a3727be..fa37a18306d 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java @@ -3,6 +3,7 @@ import static org.opentripplanner.street.model.StreetTraversalPermission.ALL; import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; public class OsmNode extends OsmEntity { @@ -54,12 +55,26 @@ public boolean isBarrier() { } /** - * Checks if this node is a subway station entrance. + * Checks if this node is a station entrance. * * @return true if it is */ - public boolean isSubwayEntrance() { - return hasTag("railway") && "subway_entrance".equals(getTag("railway")); + public boolean isStationEntrance() { + return ( + isOneOfTags("railway", Set.of("subway_entrance", "train_station_entrance")) || + isTag("public_transport", "entrance") + ); + } + + /** + * @return True if this entity provides an entrance to a platform or similar entity + */ + public boolean isEntrance() { + return ( + (isStationEntrance() || isTag("entrance", "yes") || isTag("entrance", "main")) && + !isTag("access", "private") && + !isTag("access", "no") + ); } @Override @@ -68,8 +83,8 @@ public String url() { } /** - * Check if this node represents a tagged barrier crossing if placed on an intersection - * of a highway and a barrier way. + * Check if this node represents a tagged barrier crossing if placed on an intersection of a + * highway and a barrier way. * * @return true if it has a barrier tag, or if it explicitly overrides permissions. */ diff --git a/doc/user/StopAreas.md b/doc/user/StopAreas.md index 46acbf326bc..81f801f9e5a 100644 --- a/doc/user/StopAreas.md +++ b/doc/user/StopAreas.md @@ -19,7 +19,7 @@ An example: [Huopalahti railway station in Helsinki](https://www.openstreetmap. - Set role=platform to these platform members - Platforms must be also proper areas, tagged as routable highways and `area=yes`. Also a single tag `public_transport=platform` will do. - Model required entrance points and stop linking points inside platform geometry and make them relation members, too. - - Entrances can be tagged using `railway=subway_entrance`, `highway=elevator`, `entrance=yes` or `entrance=main`. + - Entrances can be tagged using `railway=subway_entrance`, `railway=train_station_entrance`, `public_transport=entrance`, `entrance=yes`, `entrance=main` or `highway=elevator`. - Stop linking points should be tagged as [boarding locations](BoardingLocations.md), e.g. using the tag `public_transport=platform`. - Connect entrance points to the street network. Stop linking points can have reference tags to instruct how to link them with public transit stops as instructed in the document about [boarding locations](BoardingLocations.md). From e92515df343667b918f04506c089e9ecd36c33ba Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 13 Jan 2026 18:13:01 +0000 Subject: [PATCH 002/177] way should be split into segments at station entrance --- .../org/opentripplanner/graph_builder/module/osm/OsmModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 473f963cf9e..04f925be761 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -411,6 +411,7 @@ private void buildBasicGraph(OsmDatabase osmdb, VertexGenerator vertexGenerator) osmEndNode.hasTag("ele") || osmEndNode.isBoardingLocation() || osmEndNode.isBarrier() || + osmEndNode.isStationEntrance() || vertexGenerator.nodesInBarrierWays().containsKey(osmEndNode) ) { segmentCoordinates.add(osmEndNode.getCoordinate()); From 261a7dfe7a48049e472ee8c4b12ea01b857069db Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Mon, 23 Mar 2026 13:27:42 +0200 Subject: [PATCH 003/177] add support for transit group priority in gtfs api --- .../gtfs/generated/GraphQLDataFetchers.java | 2 +- .../apis/gtfs/generated/GraphQLTypes.java | 55 +++++++++++++++++-- .../TransitPreferencesMapper.java | 12 ++++ .../opentripplanner/apis/gtfs/schema.graphqls | 17 ++++++ 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 06c7395cece..f7c87e2673f 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -988,8 +988,8 @@ public interface GraphQLTrip { public DataFetcher> alerts(); public DataFetcher arrivalStoptime(); public DataFetcher bikesAllowed(); - public DataFetcher carsAllowed(); public DataFetcher blockId(); + public DataFetcher carsAllowed(); public DataFetcher departureStoptime(); public DataFetcher directionId(); public DataFetcher>> geometry(); 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 9111525db0f..11886e2e452 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 @@ -468,12 +468,6 @@ public enum GraphQLBikesAllowed { NO_INFORMATION, } - public enum GraphQLCarsAllowed { - ALLOWED, - NOT_ALLOWED, - NO_INFORMATION, - } - public static class GraphQLBoardPreferencesInput { private java.time.Duration slack; @@ -728,6 +722,12 @@ public void setGraphQLRentalDuration(java.time.Duration rentalDuration) { } } + public enum GraphQLCarsAllowed { + ALLOWED, + NOT_ALLOWED, + NO_INFORMATION, + } + public static class GraphQLCyclingOptimizationInput { private GraphQLTriangleCyclingFactorsInput triangle; @@ -4596,6 +4596,35 @@ public enum GraphQLRelativeDirection { UTURN_RIGHT, } + public static class GraphQLRelaxCostFunctionInput { + + private org.opentripplanner.core.model.basic.Cost constant; + private Double ratio; + + public GraphQLRelaxCostFunctionInput(Map args) { + if (args != null) { + this.constant = (org.opentripplanner.core.model.basic.Cost) args.get("constant"); + this.ratio = (Double) args.get("ratio"); + } + } + + public org.opentripplanner.core.model.basic.Cost getGraphQLConstant() { + return this.constant; + } + + public Double getGraphQLRatio() { + return this.ratio; + } + + public void setGraphQLConstant(org.opentripplanner.core.model.basic.Cost constant) { + this.constant = constant; + } + + public void setGraphQLRatio(Double ratio) { + this.ratio = ratio; + } + } + public static class GraphQLRouteAlertsArgs { private List types; @@ -5415,6 +5444,7 @@ public static class GraphQLTransitPreferencesInput { private GraphQLAlightPreferencesInput alight; private GraphQLBoardPreferencesInput board; private List filters; + private GraphQLRelaxCostFunctionInput relaxTransitGroupPriority; private GraphQLTimetablePreferencesInput timetable; private GraphQLTransferPreferencesInput transfer; @@ -5427,6 +5457,9 @@ public GraphQLTransitPreferencesInput(Map args) { .map(o -> o == null ? null : new GraphQLTransitFilterInput(o)) .collect(Collectors.toList()); } + this.relaxTransitGroupPriority = new GraphQLRelaxCostFunctionInput( + (Map) args.get("relaxTransitGroupPriority") + ); this.timetable = new GraphQLTimetablePreferencesInput( (Map) args.get("timetable") ); @@ -5448,6 +5481,10 @@ public List getGraphQLFilters() { return this.filters; } + public GraphQLRelaxCostFunctionInput getGraphQLRelaxTransitGroupPriority() { + return this.relaxTransitGroupPriority; + } + public GraphQLTimetablePreferencesInput getGraphQLTimetable() { return this.timetable; } @@ -5468,6 +5505,12 @@ public void setGraphQLFilters(List filters) { this.filters = filters; } + public void setGraphQLRelaxTransitGroupPriority( + GraphQLRelaxCostFunctionInput relaxTransitGroupPriority + ) { + this.relaxTransitGroupPriority = relaxTransitGroupPriority; + } + public void setGraphQLTimetable(GraphQLTimetablePreferencesInput timetable) { this.timetable = timetable; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index 3b9d8c43e50..3a042e1e2d4 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.TransferPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.utils.collection.CollectionUtils; @@ -42,6 +43,17 @@ static void setTransitPreferences( return; } + var relax = transitArgs.getGraphQLRelaxTransitGroupPriority(); + if (relax != null) { + var constant = relax.getGraphQLConstant(); + var ratio = relax.getGraphQLRatio(); + if (constant != null && ratio != null) { + transitPreferences.withRelaxTransitGroupPriority( + CostLinearFunction.of(relax.getGraphQLConstant(), relax.getGraphQLRatio()) + ); + } + } + var board = transitArgs.getGraphQLBoard(); if (board != null) { var slack = board.getGraphQLSlack(); 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 b655f72c9a0..ccded970f12 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4806,6 +4806,22 @@ input PlanVisitViaLocationInput { stopLocationIds: [String!] } +""" +A relax-cost is used to increase the limit when comparing one cost to another cost. +This is used to include more results into the result. A `ratio=2.0` means a path (itinerary) +with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" +constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed +cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually +the default. We can express the RelaxCost as a function `f(t) = constant + ratio * t`. +`f(t)=t` is the NORMAL function. +""" +input RelaxCostFunctionInput { + "The constant value to add to the limit. Must be a positive number. The value is equivalent to transit-cost-seconds. Integers are treated as seconds, but you may use the duration format. Example: '3665 = 'DT1h1m5s' = '1h1m5s'." + constant: Cost + "The factor to multiply with the 'other cost'. Minimum value is 1.0." + ratio: Float +} + "What criteria should be used when optimizing a scooter route." input ScooterOptimizationInput @oneOf { "Define optimization by weighing three criteria." @@ -5008,6 +5024,7 @@ input TransitPreferencesInput { an AND-condition. """ filters: [TransitFilterInput!] + relaxTransitGroupPriority: RelaxCostFunctionInput "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput "Preferences related to transfers between transit vehicles (typically between stops)." From af171e4338ca796f9b2476bc727b1123754b2a5e Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Mon, 23 Mar 2026 16:49:18 +0200 Subject: [PATCH 004/177] add documentation for relaxTransitGroupPriority --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 ccded970f12..cf20ab514e6 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5024,6 +5024,18 @@ input TransitPreferencesInput { an AND-condition. """ filters: [TransitFilterInput!] + """ + Relax generalized-cost when comparing itineraries with a different set of + transit-group-priorities. The groups are set server side for routes and + can not be configured in the API. + + This relaxes the comparison inside the routing engine for each stop-arrival. If two + paths have a different set of transit-group-priorities, then the generalized-cost + comparison is relaxed. The final set of paths are filtered through the normal + itinerary-filters. + + THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! + """ relaxTransitGroupPriority: RelaxCostFunctionInput "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput From 4852c1e302763f5903873c2cae44f2711b25d7a4 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Tue, 24 Mar 2026 14:06:37 +0200 Subject: [PATCH 005/177] add a test for relaxTransitGroupPriority --- .../RouteRequestMapperTransitTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index 778e201e6ec..ad79e8effe1 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -132,4 +132,29 @@ void testTimetablePreferences() { assertEquals(includePlannedCancellations, transitPreferences.includePlannedCancellations()); assertEquals(includeRealTimeCancellations, transitPreferences.includeRealtimeCancellations()); } + + @Test + void testRelaxTransitGroupPriority() { + var args = testCtx.basicRequest(); + var constant = Cost.costOfSeconds(120); + var ratio = 1.2; + var relaxTransitGroupPriority = Map.ofEntries( + entry("constant", constant), + entry("ratio", ratio) + ); + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries(entry("relaxTransitGroupPriority", relaxTransitGroupPriority)) + ) + ) + ); + var env = testCtx.executionContext(args); + var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(constant, transitPreferences.relaxTransitGroupPriority().constant()); + assertEquals(ratio, transitPreferences.relaxTransitGroupPriority().coefficient()); + } } From 1adca2fe69ae4243df8ab3f468bc9eda24312d0c Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Tue, 24 Mar 2026 16:02:27 +0200 Subject: [PATCH 006/177] refactor mapping to allow partial values and add a test case --- .../TransitPreferencesMapper.java | 15 +++++++------ .../RouteRequestMapperTransitTest.java | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index 3a042e1e2d4..0e3bb847fbd 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; +import org.opentripplanner.core.model.basic.Cost; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.TransferPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; @@ -45,13 +46,15 @@ static void setTransitPreferences( var relax = transitArgs.getGraphQLRelaxTransitGroupPriority(); if (relax != null) { - var constant = relax.getGraphQLConstant(); - var ratio = relax.getGraphQLRatio(); - if (constant != null && ratio != null) { - transitPreferences.withRelaxTransitGroupPriority( - CostLinearFunction.of(relax.getGraphQLConstant(), relax.getGraphQLRatio()) - ); + Cost constant = Cost.ZERO; + Double ratio = 1.0; + if (relax.getGraphQLConstant() != null) { + constant = relax.getGraphQLConstant(); + } + if (relax.getGraphQLRatio() != null) { + ratio = relax.getGraphQLRatio(); } + transitPreferences.withRelaxTransitGroupPriority(CostLinearFunction.of(constant, ratio)); } var board = transitArgs.getGraphQLBoard(); diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index ad79e8effe1..69dd7218457 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -157,4 +157,25 @@ void testRelaxTransitGroupPriority() { assertEquals(constant, transitPreferences.relaxTransitGroupPriority().constant()); assertEquals(ratio, transitPreferences.relaxTransitGroupPriority().coefficient()); } + + @Test + void testPartialRelaxTransitGroupPriority() { + var args = testCtx.basicRequest(); + var ratio = 1.2; + var relaxTransitGroupPriority = Map.ofEntries(entry("ratio", ratio)); + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries(entry("relaxTransitGroupPriority", relaxTransitGroupPriority)) + ) + ) + ); + var env = testCtx.executionContext(args); + var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(Cost.ZERO, transitPreferences.relaxTransitGroupPriority().constant()); + assertEquals(ratio, transitPreferences.relaxTransitGroupPriority().coefficient()); + } } From af3cb3758cd7cffb446f30bbad9e8e2a30bfb75c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 25 Mar 2026 01:42:16 +0200 Subject: [PATCH 007/177] Refactor so NearbyStop has a stopId rather than a stop --- .../ext/flex/FlexibleTransitLegTest.java | 12 +++++-- .../ext/flex/template/ClosestTripTest.java | 4 +-- .../template/FlexTemplateFactoryTest.java | 2 +- .../ext/flex/trip/UnscheduledTripTest.java | 8 ++--- .../ojp/service/CallAtStopServiceTest.java | 7 +++- .../ext/carpooling/CarpoolingService.java | 4 +-- .../service/DefaultCarpoolingService.java | 4 +-- .../opentripplanner/ext/flex/FlexIndex.java | 10 +++--- .../opentripplanner/ext/flex/FlexRouter.java | 4 +-- .../ext/flex/FlexibleTransitLeg.java | 19 +++++++++-- .../ext/flex/FlexibleTransitLegBuilder.java | 23 +++++++++++++ .../ext/flex/edgetype/FlexTripEdge.java | 22 ++++++------- .../flex/template/AbstractFlexTemplate.java | 13 ++++---- .../ext/flex/template/ClosestTrip.java | 6 ++-- .../FlexAccessEgressCallbackAdapter.java | 2 +- .../ext/flex/template/FlexAccessTemplate.java | 7 ++-- .../flex/template/FlexDirectPathFactory.java | 14 ++++---- .../ext/flex/template/FlexEgressTemplate.java | 7 ++-- .../ext/flex/trip/FlexTrip.java | 13 ++++---- .../ext/flex/trip/ScheduledDeviatedTrip.java | 30 +++++++++++------ .../ext/flex/trip/UnscheduledTrip.java | 30 +++++++++++------ .../ext/ojp/service/CallAtStopService.java | 4 ++- .../DirectTransferAnalyzer.java | 18 ++++++----- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 4 +-- .../apis/gtfs/datafetchers/StopImpl.java | 7 +++- .../gtfs/datafetchers/stopAtDistanceImpl.java | 11 +++++-- .../TransmodelGraphQLSchemaFactory.java | 3 +- .../model/stop/QuayAtDistanceType.java | 18 ++++++++--- .../nearbystops/StreetNearbyStopFinder.java | 32 ++++++------------- .../transfer/DirectTransferGenerator.java | 31 +++++++++--------- .../filter/FlexTripNearbyStopFilter.java | 9 +++--- .../PatternConsideringNearbyStopFinder.java | 9 +++--- .../filter/PatternNearbyStopFilter.java | 2 +- .../mapping/GraphPathToItineraryMapper.java | 2 ++ .../raptoradapter/router/TransitRouter.java | 10 +++--- .../router/street/AccessEgressRouter.java | 16 +++------- .../router/street/DirectFlexRouter.java | 8 ++--- .../router/street/FlexAccessEgressRouter.java | 5 ++- .../transit/mappers/AccessEgressMapper.java | 16 +++++++--- .../graphfinder/DirectGraphFinder.java | 2 +- .../routing/graphfinder/GraphFinder.java | 3 +- .../routing/graphfinder/NearbyStop.java | 24 +++++++------- .../graphfinder/NearbyStopFactory.java | 19 ++--------- .../routing/graphfinder/SiteResolver.java | 2 ++ .../StopFinderTraverseVisitor.java | 10 ++---- .../graphfinder/StreetGraphFinder.java | 6 ++-- .../graphfinder/TransitServiceResolver.java | 6 ++++ .../DefaultViaCoordinateTransferFactory.java | 13 +++----- .../api/OtpServerRequestContext.java | 1 - .../routing/graphfinder/NoopSiteResolver.java | 6 ++++ .../LegacyRouteRequestMapperTest.java | 1 - .../_RouteRequestTestContext.java | 1 - ...reetNearbyStopFinderMultipleLinksTest.java | 10 +++--- .../StreetNearbyStopFinderTest.java | 20 ++++++------ .../model/plan/TestItineraryBuilder.java | 6 ++-- .../router/street/AccessEgressRouterTest.java | 11 ++++--- .../graphfinder/DirectGraphFinderTest.java | 4 +-- .../routing/graphfinder/NearbyStopTest.java | 6 ++-- .../StopFinderTraverseVisitorTest.java | 4 +-- .../graphfinder/StreetGraphFinderTest.java | 11 +++---- 60 files changed, 338 insertions(+), 274 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java index cd843b3d18e..7cfa2a2fe70 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java @@ -26,8 +26,8 @@ class FlexibleTransitLegTest implements PlanTestConstants { private static final FlexTripEdge EDGE = new FlexTripEdge( StreetModelForTest.intersectionVertex(1, 1), StreetModelForTest.intersectionVertex(2, 2), - A.stop, - B.stop, + A.stop.getId(), + B.stop.getId(), null, 1, 2, @@ -48,6 +48,8 @@ void listsAreInitialized() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .build(); assertNotNull(leg.fareOffers()); assertNotNull(leg.listTransitAlerts()); @@ -61,6 +63,8 @@ void everythingIsNonNull() { assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFlexTripEdge(null).build() ); + assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFromStop(null).build()); + assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withToStop(null).build()); assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withAlerts(null).build()); assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFareProducts(null).build() @@ -73,6 +77,8 @@ void copyOf() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .withFareProducts(List.of(ANY_FARE_OFFER)) .withAlerts(Set.of(ALERT)) .withEmissionPerPerson(EMISSION) @@ -94,6 +100,8 @@ void timeShift() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .withFareProducts(List.of(ANY_FARE_OFFER)) .withAlerts(Set.of(ALERT)) .build(); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java index d0213119f0a..4503d4c94f3 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java @@ -61,7 +61,7 @@ public Collection getTransfersToStop(StopLocation stop) { } @Override - public Collection> getFlexTripsByStop(StopLocation stopLocation) { + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { return List.of(FLEX_TRIP); } @@ -96,7 +96,7 @@ void filter() { private static Collection closestTrips(Matcher matcher) { return ClosestTrip.of( ADAPTER, - List.of(new NearbyStop(STOP, 100, List.of(), null)), + List.of(new NearbyStop(STOP.getId(), 100, List.of(), null)), matcher, List.of(FSD), true diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java index 448b2adc5b2..7a1e1c397c2 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java @@ -330,7 +330,7 @@ void testCreateEgressTemplateForScheduledDeviatedTrip() { private static NearbyStop nearbyStop(StopLocation transferPoint) { var id = "NearbyStop:" + transferPoint.getId().getId(); return new NearbyStop( - transferPoint, + transferPoint.getId(), 0, List.of(), new State( diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d5760946a8e..e8309726917 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -566,11 +566,11 @@ void boardingAlighting() { .build() .trip(); - assertTrue(trip.isBoardingPossible(AREA_STOP1)); - assertFalse(trip.isAlightingPossible(AREA_STOP1)); + assertTrue(trip.isBoardingPossible(AREA_STOP1.getId())); + assertFalse(trip.isAlightingPossible(AREA_STOP1.getId())); - assertFalse(trip.isBoardingPossible(AREA_STOP2)); - assertTrue(trip.isAlightingPossible(AREA_STOP2)); + assertFalse(trip.isBoardingPossible(AREA_STOP2.getId())); + assertTrue(trip.isAlightingPossible(AREA_STOP2.getId())); } private static String timeToString(int time) { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java index e70c0dd21db..78100166867 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java @@ -102,7 +102,12 @@ void coordinates() { @Override public List findClosestStops(Coordinate coordinate, double radiusMeters) { return List.of( - new NearbyStop(STOP_A, 100, List.of(), TestStateBuilder.ofWalking().streetEdge().build()) + new NearbyStop( + STOP_A.getId(), + 100, + List.of(), + TestStateBuilder.ofWalking().streetEdge().build() + ) ); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java index 995ae8bb286..62c19e88246 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java @@ -3,11 +3,11 @@ import java.time.ZonedDateTime; import java.util.List; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; /** @@ -34,7 +34,7 @@ List routeAccessEgress( RouteRequest request, StreetRequest streetRequest, AccessEgressType accessOrEgress, - StopResolver stopResolver, + TransitServiceResolver transitServiceResolver, LinkingContext linkingContext, ZonedDateTime transitSearchTimeZero ); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 18337c54398..1b693ece5c1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -18,7 +18,6 @@ import org.opentripplanner.ext.carpooling.routing.InsertionPosition; import org.opentripplanner.ext.carpooling.routing.InsertionPositionFinder; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.api.request.RouteRequest; @@ -27,6 +26,7 @@ import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.linking.TemporaryVerticesContainer; @@ -233,7 +233,7 @@ public List routeAccessEgress( RouteRequest request, StreetRequest streetRequest, AccessEgressType accessOrEgress, - StopResolver stopResolver, + TransitServiceResolver transitServiceResolver, LinkingContext linkingContext, ZonedDateTime transitSearchTimeZero ) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index d230cf28e3b..80ec7eff5dd 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -17,7 +17,7 @@ public class FlexIndex { - private final Multimap> flexTripsByStop = HashMultimap.create(); + private final Multimap> flexTripsByStopId = HashMultimap.create(); private final Map routeById = new HashMap<>(); @@ -32,10 +32,10 @@ public FlexIndex(TimetableRepository timetableRepository) { for (StopLocation stop : flexTrip.getStops()) { if (stop instanceof GroupStop groupStop) { for (StopLocation stopElement : groupStop.getChildLocations()) { - flexTripsByStop.put(stopElement, flexTrip); + flexTripsByStopId.put(stopElement.getId(), flexTrip); } } else { - flexTripsByStop.put(stop, flexTrip); + flexTripsByStopId.put(stop.getId(), flexTrip); } } @@ -57,8 +57,8 @@ public FlexIndex(TimetableRepository timetableRepository) { } } - public Collection> getFlexTripsByStop(StopLocation stopLocation) { - return flexTripsByStop.get(stopLocation); + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { + return flexTripsByStopId.get(stopLocationId); } public boolean contains(Route route) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java index 4a9b4a35995..6366a3f03af 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java @@ -242,8 +242,8 @@ public Collection getTransfersToStop(StopLocation stop) { } @Override - public Collection> getFlexTripsByStop(StopLocation stopLocation) { - return flexIndex.getFlexTripsByStop(stopLocation); + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { + return flexIndex.getFlexTripsByStopId(stopLocationId); } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java index a362844ef84..33ef420ad9f 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java @@ -25,6 +25,7 @@ import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; import org.opentripplanner.utils.lang.DoubleUtils; @@ -39,6 +40,10 @@ public class FlexibleTransitLeg implements TransitLeg { private final FlexTripEdge edge; + private final StopLocation fromStop; + + private final StopLocation toStop; + private final ZonedDateTime startTime; private final ZonedDateTime endTime; @@ -53,6 +58,8 @@ public class FlexibleTransitLeg implements TransitLeg { FlexibleTransitLeg(FlexibleTransitLegBuilder builder) { this.edge = Objects.requireNonNull(builder.flexTripEdge()); + this.fromStop = Objects.requireNonNull(builder.fromStop()); + this.toStop = Objects.requireNonNull(builder.toStop()); this.startTime = TimeUtils.normalize(builder.startTime()); this.endTime = TimeUtils.normalize(builder.endTime()); this.generalizedCost = builder.generalizedCost(); @@ -150,12 +157,12 @@ public LocalDate serviceDate() { @Override public Place from() { - return Place.forFlexStop(edge.s1(), edge.getFromVertex()); + return Place.forFlexStop(fromStop, edge.getFromVertex()); } @Override public Place to() { - return Place.forFlexStop(edge.s2(), edge.getToVertex()); + return Place.forFlexStop(toStop, edge.getToVertex()); } @Override @@ -270,6 +277,14 @@ public String toString() { .toString(); } + StopLocation fromStop() { + return fromStop; + } + + StopLocation toStop() { + return toStop; + } + FlexTripEdge flexTripEdge() { return edge; } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java index 328c1a2d169..ff97d2fda70 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java @@ -10,10 +10,13 @@ import org.opentripplanner.model.fare.FareOffer; import org.opentripplanner.model.plan.Emission; import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.transit.model.site.StopLocation; public class FlexibleTransitLegBuilder { private FlexTripEdge flexTripEdge; + private StopLocation fromStop; + private StopLocation toStop; private ZonedDateTime startTime; private ZonedDateTime endTime; private int generalizedCost; @@ -25,6 +28,8 @@ public class FlexibleTransitLegBuilder { FlexibleTransitLegBuilder(FlexibleTransitLeg original) { flexTripEdge = original.flexTripEdge(); + fromStop = original.fromStop(); + toStop = original.toStop(); startTime = original.startTime(); endTime = original.endTime(); generalizedCost = original.generalizedCost(); @@ -42,6 +47,24 @@ public FlexTripEdge flexTripEdge() { return flexTripEdge; } + public FlexibleTransitLegBuilder withFromStop(StopLocation stop) { + this.fromStop = stop; + return this; + } + + public StopLocation fromStop() { + return fromStop; + } + + public FlexibleTransitLegBuilder withToStop(StopLocation stop) { + this.toStop = stop; + return this; + } + + public StopLocation toStop() { + return toStop; + } + public FlexibleTransitLegBuilder withStartTime(ZonedDateTime startTime) { this.startTime = startTime; return this; diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java b/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java index 98af1e27534..ec03e9dbaa6 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java @@ -4,6 +4,7 @@ import java.util.Objects; import org.locationtech.jts.geom.LineString; import org.opentripplanner.core.model.i18n.I18NString; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.street.model.edge.Edge; @@ -11,15 +12,14 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateEditor; -import org.opentripplanner.transit.model.site.StopLocation; /** * Flex trips edges are not connected to the graph. */ public class FlexTripEdge extends Edge { - private final StopLocation s1; - private final StopLocation s2; + private final FeedScopedId fromStopId; + private final FeedScopedId toStopId; private final FlexTrip trip; private final int boardStopPosInPattern; private final int alightStopPosInPattern; @@ -29,8 +29,8 @@ public class FlexTripEdge extends Edge { public FlexTripEdge( Vertex v1, Vertex v2, - StopLocation s1, - StopLocation s2, + FeedScopedId fromStopId, + FeedScopedId toStopId, FlexTrip trip, int boardStopPosInPattern, int alightStopPosInPattern, @@ -38,8 +38,8 @@ public FlexTripEdge( FlexPath flexPath ) { super(v1, v2); - this.s1 = s1; - this.s2 = s2; + this.fromStopId = fromStopId; + this.toStopId = toStopId; this.trip = trip; this.boardStopPosInPattern = boardStopPosInPattern; this.alightStopPosInPattern = alightStopPosInPattern; @@ -47,12 +47,12 @@ public FlexTripEdge( this.flexPath = Objects.requireNonNull(flexPath); } - public StopLocation s1() { - return s1; + public FeedScopedId fromStopId() { + return fromStopId; } - public StopLocation s2() { - return s2; + public FeedScopedId toStopId() { + return toStopId; } public int boardStopPosInPattern() { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java index 4382000582c..12ed02161c2 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java @@ -8,6 +8,7 @@ import java.util.Objects; import java.util.stream.Stream; import javax.annotation.Nullable; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexAccessEgress; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; @@ -85,12 +86,12 @@ abstract class AbstractFlexTemplate { this.maxTransferDuration = maxTransferDuration; } - StopLocation getTransferStop() { - return transferStop; + FeedScopedId getTransferStopId() { + return transferStop.getId(); } - StopLocation getAccessEgressStop() { - return accessEgress.stop; + FeedScopedId getAccessEgressStopId() { + return accessEgress.stopId; } /** @@ -174,7 +175,7 @@ protected abstract FlexPathDurations calculateFlexPathDurations( * Get the FlexTripEdge for the flex ride. */ @Nullable - protected abstract FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferStop); + protected abstract FlexTripEdge getFlexEdge(Vertex flexFromVertex, FeedScopedId transferStopId); @Nullable private FlexAccessEgress createFlexAccessEgress( @@ -182,7 +183,7 @@ private FlexAccessEgress createFlexAccessEgress( Vertex flexVertex, RegularStop stop ) { - var flexEdge = getFlexEdge(flexVertex, transferStop); + var flexEdge = getFlexEdge(flexVertex, transferStop.getId()); // Drop non-routable and very short(<10s) trips if (flexEdge == null || flexEdge.getTimeInSeconds() < MIN_FLEX_TRIP_DURATION_SECONDS) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java index ff1dd7836db..91774b60164 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java @@ -83,9 +83,9 @@ public FlexServiceDate activeDate() { ) { var map = new HashMap, ClosestTrip>(); for (NearbyStop nearbyStop : nearbyStops) { - var stop = nearbyStop.stop; - for (var trip : callbackService.getFlexTripsByStop(stop)) { - int stopPos = pickup ? trip.findBoardIndex(stop) : trip.findAlightIndex(stop); + var stopId = nearbyStop.stopId; + for (var trip : callbackService.getFlexTripsByStopId(stopId)) { + int stopPos = pickup ? trip.findBoardIndex(stopId) : trip.findAlightIndex(stopId); if (stopPos != FlexTrip.STOP_INDEX_NOT_FOUND && matcher.match(trip.getTrip())) { var existing = map.get(trip); if (existing == null || nearbyStop.isBetter(existing.nearbyStop())) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java index 60633d88230..4c47b920907 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java @@ -27,7 +27,7 @@ public interface FlexAccessEgressCallbackAdapter { Collection getTransfersToStop(StopLocation stop); /** Adapter, look at implementing service for documentation. */ - Collection> getFlexTripsByStop(StopLocation stopLocation); + Collection> getFlexTripsByStopId(FeedScopedId stopLocation); /** * Return true if date is an active service date for the given trip, and can be used for diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java index cc85b169709..fe66116de81 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java @@ -3,6 +3,7 @@ import java.time.Duration; import java.util.Collection; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; @@ -69,7 +70,7 @@ protected FlexPathDurations calculateFlexPathDurations(FlexTripEdge flexEdge, St ); } - protected FlexTripEdge getFlexEdge(Vertex flexToVertex, StopLocation transferStop) { + protected FlexTripEdge getFlexEdge(Vertex flexToVertex, FeedScopedId transferStopId) { var flexPath = calculator.calculateFlexPath( accessEgress.state.getVertex(), flexToVertex, @@ -84,8 +85,8 @@ protected FlexTripEdge getFlexEdge(Vertex flexToVertex, StopLocation transferSto return new FlexTripEdge( accessEgress.state.getVertex(), flexToVertex, - accessEgress.stop, - transferStop, + accessEgress.stopId, + transferStopId, trip, boardStopPosition, alightStopPosition, diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java index f7d5e34ff17..80a0409837d 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java @@ -9,13 +9,13 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.EdgeTraverser; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.model.filter.expr.Matcher; -import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo; @@ -64,11 +64,11 @@ public Collection calculateDirectFlexPaths( matcher ).calculateFlexEgressTemplates(streetEgresses, dates); - Multimap streetEgressByStop = HashMultimap.create(); - streetEgresses.forEach(it -> streetEgressByStop.put(it.stop, it)); + Multimap streetEgressByStopId = HashMultimap.create(); + streetEgresses.forEach(it -> streetEgressByStopId.put(it.stopId, it)); for (FlexAccessTemplate template : flexAccessTemplates) { - StopLocation transferStop = template.getTransferStop(); + var transferStopId = template.getTransferStopId(); // TODO: Document or reimplement this. Why are we using the egress to see if the // access-transfer-stop (last-stop) is used by at least one egress-template? @@ -80,9 +80,9 @@ public Collection calculateDirectFlexPaths( // Problem: Any asymmetrical restriction which apply/do not apply to the egress, // but do not apply/apply to the access, like booking-notice. if ( - flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStop().equals(transferStop)) + flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStopId().equals(transferStopId)) ) { - for (NearbyStop egress : streetEgressByStop.get(transferStop)) { + for (NearbyStop egress : streetEgressByStopId.get(transferStopId)) { createDirectGraphPath(template, egress, arriveBy, requestTime).ifPresent( directFlexPaths::add ); @@ -111,7 +111,7 @@ private Optional createDirectGraphPath( return Optional.empty(); } - var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stop); + var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stopId); if (flexEdge == null) { return Optional.empty(); diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java index 9dbbd9c0c79..58aaea058c1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java @@ -4,6 +4,7 @@ import java.time.Duration; import java.util.Collection; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; @@ -70,7 +71,7 @@ protected FlexPathDurations calculateFlexPathDurations(FlexTripEdge flexEdge, St ); } - protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferStop) { + protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, FeedScopedId transferStopId) { var flexPath = calculator.calculateFlexPath( flexFromVertex, accessEgress.state.getVertex(), @@ -85,8 +86,8 @@ protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferS return new FlexTripEdge( flexFromVertex, accessEgress.state.getVertex(), - transferStop, - accessEgress.stop, + transferStopId, + accessEgress.stopId, trip, boardStopPosition, alightStopPosition, diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java index 99185993414..4cc1e25f561 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; @@ -119,25 +120,25 @@ public Trip getTrip() { public abstract PickDrop getAlightRule(int i); - public abstract boolean isBoardingPossible(StopLocation stop); + public abstract boolean isBoardingPossible(FeedScopedId stopId); - public abstract boolean isAlightingPossible(StopLocation stop); + public abstract boolean isAlightingPossible(FeedScopedId stopId); /** - * Find the first stop-position matching the given {@code fromStop} where + * Find the first stop-position matching the given {@code fromStopId} where * boarding is allowed. * * @return stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found. */ - public abstract int findBoardIndex(StopLocation fromStop); + public abstract int findBoardIndex(FeedScopedId fromStopId); /** - * Find the first stop-position matching the given {@code toStop} where + * Find the first stop-position matching the given {@code toStopId} where * alighting is allowed. * * @return the stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found. */ - public abstract int findAlightIndex(StopLocation toStop); + public abstract int findAlightIndex(FeedScopedId fromStopId); /** * Allow each FlexTrip type to decorate or replace the router defaultCalculator. diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index d8a9eee239e..5d9cfb7c743 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -151,13 +151,13 @@ public PickDrop getAlightRule(int i) { } @Override - public boolean isBoardingPossible(StopLocation fromStop) { - return findBoardIndex(fromStop) != STOP_INDEX_NOT_FOUND; + public boolean isBoardingPossible(FeedScopedId fromStopId) { + return findBoardIndex(fromStopId) != STOP_INDEX_NOT_FOUND; } @Override - public boolean isAlightingPossible(StopLocation toStop) { - return findAlightIndex(toStop) != STOP_INDEX_NOT_FOUND; + public boolean isAlightingPossible(FeedScopedId toStopId) { + return findAlightIndex(toStopId) != STOP_INDEX_NOT_FOUND; } @Override @@ -176,18 +176,23 @@ public TransitBuilder copy( } @Override - public int findBoardIndex(StopLocation fromStop) { + public int findBoardIndex(FeedScopedId fromStopId) { for (int i = 0; i < stopTimes.length; i++) { if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(fromStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(fromStopId)) + ) { return i; } } else { - if (stop.equals(fromStop)) { + if (stop.getId().equals(fromStopId)) { return i; } } @@ -196,18 +201,23 @@ public int findBoardIndex(StopLocation fromStop) { } @Override - public int findAlightIndex(StopLocation toStop) { + public int findAlightIndex(FeedScopedId toStopId) { for (int i = stopTimes.length - 1; i >= 0; i--) { if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(toStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(toStopId)) + ) { return i; } } else { - if (stop.equals(toStop)) { + if (stop.getId().equals(toStopId)) { return i; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 23fa17891b5..48db3ca8c67 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -195,13 +195,13 @@ public PickDrop getAlightRule(int stopIndex) { } @Override - public boolean isBoardingPossible(StopLocation stop) { - return findBoardIndex(stop) != STOP_INDEX_NOT_FOUND; + public boolean isBoardingPossible(FeedScopedId stopId) { + return findBoardIndex(stopId) != STOP_INDEX_NOT_FOUND; } @Override - public boolean isAlightingPossible(StopLocation stop) { - return findAlightIndex(stop) != STOP_INDEX_NOT_FOUND; + public boolean isAlightingPossible(FeedScopedId stopId) { + return findAlightIndex(stopId) != STOP_INDEX_NOT_FOUND; } @Override @@ -220,18 +220,23 @@ public TransitBuilder copy() { } @Override - public int findBoardIndex(StopLocation fromStop) { + public int findBoardIndex(FeedScopedId fromStopId) { for (int i = 0; i < stopTimes.length; i++) { if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(fromStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(fromStopId)) + ) { return i; } } else { - if (stop.equals(fromStop)) { + if (stop.getId().equals(fromStopId)) { return i; } } @@ -240,18 +245,23 @@ public int findBoardIndex(StopLocation fromStop) { } @Override - public int findAlightIndex(StopLocation toStop) { + public int findAlightIndex(FeedScopedId toStopId) { for (int i = stopTimes.length - 1; i >= 0; i--) { if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(toStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(toStopId)) + ) { return i; } } else { - if (stop.equals(toStop)) { + if (stop.getId().equals(toStopId)) { return i; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java b/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java index 6d9c0b13b8e..da2f9c2ab1c 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java +++ b/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java @@ -64,7 +64,9 @@ public List findCallsAtStop(WgsCoordinate coordinate, StopEventReque .findClosestStops(coordinate.asJtsCoordinate(), params.maximumWalkDistance) .stream() .flatMap(nearbyStop -> { - List stopLocations = List.of(nearbyStop.stop); + List stopLocations = List.of( + transitService.getStopLocation(nearbyStop.stopId) + ); var calls1 = findCallsAtStop(stopLocations, params); return calls1.stream().map(tt -> tt.withWalkTime(nearbyStop.duration())); }) diff --git a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java index c157560c39b..608db5fcb9a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java +++ b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.stream.Collectors; import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.transferanalyzer.annotations.TransferCouldNotBeRouted; import org.opentripplanner.ext.transferanalyzer.annotations.TransferRoutingDistanceTooLong; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -78,10 +79,7 @@ public void buildGraph() { timetableRepository.getSiteRepository()::findRegularStops ); var linkingContextFactory = new LinkingContextFactory(graph, new VertexCreationService(linker)); - StreetGraphFinder nearbyStopFinderStreets = new StreetGraphFinder( - linkingContextFactory, - timetableRepository.getSiteRepository()::getRegularStop - ); + StreetGraphFinder nearbyStopFinderStreets = new StreetGraphFinder(linkingContextFactory); int stopsAnalyzed = 0; @@ -95,8 +93,8 @@ public void buildGraph() { Map stopsEuclidean = nearbyStopFinderEuclidian .findClosestStops(c0, radiusMeters) .stream() - .filter(t -> t.stop instanceof RegularStop) - .collect(Collectors.toMap(t -> (RegularStop) t.stop, t -> t)); + .filter(nearbyStop -> getStop(nearbyStop.stopId) != null) + .collect(Collectors.toMap(nearbyStop -> getStop(nearbyStop.stopId), t -> t)); Map stopsStreets = new HashMap<>(); try { @@ -104,8 +102,8 @@ public void buildGraph() { nearbyStopFinderStreets .findClosestStops(c0, radiusMeters * RADIUS_MULTIPLIER) .stream() - .filter(t -> t.stop instanceof RegularStop) - .forEach(t -> stopsStreets.putIfAbsent((RegularStop) t.stop, t)); + .filter(nearbyStop -> getStop(nearbyStop.stopId) != null) + .forEach(nearbyStop -> stopsStreets.putIfAbsent(getStop(nearbyStop.stopId), nearbyStop)); } catch (Exception ignored) {} RegularStop originStop = Objects.requireNonNull( @@ -193,6 +191,10 @@ public void buildGraph() { ); } + private RegularStop getStop(FeedScopedId id) { + return timetableRepository.getSiteRepository().getRegularStop(id); + } + private static class TransferInfo { final RegularStop origin; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 3127aae9ef1..6a612099bb7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -458,10 +458,10 @@ public DataFetcher node() { return null; case "stopAtDistance": { String[] parts = id.split(";"); - var stop = transitService.getRegularStop(FeedScopedId.parse(parts[1])); + var stopId = FeedScopedId.parse(parts[1]); // TODO: Add geometry - return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); + return new NearbyStop(stopId, Integer.parseInt(parts[0]), null, null); } case "TicketType": // TODO diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java index a4b7a700752..e6f8457d25b 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java @@ -444,7 +444,12 @@ public DataFetcher> transfers() { .filter(transfer -> maxDistance == null || transfer.getDistanceMeters() < maxDistance) .filter(transfer -> transfer.to instanceof RegularStop) .map(transfer -> - new NearbyStop(transfer.to, transfer.getDistanceMeters(), transfer.getEdges(), null) + new NearbyStop( + transfer.to.getId(), + transfer.getDistanceMeters(), + transfer.getEdges(), + null + ) ) .collect(Collectors.toList()); }, diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java index 192c4fa17c8..2dd59452f92 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java @@ -3,8 +3,10 @@ import graphql.relay.Relay; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.transit.service.TransitService; public class stopAtDistanceImpl implements GraphQLDataFetchers.GraphQLStopAtDistance { @@ -18,13 +20,18 @@ public DataFetcher id() { return environment -> new Relay.ResolvedGlobalId( "stopAtDistance", - getSource(environment).distance + ";" + getSource(environment).stop.getId().toString() + getSource(environment).distance + ";" + getSource(environment).stopId.toString() ); } @Override public DataFetcher stop() { - return environment -> getSource(environment).stop; + return environment -> + getTransitService(environment).getStopLocation(getSource(environment).stopId); + } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); } private NearbyStop getSource(DataFetchingEnvironment environment) { diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java index eede9f32cf2..f756658a755 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java @@ -794,8 +794,7 @@ private GraphQLSchema createDefault() { .filter( stopAtDistance -> environment.getArgument("authority") == null || - stopAtDistance.stop - .getId() + stopAtDistance.stopId .getFeedId() .equalsIgnoreCase(environment.getArgument("authority")) ) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java index 713173ba2e4..9dbe531fe5b 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java @@ -2,13 +2,16 @@ import graphql.Scalars; import graphql.relay.Relay; +import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import java.util.Optional; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.transit.service.TransitService; public class QuayAtDistanceType { @@ -29,10 +32,7 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { relay.toGlobalId( "QAD", Optional.ofNullable((NearbyStop) environment.getSource()) - .map( - nearbyStop -> - nearbyStop.distance + ";" + idMapper.mapToApi(nearbyStop.stop.getId()) - ) + .map(nearbyStop -> nearbyStop.distance + ";" + idMapper.mapToApi(nearbyStop.stopId)) .orElse(null) ) ) @@ -42,7 +42,11 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { GraphQLFieldDefinition.newFieldDefinition() .name("quay") .type(quayType) - .dataFetcher(environment -> ((NearbyStop) environment.getSource()).stop) + .dataFetcher(environment -> + getTransitService(environment).getStopLocation( + ((NearbyStop) environment.getSource()).stopId + ) + ) .build() ) .field( @@ -55,4 +59,8 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { ) .build(); } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); + } } 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 7267bb11c62..426450a0080 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,7 +11,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.Set; import org.opentripplanner.astar.model.ShortestPathTree; import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy; @@ -35,16 +34,13 @@ import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.streetadapter.StreetSearchRequestMapper; -import org.opentripplanner.transit.model.site.AreaStop; public class StreetNearbyStopFinder implements NearbyStopFinder { private final Duration durationLimit; private final int maxStopCount; - private final StopResolver stopResolver; private final Collection extensionRequestContexts; private final Set ignoreVertices; - private final NearbyStopFactory nearbyStopFactory; /** * Construct a NearbyStopFinder for the given graph and search radius. @@ -54,18 +50,15 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { * @param ignoreVertices A set of stop vertices to ignore and not return NearbyStops for. */ private StreetNearbyStopFinder( - StopResolver stopResolver, Duration durationLimit, int maxStopCount, Collection extensionRequestContexts, Set ignoreVertices ) { - this.stopResolver = requireNonNull(stopResolver); this.durationLimit = requireNonNull(durationLimit); this.maxStopCount = requireNonNull(maxStopCount); this.extensionRequestContexts = requireNonNull(extensionRequestContexts); this.ignoreVertices = requireNonNull(ignoreVertices); - this.nearbyStopFactory = new NearbyStopFactory(stopResolver::getRegularStop); } /** @@ -74,8 +67,8 @@ private StreetNearbyStopFinder( * @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 static Builder of(StopResolver stopResolver, Duration durationLimit, int maxStopCount) { - return new Builder(stopResolver, durationLimit, maxStopCount); + public static Builder of(Duration durationLimit, int maxStopCount) { + return new Builder(durationLimit, maxStopCount); } /** @@ -109,7 +102,7 @@ public Collection findNearbyStops( ) { OTPRequestTimeoutException.checkForTimeout(); - List stopsFound = nearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( + List stopsFound = NearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( Sets.difference(originVertices, ignoreVertices), reverseDirection, request, @@ -147,7 +140,7 @@ public Collection findNearbyStops( ShortestPathTree spt = streetSearch.getShortestPathTree(); // Only used if OTPFeature.FlexRouting.isOn() - Multimap locationsMap = ArrayListMultimap.create(); + Multimap statesForStopIds = ArrayListMultimap.create(); if (spt != null) { // TODO use GenericAStar and a traverseVisitor? Add an earliestArrival switch to genericAStar? @@ -157,8 +150,7 @@ public Collection findNearbyStops( continue; } if (targetVertex instanceof TransitStopVertex tsv && state.isFinal()) { - var stop = requireNonNull(stopResolver.getRegularStop(tsv.getId())); - stopsFound.add(NearbyStop.nearbyStopForState(state, stop)); + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getId())); } if ( OTPFeature.FlexRouting.isOn() && @@ -166,12 +158,11 @@ public Collection findNearbyStops( !streetVertex.areaStops().isEmpty() ) { for (FeedScopedId id : targetVertex.areaStops()) { - AreaStop areaStop = Objects.requireNonNull(stopResolver.getAreaStop(id)); // This is for a simplification, so that we only return one vertex from each // stop location. All vertices are added to the multimap, which is filtered // below, so that only the closest vertex is added to stopsFound if (canBoardFlex(state, reverseDirection)) { - locationsMap.put(areaStop, state); + statesForStopIds.put(id, state); } } } @@ -179,8 +170,8 @@ public Collection findNearbyStops( } if (OTPFeature.FlexRouting.isOn()) { - for (var locationStates : locationsMap.asMap().entrySet()) { - AreaStop areaStop = locationStates.getKey(); + for (var locationStates : statesForStopIds.asMap().entrySet()) { + var areaStopId = locationStates.getKey(); Collection states = locationStates.getValue(); // Select the vertex from all vertices that are reachable per AreaStop by taking // the minimum walking distance @@ -193,7 +184,7 @@ public Collection findNearbyStops( min = min.getBackState(); } - stopsFound.add(NearbyStop.nearbyStopForState(min, areaStop)); + stopsFound.add(NearbyStop.nearbyStopForState(min, areaStopId)); } } @@ -235,10 +226,8 @@ public static class Builder { private final int maxStopCount; private Collection extensionRequestContexts = List.of(); private Set ignoreVertices = Set.of(); - private final StopResolver stopResolver; - public Builder(StopResolver stopResolver, Duration durationLimit, int maxStopCount) { - this.stopResolver = stopResolver; + public Builder(Duration durationLimit, int maxStopCount) { this.durationLimit = durationLimit; this.maxStopCount = maxStopCount; } @@ -266,7 +255,6 @@ public Builder withIgnoreVertices(Set ignoreVertices) { public StreetNearbyStopFinder build() { return new StreetNearbyStopFinder( - stopResolver, durationLimit, maxStopCount, extensionRequestContexts, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java index 5eff2bf4d60..a5ff0c92acd 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java @@ -16,8 +16,6 @@ import org.opentripplanner.graph_builder.issues.StopNotLinkedForTransfers; import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder; -import org.opentripplanner.graph_builder.module.nearbystops.SiteRepositoryResolver; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.graph_builder.module.nearbystops.StraightLineNearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.graph_builder.module.transfer.api.RegularTransferParameters; @@ -245,10 +243,7 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusAsDuration) { finder = new StraightLineNearbyStopFinder(transitService, radiusAsDuration); } else { LOG.info("Creating direct transfer edges between stops using the street network from OSM..."); - final StopResolver stopResolver = new SiteRepositoryResolver( - timetableRepository.getSiteRepository() - ); - finder = StreetNearbyStopFinder.of(stopResolver, radiusAsDuration, 0).build(); + finder = StreetNearbyStopFinder.of(radiusAsDuration, 0).build(); } if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) { @@ -390,12 +385,14 @@ private void calculateDefaultTransfers( .defaultNearbyStopFinderForMode() .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), false); + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + var nearbyStop = repository.getStopLocation(sd.stopId); + if (nearbyStop == stop) { continue; } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + createPathTransfer(stop, nearbyStop, sd, distinctTransfers, mode); } } } @@ -418,16 +415,18 @@ private void calculateFlexTransfers( .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), true); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + var nearbyStop = repository.getStopLocation(sd.stopId); + if (nearbyStop == stop) { continue; } - if (sd.stop instanceof RegularStop) { + if (nearbyStop instanceof RegularStop) { continue; } // The TransferKey and PathTransfer are created differently for flex routing. - createPathTransfer(sd.stop, stop, sd, distinctTransfers, mode); + createPathTransfer(nearbyStop, stop, sd, distinctTransfers, mode); } } } @@ -492,19 +491,21 @@ private void calculateTransfersForStopWithAllowedStops( var nearbyStops = nearbyStopFinder .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), false); + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { + var nearbyStop = repository.getStopLocation(sd.stopId); // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + if (nearbyStop == stop) { continue; } - if (sd.stop.transfersNotAllowed()) { + if (nearbyStop.transfersNotAllowed()) { continue; } // Only calculate transfers between allowedStops. - if (!allowedStops.contains(sd.stop)) { + if (!allowedStops.contains(nearbyStop)) { continue; } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + createPathTransfer(stop, nearbyStop, sd, distinctTransfers, mode); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java index 8ca720b84b7..fa126cc68b2 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java @@ -23,8 +23,7 @@ class FlexTripNearbyStopFilter implements NearbyStopFilter { @Override public boolean includeFromStop(FeedScopedId id, boolean reverseDirection) { - var stop = transitService.getStopLocation(id); - return !transitService.getFlexIndex().getFlexTripsByStop(stop).isEmpty(); + return !transitService.getFlexIndex().getFlexTripsByStopId(id).isEmpty(); } @Override @@ -34,11 +33,11 @@ public Collection filterToStops( ) { MinMap, NearbyStop> closestStopForFlexTrip = new MinMap<>(); for (var it : nearbyStops) { - var stop = it.stop; - var flexTrips = transitService.getFlexIndex().getFlexTripsByStop(stop); + var stopId = it.stopId; + var flexTrips = transitService.getFlexIndex().getFlexTripsByStopId(stopId); for (FlexTrip trip : flexTrips) { - if (reverseDirection ? trip.isAlightingPossible(stop) : trip.isBoardingPossible(stop)) { + if (reverseDirection ? trip.isAlightingPossible(stopId) : trip.isBoardingPossible(stopId)) { closestStopForFlexTrip.putMin(trip, it); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java index f57a886c977..6c0a0cac0d0 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java @@ -32,10 +32,13 @@ public class PatternConsideringNearbyStopFinder implements NearbyStopFinder { private final NearbyStopFinder delegateNearbyStopFinder; + private final TransitService transitService; + public PatternConsideringNearbyStopFinder( TransitService transitService, NearbyStopFinder delegateNearbyStopFinder ) { + this.transitService = transitService; var builder = CompositeNearbyStopFilter.of().add(new PatternNearbyStopFilter(transitService)); if (OTPFeature.FlexRouting.isOn()) { @@ -80,12 +83,10 @@ public List findNearbyStops( return List.copyOf(result); } - private static Collection removeTransferNotAllowedStops( - Collection nearbyStops - ) { + private Collection removeTransferNotAllowedStops(Collection nearbyStops) { return nearbyStops .stream() - .filter(s -> !s.stop.transfersNotAllowed()) + .filter(s -> !transitService.getStopLocation(s.stopId).transfersNotAllowed()) .toList(); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java index 4ab32e4a973..c8f16f48742 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java @@ -49,7 +49,7 @@ public Collection filterToStops( Set uniqueStopsResult = new HashSet<>(); for (var it : nearbyStops) { - StopLocation stop = it.stop; + StopLocation stop = transitService.getStopLocation(it.stopId); if (stop instanceof RegularStop regularStop) { var patternsForStop = findPatternsForStop(regularStop, reverseDirection); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index e754c9df3c1..9786bdaa438 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -355,6 +355,8 @@ private Leg generateFlexLeg(List states) { return FlexibleTransitLeg.of() .withFlexTripEdge(flexEdge) + .withFromStop(siteResolver.getStopLocation(flexEdge.fromStopId())) + .withToStop(siteResolver.getStopLocation(flexEdge.toStopId())) .withStartTime(startTime) .withEndTime(endTime) .withGeneralizedCost(generalizedCost) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 92711853308..7ab655463c9 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -17,7 +17,6 @@ import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.path.RaptorPath; @@ -45,6 +44,7 @@ import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.routing.framework.DebugTimingAggregator; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -65,7 +65,6 @@ public class TransitRouter { private final AdditionalSearchDays additionalSearchDays; private final ViaCoordinateTransferFactory viaTransferResolver; private final LinkingContext linkingContext; - private final AccessEgressRouter accessEgressRouter; private final TransitServiceResolver transitServiceResolver; private final CarpoolingService carpoolingService; @@ -88,7 +87,6 @@ private TransitRouter( this.viaTransferResolver = serverContext.viaTransferResolver(); this.linkingContext = linkingContext; this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); - this.accessEgressRouter = new AccessEgressRouter(this.transitServiceResolver); this.carpoolingService = carpoolingService; } @@ -299,7 +297,7 @@ private Collection fetchAccessEgresses(AccessEgre Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); int stopCountLimit = accessEgressPreferences.maxStopCountLimit().limitForMode(mode); - var nearbyStops = accessEgressRouter.findAccessEgresses( + var nearbyStops = AccessEgressRouter.findAccessEgresses( accessRequest, streetRequest.mode(), serverContext.listExtensionRequestContexts(accessRequest), @@ -308,7 +306,8 @@ private Collection fetchAccessEgresses(AccessEgre stopCountLimit, linkingContext ); - var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops, type); + var accessEgressMapper = new AccessEgressMapper(transitServiceResolver); + var accessEgresses = accessEgressMapper.mapNearbyStops(nearbyStops, type); accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); var results = new ArrayList<>(accessEgresses); @@ -317,7 +316,6 @@ private Collection fetchAccessEgresses(AccessEgre if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( accessRequest, - accessEgressRouter, serverContext, additionalSearchDays, serverContext.flexParameters(), diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java index 34ff3d8789c..f6629fabbe6 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.stream.Collectors; import org.opentripplanner.framework.application.OTPRequestTimeoutException; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graphfinder.NearbyStop; @@ -23,18 +22,11 @@ public class AccessEgressRouter { private static final Logger LOG = LoggerFactory.getLogger(AccessEgressRouter.class); - private final StopResolver stopResolver; - private final NearbyStopFactory nearbyStopFactory; - - public AccessEgressRouter(StopResolver stopResolver) { - this.stopResolver = stopResolver; - this.nearbyStopFactory = new NearbyStopFactory(stopResolver::getRegularStop); - } /** * Find accesses or egresses. */ - public Collection findAccessEgresses( + public static Collection findAccessEgresses( RouteRequest request, StreetMode streetMode, Collection extensionRequestContexts, @@ -64,7 +56,7 @@ public Collection findAccessEgresses( var originVertices = accessOrEgress.isAccess() ? linkingContext.findVertices(request.from()) : linkingContext.findVertices(request.to()); - var streetAccessEgress = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var streetAccessEgress = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignoreVertices) .withExtensionRequestContexts(extensionRequestContexts) .build() @@ -79,7 +71,7 @@ public Collection findAccessEgresses( * Return a list of direct accesses/egresses that do not require any street search. This will * return an empty list if the source/destination is not a stopId. */ - private List findAccessEgressWithZeroDistance( + private static List findAccessEgressWithZeroDistance( RouteRequest routeRequest, StreetMode streetMode, AccessEgressType accessOrEgress, @@ -89,7 +81,7 @@ private List findAccessEgressWithZeroDistance( ? linkingContext.fromStopVertices() : linkingContext.toStopVertices(); - return nearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( + return NearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( transitStopVertices, accessOrEgress.isEgress(), routeRequest, diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java index 325c5ab0b5b..80025ca78a1 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java @@ -7,7 +7,6 @@ import org.opentripplanner.ext.flex.FlexRouter; import org.opentripplanner.ext.flex.filter.FilterMapper; import org.opentripplanner.framework.application.OTPRequestTimeoutException; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; import org.opentripplanner.routing.api.request.RouteRequest; @@ -24,15 +23,12 @@ public static List route( AdditionalSearchDays additionalSearchDays, LinkingContext linkingContext ) { - var accessEgressRouter = new AccessEgressRouter( - new TransitServiceResolver(serverContext.transitService()) - ); if (!StreetMode.FLEXIBLE.equals(request.journey().direct().mode())) { return Collections.emptyList(); } OTPRequestTimeoutException.checkForTimeout(); // Prepare access/egress transfers - Collection accessStops = accessEgressRouter.findAccessEgresses( + Collection accessStops = AccessEgressRouter.findAccessEgresses( request, request.journey().direct().mode(), serverContext.listExtensionRequestContexts(request), @@ -41,7 +37,7 @@ public static List route( 0, linkingContext ); - Collection egressStops = accessEgressRouter.findAccessEgresses( + Collection egressStops = AccessEgressRouter.findAccessEgresses( request, request.journey().direct().mode(), serverContext.listExtensionRequestContexts(request), diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java index 7336e562538..60e8cea194a 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java @@ -22,7 +22,6 @@ private FlexAccessEgressRouter() {} public static Collection routeAccessEgress( RouteRequest request, - AccessEgressRouter accessEgressRouter, OtpServerRequestContext serverContext, AdditionalSearchDays searchDays, FlexParameters config, @@ -35,7 +34,7 @@ public static Collection routeAccessEgress( TransitService transitService = serverContext.transitService(); Collection accessStops = accessOrEgress.isAccess() - ? accessEgressRouter.findAccessEgresses( + ? AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, extensionRequestContexts, @@ -47,7 +46,7 @@ public static Collection routeAccessEgress( : List.of(); Collection egressStops = accessOrEgress.isEgress() - ? accessEgressRouter.findAccessEgresses( + ? AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, extensionRequestContexts, diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java index 2205a2feb7b..d997b7df4f7 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java @@ -10,11 +10,18 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.transit.model.site.RegularStop; public class AccessEgressMapper { - public static List mapNearbyStops( + private final TransitServiceResolver resolver; + + public AccessEgressMapper(TransitServiceResolver resolver) { + this.resolver = resolver; + } + + public List mapNearbyStops( Collection accessStops, AccessEgressType accessOrEgress ) { @@ -35,16 +42,17 @@ public static Collection mapFlexAccessEgresses( .collect(Collectors.toList()); } - private static RoutingAccessEgress mapNearbyStop( + private RoutingAccessEgress mapNearbyStop( NearbyStop nearbyStop, AccessEgressType accessOrEgress ) { - if (!(nearbyStop.stop instanceof RegularStop)) { + var stop = resolver.getStopLocation(nearbyStop.stopId); + if (!(stop instanceof RegularStop)) { return null; } return new DefaultAccessEgress( - nearbyStop.stop.getIndex(), + stop.getIndex(), accessOrEgress.isEgress() ? nearbyStop.state.reverse() : nearbyStop.state ); } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java index cdf5e9a1c4f..b16cb462e03 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java @@ -41,7 +41,7 @@ public List findClosestStops(Coordinate coordinate, double radiusMet SphericalDistanceLibrary.distance(coordinate, it.getCoordinate().asJtsCoordinate()) ); if (distance < radiusMeters) { - NearbyStop sd = new NearbyStop(it, distance, null, null); + NearbyStop sd = new NearbyStop(it.getId(), distance, null, null); stopsFound.add(sd); } } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java index 1f4a09de08b..1fc372d0a4b 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java @@ -22,12 +22,11 @@ public interface GraphFinder { */ static GraphFinder getInstance( boolean graphHasStreets, - StopResolver stopResolver, Function> queryNearbyStops, LinkingContextFactory linkingContextFactory ) { return graphHasStreets - ? new StreetGraphFinder(linkingContextFactory, stopResolver) + ? new StreetGraphFinder(linkingContextFactory) : new DirectGraphFinder(queryNearbyStops); } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java index 2af052df9cc..f1f1f292617 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java @@ -7,9 +7,9 @@ import java.util.Locale; import java.util.Objects; import org.opentripplanner.astar.model.GraphPath; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.site.StopLocation; /** * A specific stop at a distance. Also includes a geometry and potentially a list of edges and a @@ -17,24 +17,24 @@ */ public class NearbyStop implements Comparable { - public final StopLocation stop; + public final FeedScopedId stopId; public final double distance; public final List edges; public final State state; - public NearbyStop(StopLocation stop, double distance, List edges, State state) { - this.stop = Objects.requireNonNull(stop); + public NearbyStop(FeedScopedId stopId, double distance, List edges, State state) { + this.stopId = Objects.requireNonNull(stopId); this.distance = distance; this.edges = edges; this.state = state; } /** - * Given a State at a StopVertex, bundle the StopVertex together with information about how far + * Given a State at a StopVertex, bundle the stop's id together with information about how far * away it is and the geometry of the path leading up to the given State. */ - public static NearbyStop nearbyStopForState(State state, StopLocation stop) { + public static NearbyStop nearbyStopForState(State state, FeedScopedId stopId) { double effectiveWalkDistance = 0.0; var graphPath = new GraphPath<>(state); var edges = new ArrayList(); @@ -42,14 +42,14 @@ public static NearbyStop nearbyStopForState(State state, StopLocation stop) { effectiveWalkDistance += edge.getEffectiveWalkDistance(); edges.add(edge); } - return new NearbyStop(stop, effectiveWalkDistance, edges, state); + return new NearbyStop(stopId, effectiveWalkDistance, edges, state); } /** * Create a NearbyStop with zero distance and no edges. */ - public static NearbyStop ofZeroDistance(StopLocation stop, State state) { - return new NearbyStop(stop, 0d, Collections.emptyList(), state); + public static NearbyStop ofZeroDistance(FeedScopedId stopId, State state) { + return new NearbyStop(stopId, 0d, Collections.emptyList(), state); } /** @@ -84,7 +84,7 @@ public Duration duration() { @Override public int hashCode() { - return Objects.hash(stop, distance, edges, state); + return Objects.hash(stopId, distance, edges, state); } @Override @@ -98,7 +98,7 @@ public boolean equals(Object o) { final NearbyStop that = (NearbyStop) o; return ( Double.compare(that.distance, distance) == 0 && - stop.equals(that.stop) && + stopId.equals(that.stopId) && Objects.equals(edges, that.edges) && Objects.equals(state, that.state) ); @@ -108,7 +108,7 @@ public String toString() { return String.format( Locale.ROOT, "stop %s at %.1f meters%s%s", - stop, + stopId, distance, edges != null ? " (" + edges.size() + " edges)" : "", state != null ? " w/state" : "" diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java index 415bb67e5db..d91ea5cb7f7 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java @@ -4,30 +4,21 @@ import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; import org.opentripplanner.streetadapter.StreetSearchRequestMapper; -import org.opentripplanner.transit.model.site.RegularStop; public class NearbyStopFactory { - private final StopResolver stopResolver; - - public NearbyStopFactory(StopResolver stopResolver) { - this.stopResolver = Objects.requireNonNull(stopResolver); - } - /** * Create zero distance NearbyStops given a list of TransitStopVertices */ - public List nearbyStopsForTransitStopVertices( + public static List nearbyStopsForTransitStopVertices( Set stopVertices, boolean reverseDirection, RouteRequest routeRequest, @@ -44,7 +35,7 @@ public List nearbyStopsForTransitStopVertices( return stopVertices .stream() - .map(s -> ofZeroDistance(stop(s.getId()), new State(s, streetSearchRequest))) + .map(s -> ofZeroDistance(s.getId(), new State(s, streetSearchRequest))) .toList(); } @@ -52,7 +43,7 @@ public List nearbyStopsForTransitStopVertices( * Given a list of Vertices, find the TransitStopVertices and create zero distance NearbyStops * for them. */ - public List nearbyStopsForTransitStopVerticesFiltered( + public static List nearbyStopsForTransitStopVerticesFiltered( Collection vertices, boolean reverseDirection, RouteRequest routeRequest, @@ -66,8 +57,4 @@ public List nearbyStopsForTransitStopVerticesFiltered( return nearbyStopsForTransitStopVertices(transitStops, reverseDirection, routeRequest, mode); } - - private RegularStop stop(FeedScopedId id) { - return Objects.requireNonNull(stopResolver.getStop(id)); - } } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java index fb09f82f5db..aa25b8b36c5 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java @@ -3,6 +3,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; /** * Interface for resolving stops and entrances by ID. @@ -12,5 +13,6 @@ */ public interface SiteResolver extends EntranceResolver { RegularStop getStop(FeedScopedId id); + StopLocation getStopLocation(FeedScopedId id); Entrance getEntrance(FeedScopedId id); } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java index 3692d9bcedf..b5d37f6ac3a 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.opentripplanner.astar.spi.SkipEdgeStrategy; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.street.model.edge.Edge; @@ -16,14 +15,12 @@ */ public class StopFinderTraverseVisitor implements TraverseVisitor { - private final StopResolver stopResolver; private final double radiusMeters; /** A list of closest stops found while walking the graph */ private final List stopsFound = new ArrayList<>(); - public StopFinderTraverseVisitor(StopResolver stopResolver, double radiusMeters) { - this.stopResolver = stopResolver; + public StopFinderTraverseVisitor(double radiusMeters) { this.radiusMeters = radiusMeters; } @@ -34,8 +31,7 @@ public void visitEdge(Edge edge) {} public void visitVertex(State state) { Vertex vertex = state.getVertex(); if (vertex instanceof TransitStopVertex tsv) { - var stop = Objects.requireNonNull(stopResolver.getStop(tsv.getId())); - stopsFound.add(NearbyStop.nearbyStopForState(state, stop)); + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getId())); } } @@ -46,7 +42,7 @@ public void visitEnqueue() {} * @return A de-duplicated list of nearby stops found by this visitor. */ public List stopsFound() { - return ListUtils.distinctByKey(stopsFound, ns -> ns.stop); + return ListUtils.distinctByKey(stopsFound, ns -> ns.stopId); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java index f6c046c4e11..8b275a63eca 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java @@ -29,16 +29,14 @@ public class StreetGraphFinder implements GraphFinder { private final LinkingContextFactory linkingContextFactory; - private final StopResolver stopResolver; - public StreetGraphFinder(LinkingContextFactory linkingContextFactory, StopResolver stopResolver) { + public StreetGraphFinder(LinkingContextFactory linkingContextFactory) { this.linkingContextFactory = linkingContextFactory; - this.stopResolver = stopResolver; } @Override public List findClosestStops(Coordinate coordinate, double radiusMeters) { - StopFinderTraverseVisitor visitor = new StopFinderTraverseVisitor(stopResolver, radiusMeters); + StopFinderTraverseVisitor visitor = new StopFinderTraverseVisitor(radiusMeters); findClosestUsingStreets( coordinate.getY(), coordinate.getX(), diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java index dd235409e24..827b8d20848 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java @@ -4,6 +4,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; /** @@ -22,6 +23,11 @@ public RegularStop getStop(FeedScopedId id) { return Objects.requireNonNull(transitService.getRegularStop(id)); } + @Override + public StopLocation getStopLocation(FeedScopedId id) { + return Objects.requireNonNull(transitService.getStopLocation(id)); + } + @Override public Entrance getEntrance(FeedScopedId id) { return transitService.getEntrance(id); diff --git a/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java b/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java index b911d4854c4..f444af63342 100644 --- a/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java @@ -7,7 +7,6 @@ import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StraightLineNearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; @@ -57,8 +56,8 @@ public List createViaTransfers( transfers.add( new ViaCoordinateTransfer( coordinate, - from.stop.getIndex(), - to.stop.getIndex(), + transitService.getStopLocation(from.stopId).getIndex(), + transitService.getStopLocation(to.stopId).getIndex(), from.edges, to.edges, (int) (from.state.getElapsedTimeSeconds() + to.state.getElapsedTimeSeconds()), @@ -78,11 +77,7 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusAsDuration) { if (!graph.hasStreets) { return new StraightLineNearbyStopFinder(transitService, radiusAsDuration); } else { - return StreetNearbyStopFinder.of( - new TransitServiceResolver(transitService), - radiusAsDuration, - 0 - ).build(); + return StreetNearbyStopFinder.of(radiusAsDuration, 0).build(); } } @@ -99,7 +94,7 @@ private List findNearbyStops( var r = finder.findNearbyStops(viaVertex, request, transferMode, reverseDirection); return r .stream() - .filter(it -> !it.stop.transfersNotAllowed()) + .filter(it -> !transitService.getStopLocation(it.stopId).transfersNotAllowed()) .toList(); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 91fc66b00bf..fc960982498 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -134,7 +134,6 @@ public interface OtpServerRequestContext { default GraphFinder graphFinder() { return GraphFinder.getInstance( graph().hasStreets, - transitService()::getRegularStop, transitService()::findRegularStopsByBoundingBox, linkingContextFactory() ); diff --git a/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java b/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java index 70119e782bd..f3d21760f5d 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java +++ b/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java @@ -4,6 +4,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; public class NoopSiteResolver implements SiteResolver { @@ -12,6 +13,11 @@ public RegularStop getStop(FeedScopedId id) { throw new NotImplementedException(); } + @Override + public StopLocation getStopLocation(FeedScopedId id) { + throw new NotImplementedException(); + } + @Override public Entrance getEntrance(FeedScopedId id) { throw new NotImplementedException(); 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 c0cb7629c30..51b2823de32 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 @@ -90,7 +90,6 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance( graph.hasStreets, - transitService::getRegularStop, transitService::findRegularStopsByBoundingBox, linkingContextFactory ), diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java index 7ed6f883f01..9281db7fd48 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java @@ -83,7 +83,6 @@ public _RouteRequestTestContext(Locale locale) { SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance( graph.hasStreets, - transitService::getRegularStop, transitService::findRegularStopsByBoundingBox, linkingContextFactory ), 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 index daa04feb01a..d55177b93e8 100644 --- 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 @@ -23,11 +23,10 @@ class StreetNearbyStopFinderMultipleLinksTest extends GraphRoutingTest { private TransitStopVertex stopA; private TransitStopVertex stopB; private TransitStopVertex stopC; - private StopResolver stopResolver; @BeforeEach protected void setUp() throws Exception { - var model = modelOf( + modelOf( new Builder() { @Override public void build() { @@ -54,7 +53,6 @@ public void build() { } } ); - this.stopResolver = new SiteRepositoryResolver(model.timetableRepository().getSiteRepository()); } @Test @@ -62,7 +60,7 @@ 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 = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -78,7 +76,7 @@ void testMaxStopCountRegression() { * Verify that the nearby stop is zero distance and corresponds to the expected vertex */ void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(0, nearbyStop.distance); assertEquals(0, nearbyStop.edges.size()); assertEquals(expected, nearbyStop.state.getVertex()); @@ -93,7 +91,7 @@ void assertStopAtDistance( double expectedDistance, NearbyStop nearbyStop ) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(expectedDistance, nearbyStop.distance); assertEquals(expected, nearbyStop.state.getVertex()); assertFalse(nearbyStop.edges.isEmpty()); 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 d32e447eb70..696a6268887 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 @@ -29,11 +29,10 @@ class StreetNearbyStopFinderTest extends GraphRoutingTest { private TransitStopVertex stopB; private TransitStopVertex stopC; private TransitStopVertex stopD; - private StopResolver stopResolver; @BeforeEach protected void setUp() throws Exception { - var model = modelOf( + modelOf( new GraphRoutingTest.Builder() { @Override public void build() { @@ -61,14 +60,13 @@ public void build() { } } ); - this.stopResolver = new SiteRepositoryResolver(model.timetableRepository().getSiteRepository()); } @Test void testIsolatedStop() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var nearbyStops = finder.findNearbyStops( isolatedStop, @@ -86,7 +84,7 @@ void testIsolatedStop() { void testMultipleStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -103,7 +101,7 @@ void testMultipleStops() { void testMaxStopCount() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 2; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -124,7 +122,7 @@ void testDurationLimit() { .withPreferences(b -> b.withWalk(w -> w.withSpeed(1.0))) .buildDefault(); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, routeRequest, StreetMode.WALK, false) ); @@ -139,7 +137,7 @@ void testIgnoreStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; Set ignore = Set.of(stopA, stopB); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignore) .build(); @@ -157,7 +155,7 @@ void testIgnoreStopsWithMaxStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 1; Set ignore = Set.of(stopA, stopB); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignore) .build(); @@ -177,7 +175,7 @@ static List sort(Collection stops) { * Verify that the nearby stop is zero distance and corresponds to the expected vertex */ void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(0, nearbyStop.distance); assertEquals(0, nearbyStop.edges.size()); assertEquals(expected, nearbyStop.state.getVertex()); @@ -192,7 +190,7 @@ void assertStopAtDistance( double expectedDistance, NearbyStop nearbyStop ) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(expectedDistance, nearbyStop.distance); assertEquals(expected, nearbyStop.state.getVertex()); assertFalse(nearbyStop.edges.isEmpty()); diff --git a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 05d75e29eb5..24b08dc7b42 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -245,8 +245,8 @@ public TestItineraryBuilder flex(int start, int end, Place to) { var edge = new FlexTripEdge( fromv, tov, - lastPlace.stop, - to.stop, + lastPlace.stop.getId(), + to.stop.getId(), flexTrip, fromStopPos, toStopPos, @@ -256,6 +256,8 @@ public TestItineraryBuilder flex(int start, int end, Place to) { FlexibleTransitLeg leg = FlexibleTransitLeg.of() .withFlexTripEdge(edge) + .withFromStop(lastPlace.stop) + .withToStop(to.stop) .withStartTime(newTime(start)) .withEndTime(newTime(end)) .withGeneralizedCost(legCost) diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java index 9467e929d8a..7374df75b9d 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.graph_builder.module.nearbystops.SiteRepositoryResolver; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.api.request.RouteRequest; @@ -227,7 +226,11 @@ private RouteRequest requestFromTo(GenericLocation from, GenericLocation to) { private String nearbyStopDescription(NearbyStop nearbyStop) { if (nearbyStop.edges.isEmpty()) { - return "direct[" + nearbyStop.stop.getName() + "]"; + return ( + "direct[" + + timetableRepository.getSiteRepository().getStopLocation(nearbyStop.stopId).getName() + + "]" + ); } else { return "street[" + stateDescription(nearbyStop.state) + "]"; } @@ -272,9 +275,7 @@ private Collection findAccessEgressFromTo( var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(verticesContainer, linkingRequest); - return new AccessEgressRouter( - new SiteRepositoryResolver(timetableRepository.getSiteRepository()) - ).findAccessEgresses( + return AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, List.of(), diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java index f77521215cd..2c9f15fe6fc 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java @@ -36,8 +36,8 @@ public void build() { @Test void findClosestStops() { - var ns1 = new NearbyStop(siteRepository.getRegularStop(S1.getId()), 0, null, null); - var ns2 = new NearbyStop(siteRepository.getRegularStop(S2.getId()), 1112, null, null); + var ns1 = new NearbyStop(S1.getId(), 0, null, null); + var ns2 = new NearbyStop(S2.getId(), 1112, null, null); var subject = new DirectGraphFinder(siteRepository::findRegularStops); var coordinate = new Coordinate(19.000, 47.500); diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java index 6f5fe5c3760..4bdb5efbc21 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java @@ -16,13 +16,13 @@ class NearbyStopTest { void testIsBetter() { // We only test the distance here, since the compareTo method used should have a more complete // unit-test including tests on state weight. - var a = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null); - var b = new NearbyStop(MODEL.stop("A").build(), 30.0, null, null); + var a = new NearbyStop(MODEL.id("A"), 20.0, null, null); + var b = new NearbyStop(MODEL.id("A"), 30.0, null, null); assertTrue(a.isBetter(b)); assertFalse(b.isBetter(a)); - var sameDistance = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null); + var sameDistance = new NearbyStop(MODEL.id("A"), 20.0, null, null); assertFalse(a.isBetter(sameDistance)); assertFalse(sameDistance.isBetter(a)); } diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java index b805fe06190..495be852d2d 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java @@ -14,14 +14,14 @@ class StopFinderTraverseVisitorTest { @Test void deduplicateStops() { - var visitor = new StopFinderTraverseVisitor(id -> STOP, 1000); + var visitor = new StopFinderTraverseVisitor(1000); assertEquals(List.of(), visitor.stopsFound()); var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP).build(); visitor.visitVertex(state1); - final NearbyStop nearbyStop = NearbyStop.nearbyStopForState(state1, STOP); + final NearbyStop nearbyStop = NearbyStop.nearbyStopForState(state1, STOP.getId()); assertEquals(List.of(nearbyStop), visitor.stopsFound()); diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index de364988b6b..2ca6b9c7e30 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -130,16 +130,13 @@ public void build() { otpModel.graph(), new VertexCreationService(vertexLinker) ); - graphFinder = new StreetGraphFinder( - linkingContextFactory, - otpModel.timetableRepository().getSiteRepository()::getRegularStop - ); + graphFinder = new StreetGraphFinder(linkingContextFactory); } @Test void findClosestStops() { - var ns1 = new NearbyStop(stop(S1), 0, null, null); - var ns2 = new NearbyStop(stop(S2), 100, null, null); + var ns1 = new NearbyStop(S1.getId(), 0, null, null); + var ns2 = new NearbyStop(S2.getId(), 100, null, null); var coordinate = new Coordinate(19.000, 47.500); assertEquals(List.of(ns1), simplify(graphFinder.findClosestStops(coordinate, 10))); @@ -487,7 +484,7 @@ void findClosestPlacesWithACarParkFilter() { private List simplify(List closestStops) { return closestStops .stream() - .map(ns -> new NearbyStop(ns.stop, ns.distance, null, null)) + .map(ns -> new NearbyStop(ns.stopId, ns.distance, null, null)) .collect(Collectors.toList()); } From 833a385ea452e683a6f038889cf85a54df775731 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 30 Mar 2026 10:41:45 +0300 Subject: [PATCH 008/177] Apply review feedback --- .../ext/transferanalyzer/DirectTransferAnalyzer.java | 12 +++++++----- .../raptoradapter/router/TransitRouter.java | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java index 608db5fcb9a..31029dba502 100644 --- a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java +++ b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java @@ -93,8 +93,8 @@ public void buildGraph() { Map stopsEuclidean = nearbyStopFinderEuclidian .findClosestStops(c0, radiusMeters) .stream() - .filter(nearbyStop -> getStop(nearbyStop.stopId) != null) - .collect(Collectors.toMap(nearbyStop -> getStop(nearbyStop.stopId), t -> t)); + .filter(nearbyStop -> getRegularStop(nearbyStop.stopId) != null) + .collect(Collectors.toMap(nearbyStop -> getRegularStop(nearbyStop.stopId), t -> t)); Map stopsStreets = new HashMap<>(); try { @@ -102,8 +102,10 @@ public void buildGraph() { nearbyStopFinderStreets .findClosestStops(c0, radiusMeters * RADIUS_MULTIPLIER) .stream() - .filter(nearbyStop -> getStop(nearbyStop.stopId) != null) - .forEach(nearbyStop -> stopsStreets.putIfAbsent(getStop(nearbyStop.stopId), nearbyStop)); + .filter(nearbyStop -> getRegularStop(nearbyStop.stopId) != null) + .forEach(nearbyStop -> + stopsStreets.putIfAbsent(getRegularStop(nearbyStop.stopId), nearbyStop) + ); } catch (Exception ignored) {} RegularStop originStop = Objects.requireNonNull( @@ -191,7 +193,7 @@ public void buildGraph() { ); } - private RegularStop getStop(FeedScopedId id) { + private RegularStop getRegularStop(FeedScopedId id) { return timetableRepository.getSiteRepository().getRegularStop(id); } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 7ab655463c9..404a02e5f78 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -67,6 +67,7 @@ public class TransitRouter { private final LinkingContext linkingContext; private final TransitServiceResolver transitServiceResolver; private final CarpoolingService carpoolingService; + private final AccessEgressMapper accessEgressMapper; private TransitRouter( RouteRequest request, @@ -88,6 +89,7 @@ private TransitRouter( this.linkingContext = linkingContext; this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); this.carpoolingService = carpoolingService; + this.accessEgressMapper = new AccessEgressMapper(transitServiceResolver); } public static TransitRouterResult route( @@ -306,7 +308,6 @@ private Collection fetchAccessEgresses(AccessEgre stopCountLimit, linkingContext ); - var accessEgressMapper = new AccessEgressMapper(transitServiceResolver); var accessEgresses = accessEgressMapper.mapNearbyStops(nearbyStops, type); accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); From e4fd80c74b92c0d75f7668f30cfc8c1099c43baa Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 25 Mar 2026 08:05:07 +0100 Subject: [PATCH 009/177] Defer State.reverse() from AccessEgressMapper to GraphPath construction State.reverse() was called eagerly for every egress NearbyStop when mapping to DefaultAccessEgress, allocating a full clone of the State chain. Profiling showed this at 8.19% of total allocation under load. The reversal is only needed for itinerary reconstruction of winning paths. GraphPath already calls State.reverse() when arriveBy is true, so removing the eager call defers reversal to the few winning paths rather than applying it to every candidate. The scalar values extracted during DefaultAccessEgress construction (weight, elapsed time, walk-only mode) are direction-independent and produce identical results on unreversed State. --- .../raptoradapter/router/TransitRouter.java | 4 ++-- .../transit/FlexAccessEgressAdapter.java | 13 ++--------- .../transit/mappers/AccessEgressMapper.java | 23 +++++-------------- .../RaptorPathToItineraryMapperTest.java | 6 +---- .../transit/DefaultAccessEgressTest.java | 19 +++++++++++++++ 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 771bcf2c278..4bc0825987f 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -308,7 +308,7 @@ private Collection fetchAccessEgresses(AccessEgre stopCountLimit, linkingContext ); - var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops, type); + var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops); accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); var results = new ArrayList<>(accessEgresses); @@ -326,7 +326,7 @@ private Collection fetchAccessEgresses(AccessEgre linkingContext ); - results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList, type)); + results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList)); } if (OTPFeature.CarPooling.isOn() && mode == StreetMode.CARPOOL) { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java index 901053c4bd2..1fc45cfcdad 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java @@ -4,7 +4,6 @@ import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.RaptorConstants; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; /** * This class is used to adapt the FlexAccessEgress into a time-dependent multi-leg DefaultAccessEgress. @@ -13,16 +12,8 @@ public class FlexAccessEgressAdapter extends DefaultAccessEgress { private final FlexAccessEgress flexAccessEgress; - public FlexAccessEgressAdapter( - FlexAccessEgress flexAccessEgress, - AccessEgressType accessOrEgress - ) { - super( - flexAccessEgress.stop().getIndex(), - accessOrEgress.isEgress() - ? flexAccessEgress.lastState().reverse() - : flexAccessEgress.lastState() - ); + public FlexAccessEgressAdapter(FlexAccessEgress flexAccessEgress) { + super(flexAccessEgress.stop().getIndex(), flexAccessEgress.lastState()); this.flexAccessEgress = flexAccessEgress; } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java index 2205a2feb7b..38c4c70a695 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java @@ -5,7 +5,6 @@ import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.ext.flex.FlexAccessEgress; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; @@ -14,38 +13,28 @@ public class AccessEgressMapper { - public static List mapNearbyStops( - Collection accessStops, - AccessEgressType accessOrEgress - ) { + public static List mapNearbyStops(Collection accessStops) { return accessStops .stream() - .map(nearbyStop -> mapNearbyStop(nearbyStop, accessOrEgress)) + .map(AccessEgressMapper::mapNearbyStop) .filter(Objects::nonNull) .collect(Collectors.toList()); } public static Collection mapFlexAccessEgresses( - Collection flexAccessEgresses, - AccessEgressType accessOrEgress + Collection flexAccessEgresses ) { return flexAccessEgresses .stream() - .map(flexAccessEgress -> new FlexAccessEgressAdapter(flexAccessEgress, accessOrEgress)) + .map(FlexAccessEgressAdapter::new) .collect(Collectors.toList()); } - private static RoutingAccessEgress mapNearbyStop( - NearbyStop nearbyStop, - AccessEgressType accessOrEgress - ) { + private static RoutingAccessEgress mapNearbyStop(NearbyStop nearbyStop) { if (!(nearbyStop.stop instanceof RegularStop)) { return null; } - return new DefaultAccessEgress( - nearbyStop.stop.getIndex(), - accessOrEgress.isEgress() ? nearbyStop.state.reverse() : nearbyStop.state - ); + return new DefaultAccessEgress(nearbyStop.stop.getIndex(), nearbyStop.state); } } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java index 53d6ff0552a..f0f06b3dbbc 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -45,7 +45,6 @@ import org.opentripplanner.raptorlegacy._data.transit.TestTransitData; import org.opentripplanner.raptorlegacy._data.transit.TestTripPattern; import org.opentripplanner.raptorlegacy._data.transit.TestTripSchedule; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; @@ -198,10 +197,7 @@ void createItineraryWithOnBoardFlexAccess() { true, RoutingBookingInfo.NOT_SET ); - RaptorAccessEgress access = new FlexAccessEgressAdapter( - flexAccessEgress, - AccessEgressType.ACCESS - ); + RaptorAccessEgress access = new FlexAccessEgressAdapter(flexAccessEgress); Transfer transfer = new Transfer(S2.getIndex(), 0, EnumSet.of(StreetMode.WALK)); RaptorTransfer raptorTransfer = new DefaultRaptorTransfer(S1.getIndex(), 0, 0, transfer); RaptorAccessEgress egress = new DefaultAccessEgress(S2.getIndex(), state); diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 2cce035a5a9..e54fec19ac7 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -101,4 +101,23 @@ void testToString() { subjectWithPenalty.toString() ); } + + /** + * Verify that the scalar values extracted during DefaultAccessEgress construction + * (duration, generalized cost, walk-only mode) are identical for reversed and unreversed + * State chains. This invariant allows deferring State.reverse() from AccessEgressMapper + * to GraphPath construction, where it is only applied to winning paths rather than all + * candidates. + */ + @Test + void scalarValuesAreIdenticalForReversedAndUnreversedState() { + var state = TestStateBuilder.ofWalking().streetEdge().streetEdge().streetEdge().build(); + + var fromUnreversed = new DefaultAccessEgress(STOP, state); + var fromReversed = new DefaultAccessEgress(STOP, state.reverse()); + + assertEquals(fromUnreversed.durationInSeconds(), fromReversed.durationInSeconds()); + assertEquals(fromUnreversed.c1(), fromReversed.c1()); + assertEquals(fromUnreversed.isWalkOnly(), fromReversed.isWalkOnly()); + } } From cace7b632498abe7491c718880e5ed7af1bd458a Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 27 Mar 2026 16:56:03 +0100 Subject: [PATCH 010/177] Document unreversed egress State contract and add direction-independence test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarify that getLastState() returns the terminal state in A* search order, not chronological order. For egress searches the State is unreversed (arriveBy=true) — reversal is deferred to GraphPath construction for winning paths only. Add a unit test verifying that durationInSeconds, c1, and isWalkOnly produce identical results on reversed and unreversed State chains. This invariant is what makes the deferred reversal safe. --- .../raptoradapter/transit/DefaultAccessEgress.java | 10 +++++++++- .../raptoradapter/transit/RoutingAccessEgress.java | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 376732dc4f5..d097cabe5a2 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -20,7 +20,15 @@ public class DefaultAccessEgress implements RoutingAccessEgress { private final TimeAndCost penalty; /** - * This should be the last state both in the case of access and egress. + * The last state from the access/egress street search. For egress searches this State is + * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to + * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for + * winning paths during itinerary mapping. This avoids the cost of cloning the entire State + * chain for every egress candidate. + *

+ * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, + * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on + * both reversed and unreversed State chains. */ private final State lastState; diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java index dbc260d3d90..0f5663253b3 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java @@ -18,7 +18,11 @@ public interface RoutingAccessEgress extends RaptorAccessEgress { RoutingAccessEgress withPenalty(TimeAndCost penalty); /** - * Return the last state both in the case of access and egress. + * Return the terminal state of the A* street search that reached the transit stop. "Last" + * refers to the search order, not chronological order — for egress searches + * ({@code request.arriveBy() == true}) the state chain runs backward in time and is not + * reversed. Callers that need a chronological state chain must wrap this in a + * {@link org.opentripplanner.astar.model.GraphPath}. */ State getLastState(); From dcdea175a73dd462021e2f7ec10e1f82c0720dc0 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 30 Mar 2026 13:30:16 +0200 Subject: [PATCH 011/177] Rename last state to final state for clarity --- .../routing/CarpoolAccessEgress.java | 2 +- .../ridehailing/RideHailingAccessAdapter.java | 2 +- .../ridehailing/RideHailingAccessShifter.java | 2 +- .../mapping/RaptorPathToItineraryMapper.java | 2 +- .../transit/DefaultAccessEgress.java | 26 +++++++++---------- .../transit/RoutingAccessEgress.java | 4 +-- .../transit/DefaultAccessEgressTest.java | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java index 35643aa1755..7f6fc597580 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java @@ -112,7 +112,7 @@ public RoutingAccessEgress withPenalty(TimeAndCost penalty) { It is never used for instances of CarpoolAccessEgress, but this might change in the future. */ @Override - public State getLastState() { + public State getFinalState() { throw new UnsupportedOperationException( "Fetching last state of CarpoolAccessEgress is not yet implemented" ); diff --git a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java index 257cf0a7ca5..07ad3c368e1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java +++ b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java @@ -14,7 +14,7 @@ public final class RideHailingAccessAdapter extends DefaultAccessEgress { private final Duration arrival; public RideHailingAccessAdapter(RoutingAccessEgress access, Duration arrival) { - super(access.stop(), access.getLastState()); + super(access.stop(), access.getFinalState()); this.arrival = arrival; } diff --git a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java index 3eea7bb03f8..9739dc300d1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java +++ b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java @@ -45,7 +45,7 @@ public static List shiftAccesses( .map(ae -> { // only time-shift access legs on a car // (there could be walk-only accesses if you're close to the stop) - if (isAccess && ae.getLastState().containsModeCar()) { + if (isAccess && ae.getFinalState().containsModeCar()) { var duration = fetchArrivalDelay(services, request, now); if (duration.isSuccess()) { return new RideHailingAccessAdapter(ae, duration.successValue()); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index f4ecbda673b..93b6acbbcd8 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -499,7 +499,7 @@ private boolean includeTransferInItinerary(Leg transitLegBeforeTransfer) { private Itinerary mapAccessEgressPathLeg(RaptorAccessEgress accessEgress) { return accessEgress .findOriginal(RoutingAccessEgress.class) - .map(RoutingAccessEgress::getLastState) + .map(RoutingAccessEgress::getFinalState) .map(GraphPath::new) .map(path -> graphPathToItineraryMapper.generateItinerary(path, request)) .orElseThrow(); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index d097cabe5a2..73fa117ca04 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -20,17 +20,17 @@ public class DefaultAccessEgress implements RoutingAccessEgress { private final TimeAndCost penalty; /** - * The last state from the access/egress street search. For egress searches this State is + * The final state from the access/egress street search. For egress searches this State is * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for - * winning paths during itinerary mapping. This avoids the cost of cloning the entire State + * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State * chain for every egress candidate. *

* The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on * both reversed and unreversed State chains. */ - private final State lastState; + private final State finalState; /** * This is public to allow unit-tests full control over the field values. @@ -40,23 +40,23 @@ public DefaultAccessEgress( int durationInSeconds, int generalizedCost, TimeAndCost penalty, - State lastState + State finalState ) { this.stop = stop; this.durationInSeconds = durationInSeconds; this.generalizedCost = generalizedCost; this.timePenalty = penalty.isZero() ? RaptorConstants.TIME_NOT_SET : penalty.timeInSeconds(); this.penalty = penalty; - this.lastState = Objects.requireNonNull(lastState); + this.finalState = Objects.requireNonNull(finalState); } - public DefaultAccessEgress(int stop, State lastState) { + public DefaultAccessEgress(int stop, State finalState) { this( stop, - (int) lastState.getElapsedTimeSeconds(), - RaptorCostConverter.toRaptorCost(lastState.getWeight()), + (int) finalState.getElapsedTimeSeconds(), + RaptorCostConverter.toRaptorCost(finalState.getWeight()), TimeAndCost.ZERO, - lastState + finalState ); } @@ -69,7 +69,7 @@ protected DefaultAccessEgress(RoutingAccessEgress other, TimeAndCost penalty) { other.durationInSeconds(), other.c1() + penalty.cost().toCentiSeconds(), penalty, - other.getLastState() + other.getFinalState() ); if (other.penalty() != TimeAndCost.ZERO) { throw new IllegalStateException("Can not add penalty twice..."); @@ -102,13 +102,13 @@ public boolean hasOpeningHours() { } @Override - public State getLastState() { - return lastState; + public State getFinalState() { + return finalState; } @Override public boolean isWalkOnly() { - return lastState.containsOnlyWalkMode(); + return finalState.containsOnlyWalkMode(); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java index 0f5663253b3..d1c084c7526 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java @@ -18,13 +18,13 @@ public interface RoutingAccessEgress extends RaptorAccessEgress { RoutingAccessEgress withPenalty(TimeAndCost penalty); /** - * Return the terminal state of the A* street search that reached the transit stop. "Last" + * Return the final state of the A* street search that reached the transit stop. "Last" * refers to the search order, not chronological order — for egress searches * ({@code request.arriveBy() == true}) the state chain runs backward in time and is not * reversed. Callers that need a chronological state chain must wrap this in a * {@link org.opentripplanner.astar.model.GraphPath}. */ - State getLastState(); + State getFinalState(); /** * Return true if all edges are traversed on foot. diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index e54fec19ac7..0239166f19c 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -63,7 +63,7 @@ void hasOpeningHours() { @Test void getLastState() { - assertEquals(LAST_STATE, subject.getLastState()); + assertEquals(LAST_STATE, subject.getFinalState()); } @Test From d64b5b69aec6ece8a85640df4e087f4aedd63d89 Mon Sep 17 00:00:00 2001 From: Vincent Paturet <46598384+vpaturet@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:25:50 +0200 Subject: [PATCH 012/177] Update application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java Co-authored-by: Leonard Ehrenfried --- .../algorithm/raptoradapter/transit/RoutingAccessEgress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java index d1c084c7526..a8b964c1891 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java @@ -18,7 +18,7 @@ public interface RoutingAccessEgress extends RaptorAccessEgress { RoutingAccessEgress withPenalty(TimeAndCost penalty); /** - * Return the final state of the A* street search that reached the transit stop. "Last" + * Return the final state of the A* street search that reached the transit stop. "Final" * refers to the search order, not chronological order — for egress searches * ({@code request.arriveBy() == true}) the state chain runs backward in time and is not * reversed. Callers that need a chronological state chain must wrap this in a From 9b7a879060387a8ff6070d7f98a6791369f6f4a7 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen <57632592+hjvuor@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:22:12 +0300 Subject: [PATCH 013/177] Update application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Joel Lappalainen --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 -- 1 file changed, 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 cf20ab514e6..c74c05fc341 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5033,8 +5033,6 @@ input TransitPreferencesInput { paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - - THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE! """ relaxTransitGroupPriority: RelaxCostFunctionInput "Preferences related to cancellations and real-time." From 26444fc9630e31388a47e6d79535495dcf49b406 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 31 Mar 2026 16:20:50 +0300 Subject: [PATCH 014/177] Add TODOs --- .../nearbystops/StreetNearbyStopFinderMultipleLinksTest.java | 1 + .../module/nearbystops/StreetNearbyStopFinderTest.java | 1 + 2 files changed, 2 insertions(+) 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 index d55177b93e8..cab190bbcfb 100644 --- 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 @@ -26,6 +26,7 @@ class StreetNearbyStopFinderMultipleLinksTest extends GraphRoutingTest { @BeforeEach protected void setUp() throws Exception { + // TODO this could be reimplemented to use StreetModelForTest modelOf( new Builder() { @Override 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 696a6268887..8d67017350e 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 @@ -32,6 +32,7 @@ class StreetNearbyStopFinderTest extends GraphRoutingTest { @BeforeEach protected void setUp() throws Exception { + // TODO this could be reimplemented to use StreetModelForTest modelOf( new GraphRoutingTest.Builder() { @Override From f87fe6f3b7c3b77f0109c49bd08fa55afea802e1 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Wed, 1 Apr 2026 09:51:20 +0300 Subject: [PATCH 015/177] generalize relaxCostFunctionInput graphql type --- .../apis/gtfs/generated/GraphQLTypes.java | 66 +++++++++---------- .../TransitPreferencesMapper.java | 10 +-- .../opentripplanner/apis/gtfs/schema.graphqls | 34 +++++----- .../RouteRequestMapperTransitTest.java | 12 ++-- 4 files changed, 62 insertions(+), 60 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 11886e2e452..3aeb1188c2b 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 @@ -1623,6 +1623,35 @@ public void setGraphQLOriginModesWithParentStation( } } + public static class GraphQLLinearCostFunctionInput { + + private Double coefficient; + private org.opentripplanner.core.model.basic.Cost constant; + + public GraphQLLinearCostFunctionInput(Map args) { + if (args != null) { + this.coefficient = (Double) args.get("coefficient"); + this.constant = (org.opentripplanner.core.model.basic.Cost) args.get("constant"); + } + } + + public Double getGraphQLCoefficient() { + return this.coefficient; + } + + public org.opentripplanner.core.model.basic.Cost getGraphQLConstant() { + return this.constant; + } + + public void setGraphQLCoefficient(Double coefficient) { + this.coefficient = coefficient; + } + + public void setGraphQLConstant(org.opentripplanner.core.model.basic.Cost constant) { + this.constant = constant; + } + } + public static class GraphQLLocalDateRangeInput { private java.time.LocalDate end; @@ -4596,35 +4625,6 @@ public enum GraphQLRelativeDirection { UTURN_RIGHT, } - public static class GraphQLRelaxCostFunctionInput { - - private org.opentripplanner.core.model.basic.Cost constant; - private Double ratio; - - public GraphQLRelaxCostFunctionInput(Map args) { - if (args != null) { - this.constant = (org.opentripplanner.core.model.basic.Cost) args.get("constant"); - this.ratio = (Double) args.get("ratio"); - } - } - - public org.opentripplanner.core.model.basic.Cost getGraphQLConstant() { - return this.constant; - } - - public Double getGraphQLRatio() { - return this.ratio; - } - - public void setGraphQLConstant(org.opentripplanner.core.model.basic.Cost constant) { - this.constant = constant; - } - - public void setGraphQLRatio(Double ratio) { - this.ratio = ratio; - } - } - public static class GraphQLRouteAlertsArgs { private List types; @@ -5444,7 +5444,7 @@ public static class GraphQLTransitPreferencesInput { private GraphQLAlightPreferencesInput alight; private GraphQLBoardPreferencesInput board; private List filters; - private GraphQLRelaxCostFunctionInput relaxTransitGroupPriority; + private GraphQLLinearCostFunctionInput relaxTransitGroupPriority; private GraphQLTimetablePreferencesInput timetable; private GraphQLTransferPreferencesInput transfer; @@ -5457,7 +5457,7 @@ public GraphQLTransitPreferencesInput(Map args) { .map(o -> o == null ? null : new GraphQLTransitFilterInput(o)) .collect(Collectors.toList()); } - this.relaxTransitGroupPriority = new GraphQLRelaxCostFunctionInput( + this.relaxTransitGroupPriority = new GraphQLLinearCostFunctionInput( (Map) args.get("relaxTransitGroupPriority") ); this.timetable = new GraphQLTimetablePreferencesInput( @@ -5481,7 +5481,7 @@ public List getGraphQLFilters() { return this.filters; } - public GraphQLRelaxCostFunctionInput getGraphQLRelaxTransitGroupPriority() { + public GraphQLLinearCostFunctionInput getGraphQLRelaxTransitGroupPriority() { return this.relaxTransitGroupPriority; } @@ -5506,7 +5506,7 @@ public void setGraphQLFilters(List filters) { } public void setGraphQLRelaxTransitGroupPriority( - GraphQLRelaxCostFunctionInput relaxTransitGroupPriority + GraphQLLinearCostFunctionInput relaxTransitGroupPriority ) { this.relaxTransitGroupPriority = relaxTransitGroupPriority; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index 0e3bb847fbd..533aa3504d4 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -47,14 +47,16 @@ static void setTransitPreferences( var relax = transitArgs.getGraphQLRelaxTransitGroupPriority(); if (relax != null) { Cost constant = Cost.ZERO; - Double ratio = 1.0; + Double coefficient = 1.0; if (relax.getGraphQLConstant() != null) { constant = relax.getGraphQLConstant(); } - if (relax.getGraphQLRatio() != null) { - ratio = relax.getGraphQLRatio(); + if (relax.getGraphQLCoefficient() != null) { + coefficient = relax.getGraphQLCoefficient(); } - transitPreferences.withRelaxTransitGroupPriority(CostLinearFunction.of(constant, ratio)); + transitPreferences.withRelaxTransitGroupPriority( + CostLinearFunction.of(constant, coefficient) + ); } var board = transitArgs.getGraphQLBoard(); 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 c74c05fc341..89cd5af070d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4505,6 +4505,18 @@ input InputUnpreferred { useUnpreferredRoutesPenalty: Int @deprecated(reason : "Use unpreferredCost instead") } +""" +A generalized linear cost function that can be applied to a cost value. +The coefficient is used to scale the input Cost linearly and the constant is used +to add a fixed offset. If we assume a cost t, then f(t) = coefficient * t + constant. +""" +input LinearCostFunctionInput { + "The coefficient that scales the Cost input linearly." + coefficient: Float + "The 0th degree constant of the function as a Cost, must be a positive integer" + constant: Cost +} + "Filters an entity by a date range." input LocalDateRangeInput { """ @@ -4806,22 +4818,6 @@ input PlanVisitViaLocationInput { stopLocationIds: [String!] } -""" -A relax-cost is used to increase the limit when comparing one cost to another cost. -This is used to include more results into the result. A `ratio=2.0` means a path (itinerary) -with twice as high cost as another one, is accepted. A `constant=$300` means a "fixed" -constant is added to the limit. A `{ratio=1.0, constant=0}` is said to be the NORMAL relaxed -cost - the limit is the same as the cost used to calculate the limit. The NORMAL is usually -the default. We can express the RelaxCost as a function `f(t) = constant + ratio * t`. -`f(t)=t` is the NORMAL function. -""" -input RelaxCostFunctionInput { - "The constant value to add to the limit. Must be a positive number. The value is equivalent to transit-cost-seconds. Integers are treated as seconds, but you may use the duration format. Example: '3665 = 'DT1h1m5s' = '1h1m5s'." - constant: Cost - "The factor to multiply with the 'other cost'. Minimum value is 1.0." - ratio: Float -} - "What criteria should be used when optimizing a scooter route." input ScooterOptimizationInput @oneOf { "Define optimization by weighing three criteria." @@ -5033,8 +5029,12 @@ input TransitPreferencesInput { paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. + + The relaxation is done using a linear function applied to the generalized cost. + If not defined, a function with a constant 0 and a coefficient of 1.0 is used, + meaning the cost comparison is not relaxed between transit groups. """ - relaxTransitGroupPriority: RelaxCostFunctionInput + relaxTransitGroupPriority: LinearCostFunctionInput "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput "Preferences related to transfers between transit vehicles (typically between stops)." diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index 69dd7218457..36753074eef 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -137,10 +137,10 @@ void testTimetablePreferences() { void testRelaxTransitGroupPriority() { var args = testCtx.basicRequest(); var constant = Cost.costOfSeconds(120); - var ratio = 1.2; + var coefficient = 1.2; var relaxTransitGroupPriority = Map.ofEntries( entry("constant", constant), - entry("ratio", ratio) + entry("coefficient", coefficient) ); args.put( "preferences", @@ -155,14 +155,14 @@ void testRelaxTransitGroupPriority() { var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); var transitPreferences = routeRequest.preferences().transit(); assertEquals(constant, transitPreferences.relaxTransitGroupPriority().constant()); - assertEquals(ratio, transitPreferences.relaxTransitGroupPriority().coefficient()); + assertEquals(coefficient, transitPreferences.relaxTransitGroupPriority().coefficient()); } @Test void testPartialRelaxTransitGroupPriority() { var args = testCtx.basicRequest(); - var ratio = 1.2; - var relaxTransitGroupPriority = Map.ofEntries(entry("ratio", ratio)); + var coefficient = 1.2; + var relaxTransitGroupPriority = Map.ofEntries(entry("coefficient", coefficient)); args.put( "preferences", Map.ofEntries( @@ -176,6 +176,6 @@ void testPartialRelaxTransitGroupPriority() { var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); var transitPreferences = routeRequest.preferences().transit(); assertEquals(Cost.ZERO, transitPreferences.relaxTransitGroupPriority().constant()); - assertEquals(ratio, transitPreferences.relaxTransitGroupPriority().coefficient()); + assertEquals(coefficient, transitPreferences.relaxTransitGroupPriority().coefficient()); } } From 271724e53823d6befa8a2a4aac0466be9ff8be42 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Wed, 1 Apr 2026 11:41:38 +0300 Subject: [PATCH 016/177] inject default values for transit group relax --- .../opentripplanner/apis/gtfs/DefaultValueInjector.java | 8 ++++++++ .../org/opentripplanner/apis/gtfs/SchemaFactoryTest.java | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index 5de98e0e2bc..572474affc9 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -315,6 +315,14 @@ private static void setTransitDefaults( .boolReq( "TimetablePreferencesInput.includeRealTimeCancellations", transit.includeRealtimeCancellations() + ) + .intReq( + "LinearCostFunctionInput.constant", + transit.relaxTransitGroupPriority().constant().toSeconds() + ) + .floatReq( + "LinearCostFunctionInput.coefficient", + transit.relaxTransitGroupPriority().coefficient() ); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index d569710ccb8..3f4acadd3eb 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -60,6 +60,13 @@ void testDefaultValueInjection() { ); assertEquals(maxTransfers, defaultMaxTransfers.getValue().intValue()); + var defaultRelaxCoefficient = (FloatValue) getDefaultValueForField( + schema, + "LinearCostFunctionInput", + "coefficient" + ); + assertEquals(1.0, defaultRelaxCoefficient.getValue().floatValue()); + var defaultNumberOfItineraries = (IntValue) getDefaultValueForArgument( schema, "planConnection", From f73ba0034b151371541b514dc6aaf229f158b886 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 7 Apr 2026 08:48:03 +0200 Subject: [PATCH 017/177] Apply review suggestions --- .../transit/DefaultAccessEgress.java | 33 ++++++++++++------- .../transit/DefaultAccessEgressTest.java | 10 +++--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 73fa117ca04..114f14be03c 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -8,6 +8,17 @@ /** * Default implementation of the RaptorAccessEgress interface. + *

+ * Implementation note: As stated in the RoutingAccessEgress interface contract {@link RoutingAccessEgress#getFinalState()}, + * this class exposes the final A* state in search order, not in chronological order. For egress searches this State is + * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to + * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for + * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State + * chain for every egress candidate. + *

+ * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, + * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on + * both reversed and unreversed State chains. */ public class DefaultAccessEgress implements RoutingAccessEgress { @@ -19,17 +30,6 @@ public class DefaultAccessEgress implements RoutingAccessEgress { /** Keep this to be able to map back to itinerary */ private final TimeAndCost penalty; - /** - * The final state from the access/egress street search. For egress searches this State is - * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to - * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for - * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State - * chain for every egress candidate. - *

- * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, - * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on - * both reversed and unreversed State chains. - */ private final State finalState; /** @@ -101,6 +101,17 @@ public boolean hasOpeningHours() { return false; } + /** + * The final state from the access/egress street search. For egress searches this State is + * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to + * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for + * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State + * chain for every egress candidate. + *

+ * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, + * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on + * both reversed and unreversed State chains. + */ @Override public State getFinalState() { return finalState; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 0239166f19c..63b3326e9b5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -16,12 +16,12 @@ class DefaultAccessEgressTest { private static final int STOP = 5; - private static final State LAST_STATE = TestStateBuilder.ofWalking().streetEdge().build(); + private static final State FINAL_STATE = TestStateBuilder.ofWalking().streetEdge().build(); public static final Duration TIME_PENALTY = Duration.ofSeconds(1); public static final Cost COST_PENALTY = Cost.costOfSeconds(11); public static final TimeAndCost PENALTY = new TimeAndCost(TIME_PENALTY, COST_PENALTY); - private final DefaultAccessEgress subject = new DefaultAccessEgress(STOP, LAST_STATE); + private final DefaultAccessEgress subject = new DefaultAccessEgress(STOP, FINAL_STATE); private final RoutingAccessEgress subjectWithPenalty = subject.withPenalty(PENALTY); @Test @@ -31,7 +31,7 @@ void canNotAddPenaltyTwice() { @Test void durationInSeconds() { - int expected = (int) LAST_STATE.getElapsedTimeSeconds(); + int expected = (int) FINAL_STATE.getElapsedTimeSeconds(); assertEquals(expected, subject.durationInSeconds()); assertEquals(expected, subjectWithPenalty.durationInSeconds()); } @@ -62,8 +62,8 @@ void hasOpeningHours() { } @Test - void getLastState() { - assertEquals(LAST_STATE, subject.getFinalState()); + void getFinalState() { + assertEquals(FINAL_STATE, subject.getFinalState()); } @Test From c23e7c530e738b0f685c344d4a6a26876592ca6d Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 1 Apr 2026 12:12:18 +0200 Subject: [PATCH 018/177] Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches When a direct-only search (no transit modes requested) returns no itineraries, attach a new NO_DIRECT_MODE_CONNECTION error code so clients can distinguish "no direct route found" from "something went wrong." This eliminates ~97% of the "routing result is empty but there is no errors" warnings in production, which are caused by clients fanning out parallel direct-mode requests (flex, foot, bicycle, car, etc.) alongside transit queries. --- .../apis/gtfs/GraphQLUtils.java | 1 + .../apis/gtfs/generated/GraphQLTypes.java | 1 + .../support/mapping/PlannerErrorMapper.java | 1 + .../apis/transmodel/model/EnumTypes.java | 5 +++++ .../routing/algorithm/RoutingWorker.java | 18 ++++++++++++++++++ .../routing/api/response/RoutingErrorCode.java | 7 +++++++ .../opentripplanner/apis/gtfs/schema.graphqls | 6 ++++++ .../apis/transmodel/schema.graphql | 2 ++ 8 files changed, 41 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 74a936fadea..b0669b8d4f5 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -35,6 +35,7 @@ public static GraphQLRoutingErrorCode toGraphQL(RoutingErrorCode code) { } return switch (code) { case LOCATION_NOT_FOUND -> GraphQLRoutingErrorCode.LOCATION_NOT_FOUND; + case NO_DIRECT_MODE_CONNECTION -> GraphQLRoutingErrorCode.NO_DIRECT_MODE_CONNECTION; case NO_STOPS_IN_RANGE -> GraphQLRoutingErrorCode.NO_STOPS_IN_RANGE; case NO_TRANSIT_CONNECTION -> GraphQLRoutingErrorCode.NO_TRANSIT_CONNECTION; case NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW -> GraphQLRoutingErrorCode.NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW; 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 9111525db0f..e146ce66636 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 @@ -4677,6 +4677,7 @@ public enum GraphQLRouteAlertType { public enum GraphQLRoutingErrorCode { LOCATION_NOT_FOUND, + NO_DIRECT_MODE_CONNECTION, NO_STOPS_IN_RANGE, NO_TRANSIT_CONNECTION, NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW, diff --git a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java index 9ec662f51e0..e87c55ac1dd 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java @@ -18,6 +18,7 @@ public static PlannerError mapMessage(RoutingError domain) { switch (domain.code) { case NO_TRANSIT_CONNECTION: case NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW: + case NO_DIRECT_MODE_CONNECTION: api = new PlannerError(Message.PATH_NOT_FOUND); break; case OUTSIDE_BOUNDS: diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java index 2348b6a5f06..a5592713501 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java @@ -338,6 +338,11 @@ public class EnumTypes { RoutingErrorCode.WALKING_BETTER_THAN_TRANSIT, "The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search" ) + .value( + "noDirectModeConnection", + RoutingErrorCode.NO_DIRECT_MODE_CONNECTION, + "No usable itineraries were found for the requested direct mode and no transit was included in the search" + ) .build(); public static final GraphQLEnumType SERVICE_ALTERATION = GraphQLEnumType.newEnum() diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index e92b337fe2c..0c57860d5e4 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -159,6 +160,8 @@ public RoutingResponse route() { result.addErrors(filterChain.getRoutingErrors()); } + result.addErrors(checkForEmptyDirectModeResult(result)); + if (LOG.isDebugEnabled()) { LOG.debug( "Return TripPlan with {} filtered itineraries out of {} total.", @@ -361,6 +364,21 @@ private void checkIfTransitConnectionExistsInSearchWindow(List itiner } } + /** + * If this is a direct-only search (no transit) and no itineraries were found, return an error + * so the client knows why no results were returned. + */ + private Collection checkForEmptyDirectModeResult(RoutingResult result) { + if ( + !request.journey().transit().enabled() && + result.errors().isEmpty() && + result.itineraries().stream().allMatch(Itinerary::isFlaggedForDeletion) + ) { + return List.of(new RoutingError(RoutingErrorCode.NO_DIRECT_MODE_CONNECTION, null)); + } + return List.of(); + } + private LinkingContext linkingContext() { return Objects.requireNonNull(currentLinkingContext); } diff --git a/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java b/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java index 363849319bc..ad85b63e713 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java +++ b/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java @@ -36,4 +36,11 @@ public enum RoutingErrorCode { * The location was found, but no stops could be found within the search radius. */ NO_STOPS_IN_RANGE, + + /** + * No usable itineraries were found for a direct-only search (no transit modes requested). + * This includes both the case where no route exists and the case where routes were found + * but all were filtered out (e.g. by quality or distance filters). + */ + NO_DIRECT_MODE_CONNECTION, } 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 98d64607068..950515a0968 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3896,6 +3896,12 @@ enum RoutingErrorCode { """ LOCATION_NOT_FOUND """ + No usable itineraries were found for the requested direct mode (e.g. walking, cycling, car, flex) + and no transit modes were included in the search. This is returned both when no route exists and + when routes were found but didn't pass quality filters. + """ + NO_DIRECT_MODE_CONNECTION + """ No stops are reachable from the start or end locations specified. You can try searching using a different access or egress mode, for example cycling instead of walking, diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index f671547e610..197515d5a4f 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1916,6 +1916,8 @@ enum ReportType { enum RoutingErrorCode { "The specified location is not close to any streets or transit stops" locationNotFound + "No usable itineraries were found for the requested direct mode and no transit was included in the search" + noDirectModeConnection "No stops are reachable from the location specified. You can try searching using a different access or egress mode" noStopsInRange "No transit connection was found between the origin and destination withing the operating day or the next day" From 25ce4677bba456e9c9fe45aac9a390dbc9b5e2c8 Mon Sep 17 00:00:00 2001 From: Max Gosau Date: Tue, 7 Apr 2026 15:07:53 +0200 Subject: [PATCH 019/177] Add deduplication of `OsmBoardingLocationVertex` for shared platform areas --- .../module/OsmBoardingLocationsModule.java | 30 +++- .../OsmBoardingLocationsModuleTest.java | 131 ++++++++++++++++-- 2 files changed, 143 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index 3ef56cb71ac..8741b145579 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -73,6 +74,8 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule { private final VertexFactory vertexFactory; private final VertexLinker linker; + private final Map existingBoardingLocationsAtAreas; + /** * @param timetableRepository This module requires the timetable repository because at the time * of the instantiation the site repository is empty. @@ -90,6 +93,7 @@ public OsmBoardingLocationsModule( this.osmInfoGraphBuildService = osmInfoGraphBuildService; this.vertexFactory = new VertexFactory(graph); this.linker = linker; + this.existingBoardingLocationsAtAreas = new HashMap<>(); } @Override @@ -175,12 +179,7 @@ private boolean connectVertexToArea(TransitStopVertex ts, Graph graph) { if (platOpt.isPresent()) { var platform = platOpt.get(); if (matchesReference(stop, platform.references())) { - var boardingLocation = makeBoardingLocation( - stop, - platform.geometry().getCentroid(), - platform.references(), - area.getName() - ); + var boardingLocation = makeBoardingLocationForArea(stop, area, platform); linker.addPermanentAreaVertex(boardingLocation, areaGroup); linkBoardingLocationToStop(ts, stop.getCode(), boardingLocation); return true; @@ -272,6 +271,25 @@ private boolean connectVertexToNode(TransitStopVertex ts, RegularStop stop, Grap return false; } + /* + * when two stops reference the same OSM platform area, only one + * OsmBoardingLocationVertex centroid is created for that area and both stops are linked to it. + */ + private OsmBoardingLocationVertex makeBoardingLocationForArea( + RegularStop stop, + Area area, + Platform platform + ) { + return existingBoardingLocationsAtAreas.computeIfAbsent(area, _ -> + makeBoardingLocation( + stop, + platform.geometry().getCentroid(), + platform.references(), + area.getName() + ) + ); + } + private OsmBoardingLocationVertex makeBoardingLocation( RegularStop stop, Point centroid, diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java index 36b5f0e6ce6..8b25addb860 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java @@ -48,14 +48,14 @@ static Stream herrenbergTestCases() { Arguments.of( false, Stream.of( - 302563833L, - 3223067049L, - 302563836L, - 3223067680L, - 302563834L, - 768590748L, - 302563839L - ) + 302563833L, + 3223067049L, + 302563836L, + 3223067680L, + 302563834L, + 768590748L, + 302563839L + ) .map(VertexLabel::osm) .collect(Collectors.toSet()) ), @@ -209,11 +209,11 @@ void testLinearPlatforms() { var graph = new Graph(); var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository(); var osmModule = OsmModuleTestFactory.of( - new DefaultOsmProvider( - ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file("moorgate.osm.pbf"), - false + new DefaultOsmProvider( + ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file("moorgate.osm.pbf"), + false + ) ) - ) .withGraph(graph) .withOsmInfoGraphBuildRepository(osmInfoRepository) .builder() @@ -340,6 +340,113 @@ TransitStopVertex getPlatformVertex() { } } + /** + * Test that when two stops reference the same OSM platform area (via ref:IFOPT), only one + * OsmBoardingLocationVertex centroid is created for that area and both stops are linked to it. + *

+ * The Herrenberg platform area (way 27558650) has ref:IFOPT=de:08115:4512:4:101;de:08115:4512:4:102, + * so both stop IDs match the same area. + */ + @Test + void testDeduplicationOfAreaBoardinglocations() { + File file = ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file( + "herrenberg-minimal.osm.pbf" + ); + + // Two stops that both match the same platform area via ref:IFOPT + RegularStop platform1 = testModel + .stop("de:08115:4512:4:101") + .withCoordinate(48.59328, 8.86128) + .build(); + RegularStop platform2 = testModel + .stop("de:08115:4512:4:102") + .withCoordinate(48.59328, 8.86128) + .build(); + + var siteRepo = testModel + .siteRepositoryBuilder() + .withRegularStops(List.of(platform1, platform2)) + .build(); + + var graph = new Graph(); + var timetableRepository = new TimetableRepository(siteRepo); + var factory = new VertexFactory(graph); + + var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository(); + var osmModule = OsmModuleTestFactory.of(new DefaultOsmProvider(file, false)) + .withGraph(graph) + .withOsmInfoGraphBuildRepository(osmInfoRepository) + .builder() + .withBoardingAreaRefTags(Set.of("ref", "ref:IFOPT")) + .withAreaVisibility(true) + .build(); + + osmModule.buildGraph(); + + var platformVertex1 = factory.transitStop(ofStop(platform1)); + var platformVertex2 = factory.transitStop(ofStop(platform2)); + + timetableRepository.index(); + graph.index(); + + // Both vertices should start unlinked + assertEquals(0, platformVertex1.getIncoming().size()); + assertEquals(0, platformVertex1.getOutgoing().size()); + assertEquals(0, platformVertex2.getIncoming().size()); + assertEquals(0, platformVertex2.getOutgoing().size()); + + var osmService = new DefaultOsmInfoGraphBuildService(osmInfoRepository); + new OsmBoardingLocationsModule( + graph, + timetableRepository, + VertexLinkerTestFactory.of(graph), + osmService + ).buildGraph(); + + // Both vertices should now be linked + assertEquals(1, platformVertex1.getIncoming().size()); + assertEquals(1, platformVertex1.getOutgoing().size()); + assertEquals(1, platformVertex2.getIncoming().size()); + assertEquals(1, platformVertex2.getOutgoing().size()); + + var boardingLocations = graph.getVerticesOfType(OsmBoardingLocationVertex.class); + + // Only one centroid should exist for the shared platform area + var areaCentroids = boardingLocations + .stream() + .filter( + bl -> + bl.references.contains(platform1.getId().getId()) || + bl.references.contains(platform2.getId().getId()) + ) + .toList(); + assertEquals( + 1, + areaCentroids.size(), + "Expected exactly one OsmBoardingLocationVertex for the shared platform area, but found " + + areaCentroids.size() + ); + + // Both transit stop vertices should be connected to the same boarding location vertex + var linkedVertex1 = platformVertex1 + .getOutgoing() + .stream() + .findFirst() + .orElseThrow() + .getToVertex(); + var linkedVertex2 = platformVertex2 + .getOutgoing() + .stream() + .findFirst() + .orElseThrow() + .getToVertex(); + assertEquals( + linkedVertex1, + linkedVertex2, + "Both stops should be linked to the same deduplicated boarding location vertex" + ); + } + /** * Assert that a split vertex is near to the given centroid, and it is possible to travel between * the original vertices through the split vertex in a straight line From bd2ed163f458ab83ad207d48d688cf5e1930cfd9 Mon Sep 17 00:00:00 2001 From: Max Gosau Date: Tue, 7 Apr 2026 15:08:31 +0200 Subject: [PATCH 020/177] Refactor `OsmBoardingLocationsModule` for simplicity --- .../module/OsmBoardingLocationsModule.java | 23 +++------- .../OsmBoardingLocationsModuleTest.java | 44 ++++++++++--------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index 8741b145579..781096fdb29 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -65,7 +65,7 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule { private static final LocalizedString LOCALIZED_PLATFORM_NAME = new LocalizedString( "name.platform" ); - private final double searchRadiusDegrees = SphericalDistanceLibrary.metersToDegrees(250); + private static final double SEARCH_RADIUS_DEGREES = SphericalDistanceLibrary.metersToDegrees(250); private final Graph graph; @@ -105,15 +105,8 @@ public void buildGraph() { for (TransitStopVertex ts : graph.getVerticesOfType(TransitStopVertex.class)) { // if the street is already linked there is no need to link it again, - // could happened if using the prune isolated island - boolean alreadyLinked = false; - for (Edge e : ts.getOutgoing()) { - if (e instanceof StreetTransitStopLink) { - alreadyLinked = true; - break; - } - } - if (alreadyLinked) { + // could happen if using the prune isolated island + if (ts.getOutgoing().stream().anyMatch(StreetTransitStopLink.class::isInstance)) { continue; } // only connect transit stops that are not part of a pathway network @@ -150,7 +143,7 @@ private Envelope getEnvelope(TransitStopVertex ts) { Envelope envelope = new Envelope(ts.getCoordinate()); double xscale = Math.cos((ts.getCoordinate().y * Math.PI) / 180); - envelope.expandBy(searchRadiusDegrees / xscale, searchRadiusDegrees); + envelope.expandBy(SEARCH_RADIUS_DEGREES / xscale, SEARCH_RADIUS_DEGREES); return envelope; } @@ -205,13 +198,7 @@ private boolean connectVertexToWay(TransitStopVertex ts, RegularStop stop, Graph .findPlatform(edge) .ifPresent(platform -> { if (matchesReference(stop, platform.references())) { - if (!nearbyEdges.containsKey(platform)) { - var list = new ArrayList(); - list.add(edge); - nearbyEdges.put(platform, list); - } else { - nearbyEdges.get(platform).add(edge); - } + nearbyEdges.computeIfAbsent(platform, _ -> new ArrayList<>()).add(edge); } }); } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java index 8b25addb860..b746d66b303 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java @@ -48,14 +48,14 @@ static Stream herrenbergTestCases() { Arguments.of( false, Stream.of( - 302563833L, - 3223067049L, - 302563836L, - 3223067680L, - 302563834L, - 768590748L, - 302563839L - ) + 302563833L, + 3223067049L, + 302563836L, + 3223067680L, + 302563834L, + 768590748L, + 302563839L + ) .map(VertexLabel::osm) .collect(Collectors.toSet()) ), @@ -64,8 +64,9 @@ static Stream herrenbergTestCases() { } /** - * We test that the platform area at Herrenberg station (https://www.openstreetmap.org/way/27558650) - * is correctly linked to the stop even though it is not the closest edge to the stop. + * We test that the platform area at Herrenberg station + * (https://www.openstreetmap.org/way/27558650) is correctly linked to the stop even though it is + * not the closest edge to the stop. */ @ParameterizedTest( name = "add boarding locations and link them to platform edges when skipVisibility={0}" @@ -201,19 +202,20 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti } /** - * We test that the underground platforms at Moorgate station (https://www.openstreetmap.org/way/1328222021) - * is correctly linked to the stop even though it is not the closest edge to the stop. + * We test that the underground platforms at Moorgate station + * (https://www.openstreetmap.org/way/1328222021) is correctly linked to the stop even though it + * is not the closest edge to the stop. */ @Test void testLinearPlatforms() { var graph = new Graph(); var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository(); var osmModule = OsmModuleTestFactory.of( - new DefaultOsmProvider( - ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file("moorgate.osm.pbf"), - false - ) + new DefaultOsmProvider( + ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file("moorgate.osm.pbf"), + false ) + ) .withGraph(graph) .withOsmInfoGraphBuildRepository(osmInfoRepository) .builder() @@ -344,8 +346,8 @@ TransitStopVertex getPlatformVertex() { * Test that when two stops reference the same OSM platform area (via ref:IFOPT), only one * OsmBoardingLocationVertex centroid is created for that area and both stops are linked to it. *

- * The Herrenberg platform area (way 27558650) has ref:IFOPT=de:08115:4512:4:101;de:08115:4512:4:102, - * so both stop IDs match the same area. + * The Herrenberg platform area (way 27558650) has + * ref:IFOPT=de:08115:4512:4:101;de:08115:4512:4:102, so both stop IDs match the same area. */ @Test void testDeduplicationOfAreaBoardinglocations() { @@ -417,7 +419,7 @@ void testDeduplicationOfAreaBoardinglocations() { .filter( bl -> bl.references.contains(platform1.getId().getId()) || - bl.references.contains(platform2.getId().getId()) + bl.references.contains(platform2.getId().getId()) ) .toList(); assertEquals( @@ -486,8 +488,8 @@ private static void assertSplitVertex( } /** - * Assert that there is a one-way path from the beginning through the given vertex to the end - * or vice versa. + * Assert that there is a one-way path from the beginning through the given vertex to the end or + * vice versa. */ private static void assertConnections(Vertex vertex, Vertex beginning, Vertex end) { if (vertex == beginning || vertex == end) { From 578e6867858044f70c5a5ae9f78c65a991da7b2a Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 8 Apr 2026 08:37:55 +0200 Subject: [PATCH 021/177] Add dedicated error message for no direct mode connection --- .../src/main/java/org/opentripplanner/api/common/Message.java | 1 + .../apis/support/mapping/PlannerErrorMapper.java | 4 +++- domain-core/src/main/resources/Message.properties | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/api/common/Message.java b/application/src/main/java/org/opentripplanner/api/common/Message.java index fd0792134ac..89bde9d2f58 100644 --- a/application/src/main/java/org/opentripplanner/api/common/Message.java +++ b/application/src/main/java/org/opentripplanner/api/common/Message.java @@ -29,6 +29,7 @@ public enum Message { GEOCODE_INTERMEDIATE_NOT_FOUND(465), TOO_CLOSE(409), LOCATION_NOT_ACCESSIBLE(470), + NO_DIRECT_MODE_CONNECTION(480), UNDERSPECIFIED_TRIANGLE(370), TRIANGLE_NOT_AFFINE(371), diff --git a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java index e87c55ac1dd..302992020b1 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java @@ -18,9 +18,11 @@ public static PlannerError mapMessage(RoutingError domain) { switch (domain.code) { case NO_TRANSIT_CONNECTION: case NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW: - case NO_DIRECT_MODE_CONNECTION: api = new PlannerError(Message.PATH_NOT_FOUND); break; + case NO_DIRECT_MODE_CONNECTION: + api = new PlannerError(Message.NO_DIRECT_MODE_CONNECTION); + break; case OUTSIDE_BOUNDS: api = new PlannerError(Message.OUTSIDE_BOUNDS); api.setMissing(List.of(domain.inputField.name())); diff --git a/domain-core/src/main/resources/Message.properties b/domain-core/src/main/resources/Message.properties index eafca543bd4..471713abfd4 100644 --- a/domain-core/src/main/resources/Message.properties +++ b/domain-core/src/main/resources/Message.properties @@ -19,6 +19,7 @@ GEOCODE_TO_NOT_FOUND = Destination is unknown. Can you be a bit more descr GEOCODE_FROM_TO_NOT_FOUND = Both origin and destination are unknown. Can you be a bit more descriptive? GEOCODE_INTERMEDIATE_NOT_FOUND = An intermediate destination is unknown. Can you be a bit more descriptive?. TOO_CLOSE = Origin is within a trivial distance of the destination. +NO_DIRECT_MODE_CONNECTION = No route found for the requested direct mode. The origin and destination may be too far apart or not connected by the requested mode of transport. UNDERSPECIFIED_TRIANGLE = All of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set if any are TRIANGLE_NOT_AFFINE = The values of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must sum to 1 From 3d8234ef6d8bbdbb081722d57fa1d87f4650c9bf Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 8 Apr 2026 14:06:52 +0100 Subject: [PATCH 022/177] add test for OsmNode::isEntrance --- .../osm/model/OsmNodeTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java index 895907efc95..ec0093265f8 100644 --- a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java +++ b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java @@ -54,4 +54,29 @@ public void isTaggedBarrierCrossing() { node.addTag("entrance", "main"); assertTrue(node.isTaggedBarrierCrossing()); } + + @Test + public void isEntrance() { + OsmNode node = new OsmNode(); + node.addTag("entrance", "main"); + assertTrue(node.isEntrance()); + assertFalse(node.isStationEntrance()); + node.addTag("railway", "train_station_entrance"); + assertTrue(node.isEntrance()); + + node = new OsmNode(); + node.addTag("railway", "train_station_entrance"); + assertTrue(node.isEntrance()); + assertTrue(node.isStationEntrance()); + + node = new OsmNode(); + node.addTag("railway", "subway_entrance"); + assertTrue(node.isEntrance()); + assertTrue(node.isStationEntrance()); + + node = new OsmNode(); + node.addTag("public_transport", "entrance"); + assertTrue(node.isEntrance()); + assertTrue(node.isStationEntrance()); + } } From 29b6550b17116dc93fd6b65957b9ef5ecb680c24 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 8 Apr 2026 14:27:50 +0100 Subject: [PATCH 023/177] add test for marking entrances in stop area relations as public transport entrances --- .../module/osm/OsmDatabaseTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java index dc5058303d0..a3fd63f1782 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java @@ -223,4 +223,42 @@ void testWayIsntKeptForAreas() { // innerRingWithBarrier should not be polluted with the highway tag when fetched from the way assertFalse(osmdb.getWay(innerRingWithBarrier.getId()).hasTag("highway")); } + + @Test + void testEntranceTagAdded() { + var entrance1 = new OsmNode(0, 0); + entrance1.setId(1); + entrance1.addTag("entrance", "yes"); + + var entrance2 = new OsmNode(0, 0); + entrance2.setId(2); + entrance2.addTag("entrance", "yes"); + + var footway = new OsmWay(); + footway.addNodeRef(1); + footway.addNodeRef(2); + footway.addTag("highway", "footway"); + + var stopArea = new OsmRelation(); + stopArea.addTag("type", "public_transport"); + stopArea.addTag("public_transport", "stop_area"); + + var member = new OsmRelationMember(); + member.setType(OsmMemberType.NODE); + member.setRef(1); + stopArea.addMember(member); + + var osmdb = new OsmDatabase(DataImportIssueStore.NOOP); + osmdb.addRelation(stopArea); + osmdb.doneFirstPhaseRelations(); + osmdb.addWay(footway); + osmdb.doneSecondPhaseWays(); + osmdb.addNode(entrance1); + osmdb.addNode(entrance2); + osmdb.doneThirdPhaseNodes(); + osmdb.postLoad(); + + assertTrue(entrance1.isTag("public_transport", "entrance")); + assertFalse(entrance2.isTag("public_transport", "entrance")); + } } From ef396edefaea3f0132edfc8d28132efc37e0263a Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Wed, 8 Apr 2026 15:43:27 +0200 Subject: [PATCH 024/177] refactor: Introduce AbstractFetchAccessEgress and use in TransitRouter --- .../router/AbstractFetchAccessEgress.java | 161 ++++++++++++++++++ .../raptoradapter/router/TransitRouter.java | 110 ++---------- 2 files changed, 174 insertions(+), 97 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java new file mode 100644 index 00000000000..6ed226ee91f --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java @@ -0,0 +1,161 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.router; + +import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.ACCESS; +import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.EGRESS; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.opentripplanner.ext.carpooling.CarpoolingService; +import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.FlexAccessEgressRouter; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.routing.linking.LinkingContext; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.model.StreetMode; + +class AbstractFetchAccessEgress { + + private final RouteRequest request; + private final OtpServerRequestContext serverContext; + private final ZonedDateTime transitSearchTimeZero; + private final AdditionalSearchDays additionalSearchDays; + private final LinkingContext linkingContext; + private final AccessEgressRouter accessEgressRouter; + private final TransitServiceResolver transitServiceResolver; + private final CarpoolingService carpoolingService; + + public AbstractFetchAccessEgress( + RouteRequest request, + OtpServerRequestContext serverContext, + ZonedDateTime transitSearchTimeZero, + AdditionalSearchDays additionalSearchDays, + LinkingContext linkingContext, + AccessEgressRouter accessEgressRouter, + TransitServiceResolver transitServiceResolver, + CarpoolingService carpoolingService + ) { + this.request = request; + this.serverContext = serverContext; + this.transitSearchTimeZero = transitSearchTimeZero; + this.additionalSearchDays = additionalSearchDays; + this.linkingContext = linkingContext; + this.accessEgressRouter = accessEgressRouter; + this.transitServiceResolver = transitServiceResolver; + this.carpoolingService = carpoolingService; + } + + Collection fetchAccess() { + return fetchAccessEgresses(ACCESS); + } + + Collection fetchEgress() { + return fetchAccessEgresses(EGRESS); + } + + private Collection fetchAccessEgresses(AccessEgressType type) { + var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress(); + StreetMode mode = streetRequest.mode(); + + // Prepare access/egress lists + var accessBuilder = request.copyOf(); + + if (type.isAccess()) { + accessBuilder.withPreferences(p -> { + p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); + p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); + p.withScooter(s -> + s.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)) + ); + }); + } + + var accessRequest = accessBuilder.buildRequest(); + + var accessEgressPreferences = accessRequest.preferences().street().accessEgress(); + + Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); + int stopCountLimit = accessEgressPreferences.maxStopCountLimit().limitForMode(mode); + + var nearbyStops = accessEgressRouter.findAccessEgresses( + accessRequest, + streetRequest.mode(), + serverContext.listExtensionRequestContexts(accessRequest), + type, + durationLimit, + stopCountLimit, + linkingContext + ); + var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops, type); + accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); + + var results = new ArrayList<>(accessEgresses); + + // Special handling of flex accesses + if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { + var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( + accessRequest, + accessEgressRouter, + serverContext, + additionalSearchDays, + serverContext.flexParameters(), + serverContext.listExtensionRequestContexts(accessRequest), + type, + linkingContext + ); + + results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList, type)); + } + + if (OTPFeature.CarPooling.isOn() && mode == StreetMode.CARPOOL) { + var carpoolAccessEgressList = carpoolingService.routeAccessEgress( + accessRequest, + streetRequest, + type, + transitServiceResolver, + linkingContext, + transitSearchTimeZero + ); + results.addAll(carpoolAccessEgressList); + } + + return results; + } + + /** + * Given a list of {@code results} shift the access ones that contain driving so that they only + * start at the time when the ride hailing vehicle can actually be there to pick up passengers. + *

+ * If there are accesses/egresses with only walking, then they remain unchanged. + *

+ * This method is a good candidate to be moved to the access/egress filter chain when that has + * been added. + */ + private List timeshiftRideHailing( + StreetRequest streetRequest, + AccessEgressType type, + List accessEgressList + ) { + if (streetRequest.mode() != StreetMode.CAR_HAILING) { + return accessEgressList; + } + return RideHailingAccessShifter.shiftAccesses( + type.isAccess(), + accessEgressList, + serverContext.rideHailingServices(), + request, + Instant.now() + ); + } + +} diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 771bcf2c278..283c5c07c01 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -68,6 +68,7 @@ public class TransitRouter { private final AccessEgressRouter accessEgressRouter; private final TransitServiceResolver transitServiceResolver; private final CarpoolingService carpoolingService; + private final AbstractFetchAccessEgress fetchAccessEgress; private TransitRouter( RouteRequest request, @@ -90,6 +91,16 @@ private TransitRouter( this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); this.accessEgressRouter = new AccessEgressRouter(this.transitServiceResolver); this.carpoolingService = carpoolingService; + this.fetchAccessEgress = new AbstractFetchAccessEgress( + request, + serverContext, + transitSearchTimeZero, + additionalSearchDays, + linkingContext, + accessEgressRouter, + transitServiceResolver, + carpoolingService + ); } public static TransitRouterResult route( @@ -263,113 +274,18 @@ private AccessEgresses fetchAccessEgresses() { private Collection fetchAccess() { debugTimingAggregator.startedAccessCalculating(); - var list = fetchAccessEgresses(ACCESS); + var list = fetchAccessEgress.fetchAccess(); debugTimingAggregator.finishedAccessCalculating(); return list; } private Collection fetchEgress() { debugTimingAggregator.startedEgressCalculating(); - var list = fetchAccessEgresses(EGRESS); + var list = fetchAccessEgress.fetchEgress(); debugTimingAggregator.finishedEgressCalculating(); return list; } - private Collection fetchAccessEgresses(AccessEgressType type) { - var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress(); - StreetMode mode = streetRequest.mode(); - - // Prepare access/egress lists - var accessBuilder = request.copyOf(); - - if (type.isAccess()) { - accessBuilder.withPreferences(p -> { - p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); - p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); - p.withScooter(s -> - s.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)) - ); - }); - } - - var accessRequest = accessBuilder.buildRequest(); - - var accessEgressPreferences = accessRequest.preferences().street().accessEgress(); - - Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); - int stopCountLimit = accessEgressPreferences.maxStopCountLimit().limitForMode(mode); - - var nearbyStops = accessEgressRouter.findAccessEgresses( - accessRequest, - streetRequest.mode(), - serverContext.listExtensionRequestContexts(accessRequest), - type, - durationLimit, - stopCountLimit, - linkingContext - ); - var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops, type); - accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); - - var results = new ArrayList<>(accessEgresses); - - // Special handling of flex accesses - if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { - var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( - accessRequest, - accessEgressRouter, - serverContext, - additionalSearchDays, - serverContext.flexParameters(), - serverContext.listExtensionRequestContexts(accessRequest), - type, - linkingContext - ); - - results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList, type)); - } - - if (OTPFeature.CarPooling.isOn() && mode == StreetMode.CARPOOL) { - var carpoolAccessEgressList = carpoolingService.routeAccessEgress( - accessRequest, - streetRequest, - type, - transitServiceResolver, - linkingContext, - transitSearchTimeZero - ); - results.addAll(carpoolAccessEgressList); - } - - return results; - } - - /** - * Given a list of {@code results} shift the access ones that contain driving so that they only - * start at the time when the ride hailing vehicle can actually be there to pick up passengers. - *

- * If there are accesses/egresses with only walking, then they remain unchanged. - *

- * This method is a good candidate to be moved to the access/egress filter chain when that has - * been added. - */ - private List timeshiftRideHailing( - StreetRequest streetRequest, - AccessEgressType type, - List accessEgressList - ) { - if (streetRequest.mode() != StreetMode.CAR_HAILING) { - return accessEgressList; - } - return RideHailingAccessShifter.shiftAccesses( - type.isAccess(), - accessEgressList, - serverContext.rideHailingServices(), - request, - Instant.now() - ); - } - private RaptorRoutingRequestTransitData createRequestTransitDataProvider( RaptorTransitData raptorTransitData ) { From 31ef07b21ea0ce5cb810e80ef222e889cac5f016 Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Thu, 9 Apr 2026 10:41:18 +0200 Subject: [PATCH 025/177] refactor: Move unused TransitRouter deps into AbstractFetchAccessEgress --- .../router/AbstractFetchAccessEgress.java | 9 +++----- .../raptoradapter/router/TransitRouter.java | 22 +------------------ 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java index 6ed226ee91f..f20c7427c07 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java @@ -41,8 +41,6 @@ public AbstractFetchAccessEgress( ZonedDateTime transitSearchTimeZero, AdditionalSearchDays additionalSearchDays, LinkingContext linkingContext, - AccessEgressRouter accessEgressRouter, - TransitServiceResolver transitServiceResolver, CarpoolingService carpoolingService ) { this.request = request; @@ -50,9 +48,9 @@ public AbstractFetchAccessEgress( this.transitSearchTimeZero = transitSearchTimeZero; this.additionalSearchDays = additionalSearchDays; this.linkingContext = linkingContext; - this.accessEgressRouter = accessEgressRouter; - this.transitServiceResolver = transitServiceResolver; this.carpoolingService = carpoolingService; + this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); + this.accessEgressRouter = new AccessEgressRouter(transitServiceResolver); } Collection fetchAccess() { @@ -89,7 +87,7 @@ private Collection fetchAccessEgresses(AccessEgre var nearbyStops = accessEgressRouter.findAccessEgresses( accessRequest, - streetRequest.mode(), + mode, serverContext.listExtensionRequestContexts(accessRequest), type, durationLimit, @@ -157,5 +155,4 @@ private List timeshiftRideHailing( Instant.now() ); } - } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 283c5c07c01..0a40aa35a48 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -1,10 +1,5 @@ package org.opentripplanner.routing.algorithm.raptoradapter.router; -import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.ACCESS; -import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.EGRESS; - -import java.time.Duration; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; @@ -15,9 +10,7 @@ import javax.annotation.Nullable; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.CarpoolingService; -import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.path.RaptorPath; @@ -25,21 +18,16 @@ import org.opentripplanner.raptor.extensions.extrasearch.ExtraMcRouterSearch; import org.opentripplanner.routing.algorithm.mapping.RaptorPathToItineraryMapper; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressPenaltyDecorator; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgresses; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.FlexAccessEgressRouter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.DirectTransitRequestMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.DefaultTransitDataProviderFilter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; import org.opentripplanner.routing.algorithm.transferoptimization.configure.TransferOptimizationServiceConfigurator; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.request.StreetRequest; import org.opentripplanner.routing.api.response.InputField; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -48,7 +36,6 @@ import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.framework.EntityNotFoundException; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.site.StopLocation; @@ -65,9 +52,6 @@ public class TransitRouter { private final AdditionalSearchDays additionalSearchDays; private final ViaCoordinateTransferFactory viaTransferResolver; private final LinkingContext linkingContext; - private final AccessEgressRouter accessEgressRouter; - private final TransitServiceResolver transitServiceResolver; - private final CarpoolingService carpoolingService; private final AbstractFetchAccessEgress fetchAccessEgress; private TransitRouter( @@ -88,17 +72,13 @@ private TransitRouter( this.debugTimingAggregator = debugTimingAggregator; this.viaTransferResolver = serverContext.viaTransferResolver(); this.linkingContext = linkingContext; - this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); - this.accessEgressRouter = new AccessEgressRouter(this.transitServiceResolver); - this.carpoolingService = carpoolingService; + this.fetchAccessEgress = new AbstractFetchAccessEgress( request, serverContext, transitSearchTimeZero, additionalSearchDays, linkingContext, - accessEgressRouter, - transitServiceResolver, carpoolingService ); } From 514f5f9154b0dd9c2d8d6236ed38b728297e7a6b Mon Sep 17 00:00:00 2001 From: Max Gosau Date: Mon, 13 Apr 2026 14:46:27 +0200 Subject: [PATCH 026/177] Extend deduplication of `OsmBoardingLocationVertex` to all platform types --- .../module/OsmBoardingLocationsModule.java | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index 781096fdb29..948904d6e45 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -74,7 +74,7 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule { private final VertexFactory vertexFactory; private final VertexLinker linker; - private final Map existingBoardingLocationsAtAreas; + private final Map existingBoardingLocationsAtAreas; /** * @param timetableRepository This module requires the timetable repository because at the time @@ -172,7 +172,11 @@ private boolean connectVertexToArea(TransitStopVertex ts, Graph graph) { if (platOpt.isPresent()) { var platform = platOpt.get(); if (matchesReference(stop, platform.references())) { - var boardingLocation = makeBoardingLocationForArea(stop, area, platform); + var boardingLocation = getOrMakeBoardingLocationForPlatform( + stop, + platform, + area.getName() + ); linker.addPermanentAreaVertex(boardingLocation, areaGroup); linkBoardingLocationToStop(ts, stop.getCode(), boardingLocation); return true; @@ -203,26 +207,32 @@ private boolean connectVertexToWay(TransitStopVertex ts, RegularStop stop, Graph }); } - for (var platformEdgeList : nearbyEdges.entrySet()) { - Platform platform = platformEdgeList.getKey(); - var name = platform.name(); - var boardingLocation = makeBoardingLocation( - stop, - platform.geometry().getCentroid(), - platform.references(), - name - ); - for (var vertex : linker.linkToSpecificStreetEdgesPermanently( - boardingLocation, - new TraverseModeSet(TraverseMode.WALK), - LinkingDirection.BIDIRECTIONAL, - platformEdgeList.getValue().stream().map(StreetEdge.class::cast).collect(Collectors.toSet()) - )) { - linkBoardingLocationToStop(ts, stop.getCode(), vertex); - } - return true; - } - return false; + return nearbyEdges + .entrySet() + .stream() + .findFirst() + .map(platformEdgeList -> { + Platform platform = platformEdgeList.getKey(); + var boardingLocation = getOrMakeBoardingLocationForPlatform( + stop, + platform, + platform.name() + ); + for (var vertex : linker.linkToSpecificStreetEdgesPermanently( + boardingLocation, + new TraverseModeSet(TraverseMode.WALK), + LinkingDirection.BIDIRECTIONAL, + platformEdgeList + .getValue() + .stream() + .map(StreetEdge.class::cast) + .collect(Collectors.toSet()) + )) { + linkBoardingLocationToStop(ts, stop.getCode(), vertex); + } + return true; + }) + .orElse(false); } /** @@ -259,21 +269,16 @@ private boolean connectVertexToNode(TransitStopVertex ts, RegularStop stop, Grap } /* - * when two stops reference the same OSM platform area, only one - * OsmBoardingLocationVertex centroid is created for that area and both stops are linked to it. + * when two or more stops reference the same OSM platform, only one + * OsmBoardingLocationVertex is created for that platform and both stops are linked to it. */ - private OsmBoardingLocationVertex makeBoardingLocationForArea( + private OsmBoardingLocationVertex getOrMakeBoardingLocationForPlatform( RegularStop stop, - Area area, - Platform platform + Platform platform, + I18NString name ) { - return existingBoardingLocationsAtAreas.computeIfAbsent(area, _ -> - makeBoardingLocation( - stop, - platform.geometry().getCentroid(), - platform.references(), - area.getName() - ) + return existingBoardingLocationsAtAreas.computeIfAbsent(platform, _ -> + makeBoardingLocation(stop, platform.geometry().getCentroid(), platform.references(), name) ); } From e1f9f6bb001a9c549b502528fad252e2a5a6430c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 13 Apr 2026 17:38:34 +0300 Subject: [PATCH 027/177] Change stop comparison --- .../graph_builder/module/transfer/DirectTransferGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java index a5ff0c92acd..4ea59286d85 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java @@ -389,7 +389,7 @@ private void calculateDefaultTransfers( for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. var nearbyStop = repository.getStopLocation(sd.stopId); - if (nearbyStop == stop) { + if (nearbyStop.equals(stop)) { continue; } createPathTransfer(stop, nearbyStop, sd, distinctTransfers, mode); From 668ddbaf8d5a2a4ef65c40ba12ea4c8dc92f5abf Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 13 Apr 2026 18:19:04 +0100 Subject: [PATCH 028/177] add test for marking entrances in stop area relations as public transport entrances --- .../module/configure/GraphBuilderModules.java | 2 +- .../graph_builder/module/osm/OsmDatabase.java | 5 +---- .../graph_builder/module/osm/OsmModule.java | 2 +- .../module/osm/OsmModuleBuilder.java | 8 ++++---- .../module/osm/VertexGenerator.java | 8 ++++---- .../osm/parameters/OsmExtractParameters.java | 10 +++++----- .../parameters/OsmExtractParametersBuilder.java | 16 ++++++++-------- .../osm/parameters/OsmProcessingParameters.java | 4 ++-- .../org/opentripplanner/osm/model/OsmNode.java | 12 ++++++++++++ .../standalone/config/buildconfig/OsmConfig.java | 12 +++++++----- doc/user/BuildConfiguration.md | 4 ++-- test/performance/helsinki/build-config.json | 2 +- 12 files changed, 48 insertions(+), 37 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 5e198c3fb79..e5cb941c8f5 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -106,7 +106,7 @@ static OsmModule provideOsmModule( .withIncludeInclinedEdgeLevelInfo(config.includeInclinedEdgeLevelInfo) .withMaxAreaNodes(config.maxAreaNodes) .withBoardingAreaRefTags(config.boardingLocationTags) - .withIncludeOsmSubwayEntrances(config.osmDefaults.includeOsmSubwayEntrances()) + .withIncludeOsmStationEntrances(config.osmDefaults.includeOsmStationEntrances()) .withIssueStore(issueStore) .build(); } 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 c2433d70113..e847fee6478 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 @@ -988,10 +988,7 @@ private void processPublicTransportStopArea(OsmRelation relation) { switch (member.getType()) { case NODE -> { var node = nodesById.get(member.getRef()); - if ( - node != null && - (node.isEntrance() || node.isBoardingLocation() || node.isTag("highway", "elevator")) - ) { + if (node != null && node.isPlatformAccess()) { platformNodes.add(node); if (node.isEntrance()) { // mark it as a station entrance so it can be returned in walk step descriptions. diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 8c099fd315e..e0eb4dcdefa 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -126,7 +126,7 @@ public void buildGraph() { osmdb, graph, params.boardingAreaRefTags(), - params.includeOsmSubwayEntrances(), + params.includeOsmStationEntrances(), issueStore ); for (var provider : providers) { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index 7f39456603f..bbaa3804d1d 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -34,7 +34,7 @@ public class OsmModuleBuilder { private boolean staticParkAndRide = false; private boolean staticBikeParkAndRide = false; private boolean includeInclinedEdgeLevelInfo = false; - private boolean includeOsmSubwayEntrances = false; + private boolean includeOsmStationEntrances = false; private int maxAreaNodes = StreetConstants.DEFAULT_MAX_AREA_NODES; public OsmModuleBuilder( @@ -98,8 +98,8 @@ public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) { return this; } - public OsmModuleBuilder withIncludeOsmSubwayEntrances(boolean includeOsmSubwayEntrances) { - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + public OsmModuleBuilder withIncludeOsmStationEntrances(boolean includeOsmStationEntrances) { + this.includeOsmStationEntrances = includeOsmStationEntrances; return this; } @@ -121,7 +121,7 @@ public OsmModule build() { staticParkAndRide, staticBikeParkAndRide, includeInclinedEdgeLevelInfo, - includeOsmSubwayEntrances + includeOsmStationEntrances ) ); } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 5d4158452f0..4602979bf89 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -63,7 +63,7 @@ class VertexGenerator { private final Map> splitVerticesOnBarriers = new HashMap<>(); private final OsmDatabase osmdb; private final Set boardingAreaRefTags; - private final Boolean includeOsmSubwayEntrances; + private final Boolean includeOsmStationEntrances; private final VertexFactory vertexFactory; private final DataImportIssueStore issueStore; @@ -71,13 +71,13 @@ public VertexGenerator( OsmDatabase osmdb, Graph graph, Set boardingAreaRefTags, - boolean includeOsmSubwayEntrances, + boolean includeOsmStationEntrances, DataImportIssueStore issueStore ) { this.osmdb = osmdb; this.vertexFactory = new VertexFactory(graph); this.boardingAreaRefTags = boardingAreaRefTags; - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + this.includeOsmStationEntrances = includeOsmStationEntrances; this.issueStore = issueStore; } @@ -134,7 +134,7 @@ IntersectionVertex getVertexForOsmNode( } } - if (includeOsmSubwayEntrances && node.isStationEntrance()) { + if (includeOsmStationEntrances && node.isStationEntrance()) { String ref = node.getTag("ref"); iv = vertexFactory.stationEntrance( nid, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java index 0e553b1b180..856cc87834a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java @@ -15,13 +15,13 @@ public record OsmExtractParameters( URI source, OsmTagMapperSource osmTagMapper, ZoneId timeZone, - boolean includeOsmSubwayEntrances + boolean includeOsmStationEntrances ) implements DataSourceConfig { public static final OsmTagMapperSource DEFAULT_OSM_TAG_MAPPER = OsmTagMapperSource.DEFAULT; public static final ZoneId DEFAULT_TIME_ZONE = null; - public static final boolean DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES = false; + public static final boolean DEFAULT_INCLUDE_OSM_STATION_ENTRANCES = false; public static final OsmExtractParameters DEFAULT = new OsmExtractParametersBuilder().build(); @@ -30,7 +30,7 @@ public record OsmExtractParameters( builder.getSource(), builder.getOsmTagMapper(), builder.getTimeZone(), - builder.includeOsmSubwayEntrances() + builder.includeOsmStationEntrances() ); } @@ -48,8 +48,8 @@ public ZoneId timeZone() { return timeZone; } - public boolean includeOsmSubwayEntrances() { - return includeOsmSubwayEntrances; + public boolean includeOsmStationEntrances() { + return includeOsmStationEntrances; } public OsmExtractParametersBuilder copyOf() { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java index 66c65e05d81..ccf7d2b9d0f 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java @@ -24,18 +24,18 @@ public class OsmExtractParametersBuilder { */ private ZoneId timeZone; - private boolean includeOsmSubwayEntrances; + private boolean includeOsmStationEntrances; public OsmExtractParametersBuilder() { this.osmTagMapper = OsmExtractParameters.DEFAULT_OSM_TAG_MAPPER; this.timeZone = OsmExtractParameters.DEFAULT_TIME_ZONE; - this.includeOsmSubwayEntrances = OsmExtractParameters.DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES; + this.includeOsmStationEntrances = OsmExtractParameters.DEFAULT_INCLUDE_OSM_STATION_ENTRANCES; } public OsmExtractParametersBuilder(OsmExtractParameters original) { this.osmTagMapper = original.osmTagMapper(); this.timeZone = original.timeZone(); - this.includeOsmSubwayEntrances = original.includeOsmSubwayEntrances(); + this.includeOsmStationEntrances = original.includeOsmStationEntrances(); } public OsmExtractParametersBuilder withSource(URI source) { @@ -53,10 +53,10 @@ public OsmExtractParametersBuilder withTimeZone(ZoneId timeZone) { return this; } - public OsmExtractParametersBuilder withIncludeOsmSubwayEntrances( - boolean includeOsmSubwayEntrances + public OsmExtractParametersBuilder withIncludeOsmStationEntrances( + boolean includeOsmStationEntrances ) { - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + this.includeOsmStationEntrances = includeOsmStationEntrances; return this; } @@ -72,8 +72,8 @@ public ZoneId getTimeZone() { return timeZone; } - public boolean includeOsmSubwayEntrances() { - return includeOsmSubwayEntrances; + public boolean includeOsmStationEntrances() { + return includeOsmStationEntrances; } public OsmExtractParameters build() { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java index db3fb1009af..c18982e7f4e 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java @@ -14,7 +14,7 @@ * @param staticParkAndRide Whether we should create car P+R stations from OSM data. * @param staticBikeParkAndRide Whether we should create bike P+R stations from OSM data. * @param includeInclinedEdgeLevelInfo Whether level info for inclined edges should be stored. - * @param includeOsmSubwayEntrances Whether we should create subway entrances from OSM data. + * @param includeOsmStationEntrances Whether we should create station entrances from OSM data. */ public record OsmProcessingParameters( Set boardingAreaRefTags, @@ -25,7 +25,7 @@ public record OsmProcessingParameters( boolean staticParkAndRide, boolean staticBikeParkAndRide, boolean includeInclinedEdgeLevelInfo, - boolean includeOsmSubwayEntrances + boolean includeOsmStationEntrances ) { public OsmProcessingParameters { boardingAreaRefTags = Set.copyOf(Objects.requireNonNull(boardingAreaRefTags)); diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java index 25503a59c6b..19de99a2ea0 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java @@ -108,4 +108,16 @@ public boolean isTaggedBarrierCrossing() { overridePermissions(NONE) != NONE ); } + + /** + * Check if this node represents access to a platform. + *

+ * If this node appears inside a platform area and belongs to the same public transport relation, + * the platform will be kept even if it isn't physically linked to this node so that + * {@link org.opentripplanner.graph_builder.module.OsmBoardingLocationsModule} can associate the + * transit stop with the physical platform. + */ + public boolean isPlatformAccess() { + return isEntrance() || isBoardingLocation() || isTag("highway", "elevator"); + } } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java index fadd998b8cc..7d71518e2ef 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java @@ -86,13 +86,15 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters( .docDefaultValue(docDefaults.timeZone()) .asZoneId(defaults.timeZone()) ) - .withIncludeOsmSubwayEntrances( + .withIncludeOsmStationEntrances( node - .of("includeOsmSubwayEntrances") + .of("includeOsmStationEntrances") .since(V2_7) - .summary("Whether to include subway entrances from the OSM data." + documentationAddition) - .docDefaultValue(docDefaults.includeOsmSubwayEntrances()) - .asBoolean(defaults.includeOsmSubwayEntrances()) + .summary( + "Whether to include station entrances from the OSM data." + documentationAddition + ) + .docDefaultValue(docDefaults.includeOsmStationEntrances()) + .asBoolean(defaults.includeOsmStationEntrances()) ); } } diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 82374bef57f..d30b6d64a65 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -85,12 +85,12 @@ Sections follow that describe particular settings in more depth. |    [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | |    [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | | [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 | -|       includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.7 | +|       includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.7 | |       [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 | |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | |       timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 | | osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | -|    includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. | *Optional* | `false` | 2.7 | +|    includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. | *Optional* | `false` | 2.7 | |    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 | |    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 | | [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | diff --git a/test/performance/helsinki/build-config.json b/test/performance/helsinki/build-config.json index efbd90135bf..4000edc9b2c 100644 --- a/test/performance/helsinki/build-config.json +++ b/test/performance/helsinki/build-config.json @@ -28,7 +28,7 @@ "osmDefaults": { "timeZone": "Europe/Helsinki", "osmTagMapping": "finland", - "includeOsmSubwayEntrances": true + "includeOsmStationEntrances": true }, "demDefaults": { "elevationUnitMultiplier": 0.1 From e1db7129df6e1e6ac71a4e12a0addfc9b54d8578 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 9 Apr 2026 09:39:18 +0200 Subject: [PATCH 029/177] Fix cost mismatch by anchoring pattern-ride pareto cost to last stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `onTripRelativeRidingCost(boardTime)` → `transitCost(relativeTravelTime(boardTime))`. The old formula `-boardTime * r` varied by headway between trips in the same pattern, so non-optimal rides survived the patternRides pareto set. With `lastArrival - boardTime`, all trips in the same pattern produce the same relative cost at equivalent boarding positions, enabling correct pareto dominance and eliminating the cost-mismatch warnings (#3623). --- .../sorlandsbanen/CoachCostCalculator.java | 4 +- .../transit/cost/DefaultCostCalculator.java | 13 +---- .../transit/cost/PatternCostCalculator.java | 4 +- .../cost/WheelchairCostCalculator.java | 4 +- .../FrequencyBoardOrAlightEvent.java | 5 ++ .../request/TripScheduleWithOffset.java | 10 ++++ .../_data/transit/TestTripSchedule.java | 6 +- .../cost/DefaultCostCalculatorTest.java | 10 ++-- .../cost/PatternCostCalculatorTest.java | 4 -- .../MultiCriteriaRoutingStrategy.java | 30 +++++----- .../path/DestinationArrivalPaths.java | 1 - .../raptor/spi/RaptorCostCalculator.java | 7 +-- .../raptor/spi/RaptorTripSchedule.java | 18 ++++++ .../_data/transit/TestCostCalculator.java | 10 +--- .../_data/transit/TestTripSchedule.java | 5 ++ .../_data/transit/TestTripScheduleTest.java | 56 +++++++++++++++++++ 16 files changed, 132 insertions(+), 55 deletions(-) create mode 100644 raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java index 7f2d112b61d..404de0b2a1a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java @@ -40,8 +40,8 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitTime, T tripScheduledBoarded) { + return delegate.transitCost(transitTime, tripScheduledBoarded); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 9185f8da585..4262665d777 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -91,12 +91,8 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, DefaultTripSchedule tripScheduledBoarded) { - // The relative-transit-time is time spent on transit. We do not know the alight-stop, so - // it is impossible to calculate the "correct" time. But the only thing that maters is that - // the relative difference between to boardings are correct, assuming riding the same trip. - // So, we can use the negative board time as relative-transit-time. - return -boardTime * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()); + public int transitCost(int transitTime, T tripScheduledBoarded) { + return transitTime * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()); } @Override @@ -107,10 +103,7 @@ public int transitArrivalCost( T trip, int toStopIndex ) { - int cost = - boardCost + - transitFactors.factor(trip.transitReluctanceFactorIndex()) * transitTime + - waitFactor * alightSlack; + int cost = boardCost + transitCost(transitTime, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java index 25b98f92d29..d4d10a66107 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java @@ -40,8 +40,8 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitTime, T tripScheduledBoarded) { + return delegate.transitCost(transitTime, tripScheduledBoarded); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java index a5b103ebe18..07a45197733 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java @@ -44,8 +44,8 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitTime, T tripScheduledBoarded) { + return delegate.transitCost(transitTime, tripScheduledBoarded); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java index a9ef6ac61c4..ef12abf7f2e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java @@ -111,6 +111,11 @@ public int tripSortIndex() { @Override public abstract int departure(int stopPosInPattern); + @Override + public int relativeTravelTime(int boardTime) { + return offset - boardTime; + } + @Override public RaptorTripPattern pattern() { return raptorTripPattern; diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java index e03e8b723d7..842f0358113 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java @@ -50,6 +50,16 @@ public int departure(int stopPosInPattern) { return pattern.departureTime(stopPosInPattern, tripIndexForDates); } + @Override + public int relativeTravelTime(int boardTime) { + // sortIndex is the arrival time at stop 0, which is fixed per trip. Since all trips in the + // same pattern have the same travel-time structure, (sortIndex - boardTime) satisfies both + // invariants of relativeTravelTime: the difference between two boardings equals the actual + // transit time between the stops, and the value is identical across trips for the same + // boarding position. + return sortIndex - boardTime; + } + @Override public RaptorTripPattern pattern() { return pattern; diff --git a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java index fd4f52d175d..1a7dc52e2da 100644 --- a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java +++ b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java @@ -15,7 +15,6 @@ /** * An implementation of the {@link RaptorTripSchedule} for unit-testing. *

- * The {@link RaptorTripPattern} for this schedule return {@code stopIndex == stopPosInPattern + 1 } * * @deprecated This was earlier part of Raptor and should not be used outside the Raptor * module. Use the OTP model entities instead. @@ -83,6 +82,11 @@ public int departure(int stopPosInPattern) { return departureTimes[stopPosInPattern]; } + @Override + public int relativeTravelTime(int boardTime) { + return arrivalTimes[arrivalTimes.length - 1] - boardTime; + } + public TestTripPattern pattern() { return pattern; } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java index 7698eac4546..208651dfed5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java @@ -60,11 +60,11 @@ public void transitArrivalCost() { } @Test - public void onTripRidingCost() { - assertEquals(0, subject.onTripRelativeRidingCost(0, TRIP_1), "Board cost"); - assertEquals(0, subject.onTripRelativeRidingCost(0, TRIP_2), "Board cost"); - assertEquals(-100, subject.onTripRelativeRidingCost(1, TRIP_1), "Board cost"); - assertEquals(-80, subject.onTripRelativeRidingCost(1, TRIP_2), "Board cost"); + public void transitCost() { + assertEquals(0, subject.transitCost(0, TRIP_1)); + assertEquals(0, subject.transitCost(0, TRIP_2)); + assertEquals(100, subject.transitCost(1, TRIP_1)); + assertEquals(80, subject.transitCost(1, TRIP_2)); } @Test diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java index 0c0dfa103b3..0e32cffaba4 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java @@ -168,10 +168,6 @@ public String toString() { return "RoutePenaltyTC {" + sb.substring(sb.isEmpty() ? 0 : 2) + "}"; } - boolean isDefault() { - return !(unPreferredAgency || unPreferredRoute); - } - RaptorCostCalculator createCostCalculator(TestTripSchedule schedule) { GeneralizedCostParameters costParams = GeneralizedCostParametersMapper.map( createRouteRequest(), diff --git a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java index 3d9e00f490a..a0899e009f8 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java @@ -167,20 +167,18 @@ private void board( } final int boardC1 = calculateCostAtBoardTime(prevArrival, boarding); - final int relativeBoardC1 = boardC1 + calculateOnTripRelativeCost(boardTime, trip); - patternRides.add( - patternRideFactory.createPatternRide( - prevArrival, - stopIndex, - boarding.stopPositionInPattern(), - boardTime, - boardC1, - relativeBoardC1, - trip - ) + var patternRide = patternRideFactory.createPatternRide( + prevArrival, + stopIndex, + boarding.stopPositionInPattern(), + boardTime, + boardC1, + relativeBoardC1, + trip ); + patternRides.add(patternRide); } private void boardWithRegularTransfer( @@ -245,13 +243,13 @@ private int calculateCostAtBoardTime( } /** - * Calculate a cost for riding a trip. It should include the cost from the beginning of the - * journey all the way until a trip is boarded. The cost is used to compare trips boarding the - * same pattern with the same number of transfers. It is ok for the cost to be relative to any + * Calculate a cost for riding a trip. The cost is used to compare trips boarding in the same + * pattern with the same number of transfers. It is ok for the cost to be relative to any * point in place or time - as long as it can be used to compare to paths that started at the - * origin in the same iteration, having used the same number-of-rounds to board the same trip. + * origin in the same iteration, having used the same number-of-rounds to board trips in the same + * pattern. */ private int calculateOnTripRelativeCost(int boardTime, T tripSchedule) { - return c1Calculator.onTripRelativeRidingCost(boardTime, tripSchedule); + return c1Calculator.transitCost(tripSchedule.relativeTravelTime(boardTime), tripSchedule); } } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index 57873a9d93c..7d77aa6d53c 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -243,7 +243,6 @@ private void assertGeneralizedCostIsCalculatedCorrectByMapper( RaptorPath path ) { if (path.c1() != destArrival.c1()) { - // TODO - Bug: Cost mismatch stop-arrivals and paths #3623 THROTTLE_MISS_MATCH.throttle(() -> LOG.warn( "Cost mismatch - Mapper: {}, stop-arrivals: {}, path: {} {}", diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java index b5114391bb7..fa1871b2acf 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java @@ -28,12 +28,9 @@ int boardingCost( ); /** - * Calculate cost of boarding a trip. This should be the cost of the waiting time, any board and - * transfer cost, and the penalty for the board stop visit. This cost should NOT include the - * previous stop arrival cost, but the incremental cost to be added to the previous stop arrival - * cost. + * Calculate the cost of riding a trip for the given {@code transitTime}. */ - int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded); + int transitCost(int transitTime, T tripScheduledBoarded); /** * Calculate the value when arriving by transit. diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java index 877d8244936..d96dd51eb84 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java @@ -57,6 +57,24 @@ default int departure(int startStopPos, int stopIndex) { return departure(pattern().findStopPositionAfter(startStopPos, stopIndex)); } + /// The relative-travel-time is a proxy for time spent on transit from the boarding stop to the + /// last stop in the pattern. We do not know the alight-stop, so it is impossible to calculate + /// the "correct" time. But the only thing that matters is that the relative difference between + /// two boardings is correct. Compute a "relative-time" which can be used to compare the + /// travel-time cost for any two boardings in the same pattern. + /// + /// Two invariants must hold: + /// + /// - For two boardings at stops i and j on the same trip (where i comes before j in the + /// pattern), `relativeTravelTime(boardAtI) - relativeTravelTime(boardAtJ)` must equal the + /// actual transit time from stop i to stop j. If the board time at position 3 is 10:00 and at + /// the next stop is 10:05, then the value at position 3 is larger by 5*60s = 300 than at the + /// next stop. + /// - All trips in the same pattern must return the same value for the last arrival stop. If the + /// value for one trip at the last stop is 56_000, then it must be 56_000 for the last stop in + /// all other trips in the same pattern. + int relativeTravelTime(int boardTime); + /** * Return the pattern for this trip. */ diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java index ee8a1432e50..df04e3d656e 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java @@ -68,12 +68,8 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, TestTripSchedule tripScheduledBoarded) { - // The relative-transit-time is time spent on transit. We do not know the alight-stop, so - // it is impossible to calculate the "correct" time. But the only thing that maters is that - // the relative difference between to boardings are correct, assuming riding the same trip. - // So, we can use the negative board time as relative-transit-time. - return -boardTime * TRANSIT_RELUCTANCE; + public int transitCost(int transitTime, TestTripSchedule tripScheduledBoarded) { + return transitTime * TRANSIT_RELUCTANCE; } @Override @@ -84,7 +80,7 @@ public int transitArrivalCost( TestTripSchedule trip, int toStopIndex ) { - int cost = boardCost + TRANSIT_RELUCTANCE * transitTime + waitFactor * alightSlack; + int cost = boardCost + transitCost(transitTime, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java index 01e5a17c4f5..54604dcb2c7 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java @@ -52,6 +52,11 @@ public int departure(int stopPosInPattern) { return departureTimes[stopPosInPattern]; } + @Override + public int relativeTravelTime(int boardTime) { + return arrivalTimes[arrivalTimes.length - 1] - boardTime; + } + @Override public RaptorTripPattern pattern() { return pattern; diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java new file mode 100644 index 00000000000..db26dadf423 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java @@ -0,0 +1,56 @@ +package org.opentripplanner.raptor._data.transit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Verify that {@link TestTripSchedule} satisfies the {@code relativeTravelTime} contract defined + * in {@link org.opentripplanner.raptor.spi.RaptorTripSchedule#relativeTravelTime(int)}. + */ +class TestTripScheduleTest { + + private static final int HEADWAY = 600; + + /** + * Two trips in the same pattern, 10 minutes apart. + * + *

+   *   Stop:    A       B       C
+   *   Trip 1: 10:00  10:05  10:10
+   *   Trip 2: 10:10  10:15  10:20  (Trip 1 + 10 min headway)
+   * 
+ */ + private final TestTripSchedule trip1 = TestTripSchedule.schedule("10:00 10:05 10:10").build(); + private final TestTripSchedule trip2 = TestTripSchedule.schedule() + .times(trip1.arrival(0) + HEADWAY, trip1.arrival(1) + HEADWAY, trip1.arrival(2) + HEADWAY) + .build(); + + @Test + void relativeTravelTimeDecreasesByActualTransitTimeBetweenStops() { + int boardAtA = trip1.departure(0); + int boardAtB = trip1.departure(1); + int actualTimeBetweenAAndB = boardAtB - boardAtA; + + assertEquals( + actualTimeBetweenAAndB, + trip1.relativeTravelTime(boardAtA) - trip1.relativeTravelTime(boardAtB) + ); + } + + @Test + void relativeTravelTimeIsIdenticalAcrossTripsInTheSamePatternForEquivalentBoardingPositions() { + assertEquals( + trip1.relativeTravelTime(trip1.departure(0)), + trip2.relativeTravelTime(trip2.departure(0)) + ); + assertEquals( + trip1.relativeTravelTime(trip1.departure(1)), + trip2.relativeTravelTime(trip2.departure(1)) + ); + assertEquals( + trip1.relativeTravelTime(trip1.departure(2)), + trip2.relativeTravelTime(trip2.departure(2)) + ); + } +} From 3b65bee44a2d7de76827d85492addf35968e0756 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 14 Apr 2026 16:03:28 +0200 Subject: [PATCH 030/177] review: Update visibility and improve documentation in cost calculation classes --- .../cost/SingleValueFactorStrategy.java | 2 +- .../raptor/spi/RaptorCostCalculator.java | 13 ++++++---- .../raptor/spi/RaptorCostConverter.java | 24 ++++++++++--------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java index 018f4dfe10d..3537d4af665 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java @@ -12,7 +12,7 @@ final class SingleValueFactorStrategy implements FactorStrategy { private final int factor; - SingleValueFactorStrategy(int factor) { + private SingleValueFactorStrategy(int factor) { this.factor = factor; } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java index fa1871b2acf..72a239a1477 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java @@ -1,10 +1,13 @@ package org.opentripplanner.raptor.spi; -/** - * The responsibility is to calculate multi-criteria value (like the generalized cost). - *

- * The implementation should be immutable and thread safe. - */ +/// +/// The responsibility is to calculate multi-criteria value (like the generalized cost). +/// +/// The implementation should be immutable and thread safe. +/// +/// See {@link RaptorCostConverter} for resolution and units for time/duration and +/// c1(generalized-cost). +/// public interface RaptorCostCalculator { /** * The cost is zero (0) if it's not calculated or if the cost "element" have no cost associated. diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java index bc8d187d54d..eb0ef019403 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java @@ -2,17 +2,19 @@ import java.time.Duration; -/** - * Convert Raptor internal cost to OTP domain model cost, and back. This is provided by the Raptor - * module as a utility, but not used in Raptor except in unit-tests. - *

- * Inside Raptor the cost unit is 1/100 of a "transit second" using {@code int} as type. In the - * OTP internal domain the unit used for cost is one "transit second" with type {@code double}. - * Cost in raptor is calculated using ints to speed up the calculations and to save memory. - *

- * The reason for using 1/100 of a second resolution is that we want a cost factor of {@code 0.99} - * to win over a cost factor of {@code 1.00}. - */ +/// Convert Raptor internal cost to OTP domain model cost, and back. This can also be used to +/// convert between time/duration in seconds to Raptor cost. Raptor does not require the cost to +/// be in `transit-centi-seconds`(1/100 seconds), the caller/user may use any resolution for cost. +/// For time/duration the resolution is 1 second. +/// +/// This is provided by the Raptor module as a utility, but not used in Raptor except in unit-tests. +/// +/// Inside Raptor the cost unit is 1/100 of a "transit second" using {@code int} as type. In the +/// OTP internal domain the unit used for cost is one "transit second" with type {@code double}. +/// Cost in raptor is calculated using `int`s to speed up the calculations and to save memory. +/// +/// The reason for using 1/100 of a second resolution is that we want a cost factor of {@code 0.99} +/// to win over a cost factor of {@code 1.00}. public final class RaptorCostConverter { private static final int NOT_SET = -1; From 581d75661fc67e4fcf6ef476b20f8bdd8e0d9a73 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 1 Apr 2026 20:47:24 +0200 Subject: [PATCH 031/177] Make state immutable --- .../street/search/state/State.java | 60 +++-- .../street/search/state/StateEditor.java | 240 ++++++++++-------- .../street/search/state/StateEditorTest.java | 2 +- .../strategy/DominanceFunctionTest.java | 8 +- .../utils/lang/DoubleUtils.java | 10 + 5 files changed, 185 insertions(+), 135 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index 5bae96f41c9..adb1a364ab9 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -19,9 +19,10 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; import org.opentripplanner.street.search.request.StreetSearchRequest; +import org.opentripplanner.utils.lang.DoubleUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; -public class State implements AStarState, Cloneable { +public class State implements AStarState { private static final State[] EMPTY_STATES = {}; private final StreetSearchRequest request; @@ -29,26 +30,26 @@ public class State implements AStarState, Cloneable { /* Data which is likely to change at most traversals */ // the current time at this state, in milliseconds since UNIX epoch - protected long time_ms; + protected final long time_ms; // accumulated weight up to this state - public double weight; + public final double weight; // associate this state with a vertex in the graph - protected Vertex vertex; + protected final Vertex vertex; // allow path reconstruction from states - protected State backState; + @Nullable + protected final State backState; - public Edge backEdge; - - /* StateData contains data which is unlikely to change as often */ - public StateData stateData; + @Nullable + public final Edge backEdge; // how far have we traversed through the graph - public double traversalDistance_m; + public final double traversalDistance_m; - /* CONSTRUCTORS */ + /* StateData contains data which is unlikely to change as often */ + public final StateData stateData; /** * Create an initial state, forcing vertex to the specified value. Useful for tests, etc. @@ -62,11 +63,12 @@ public State(Vertex vertex, StreetSearchRequest streetSearchRequest) { ); } - public State(Vertex vertex, Instant startTime, StateData stateData, StreetSearchRequest request) { + State(Vertex vertex, Instant startTime, StateData stateData, StreetSearchRequest request) { this.request = request; this.weight = 0; - this.vertex = vertex; + this.vertex = Objects.requireNonNull(vertex); this.backState = null; + this.backEdge = null; this.stateData = stateData; if (request.arriveBy() && !vertex.rentalRestrictions().noDropOffNetworks().isEmpty()) { this.stateData.noRentalDropOffZonesAtStartOfReverseSearch = vertex @@ -77,6 +79,26 @@ public State(Vertex vertex, Instant startTime, StateData stateData, StreetSearch this.time_ms = startTime.toEpochMilli(); } + State( + StreetSearchRequest request, + double weight, + Vertex vertex, + @Nullable State backState, + @Nullable Edge backEdge, + StateData stateData, + double traversalDistance_m, + long time_ms + ) { + this.request = Objects.requireNonNull(request); + this.weight = DoubleUtils.requireNonNegative(weight); + this.vertex = Objects.requireNonNull(vertex); + this.backState = backState; + this.backEdge = backEdge; + this.stateData = Objects.requireNonNull(stateData); + this.traversalDistance_m = DoubleUtils.requireNonNegative(traversalDistance_m); + this.time_ms = time_ms; + } + /** * Create an initial state representing the beginning of a search for the given routing request. * Initial "parent-less" states can only be created at the beginning of a trip. elsewhere, all @@ -480,16 +502,6 @@ public boolean containsOnlyWalkMode() { return true; } - protected State clone() { - State ret; - try { - ret = (State) super.clone(); - } catch (CloneNotSupportedException e1) { - throw new IllegalStateException("This is not happening"); - } - return ret; - } - /** * Returns an efficient iterable that allows traversing the edge chain backwards. */ @@ -514,7 +526,7 @@ public String toString() { } void checkNegativeWeight() { - double dw = this.weight - backState.weight; + double dw = weight - backState.weight; if (dw < 0) { throw new NegativeWeightException(dw + " on edge " + backEdge); } diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index d832064c587..460ba40fd4d 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -23,8 +23,15 @@ public class StateEditor { private static final Logger LOG = LoggerFactory.getLogger(StateEditor.class); + private State backState; + private final Edge backEdge; + private Vertex vertex; + private StateData stateData; + private double weight; + private long time_ms; + private double traversalDistance_m; - protected State child; + protected State original; private boolean spawned = false; @@ -34,21 +41,35 @@ public class StateEditor { /* CONSTRUCTORS */ + /** + * The very first state in the chain before any iteration has started. + */ public StateEditor(Vertex v, StreetSearchRequest request) { - child = new State(v, request); + this.original = new State(v, request); + this.stateData = original.stateData.clone(); + this.backState = null; + this.backEdge = null; + this.vertex = v; + this.time_ms = request.startTime().toEpochMilli(); + this.weight = 0; + this.traversalDistance_m = 0; } public StateEditor(State parent, Edge e) { - child = parent.clone(); - child.backState = parent; - child.backEdge = e; + this.original = parent; + this.stateData = parent.stateData.clone(); + this.backState = parent; + this.backEdge = e; + this.time_ms = parent.time_ms; + this.weight = parent.weight; + this.traversalDistance_m = parent.traversalDistance_m; + final Vertex parentVertex = parent.vertex; if (e == null) { - child.backState = null; - child.vertex = parentVertex; - child.stateData = child.stateData.clone(); + this.backState = null; + this.vertex = parentVertex; return; } @@ -56,8 +77,8 @@ public StateEditor(State parent, Edge e) { final Vertex toVertex = e.getToVertex(); if (fromVertex == null || toVertex == null) { - child.vertex = parentVertex; - child.stateData = child.stateData.clone(); + this.vertex = parentVertex; + this.stateData = original.stateData.clone(); LOG.error("From or to vertex is null for {}", e); defectiveTraversal = true; return; @@ -69,10 +90,10 @@ public StateEditor(State parent, Edge e) { // can't know the direction of travel from the above check. The expression below is simplified // fromVertex.equals(toVertex) ? parent.getOptions().arriveBy : false; traversingBackward = fromVertex.equals(toVertex) && parent.getRequest().arriveBy(); - child.vertex = toVertex; + this.vertex = toVertex; } else if (parentVertex.equals(toVertex)) { traversingBackward = true; - child.vertex = fromVertex; + this.vertex = fromVertex; } else { // Parent state is not at either end of edge. LOG.warn("Edge is not connected to parent state: {}", e); @@ -109,31 +130,36 @@ public State makeState() { // if something was flagged incorrect, do not make a new state if (defectiveTraversal) { - LOG.error("Defective traversal flagged on edge " + child.backEdge); + LOG.error("Defective traversal flagged on edge " + original.backEdge); return null; } - if (child.backState != null) { - // make it impossible to use a state with lower weight than its - // parent. - child.checkNegativeWeight(); - + if (backState != null) { // check that time changes are coherent with edge traversal // direction if ( traversingBackward - ? (child.getTimeDeltaMilliseconds() > 0) - : (child.getTimeDeltaMilliseconds() < 0) + ? (original.getTimeDeltaMilliseconds() > 0) + : (original.getTimeDeltaMilliseconds() < 0) ) { LOG.trace( "Time was incremented the wrong direction during state editing. {}", - child.backEdge + original.backEdge ); return null; } } spawned = true; - return child; + return new State( + original.getRequest(), + weight, + vertex, + backState, + backEdge, + stateData, + traversalDistance_m, + time_ms + ); } /** @@ -145,7 +171,7 @@ public State[] makeStateArray() { } public String toString() { - return "StateEditor{" + child + "}"; + return "StateEditor{" + original + "}"; } /* PUBLIC METHODS TO MODIFY A STATE BEFORE IT IS USED */ @@ -158,7 +184,7 @@ public void incrementWeight(double weight) { "A state's weight is being incremented by " + weight + " while traversing edge " + - child.backEdge + original.backEdge ); defectiveTraversal = true; return; @@ -166,12 +192,12 @@ public void incrementWeight(double weight) { if (weight < 0) { LOG.warn( "A state's weight is being incremented by a negative amount while traversing edge " + - child.backEdge + original.backEdge ); defectiveTraversal = true; return; } - child.weight += weight; + this.weight += weight; } /** @@ -183,12 +209,12 @@ public void incrementTimeInMilliseconds(long milliseconds) { if (milliseconds < 0) { LOG.warn( "A state's time is being incremented by a negative amount while traversing edge " + - child.getBackEdge() + original.getBackEdge() ); defectiveTraversal = true; return; } - child.time_ms += (traversingBackward ? -milliseconds : milliseconds); + this.time_ms += (traversingBackward ? -milliseconds : milliseconds); } public void incrementTimeInSeconds(long seconds) { @@ -202,63 +228,63 @@ public void incrementTraversalDistanceMeters(double length) { if (length < 0) { throw new IllegalArgumentException("Traversal distance cannot be negative"); } - child.traversalDistance_m += length; + this.traversalDistance_m += length; } /* Basic Setters */ public void resetEnteredNoThroughTrafficArea() { - if (!child.stateData.enteredNoThroughTrafficArea) { + if (!stateData.enteredNoThroughTrafficArea) { return; } cloneStateDataAsNeeded(); - child.stateData.enteredNoThroughTrafficArea = false; + stateData.enteredNoThroughTrafficArea = false; } public void setEnteredNoThroughTrafficArea() { - if (child.stateData.enteredNoThroughTrafficArea) { + if (stateData.enteredNoThroughTrafficArea) { return; } cloneStateDataAsNeeded(); - child.stateData.enteredNoThroughTrafficArea = true; + stateData.enteredNoThroughTrafficArea = true; } public void leaveNoRentalDropOffArea() { - if (!child.stateData.insideNoRentalDropOffArea) { + if (!stateData.insideNoRentalDropOffArea) { return; } cloneStateDataAsNeeded(); - child.stateData.insideNoRentalDropOffArea = false; + stateData.insideNoRentalDropOffArea = false; } public void enterNoRentalDropOffArea() { - if (child.stateData.insideNoRentalDropOffArea) { + if (stateData.insideNoRentalDropOffArea) { return; } cloneStateDataAsNeeded(); - child.stateData.insideNoRentalDropOffArea = true; + stateData.insideNoRentalDropOffArea = true; } public void setBackMode(TraverseMode mode) { - if (mode == child.stateData.backMode) { + if (mode == original.stateData.backMode) { return; } cloneStateDataAsNeeded(); - child.stateData.backMode = mode; + stateData.backMode = mode; } public void setBackWalkingBike(boolean walkingBike) { - if (walkingBike == child.stateData.backWalkingBike) { + if (walkingBike == stateData.backWalkingBike) { return; } cloneStateDataAsNeeded(); - child.stateData.backWalkingBike = walkingBike; + stateData.backWalkingBike = walkingBike; } public void beginFloatingVehicleRenting( @@ -269,18 +295,18 @@ public void beginFloatingVehicleRenting( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.insideNoRentalDropOffArea = false; + stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.insideNoRentalDropOffArea = false; } else { - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } } @@ -293,20 +319,20 @@ public void beginVehicleRentingAtStation( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = mayKeep; - child.stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = mayKeep; + stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } else { - child.stateData.mayKeepRentedVehicleAtDestination = mayKeep; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.mayKeepRentedVehicleAtDestination = mayKeep; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } } @@ -318,20 +344,20 @@ public void dropOffRentedVehicleAtStation( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } else { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } } @@ -343,22 +369,22 @@ public void dropFloatingVehicle( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; - child.stateData.currentMode = formFactor != null + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; + stateData.currentMode = formFactor != null ? formFactor.traverseMode - : StreetModeToRentalTraverseModeMapper.map(child.getRequest().mode()); - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + : StreetModeToRentalTraverseModeMapper.map(original.getRequest().mode()); + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } else { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } } @@ -371,8 +397,8 @@ public void setVehicleParked(boolean vehicleParked, TraverseMode nonTransitMode) resetEnteredNoThroughTrafficArea(); cloneStateDataAsNeeded(); - child.stateData.vehicleParked = vehicleParked; - child.stateData.currentMode = nonTransitMode; + stateData.vehicleParked = vehicleParked; + stateData.currentMode = nonTransitMode; } /** @@ -381,38 +407,38 @@ public void setVehicleParked(boolean vehicleParked, TraverseMode nonTransitMode) */ public void setFromState(State state) { cloneStateDataAsNeeded(); - child.stateData.currentMode = state.stateData.currentMode; - child.stateData.carPickupState = state.stateData.carPickupState; - child.stateData.vehicleParked = state.stateData.vehicleParked; - child.stateData.backWalkingBike = state.stateData.backWalkingBike; + stateData.currentMode = state.stateData.currentMode; + stateData.carPickupState = state.stateData.carPickupState; + stateData.vehicleParked = state.stateData.vehicleParked; + stateData.backWalkingBike = state.stateData.backWalkingBike; } public void setCarPickupState(CarPickupState carPickupState) { cloneStateDataAsNeeded(); - child.stateData.carPickupState = carPickupState; + stateData.carPickupState = carPickupState; switch (carPickupState) { - case WALK_TO_PICKUP, WALK_FROM_DROP_OFF -> child.stateData.currentMode = TraverseMode.WALK; - case IN_CAR -> child.stateData.currentMode = TraverseMode.CAR; + case WALK_TO_PICKUP, WALK_FROM_DROP_OFF -> stateData.currentMode = TraverseMode.WALK; + case IN_CAR -> stateData.currentMode = TraverseMode.CAR; } } public void setTimeSeconds(long seconds) { - child.time_ms = 1000 * seconds; + this.time_ms = 1000 * seconds; } public void setTimeMilliseconds(long milliseconds) { - child.time_ms = milliseconds; + this.time_ms = milliseconds; } /* PUBLIC GETTER METHODS */ public State getBackState() { - return child.getBackState(); + return backState; } public void resetStartedInNoDropOffZone() { cloneStateDataAsNeeded(); - child.stateData.noRentalDropOffZonesAtStartOfReverseSearch = Set.of(); + stateData.noRentalDropOffZonesAtStartOfReverseSearch = Set.of(); } /* PRIVATE METHODS */ @@ -423,8 +449,8 @@ public void resetStartedInNoDropOffZone() { * older states. */ private void cloneStateDataAsNeeded() { - if (child.backState != null && child.stateData == child.backState.stateData) { - child.stateData = child.stateData.clone(); + if (backState != null && stateData == backState.stateData) { + this.stateData = backState.stateData.clone(); } } } diff --git a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java index a8eea5e9dff..508b15ab4eb 100644 --- a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java @@ -27,7 +27,7 @@ public final void testIncrementTimeInMilliseconds() { stateEditor.setTimeSeconds(0); stateEditor.incrementTimeInMilliseconds(999999999); - assertEquals(999999999, stateEditor.child.getTimeMilliseconds()); + assertEquals(999999999, stateEditor.makeState().getTimeMilliseconds()); } @Test diff --git a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java index 59ef4f3fbb4..e7d7293c577 100644 --- a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java @@ -5,6 +5,7 @@ import static org.opentripplanner.street.model.StreetModelFactory.intersectionVertex; import java.time.Instant; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.spi.DominanceFunction; import org.opentripplanner.street.model.StreetMode; @@ -14,6 +15,7 @@ import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateData; +@Disabled public class DominanceFunctionTest { @Test @@ -28,8 +30,8 @@ public void testGeneralDominanceFunction() { StateData stateData = StateData.getBaseCaseStateData(streetSearchRequest); State stateA = new State(fromVertex, Instant.EPOCH, stateData, streetSearchRequest); State stateB = new State(toVertex, Instant.EPOCH, stateData, streetSearchRequest); - stateA.weight = 1; - stateB.weight = 2; + //stateA.weight = 1; + //stateB.weight = 2; assertTrue(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateA, stateB)); assertFalse(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateB, stateA)); @@ -58,7 +60,7 @@ public void noDropOffZone() { var editor = outsideZone.edit(edge); editor.enterNoRentalDropOffArea(); var insideZone = editor.makeState(); - insideZone.weight = 1; + //insideZone.weight = 1; assertFalse(dominanceF.betterOrEqualAndComparable(insideZone, outsideZone)); assertFalse(dominanceF.betterOrEqualAndComparable(outsideZone, insideZone)); diff --git a/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java b/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java index 3bef4d0917b..1c1596fbca9 100644 --- a/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java +++ b/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java @@ -74,6 +74,16 @@ public static boolean doubleEquals(double a, double b) { return Double.compare(a, b) == 0; } + /** + * Throws an exception if the value is less than zero. + */ + public static double requireNonNegative(double value) { + if (value < 0) { + throw new IllegalArgumentException("Value is required to be non-negative, but was: " + value); + } + return value; + } + public static double requireInRange(double value, double min, double max) { return requireInRange(value, min, max, "value"); } From e2ecb51afaa0cd8b2fd1a78d360a68abfa59c48b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 1 Apr 2026 21:38:24 +0200 Subject: [PATCH 032/177] Make tests pass --- .../opentripplanner/street/search/state/State.java | 2 +- .../street/search/strategy/DominanceFunctionTest.java | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index adb1a364ab9..c2fac99ea9d 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -79,7 +79,7 @@ public State(Vertex vertex, StreetSearchRequest streetSearchRequest) { this.time_ms = startTime.toEpochMilli(); } - State( + public State( StreetSearchRequest request, double weight, Vertex vertex, diff --git a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java index e7d7293c577..2bca2a14f76 100644 --- a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java @@ -4,8 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.street.model.StreetModelFactory.intersectionVertex; -import java.time.Instant; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.spi.DominanceFunction; import org.opentripplanner.street.model.StreetMode; @@ -15,7 +13,6 @@ import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateData; -@Disabled public class DominanceFunctionTest { @Test @@ -28,10 +25,8 @@ public void testGeneralDominanceFunction() { StreetSearchRequest streetSearchRequest = StreetSearchRequest.of().build(); StateData stateData = StateData.getBaseCaseStateData(streetSearchRequest); - State stateA = new State(fromVertex, Instant.EPOCH, stateData, streetSearchRequest); - State stateB = new State(toVertex, Instant.EPOCH, stateData, streetSearchRequest); - //stateA.weight = 1; - //stateB.weight = 2; + State stateA = new State(streetSearchRequest, 1, fromVertex, null, null, stateData, 0, 0); + State stateB = new State(streetSearchRequest, 2, toVertex, null ,null, stateData, 0,0); assertTrue(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateA, stateB)); assertFalse(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateB, stateA)); @@ -52,7 +47,7 @@ public void noDropOffZone() { StateData stateData = StateData.getBaseCaseStateData(req); - State outsideZone = new State(fromVertex, Instant.EPOCH, stateData, req); + State outsideZone = new State(req, 0, fromVertex, null, null,stateData, 0, 0); assertFalse(outsideZone.isInsideNoRentalDropOffArea()); var edge = StreetModelFactory.streetEdge(fromVertex, toVertex); From 69d5940e0d33e3fcc9e1ac49b855271ebe3bd357 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 1 Apr 2026 21:58:17 +0200 Subject: [PATCH 033/177] Remove unnecessary checks for defective traversal --- .../street/search/state/StateEditor.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index 460ba40fd4d..c244fd7d437 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -23,9 +23,9 @@ public class StateEditor { private static final Logger LOG = LoggerFactory.getLogger(StateEditor.class); - private State backState; + private final State backState; private final Edge backEdge; - private Vertex vertex; + private final Vertex vertex; private StateData stateData; private double weight; private long time_ms; @@ -64,26 +64,11 @@ public StateEditor(State parent, Edge e) { this.weight = parent.weight; this.traversalDistance_m = parent.traversalDistance_m; - final Vertex parentVertex = parent.vertex; - if (e == null) { - this.backState = null; - this.vertex = parentVertex; - return; - } - final Vertex fromVertex = e.getFromVertex(); final Vertex toVertex = e.getToVertex(); - if (fromVertex == null || toVertex == null) { - this.vertex = parentVertex; - this.stateData = original.stateData.clone(); - LOG.error("From or to vertex is null for {}", e); - defectiveTraversal = true; - return; - } - // Note that we use equals(), not ==, here to allow for dynamically created vertices if (parentVertex.equals(fromVertex)) { // from and to vertices are the same on eg. vehicle rental and parking vertices, thus, we @@ -101,6 +86,7 @@ public StateEditor(State parent, Edge e) { LOG.warn(" to vertex: {}", toVertex); LOG.warn(" parent vertex: {}", parentVertex); defectiveTraversal = true; + this.vertex = null; } if (traversingBackward != parent.getRequest().arriveBy()) { From e37b738b25db56cf333f90c0fb58d7eb0645677e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 1 Apr 2026 22:27:10 +0200 Subject: [PATCH 034/177] Reduce cloning --- .../org/opentripplanner/street/search/state/StateEditor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index c244fd7d437..97613d66fbf 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -46,7 +46,7 @@ public class StateEditor { */ public StateEditor(Vertex v, StreetSearchRequest request) { this.original = new State(v, request); - this.stateData = original.stateData.clone(); + this.stateData = original.stateData; this.backState = null; this.backEdge = null; this.vertex = v; @@ -57,7 +57,7 @@ public StateEditor(Vertex v, StreetSearchRequest request) { public StateEditor(State parent, Edge e) { this.original = parent; - this.stateData = parent.stateData.clone(); + this.stateData = parent.stateData; this.backState = parent; this.backEdge = e; this.time_ms = parent.time_ms; From bcfa89a5ae0fd8c7dda92980ad77367d5b93f630 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 1 Apr 2026 22:40:11 +0200 Subject: [PATCH 035/177] Remove original --- .../street/search/state/State.java | 8 ----- .../street/search/state/StateEditor.java | 31 +++++++++---------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index c2fac99ea9d..8873af10ba0 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -6,7 +6,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.AStarState; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType.PropulsionType; @@ -163,13 +162,6 @@ public static boolean isEmpty(State[] s) { return s.length == 0; } - /** - * Takes a stream of states and converts it to an array while removing nulls. - */ - public static State[] ofStream(Stream states) { - return states.filter(Objects::nonNull).toArray(State[]::new); - } - /** * Create a state editor to produce a child of this state, which will be the result of traversing * the given edge. diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index 97613d66fbf..0efdebfd0b8 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -23,6 +23,7 @@ public class StateEditor { private static final Logger LOG = LoggerFactory.getLogger(StateEditor.class); + private final StreetSearchRequest request; private final State backState; private final Edge backEdge; private final Vertex vertex; @@ -31,8 +32,6 @@ public class StateEditor { private long time_ms; private double traversalDistance_m; - protected State original; - private boolean spawned = false; private boolean defectiveTraversal = false; @@ -45,8 +44,8 @@ public class StateEditor { * The very first state in the chain before any iteration has started. */ public StateEditor(Vertex v, StreetSearchRequest request) { - this.original = new State(v, request); - this.stateData = original.stateData; + this.request = request; + this.stateData = new State(v, request).stateData; this.backState = null; this.backEdge = null; this.vertex = v; @@ -56,7 +55,7 @@ public StateEditor(Vertex v, StreetSearchRequest request) { } public StateEditor(State parent, Edge e) { - this.original = parent; + this.request = parent.getRequest(); this.stateData = parent.stateData; this.backState = parent; this.backEdge = e; @@ -116,7 +115,7 @@ public State makeState() { // if something was flagged incorrect, do not make a new state if (defectiveTraversal) { - LOG.error("Defective traversal flagged on edge " + original.backEdge); + LOG.error("Defective traversal flagged on edge " + backEdge); return null; } @@ -125,19 +124,19 @@ public State makeState() { // direction if ( traversingBackward - ? (original.getTimeDeltaMilliseconds() > 0) - : (original.getTimeDeltaMilliseconds() < 0) + ? (backState.getTimeDeltaMilliseconds() > 0) + : (backState.getTimeDeltaMilliseconds() < 0) ) { LOG.trace( "Time was incremented the wrong direction during state editing. {}", - original.backEdge + backEdge ); return null; } } spawned = true; return new State( - original.getRequest(), + request, weight, vertex, backState, @@ -157,7 +156,7 @@ public State[] makeStateArray() { } public String toString() { - return "StateEditor{" + original + "}"; + return "StateEditor{" + backState + "}"; } /* PUBLIC METHODS TO MODIFY A STATE BEFORE IT IS USED */ @@ -170,7 +169,7 @@ public void incrementWeight(double weight) { "A state's weight is being incremented by " + weight + " while traversing edge " + - original.backEdge + backEdge ); defectiveTraversal = true; return; @@ -178,7 +177,7 @@ public void incrementWeight(double weight) { if (weight < 0) { LOG.warn( "A state's weight is being incremented by a negative amount while traversing edge " + - original.backEdge + backEdge ); defectiveTraversal = true; return; @@ -195,7 +194,7 @@ public void incrementTimeInMilliseconds(long milliseconds) { if (milliseconds < 0) { LOG.warn( "A state's time is being incremented by a negative amount while traversing edge " + - original.getBackEdge() + backState ); defectiveTraversal = true; return; @@ -256,7 +255,7 @@ public void enterNoRentalDropOffArea() { } public void setBackMode(TraverseMode mode) { - if (mode == original.stateData.backMode) { + if (mode == stateData.backMode) { return; } @@ -359,7 +358,7 @@ public void dropFloatingVehicle( stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; stateData.currentMode = formFactor != null ? formFactor.traverseMode - : StreetModeToRentalTraverseModeMapper.map(original.getRequest().mode()); + : StreetModeToRentalTraverseModeMapper.map(request.mode()); stateData.vehicleRentalNetwork = network; stateData.rentalVehicleFormFactor = formFactor; stateData.rentalVehiclePropulsionType = propulsionType; From a4171ed7432a3187ed59ddfb371e30a15fc8bff0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 2 Apr 2026 08:12:03 +0200 Subject: [PATCH 036/177] Reduce builder churn --- .../street/search/state/State.java | 18 +++++++----------- .../street/search/state/StateEditor.java | 8 -------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index 8873af10ba0..c96fa7d7dad 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -517,13 +517,6 @@ public String toString() { .toString(); } - void checkNegativeWeight() { - double dw = weight - backState.weight; - if (dw < 0) { - throw new NegativeWeightException(dw + " on edge " + backEdge); - } - } - private int getAbsTimeDeltaMilliseconds() { return Math.abs(getTimeDeltaMilliseconds()); } @@ -540,10 +533,13 @@ private State reversedClone() { // these must be getTime(), not getTimeAccurate(), so that the reversed path (which does not // have arriveBy true anymore) has times which round correctly, as the rounding rules // depend on arriveBy - StreetSearchRequest reversedRequest = request - .copyOfReversed(getTime()) - .withUseRentalAvailability(false) - .build(); + var builder = request + .copyOfReversed(getTime()); + // mutating the builder is a hot spot, only do it if needed + if(request.mode().includesRenting()){ + builder.withUseRentalAvailability(false); + } + var reversedRequest = builder.build(); StateData newStateData = stateData.clone(); newStateData.backMode = null; return new State(this.vertex, getTime(), newStateData, reversedRequest); diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index 0efdebfd0b8..1d39c496023 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -32,8 +32,6 @@ public class StateEditor { private long time_ms; private double traversalDistance_m; - private boolean spawned = false; - private boolean defectiveTraversal = false; private boolean traversingBackward; @@ -108,11 +106,6 @@ public StateEditor(State parent, Edge e) { */ @Nullable public State makeState() { - // check that this editor has not been used already - if (spawned) { - throw new IllegalStateException("A StateEditor can only be used once."); - } - // if something was flagged incorrect, do not make a new state if (defectiveTraversal) { LOG.error("Defective traversal flagged on edge " + backEdge); @@ -134,7 +127,6 @@ public State makeState() { return null; } } - spawned = true; return new State( request, weight, From a008f623e7ca7d215e6e7018e4f274a0fcdb9291 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 2 Apr 2026 09:02:45 +0200 Subject: [PATCH 037/177] Optimize comparison of street vertex --- .../street/model/vertex/StreetLocation.java | 10 +++++++--- .../org/opentripplanner/street/search/state/State.java | 5 ++--- .../street/search/state/StateEditor.java | 10 ++-------- .../street/search/strategy/DominanceFunctionTest.java | 6 +++--- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java b/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java index 4af761dbdb9..e3e0a4d5858 100644 --- a/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java +++ b/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java @@ -2,6 +2,7 @@ import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.i18n.I18NString; +import org.opentripplanner.utils.lang.DoubleUtils; /** * Represents a location on a street, somewhere between the two corners. This is used when computing @@ -43,9 +44,12 @@ public int hashCode() { } public boolean equals(Object o) { - if (o instanceof StreetLocation) { - StreetLocation other = (StreetLocation) o; - return other.getCoordinate().equals(getCoordinate()); + if (o instanceof StreetLocation other) { + // avoid allocations of Coordinate by comparing the doubles directly + return ( + DoubleUtils.doubleEquals(other.getLat(), this.getLat()) && + DoubleUtils.doubleEquals(other.getLon(), this.getLon()) + ); } return false; } diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index c96fa7d7dad..5d914a8f73c 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -533,10 +533,9 @@ private State reversedClone() { // these must be getTime(), not getTimeAccurate(), so that the reversed path (which does not // have arriveBy true anymore) has times which round correctly, as the rounding rules // depend on arriveBy - var builder = request - .copyOfReversed(getTime()); + var builder = request.copyOfReversed(getTime()); // mutating the builder is a hot spot, only do it if needed - if(request.mode().includesRenting()){ + if (request.mode().includesRenting()) { builder.withUseRentalAvailability(false); } var reversedRequest = builder.build(); diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index 1d39c496023..a820b3de14a 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -120,10 +120,7 @@ public State makeState() { ? (backState.getTimeDeltaMilliseconds() > 0) : (backState.getTimeDeltaMilliseconds() < 0) ) { - LOG.trace( - "Time was incremented the wrong direction during state editing. {}", - backEdge - ); + LOG.trace("Time was incremented the wrong direction during state editing. {}", backEdge); return null; } } @@ -158,10 +155,7 @@ public String toString() { public void incrementWeight(double weight) { if (Double.isInfinite(weight) || Double.isNaN(weight)) { LOG.warn( - "A state's weight is being incremented by " + - weight + - " while traversing edge " + - backEdge + "A state's weight is being incremented by " + weight + " while traversing edge " + backEdge ); defectiveTraversal = true; return; diff --git a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java index 2bca2a14f76..f2ca66526fe 100644 --- a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java @@ -25,8 +25,8 @@ public void testGeneralDominanceFunction() { StreetSearchRequest streetSearchRequest = StreetSearchRequest.of().build(); StateData stateData = StateData.getBaseCaseStateData(streetSearchRequest); - State stateA = new State(streetSearchRequest, 1, fromVertex, null, null, stateData, 0, 0); - State stateB = new State(streetSearchRequest, 2, toVertex, null ,null, stateData, 0,0); + State stateA = new State(streetSearchRequest, 1, fromVertex, null, null, stateData, 0, 0); + State stateB = new State(streetSearchRequest, 2, toVertex, null, null, stateData, 0, 0); assertTrue(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateA, stateB)); assertFalse(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateB, stateA)); @@ -47,7 +47,7 @@ public void noDropOffZone() { StateData stateData = StateData.getBaseCaseStateData(req); - State outsideZone = new State(req, 0, fromVertex, null, null,stateData, 0, 0); + State outsideZone = new State(req, 0, fromVertex, null, null, stateData, 0, 0); assertFalse(outsideZone.isInsideNoRentalDropOffArea()); var edge = StreetModelFactory.streetEdge(fromVertex, toVertex); From decb39f0ff4671bb4cf51a0723bdbc069074652d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 13 Apr 2026 12:52:50 +0200 Subject: [PATCH 038/177] Throw exceptions for obviously incorrect data --- .../street/search/state/StateEditor.java | 8 ++------ .../street/search/state/StateEditorTest.java | 12 +++++------- .../search/strategy/DominanceFunctionTest.java | 3 +-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index a820b3de14a..ff02b50d9b2 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -154,19 +154,15 @@ public String toString() { public void incrementWeight(double weight) { if (Double.isInfinite(weight) || Double.isNaN(weight)) { - LOG.warn( + throw new IllegalArgumentException( "A state's weight is being incremented by " + weight + " while traversing edge " + backEdge ); - defectiveTraversal = true; - return; } if (weight < 0) { - LOG.warn( + throw new IllegalArgumentException( "A state's weight is being incremented by a negative amount while traversing edge " + backEdge ); - defectiveTraversal = true; - return; } this.weight += weight; } diff --git a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java index 508b15ab4eb..da5a23fe870 100644 --- a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Nested; @@ -45,9 +45,7 @@ public final void testNanWeightIncrement() { StateEditor stateEditor = new StateEditor(vertex, StreetSearchRequest.of().build()); stateEditor.setTimeSeconds(0); - stateEditor.incrementWeight(Double.NaN); - - assertNull(stateEditor.makeState()); + assertThrows(IllegalArgumentException.class, () -> stateEditor.incrementWeight(Double.NaN)); } @Test @@ -55,9 +53,9 @@ public final void testInfinityWeightIncrement() { StateEditor stateEditor = new StateEditor(vertex, StreetSearchRequest.of().build()); stateEditor.setTimeSeconds(0); - stateEditor.incrementWeight(Double.NEGATIVE_INFINITY); - - assertNull(stateEditor.makeState(), "Infinity weight increment"); + assertThrows(IllegalArgumentException.class, () -> + stateEditor.incrementWeight(Double.NEGATIVE_INFINITY) + ); } @Nested diff --git a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java index f2ca66526fe..24912a93eec 100644 --- a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java @@ -13,7 +13,7 @@ import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateData; -public class DominanceFunctionTest { +class DominanceFunctionTest { @Test public void testGeneralDominanceFunction() { @@ -55,7 +55,6 @@ public void noDropOffZone() { var editor = outsideZone.edit(edge); editor.enterNoRentalDropOffArea(); var insideZone = editor.makeState(); - //insideZone.weight = 1; assertFalse(dominanceF.betterOrEqualAndComparable(insideZone, outsideZone)); assertFalse(dominanceF.betterOrEqualAndComparable(outsideZone, insideZone)); From 6ffa0ae1f6c3cdb0237776c4f79ddea31a85061e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 11:48:20 +0200 Subject: [PATCH 039/177] Make class final --- .../java/org/opentripplanner/street/search/state/State.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index 5d914a8f73c..62634445265 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -21,7 +21,7 @@ import org.opentripplanner.utils.lang.DoubleUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; -public class State implements AStarState { +public final class State implements AStarState { private static final State[] EMPTY_STATES = {}; private final StreetSearchRequest request; From bf4a2ca324a93daf4e54afbfc36e782b3468aac7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 11:49:23 +0200 Subject: [PATCH 040/177] Update visibility modifier --- .../java/org/opentripplanner/street/search/state/State.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index 62634445265..0a34fcf649a 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -29,17 +29,17 @@ public final class State implements AStarState { /* Data which is likely to change at most traversals */ // the current time at this state, in milliseconds since UNIX epoch - protected final long time_ms; + final long time_ms; // accumulated weight up to this state public final double weight; // associate this state with a vertex in the graph - protected final Vertex vertex; + final Vertex vertex; // allow path reconstruction from states @Nullable - protected final State backState; + private final State backState; @Nullable public final Edge backEdge; From 9bca75f77dafa905064760ce377916433bf9360f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 14 Apr 2026 16:17:29 +0200 Subject: [PATCH 041/177] review: Use transit-duration over transit-time to improve readability --- .../sorlandsbanen/CoachCostCalculator.java | 24 ++++++++++++------ .../opentripplanner/model/plan/Itinerary.java | 6 ++--- .../transit/cost/DefaultCostCalculator.java | 20 +++++++++------ .../transit/cost/PatternCostCalculator.java | 18 +++++++------ .../cost/WheelchairCostCalculator.java | 16 +++++++----- .../FrequencyBoardOrAlightEvent.java | 2 +- .../request/TripScheduleWithOffset.java | 4 +-- .../model/MinSafeTransferTimeCalculator.java | 4 +-- .../_data/transit/TestTripSchedule.java | 2 +- .../MultiCriteriaRoutingStrategy.java | 2 +- .../raptor/spi/RaptorCostCalculator.java | 25 ++++++++++++------- .../raptor/spi/RaptorTripSchedule.java | 16 ++++++------ .../_data/transit/TestCostCalculator.java | 20 +++++++++------ .../_data/transit/TestTripSchedule.java | 2 +- .../_data/transit/TestTripScheduleTest.java | 18 ++++++------- 15 files changed, 107 insertions(+), 72 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java index 404de0b2a1a..8fe949c6923 100644 --- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java @@ -40,24 +40,30 @@ public int boardingCost( } @Override - public int transitCost(int transitTime, T tripScheduledBoarded) { - return delegate.transitCost(transitTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - int cost = delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStopIndex); + int cost = delegate.transitArrivalCost( + boardCost, + alightSlack, + transitDuration, + trip, + toStopIndex + ); // This is a bit ugly, since it relies on the fact that the 'transitReluctanceFactorIndex' // returns the 'route.getMode().ordinal()' if (trip.transitReluctanceFactorIndex() == TransitMode.COACH.ordinal()) { - cost += transitTime * EXTRA_RELUCTANCE_ON_COACH; + cost += transitDuration * EXTRA_RELUCTANCE_ON_COACH; } return cost; } @@ -68,8 +74,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java index ff49eae34aa..505f52775e8 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -611,9 +611,9 @@ public String toString() { .addTime("end", legs().getLast().endTime()) .addNum("nTransfers", numberOfTransfers) .addDuration("duration", totalDuration) - .addDuration("nonTransitTime", totalStreetDuration) - .addDuration("transitTime", totalTransitDuration) - .addDuration("waitingTime", totalWaitingDuration) + .addDuration("nonTransitDuration", totalStreetDuration) + .addDuration("transitDuration", totalTransitDuration) + .addDuration("waitingDuration", totalWaitingDuration) .addObj("generalizedCost", generalizedCost) .addNum("generalizedCost2", generalizedCost2) .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 4262665d777..0921cfc4c64 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -91,19 +91,21 @@ public int boardingCost( } @Override - public int transitCost(int transitTime, T tripScheduledBoarded) { - return transitTime * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return ( + transitDuration * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()) + ); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - int cost = boardCost + transitCost(transitTime, trip) + waitFactor * alightSlack; + int cost = boardCost + transitCost(transitDuration, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. @@ -120,16 +122,20 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { if (minNumTransfers > -1) { return ( boardCostOnly + boardAndTransferCost * minNumTransfers + - transitFactors.minFactor() * minTravelTime + transitFactors.minFactor() * minTravelDuration ); } else { // Remove cost that was added during alighting similar as we do in the costEgress() method - int fixedCost = transitFactors.minFactor() * minTravelTime; + int fixedCost = transitFactors.minFactor() * minTravelDuration; return stopBoardAlightTransferCosts == null ? fixedCost diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java index d4d10a66107..e236a6f1f02 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java @@ -40,29 +40,29 @@ public int boardingCost( } @Override - public int transitCost(int transitTime, T tripScheduledBoarded) { - return delegate.transitCost(transitTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { int defaultCost = delegate.transitArrivalCost( boardCost, alightSlack, - transitTime, + transitDuration, trip, toStopIndex ); boolean includeUnpreferredCost = unpreferredPatterns.get(trip.pattern().patternIndex()); if (includeUnpreferredCost) { - int unpreferredCostValue = unpreferredCost.calculateRaptorCost(transitTime); + int unpreferredCostValue = unpreferredCost.calculateRaptorCost(transitDuration); return defaultCost + unpreferredCostValue; } else { return defaultCost; @@ -75,8 +75,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java index 07a45197733..05715182e99 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java @@ -44,19 +44,19 @@ public int boardingCost( } @Override - public int transitCost(int transitTime, T tripScheduledBoarded) { - return delegate.transitCost(transitTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - return delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStopIndex); + return delegate.transitArrivalCost(boardCost, alightSlack, transitDuration, trip, toStopIndex); } @Override @@ -65,8 +65,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java index ef92fc29c92..8650e7a5034 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java @@ -112,7 +112,7 @@ public int tripSortIndex() { public abstract int departure(int stopPosInPattern); @Override - public int relativeTravelTime(int boardTime) { + public int relativeTravelDuration(int boardTime) { return offset - boardTime; } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java index d11065f4c5d..b9f1a18ad2c 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java @@ -51,10 +51,10 @@ public int departure(int stopPosInPattern) { } @Override - public int relativeTravelTime(int boardTime) { + public int relativeTravelDuration(int boardTime) { // sortIndex is the arrival time at stop 0, which is fixed per trip. Since all trips in the // same pattern have the same travel-time structure, (sortIndex - boardTime) satisfies both - // invariants of relativeTravelTime: the difference between two boardings equals the actual + // invariants of relativeTravelDuration: the difference between two boardings equals the actual // transit time between the stops, and the value is identical across trips for the same // boarding position. return sortIndex - boardTime; diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java index 9c229698586..a1c496509b2 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java @@ -81,12 +81,12 @@ public MinSafeTransferTimeCalculator(RaptorSlackProvider slackProvider) { */ public static int minSafeTransferTimeOp( Collection list, - ToIntFunction transitTimeSeconds + ToIntFunction transitDurationSeconds ) { if (list.isEmpty()) { return MIN_SAFE_TRANSFER_TIME_LIMIT_UPPER_BOUND; } - int minTransitTime = list.stream().mapToInt(transitTimeSeconds).min().getAsInt(); + int minTransitTime = list.stream().mapToInt(transitDurationSeconds).min().getAsInt(); int minSafeTransitTime = IntUtils.round((minTransitTime * P) / 100.0); return bound( minSafeTransitTime, diff --git a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java index ce918d168f8..0a67e3be719 100644 --- a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java +++ b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java @@ -84,7 +84,7 @@ public int departure(int stopPosInPattern) { } @Override - public int relativeTravelTime(int boardTime) { + public int relativeTravelDuration(int boardTime) { return arrivalTimes[arrivalTimes.length - 1] - boardTime; } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java index 15bba912468..860d132e622 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java @@ -252,6 +252,6 @@ private int calculateCostAtBoardTime( * pattern. */ private int calculateOnTripRelativeCost(int boardTime, T tripSchedule) { - return c1Calculator.transitCost(tripSchedule.relativeTravelTime(boardTime), tripSchedule); + return c1Calculator.transitCost(tripSchedule.relativeTravelDuration(boardTime), tripSchedule); } } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java index 72a239a1477..2544c445734 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java @@ -31,28 +31,35 @@ int boardingCost( ); /** - * Calculate the cost of riding a trip for the given {@code transitTime}. + * Calculate the cost of riding a trip for the given {@code transitDuration} in seconds. */ - int transitCost(int transitTime, T tripScheduledBoarded); + int transitCost(int transitDuration, T tripScheduledBoarded); /** * Calculate the value when arriving by transit. */ - int transitArrivalCost(int boardCost, int alightSlack, int transitTime, T trip, int toStopIndex); + int transitArrivalCost( + int boardCost, + int alightSlack, + int transitDuration, + T trip, + int toStopIndex + ); /** - * Calculate the value, when waiting between the last transit and egress paths + * Calculate the cost of waiting, when waiting between two transit legs. The wait duration is + * in seconds, and include board and alight slack. */ - int waitCost(int waitTimeInSeconds); + int waitCost(int waitDuration); /** * Used for estimating the remaining value for a criteria at a given stop arrival. The calculated * value should be an optimistic estimate for the heuristics to work properly. So, to calculate - * the generalized cost for given the {@code minTravelTime} and {@code minNumTransfers} returning - * the greatest value, which is guaranteed to be less than the - * real value would be correct and a good choice. + * the generalized cost for given the {@code minTravelDuration} and {@code minNumTransfers} + * returning the greatest value, which is guaranteed to be less than the real value + * would be correct and a good choice. */ - int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex); + int calculateRemainingMinCost(int minTravelDuration, int minNumTransfers, int fromStopIndex); /** * This method allows the cost calculator to add cost in addition to the generalized-cost of the diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java index d96dd51eb84..d62c4cc7ab6 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java @@ -57,23 +57,23 @@ default int departure(int startStopPos, int stopIndex) { return departure(pattern().findStopPositionAfter(startStopPos, stopIndex)); } - /// The relative-travel-time is a proxy for time spent on transit from the boarding stop to the - /// last stop in the pattern. We do not know the alight-stop, so it is impossible to calculate + /// The relative-travel-duration is a proxy for time spent on transit from the boarding stop to + /// the last stop in the pattern. We do not know the alight-stop, so it is impossible to calculate /// the "correct" time. But the only thing that matters is that the relative difference between /// two boardings is correct. Compute a "relative-time" which can be used to compare the /// travel-time cost for any two boardings in the same pattern. /// /// Two invariants must hold: /// - /// - For two boardings at stops i and j on the same trip (where i comes before j in the - /// pattern), `relativeTravelTime(boardAtI) - relativeTravelTime(boardAtJ)` must equal the - /// actual transit time from stop i to stop j. If the board time at position 3 is 10:00 and at - /// the next stop is 10:05, then the value at position 3 is larger by 5*60s = 300 than at the - /// next stop. + /// - For two boardings at stops `i` and `j` on the same trip (where `i` comes before `j` in the + /// pattern), `relativeTravelDuration(boardAtI) - relativeTravelDuration(boardAtJ)` must equal + /// the actual transit duration from stop `i` to stop `j`. If the board time at position 3 is + /// 10:00 and at the next stop is 10:05, then the value at position 3 is larger by 5*60s = 300 + /// than at the next stop. /// - All trips in the same pattern must return the same value for the last arrival stop. If the /// value for one trip at the last stop is 56_000, then it must be 56_000 for the last stop in /// all other trips in the same pattern. - int relativeTravelTime(int boardTime); + int relativeTravelDuration(int boardTime); /** * Return the pattern for this trip. diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java index df04e3d656e..93a8485503b 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java @@ -68,19 +68,19 @@ public int boardingCost( } @Override - public int transitCost(int transitTime, TestTripSchedule tripScheduledBoarded) { - return transitTime * TRANSIT_RELUCTANCE; + public int transitCost(int transitDuration, TestTripSchedule tripScheduledBoarded) { + return transitDuration * TRANSIT_RELUCTANCE; } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, TestTripSchedule trip, int toStopIndex ) { - int cost = boardCost + transitCost(transitTime, trip) + waitFactor * alightSlack; + int cost = boardCost + transitCost(transitDuration, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. @@ -97,18 +97,22 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { if (minNumTransfers > -1) { return ( boardCost + ((boardCost + transferCost) * minNumTransfers) + - (TRANSIT_RELUCTANCE * minTravelTime) + (TRANSIT_RELUCTANCE * minTravelDuration) ); } else { // Remove cost that was added during alighting similar as we do in the costEgress() method return stopBoardAlightTransferCosts == null - ? (TRANSIT_RELUCTANCE * minTravelTime) - : (TRANSIT_RELUCTANCE * minTravelTime) - stopBoardAlightTransferCosts[fromStopIndex]; + ? (TRANSIT_RELUCTANCE * minTravelDuration) + : (TRANSIT_RELUCTANCE * minTravelDuration) - stopBoardAlightTransferCosts[fromStopIndex]; } } diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java index 54604dcb2c7..6a08cbe7480 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java @@ -53,7 +53,7 @@ public int departure(int stopPosInPattern) { } @Override - public int relativeTravelTime(int boardTime) { + public int relativeTravelDuration(int boardTime) { return arrivalTimes[arrivalTimes.length - 1] - boardTime; } diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java index db26dadf423..4b5d7ec9f35 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.Test; /** - * Verify that {@link TestTripSchedule} satisfies the {@code relativeTravelTime} contract defined - * in {@link org.opentripplanner.raptor.spi.RaptorTripSchedule#relativeTravelTime(int)}. + * Verify that {@link TestTripSchedule} satisfies the {@code relativeTravelDuration} contract defined + * in {@link org.opentripplanner.raptor.spi.RaptorTripSchedule#relativeTravelDuration(int)}. */ class TestTripScheduleTest { @@ -34,23 +34,23 @@ void relativeTravelTimeDecreasesByActualTransitTimeBetweenStops() { assertEquals( actualTimeBetweenAAndB, - trip1.relativeTravelTime(boardAtA) - trip1.relativeTravelTime(boardAtB) + trip1.relativeTravelDuration(boardAtA) - trip1.relativeTravelDuration(boardAtB) ); } @Test void relativeTravelTimeIsIdenticalAcrossTripsInTheSamePatternForEquivalentBoardingPositions() { assertEquals( - trip1.relativeTravelTime(trip1.departure(0)), - trip2.relativeTravelTime(trip2.departure(0)) + trip1.relativeTravelDuration(trip1.departure(0)), + trip2.relativeTravelDuration(trip2.departure(0)) ); assertEquals( - trip1.relativeTravelTime(trip1.departure(1)), - trip2.relativeTravelTime(trip2.departure(1)) + trip1.relativeTravelDuration(trip1.departure(1)), + trip2.relativeTravelDuration(trip2.departure(1)) ); assertEquals( - trip1.relativeTravelTime(trip1.departure(2)), - trip2.relativeTravelTime(trip2.departure(2)) + trip1.relativeTravelDuration(trip1.departure(2)), + trip2.relativeTravelDuration(trip2.departure(2)) ); } } From 96ddd93063f5d5ad57713b4d19243b04974ac089 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 14 Apr 2026 21:24:09 +0300 Subject: [PATCH 042/177] Use equals for all stop comparisons in DirectTransferGenerator --- .../module/transfer/DirectTransferGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java index 4ea59286d85..ae953f0a948 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java @@ -419,7 +419,7 @@ private void calculateFlexTransfers( for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. var nearbyStop = repository.getStopLocation(sd.stopId); - if (nearbyStop == stop) { + if (nearbyStop.equals(stop)) { continue; } if (nearbyStop instanceof RegularStop) { @@ -495,7 +495,7 @@ private void calculateTransfersForStopWithAllowedStops( for (NearbyStop sd : nearbyStops) { var nearbyStop = repository.getStopLocation(sd.stopId); // Skip the origin stop, loop transfers are not needed. - if (nearbyStop == stop) { + if (nearbyStop.equals(stop)) { continue; } if (nearbyStop.transfersNotAllowed()) { From d1b3a593c4b06054ec29551ccd5e74412d1ebd6c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 11:08:34 +0200 Subject: [PATCH 043/177] Remove GraphVisualizer # Conflicts: # application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java # application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java # application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java # application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java --- application/pom.xml | 6 - .../startup/OptionsView.java | 11 +- .../startup/StartupModel.java | 14 - .../router/street/DirectStreetRouter.java | 1 - .../routing/impl/GraphPathFinder.java | 16 +- .../opentripplanner/standalone/OTPMain.java | 5 - .../api/OtpServerRequestContext.java | 10 - .../config/CommandLineParameters.java | 6 - .../configure/ConstructApplication.java | 10 - .../ConstructApplicationFactory.java | 7 - .../configure/ConstructApplicationModule.java | 10 - .../server/DefaultServerRequestContext.java | 11 - .../visualizer/GraphVisualizer.java | 1568 ----------------- .../visualizer/RouteDialog.java | 52 - .../opentripplanner/visualizer/ShowGraph.java | 1222 ------------- .../visualizer/VertexSelectionListener.java | 11 - .../visualizer/VisualTraverseVisitor.java | 52 - .../visualizer/package-info.md | 5 - .../opentripplanner/TestServerContext.java | 1 - .../module/osm/OsmModuleTest.java | 2 +- .../integration/BarrierRoutingTest.java | 2 +- .../integration/BicycleRoutingTest.java | 2 +- .../street/integration/CarRoutingTest.java | 2 +- .../SplitEdgeTurnRestrictionsTest.java | 2 +- .../street/integration/WalkRoutingTest.java | 2 +- .../transit/speed_test/SpeedTest.java | 1 - doc/user/Frontends.md | 6 - 27 files changed, 10 insertions(+), 3027 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/package-info.md diff --git a/application/pom.xml b/application/pom.xml index e02f9d16ff3..d55f7c9c911 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -290,12 +290,6 @@ onebusaway-gtfs 11.2.2 - - - org.processing - core - 2.2.1 - net.java.dev.jets3t diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java index 08907346a76..3d52241b122 100644 --- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java +++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java @@ -16,7 +16,6 @@ class OptionsView { private final JCheckBox buildTransitGraphChk; private final JCheckBox saveGraphChk; private final JCheckBox startOptServerChk; - private final JCheckBox startOptVisualizerChk; private final StartupModel model; OptionsView(StartupModel model) { @@ -25,7 +24,6 @@ class OptionsView { this.buildTransitGraphChk = new JCheckBox("Transit graph", model.isBuildTransit()); this.saveGraphChk = new JCheckBox("Save graph", model.isSaveGraph()); this.startOptServerChk = new JCheckBox("Serve graph", model.isServeGraph()); - this.startOptVisualizerChk = new JCheckBox("Visualizer", model.isVisualizer()); panel.add(Box.createGlue()); addComp(createBuildBox(), panel); @@ -58,7 +56,6 @@ private JComponent createActionBox() { addVerticalSectionSpace(actionBox); addComp(saveGraphChk, actionBox); addComp(startOptServerChk, actionBox); - addComp(startOptVisualizerChk, actionBox); return actionBox; } @@ -79,7 +76,6 @@ private void bindCheckBoxesToModel() { bind(buildTransitGraphChk, model::setBuildTransit); bind(saveGraphChk, model::setSaveGraph); bind(startOptServerChk, model::setServeGraph); - bind(startOptVisualizerChk, model::setVisualizer); } private boolean buildStreet() { @@ -93,12 +89,7 @@ private boolean buildTransit() { private void onBuildGraphChkChanged() { saveGraphChk.setEnabled(buildStreet() || buildTransit()); startOptServerChk.setEnabled(buildTransit() || !buildStreet()); - startOptVisualizerChk.setEnabled(buildTransit() || !buildStreet()); } - private void onStartOptServerChkChanged() { - startOptVisualizerChk.setEnabled( - startOptServerChk.isEnabled() && startOptServerChk.isSelected() - ); - } + private void onStartOptServerChkChanged() {} } diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java index 7416b4a14d0..d9dde7201ea 100644 --- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java +++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java @@ -22,7 +22,6 @@ public class StartupModel { private boolean buildTransit = true; private boolean saveGraph = false; private boolean serveGraph = true; - private boolean visualizer = false; public void subscribeCmdLineUpdates(Consumer commandLineChange) { this.commandLineChange = commandLineChange; @@ -108,15 +107,6 @@ public void setServeGraph(boolean serveGraph) { notifyChangeListener(); } - public boolean isVisualizer() { - return visualizer; - } - - public void setVisualizer(boolean visualizer) { - this.visualizer = visualizer; - notifyChangeListener(); - } - @Override public String toString() { return String.join("", asOtpArgs()); @@ -159,10 +149,6 @@ public String[] asOtpArgs() { if (serveGraph && !buildStreetOnly()) { args.add("--serve"); } - if (serveGraph && !buildStreetOnly() && visualizer) { - args.add("--visualize"); - } - args.add(getDataSourceDirectory()); return args.toArray(new String[0]); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java index 71912b676c1..3abd84dbec6 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java @@ -40,7 +40,6 @@ public static List route( // we could also get a persistent router-scoped GraphPathFinder but there's no setup cost here GraphPathFinder gpFinder = new GraphPathFinder( - serverContext.traverseVisitor(), serverContext.listExtensionRequestContexts(request), maxCarSpeed ); diff --git a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index 7959869bd4d..fd39e783543 100644 --- a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -6,6 +6,7 @@ import java.util.Set; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; +import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.routing.api.request.RouteRequest; @@ -51,23 +52,18 @@ public class GraphPathFinder { private static final Logger LOG = LoggerFactory.getLogger(GraphPathFinder.class); - @Nullable - private final TraverseVisitor traverseVisitor; - private final Collection extensionRequestContexts; private final float maxCarSpeed; - public GraphPathFinder(@Nullable TraverseVisitor traverseVisitor) { - this(traverseVisitor, List.of(), StreetConstants.DEFAULT_MAX_CAR_SPEED); + public GraphPathFinder() { + this(List.of(), StreetConstants.DEFAULT_MAX_CAR_SPEED); } public GraphPathFinder( - @Nullable TraverseVisitor traverseVisitor, Collection extensionRequestContexts, float maxCarSpeed ) { - this.traverseVisitor = traverseVisitor; this.extensionRequestContexts = Objects.requireNonNull(extensionRequestContexts); this.maxCarSpeed = maxCarSpeed; } @@ -98,12 +94,6 @@ public List getPaths(RouteRequest request, Set from, Set traverseVisitor(); - default GraphFinder graphFinder() { return GraphFinder.getInstance( graph().hasStreets, diff --git a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java index 8b84a3e9f5a..101bbeaee7d 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java @@ -127,12 +127,6 @@ public class CommandLineParameters { ) public Integer port = DEFAULT_PORT; - @Parameter( - names = { "--visualize" }, - description = "Open a graph visualizer window for debugging." - ) - public boolean visualize; - @Parameter( names = { "--abortOnUnknownConfig" }, description = "Abort the startup if configuration files are found to contain unknown parameters." diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 73cc75f1a1c..de0d61c5537 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -44,7 +44,6 @@ import org.opentripplanner.updater.configure.UpdaterConfigurator; import org.opentripplanner.updater.trip.TimetableSnapshotManager; import org.opentripplanner.utils.logging.ProgressTracker; -import org.opentripplanner.visualizer.GraphVisualizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,10 +101,6 @@ public class ConstructApplication { this.graphBuilderDataSources = graphBuilderDataSources; this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository; - // We create the optional GraphVisualizer here, because it would be significant more complex to - // use Dagger DI to do it - passing in a parameter to enable it or not. - var graphVisualizer = cli.visualize ? new GraphVisualizer(graph) : null; - ConstructApplicationFactory.Builder builder = DaggerConstructApplicationFactory.builder(); this.factory = builder .configModel(config) @@ -113,7 +108,6 @@ public class ConstructApplication { .streetDetailsRepository(streetDetailsRepository) .timetableRepository(timetableRepository) .transferRepository(transferRepository) - .graphVisualizer(graphVisualizer) .worldEnvelopeRepository(worldEnvelopeRepository) .vehicleParkingRepository(vehicleParkingRepository) .emissionRepository(emissionRepository) @@ -364,10 +358,6 @@ public RaptorConfig raptorConfig() { return factory.raptorConfig(); } - public GraphVisualizer graphVisualizer() { - return factory.graphVisualizer(); - } - private OtpServerRequestContext createServerContext() { return factory.createServerContext(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 26f082ccced..343e8cda451 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -66,7 +66,6 @@ import org.opentripplanner.transit.service.TimetableRepository; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.trip.TimetableSnapshotManager; -import org.opentripplanner.visualizer.GraphVisualizer; /** * A Factory used by the Dagger dependency injection system to create the components of OTP, which @@ -136,9 +135,6 @@ public interface ConstructApplicationFactory { @Nullable EmpiricalDelayRepository empiricalDelayRepository(); - @Nullable - GraphVisualizer graphVisualizer(); - TransitService transitService(); OtpServerRequestContext createServerContext(); @@ -184,9 +180,6 @@ interface Builder { @BindsInstance Builder transferRepository(TransferRepository transferRepository); - @BindsInstance - Builder graphVisualizer(@Nullable GraphVisualizer graphVisualizer); - @BindsInstance Builder worldEnvelopeRepository(WorldEnvelopeRepository worldEnvelopeRepository); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index c27861705fa..54316995b58 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -9,7 +9,6 @@ import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.configure.GtfsSchema; import org.opentripplanner.apis.transmodel.configure.TransmodelSchema; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; import org.opentripplanner.ext.empiricaldelay.EmpiricalDelayService; @@ -40,7 +39,6 @@ import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transfer.regular.RegularTransferService; import org.opentripplanner.transit.service.TransitService; -import org.opentripplanner.visualizer.GraphVisualizer; @Module public class ConstructApplicationModule { @@ -65,7 +63,6 @@ OtpServerRequestContext providesServerContext( @Nullable DataOverlayParameterBindings dataOverlayParameterBindings, @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, - @Nullable TraverseVisitor traverseVisitor, @Nullable @EmissionDecorator ItineraryDecorator emissionItineraryDecorator, StreetDetailsService streetDetailsService, @Nullable @GtfsSchema GraphQLSchema gtfsSchema, @@ -121,7 +118,6 @@ OtpServerRequestContext providesServerContext( transmodelSchema, sorlandsbanenService, stopConsolidationService, - traverseVisitor, transmodelAPIParameters ); } @@ -131,10 +127,4 @@ OtpServerRequestContext providesServerContext( public FareService fareService(FareServiceFactory fareServiceFactory) { return fareServiceFactory.makeFareService(); } - - @Provides - @Nullable - TraverseVisitor traverseVisitor(@Nullable GraphVisualizer graphVisualizer) { - return graphVisualizer == null ? null : graphVisualizer.traverseVisitor; - } } diff --git a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 99cb7d852da..a3d2bb8839b 100644 --- a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -8,7 +8,6 @@ import org.opentripplanner.apis.gtfs.configure.GtfsSchema; import org.opentripplanner.apis.transmodel.TransmodelAPIParameters; import org.opentripplanner.apis.transmodel.configure.TransmodelSchema; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; import org.opentripplanner.ext.empiricaldelay.EmpiricalDelayService; @@ -103,9 +102,6 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { @Nullable private final StopConsolidationService stopConsolidationService; - @Nullable - private final TraverseVisitor traverseVisitor; - private final TriasApiParameters triasApiParameters; private final OjpApiParameters ojpApiParameters; @@ -159,7 +155,6 @@ public DefaultServerRequestContext( @Nullable @TransmodelSchema GraphQLSchema transmodelSchema, @Nullable SorlandsbanenNorwayService sorlandsbanenService, @Nullable StopConsolidationService stopConsolidationService, - @Nullable TraverseVisitor traverseVisitor, TransmodelAPIParameters transmodelAPIParameters ) { this.debugUiConfig = debugUiConfig; @@ -197,7 +192,6 @@ public DefaultServerRequestContext( this.gtfsSchema = gtfsSchema; this.sorlandsbanenService = sorlandsbanenService; this.stopConsolidationService = stopConsolidationService; - this.traverseVisitor = traverseVisitor; this.transmodelAPIParameters = transmodelAPIParameters; } @@ -298,11 +292,6 @@ public MeterRegistry meterRegistry() { return meterRegistry; } - @Override - public TraverseVisitor traverseVisitor() { - return traverseVisitor; - } - @Override public FlexParameters flexParameters() { return flexParameters; diff --git a/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java b/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java index 6e7aba5082c..e69de29bb2d 100644 --- a/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java +++ b/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java @@ -1,1568 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import javax.swing.AbstractListModel; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.DefaultListCellRenderer; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.JTextField; -import javax.swing.ListModel; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingUtilities; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import org.locationtech.jts.geom.Coordinate; -import org.opentripplanner.api.common.LocationStringParser; -import org.opentripplanner.api.parameter.ApiRequestMode; -import org.opentripplanner.api.parameter.QualifiedModeSet; -import org.opentripplanner.astar.model.ShortestPathTree; -import org.opentripplanner.astar.spi.DominanceFunction; -import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.RouteRequestBuilder; -import org.opentripplanner.routing.impl.GraphPathFinder; -import org.opentripplanner.routing.linking.LinkingContextFactory; -import org.opentripplanner.routing.linking.internal.VertexCreationService; -import org.opentripplanner.routing.linking.mapping.LinkingContextRequestMapper; -import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.street.linking.TemporaryVerticesContainer; -import org.opentripplanner.street.linking.VertexLinker; -import org.opentripplanner.street.linking.VisibilityMode; -import org.opentripplanner.street.model.StreetConstants; -import org.opentripplanner.street.model.VehicleRoutingOptimizeType; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.path.StreetPath; -import org.opentripplanner.street.model.vertex.IntersectionVertex; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.model.vertex.VertexLabel; -import org.opentripplanner.street.search.state.State; -import org.opentripplanner.street.search.strategy.DominanceFunctions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Exit on window close. - */ -class ExitListener extends WindowAdapter { - - public void windowClosing(WindowEvent event) { - System.exit(0); - } -} - -/** - * DisplayVertex holds a vertex, but has a toString value that's a little more useful. - */ -class DisplayVertex { - - public Vertex vertex; - - public DisplayVertex(Vertex v) { - vertex = v; - } - - public String toString() { - String label = vertex.getLabelString(); - if (label.contains("osm node")) { - label = vertex.getDefaultName(); - } - return label; - } -} - -/** - * This is a ListModel that holds Edges. It gets its edges from a PatternBoard/PatternAlight, hence - * the iterable. - */ -class EdgeListModel extends AbstractListModel { - - private final ArrayList edges; - - EdgeListModel(Iterable edges) { - this.edges = new ArrayList<>(); - for (Edge e : edges) { - this.edges.add(e); - } - } - - public int getSize() { - return edges.size(); - } - - public Edge getElementAt(int index) { - return edges.get(index); - } -} - -/** - * A list of vertices where the internal container is exposed. - */ -class VertexList extends AbstractListModel { - - public List selected; - - VertexList(List selected) { - this.selected = selected; - } - - public int getSize() { - return selected.size(); - } - - public DisplayVertex getElementAt(int index) { - return new DisplayVertex(selected.get(index)); - } -} - -/** - * A simple visualizer for graphs. It shows (using ShowGraph) a map of the graph, intersections and - * TransitStops only, and allows a user to select stops, examine incoming and outgoing edges, and - * examine trip patterns. It's meant mainly for debugging, so it's totally OK if it develops (say) a - * bunch of weird buttons designed to debug specific cases. - *

- * 2024-01-26: We talked about the visualizer in the developer meeting and while the code is a bit - * dusty, we decided that we want to keep the option open to build make the visualization of routing - * steps work again in the future and won't delete it. - */ -public class GraphVisualizer extends JFrame implements VertexSelectionListener { - - private static final Logger LOG = LoggerFactory.getLogger(GraphVisualizer.class); - private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern( - "yyyy-MM-dd HH:mm:ss z" - ); - public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss z"); - - /* The graph from the router we are visualizing, note that it will not be updated if the router reloads. */ - private final Graph graph; - private JPanel leftPanel; - - /* The Processing applet that actually displays the graph. */ - private ShowGraph showGraph; - - /* The set of callbacks that display search progress on the showGraph Processing applet. */ - public TraverseVisitor traverseVisitor; - - public JList nearbyVertices; - - private JList outgoingEdges; - - private JList incomingEdges; - - private JTextField sourceVertex; - - private JTextField sinkVertex; - - private JCheckBox walkCheckBox; - - private JCheckBox bikeCheckBox; - - private JCheckBox trainCheckBox; - - private JCheckBox busCheckBox; - - private JCheckBox ferryCheckBox; - - private JCheckBox transitCheckBox; - - private JCheckBox carCheckBox; - - private JTextField searchDate; - private JTextField boardingPenaltyField; - private DefaultListModel issueMatchesModel; - private JList issueMatches; - private DefaultListModel metadataModel; - private HashSet closed; - private Vertex tracingVertex; - private HashSet open; - private HashSet seen; - private JList metadataList; - private JRadioButton opQuick; - private JRadioButton opSafe; - private JRadioButton opFlat; - private JRadioButton opGreenways; - private ButtonGroup optimizeTypeGrp; - private JTextField maxWalkField; - private JTextField walkSpeed; - private JTextField bikeSpeed; - private JTextField heuristicWeight; - private JCheckBox softWalkLimiting; - private JTextField softWalkPenalty; - private JTextField softWalkOverageRate; - private JCheckBox arriveByCheckBox; - private JLabel searchTimeElapsedLabel; - private JCheckBox dontUseGraphicalCallbackCheckBox; - private JTextField nPaths; - private JList pathsList; - private JList pathStates; - private JCheckBox showTransitCheckbox; - private JCheckBox showStreetsCheckbox; - private JCheckBox showMultistateVerticesCheckbox; - private JCheckBox showHighlightedCheckbox; - private JCheckBox showSPTCheckbox; - private ShortestPathTree spt; - private JTextField sptFlattening; - private JTextField sptThickness; - private JPopupMenu popup; - private StreetPath firstComparePath; - private StreetPath secondComparePath; - private JList firstComparePathStates; - private JList secondComparePathStates; - private JList secondStateData; - private JList firstStateData; - protected State lastStateClicked = null; - private JCheckBox longDistanceModeCheckbox; - - public GraphVisualizer(Graph graph) { - super(); - setTitle("GraphVisualizer"); - setExtendedState(JFrame.MAXIMIZED_BOTH); - this.graph = graph; - } - - public void run() { - LOG.info("Starting up graph visualizer..."); - this.init(); - this.setVisible(true); - } - - public void init() { - final JTabbedPane tabbedPane = new JTabbedPane(); - - final Container mainTab = makeMainTab(); - Container prefsPanel = makePrefsPanel(); - Container diffTab = makeDiffTab(); - - tabbedPane.addTab("Main", null, mainTab, "Pretty much everything"); - - tabbedPane.addTab("Prefs", null, prefsPanel, "Routing preferences"); - - tabbedPane.addTab("Diff", null, diffTab, "multistate path diffs"); - - //Add the tabbed pane to this panel. - add(tabbedPane); - - //The following line enables to use scrolling tabs. - tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - - // startup the graphical pane; ensure closing works; draw the window - showGraph.init(); - addWindowListener(new ExitListener()); - pack(); - - // make sure the showGraph quits drawing when we switch tabs - tabbedPane.addChangeListener( - new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (tabbedPane.getSelectedComponent().equals(mainTab)) { - showGraph.loop(); - } else { - showGraph.noLoop(); - } - } - } - ); - } - - public void verticesSelected(final List selected) { - // sort vertices by name - Collections.sort( - selected, - new Comparator<>() { - @Override - public int compare(Vertex arg0, Vertex arg1) { - return arg0.getLabelString().compareTo(arg1.getLabelString()); - } - } - ); - ListModel data = new VertexList(selected); - nearbyVertices.setModel(data); - - // pick out an intersection vertex and find the path - // if the spt is already available - Vertex target = null; - for (Vertex vv : selected) { - if (vv instanceof IntersectionVertex) { - target = vv; - break; - } - } - if (target != null && spt != null) { - var paths = spt.getPaths(target).stream().map(StreetPath::new).toList(); - showPathsInPanel(paths); - } - } - - public Graph getGraph() { - return graph; - } - - protected JComponent makeTextPanel(String text) { - JPanel panel = new JPanel(false); - JLabel filler = new JLabel(text); - filler.setHorizontalAlignment(JLabel.CENTER); - panel.setLayout(new GridLayout(1, 1)); - panel.add(filler); - return panel; - } - - protected void trace() { - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected == null) { - return; - } - Vertex v = selected.vertex; - - if (tracingVertex != v) { - tracingVertex = v; - closed = new HashSet<>(); - open = new HashSet<>(); - open.add(v); - seen = new HashSet<>(); - } - HashSet newOpen = new HashSet<>(); - for (Vertex v2 : open) { - closed.add(v2); - for (Edge e : v2.getOutgoing()) { - Vertex target = e.getToVertex(); - if (closed.contains(target)) { - continue; - } - newOpen.add(target); - } - } - seen.addAll(newOpen); - open = newOpen; - showGraph.setHighlightedVertices(seen); - } - - protected void traceOld() { - HashSet seenVertices = new HashSet<>(); - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected == null) { - System.out.println("no vertex selected"); - return; - } - Vertex v = selected.vertex; - System.out.println("initial vertex: " + v); - Queue toExplore = new LinkedList<>(); - toExplore.add(v); - seenVertices.add(v); - while (!toExplore.isEmpty()) { - Vertex src = toExplore.poll(); - for (Edge e : src.getOutgoing()) { - Vertex tov = e.getToVertex(); - if (!seenVertices.contains(tov)) { - seenVertices.add(tov); - toExplore.add(tov); - } - } - } - showGraph.setHighlightedVertices(seenVertices); - } - - protected void checkGraph() { - HashSet seenVertices = new HashSet<>(); - Collection allVertices = getGraph().getVertices(); - Vertex v = allVertices.iterator().next(); - System.out.println("initial vertex: " + v); - Queue toExplore = new LinkedList<>(); - toExplore.add(v); - seenVertices.add(v); - while (!toExplore.isEmpty()) { - Vertex src = toExplore.poll(); - for (Edge e : src.getOutgoing()) { - Vertex tov = e.getToVertex(); - if (!seenVertices.contains(tov)) { - seenVertices.add(tov); - toExplore.add(tov); - } - } - } - - System.out.println( - "After investigation, visited " + seenVertices.size() + " of " + allVertices.size() - ); - - /* now, let's find an unvisited vertex */ - for (Vertex u : allVertices) { - if (!seenVertices.contains(u)) { - System.out.println("unvisited vertex" + u); - break; - } - } - } - - protected void route(String from, String to) { - Instant when; - // Year + 1900 - try { - when = ZonedDateTime.parse(searchDate.getText(), DATE_FORMAT).toInstant(); - } catch (DateTimeParseException e) { - searchDate.setText("Format: " + DATE_FORMAT.toString()); - return; - } - List modes = new ArrayList<>(); - if (walkCheckBox.isSelected()) { - modes.add(ApiRequestMode.WALK.name()); - } - if (bikeCheckBox.isSelected()) { - modes.add(ApiRequestMode.BICYCLE.name()); - } - if (carCheckBox.isSelected()) { - modes.add(ApiRequestMode.CAR.name()); - } - if (ferryCheckBox.isSelected()) { - modes.add(ApiRequestMode.FERRY.name()); - } - if (trainCheckBox.isSelected()) { - modes.add(ApiRequestMode.RAIL.name()); - modes.add(ApiRequestMode.TRAM.name()); - modes.add(ApiRequestMode.SUBWAY.name()); - modes.add(ApiRequestMode.FUNICULAR.name()); - modes.add(ApiRequestMode.GONDOLA.name()); - } - if (busCheckBox.isSelected()) { - modes.add(ApiRequestMode.BUS.name()); - modes.add(ApiRequestMode.CABLE_CAR.name()); - } - if (transitCheckBox.isSelected()) { - modes.add(ApiRequestMode.TRANSIT.name()); - } - - // TODO: This should use the configured defaults, not the code defaults - RouteRequestBuilder builder = RouteRequest.of(); - QualifiedModeSet qualifiedModeSet = new QualifiedModeSet(modes.toArray(String[]::new)); - builder.withJourney(b -> b.withModes(qualifiedModeSet.getRequestModes())); - - builder.withArriveBy(arriveByCheckBox.isSelected()); - builder.withDateTime(when); - builder.withFrom(LocationStringParser.fromOldStyleString(from)); - builder.withTo(LocationStringParser.fromOldStyleString(to)); - builder.withNumItineraries(Integer.parseInt(this.nPaths.getText())); - - builder.withPreferences(preferences -> { - preferences.withWalk(walk -> { - // override low 2-4 minute values - walk.withBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60); - walk.withSpeed(Float.parseFloat(walkSpeed.getText())); - }); - preferences.withBike(bike -> - bike - .withSpeed(Float.parseFloat(bikeSpeed.getText())) - // TODO LG Add ui element for bike board cost (for now bike = 2 * walk) - .withBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60 * 2) - // there should be a ui element for walk distance and optimize type - .withOptimizeType(getSelectedOptimizeType()) - ); - preferences.withScooter(scooter -> - scooter - .withSpeed(Float.parseFloat(bikeSpeed.getText())) - // there should be a ui element for walk distance and optimize type - .withOptimizeType(getSelectedOptimizeType()) - ); - }); - - // apply callback if the options call for it - // if( dontUseGraphicalCallbackCheckBox.isSelected() ){ - // TODO perhaps avoid using a GraphPathFinder and go one level down the call chain directly to a GenericAStar - // TODO perhaps instead of giving the pathservice a callback, we can just put the visitor in the routing request - GraphPathFinder finder = new GraphPathFinder(traverseVisitor); - - var request = builder.buildRequest(); - long t0 = System.currentTimeMillis(); - // TODO: check options properly intialized (AMB) - try (var temporaryVerticesContainer = new TemporaryVerticesContainer()) { - var linkingContextFactory = new LinkingContextFactory( - graph, - new VertexCreationService( - new VertexLinker( - graph, - VisibilityMode.TRAVERSE_AREA_EDGES, - StreetConstants.DEFAULT_MAX_AREA_NODES, - true - ) - ) - ); - var linkingRequest = LinkingContextRequestMapper.map(request); - var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - List paths = finder.graphPathFinderEntryPoint(request, linkingContext); - long dt = System.currentTimeMillis() - t0; - searchTimeElapsedLabel.setText("search time elapsed: " + dt + "ms"); - - // grab the spt from the visitor - // TODO somehow yank the SPT out of the depths of the call stack... but there multiple SPTs here. - // This is why we should probably just use AStar directly. - /* - spt = vis.spt; - showGraph.setSPT(spt); - System.out.println( "got spt:"+spt ); - */ - - if (paths == null) { - System.out.println("no path"); - showGraph.highlightStreetPath(null); - return; - } - - // now's a convenient time to set graphical SPT weights - showGraph.simpleSPT.setWeights(); - - showPathsInPanel(paths); - - // now's a good time to set showGraph's SPT drawing weights - showGraph.setSPTFlattening(Float.parseFloat(sptFlattening.getText())); - showGraph.setSPTThickness(Float.parseFloat(sptThickness.getText())); - showGraph.redraw(); - } - } - - VehicleRoutingOptimizeType getSelectedOptimizeType() { - if (opQuick.isSelected()) { - return VehicleRoutingOptimizeType.SHORTEST_DURATION; - } - if (opSafe.isSelected()) { - return VehicleRoutingOptimizeType.SAFE_STREETS; - } - if (opFlat.isSelected()) { - return VehicleRoutingOptimizeType.FLAT_STREETS; - } - if (opGreenways.isSelected()) { - return VehicleRoutingOptimizeType.SAFEST_STREETS; - } - return VehicleRoutingOptimizeType.SHORTEST_DURATION; - } - - private Container makeDiffTab() { - JPanel pane = new JPanel(); - pane.setLayout(new GridLayout(0, 2)); - - firstStateData = new JList<>(); - secondStateData = new JList<>(); - - // a place to list the states of the first path - firstComparePathStates = new JList<>(); - JScrollPane stScrollPane = new JScrollPane(firstComparePathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - firstComparePathStates.addListSelectionListener( - new ComparePathStatesClickListener(firstStateData) - ); - - // a place to list the states of the second path - secondComparePathStates = new JList<>(); - stScrollPane = new JScrollPane(secondComparePathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - secondComparePathStates.addListSelectionListener( - new ComparePathStatesClickListener(secondStateData) - ); - - // a place to list details of a state selected from the first path - stScrollPane = new JScrollPane(firstStateData); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - - // a place to list details of a state selected from the second path - stScrollPane = new JScrollPane(secondStateData); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - - // A button that executes the 'dominates' function between the two states - // this is useful only if you have a breakpoint set up - JButton dominateButton = new JButton(); - dominateButton.setText("dominates"); - dominateButton.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - State s1 = firstComparePathStates.getSelectedValue(); - State s2 = secondComparePathStates.getSelectedValue(); - DominanceFunction pareto = new DominanceFunctions.Pareto(); - System.out.println("s1 dominates s2:" + pareto.betterOrEqualAndComparable(s1, s2)); - System.out.println("s2 dominates s1:" + pareto.betterOrEqualAndComparable(s2, s1)); - } - } - ); - pane.add(dominateButton); - - // A button that executes the 'traverse' function leading to the last clicked state - // in either window. Also only useful if you set a breakpoint. - JButton traverseButton = new JButton(); - traverseButton.setText("traverse"); - traverseButton.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (lastStateClicked == null) { - return; - } - - Edge backEdge = lastStateClicked.getBackEdge(); - State backState = lastStateClicked.getBackState(); - - backEdge.traverse(backState); - } - } - ); - pane.add(traverseButton); - - return pane; - } - - private Container makeMainTab() { - Container pane = new JPanel(); - pane.setLayout(new BorderLayout()); - - // init center graphical panel - showGraph = new ShowGraph(this, getGraph()); - pane.add(showGraph, BorderLayout.CENTER); - traverseVisitor = new VisualTraverseVisitor(showGraph); - - // init left panel - leftPanel = new JPanel(); - leftPanel.setLayout(new BorderLayout()); - - pane.add(leftPanel, BorderLayout.LINE_START); - - initRoutingSubpanel(); - initVertexInfoSubpanel(); - initControlButtons(); - - // init right panel - initRightPanel(pane); - return pane; - } - - private JComponent makePrefsPanel() { - JPanel pane = new JPanel(); - pane.setLayout(new GridLayout(0, 2)); - - // 4 rows (7 elements): transport mode options - walkCheckBox = new JCheckBox("walk"); - walkCheckBox.setSelected(true); - pane.add(walkCheckBox); - bikeCheckBox = new JCheckBox("bike"); - pane.add(bikeCheckBox); - trainCheckBox = new JCheckBox("trainish"); - pane.add(trainCheckBox); - busCheckBox = new JCheckBox("busish"); - pane.add(busCheckBox); - ferryCheckBox = new JCheckBox("ferry"); - pane.add(ferryCheckBox); - transitCheckBox = new JCheckBox("transit"); - transitCheckBox.setSelected(true); - pane.add(transitCheckBox); - carCheckBox = new JCheckBox("car"); - pane.add(carCheckBox); - - // GridLayout does not support empty cells, so a dummy label is used to fix the layout. - JLabel dummyLabel = new JLabel(""); - pane.add(dummyLabel); - - // row: arrive by? - JLabel arriveByLabel = new JLabel("Arrive by?:"); - pane.add(arriveByLabel); - arriveByCheckBox = new JCheckBox("arrive by"); - pane.add(arriveByCheckBox); - - // row: boarding penalty - JLabel boardPenaltyLabel = new JLabel("Boarding penalty (min):"); - pane.add(boardPenaltyLabel); - boardingPenaltyField = new JTextField("5"); - pane.add(boardingPenaltyField); - - // row: max walk - JLabel maxWalkLabel = new JLabel("Maximum walk (meters):"); - pane.add(maxWalkLabel); - maxWalkField = new JTextField("5000"); - pane.add(maxWalkField); - - // row: walk speed - JLabel walkSpeedLabel = new JLabel("Walk speed (m/s):"); - pane.add(walkSpeedLabel); - walkSpeed = new JTextField("1.33"); - pane.add(walkSpeed); - - // row: bike speed - JLabel bikeSpeedLabel = new JLabel("Bike speed (m/s):"); - pane.add(bikeSpeedLabel); - bikeSpeed = new JTextField("5.0"); - pane.add(bikeSpeed); - - // row: heuristic weight - JLabel heuristicWeightLabel = new JLabel("Heuristic weight:"); - pane.add(heuristicWeightLabel); - heuristicWeight = new JTextField("1.0"); - pane.add(heuristicWeight); - - // row: soft walk? - JLabel softWalkLimitLabel = new JLabel("Soft walk-limit?:"); - pane.add(softWalkLimitLabel); - softWalkLimiting = new JCheckBox("soft walk-limiting"); - pane.add(softWalkLimiting); - - // row: soft walk-limit penalty - JLabel softWalkLimitPenaltyLabel = new JLabel("Soft walk-limiting penalty:"); - pane.add(softWalkLimitPenaltyLabel); - softWalkPenalty = new JTextField("60.0"); - pane.add(softWalkPenalty); - - // row: soft walk-limit overage - JLabel softWalkLimitOverageLabel = new JLabel("Soft walk-limiting overage:"); - pane.add(softWalkLimitOverageLabel); - softWalkOverageRate = new JTextField("5.0"); - pane.add(softWalkOverageRate); - - // row: nPaths - JLabel nPathsLabel = new JLabel("nPaths:"); - pane.add(nPathsLabel); - nPaths = new JTextField("1"); - pane.add(nPaths); - - // viz preferences - ItemListener onChangeVizPrefs = new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - showGraph.setShowTransit(showTransitCheckbox.isSelected()); - showGraph.setShowStreets(showStreetsCheckbox.isSelected()); - showGraph.setShowMultistateVertices(showMultistateVerticesCheckbox.isSelected()); - showGraph.setShowHightlights(showHighlightedCheckbox.isSelected()); - showGraph.setShowSPT(showSPTCheckbox.isSelected()); - showGraph.redraw(); - } - }; - showTransitCheckbox = new JCheckBox("show transit"); - showTransitCheckbox.setSelected(true); - showTransitCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showTransitCheckbox); - showStreetsCheckbox = new JCheckBox("show streets"); - showStreetsCheckbox.setSelected(true); - showStreetsCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showStreetsCheckbox); - showHighlightedCheckbox = new JCheckBox("show highlighted"); - showHighlightedCheckbox.setSelected(true); - showHighlightedCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showHighlightedCheckbox); - showSPTCheckbox = new JCheckBox("show SPT"); - showSPTCheckbox.setSelected(true); - showSPTCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showSPTCheckbox); - showMultistateVerticesCheckbox = new JCheckBox("show multistate vertices"); - showMultistateVerticesCheckbox.setSelected(true); - showMultistateVerticesCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showMultistateVerticesCheckbox); - - // GridLayout does not support empty cells, so a dummy label is used to fix the layout. - JLabel dummyLabel2 = new JLabel(""); - pane.add(dummyLabel2); - - // row: SPT flattening - JLabel sptFlatteningLabel = new JLabel("SPT flattening:"); - pane.add(sptFlatteningLabel); - sptFlattening = new JTextField("0.3"); - pane.add(sptFlattening); - - // row: SPT thickness - JLabel sptThicknessLabel = new JLabel("SPT thickness:"); - pane.add(sptThicknessLabel); - sptThickness = new JTextField("0.1"); - pane.add(sptThickness); - - // radio buttons: optimize type - JLabel optimizeTypeLabel = new JLabel("Optimize type:"); - pane.add(optimizeTypeLabel); - - opQuick = new JRadioButton("Quick"); - opQuick.setSelected(true); - opSafe = new JRadioButton("Safe"); - opFlat = new JRadioButton("Flat"); - opGreenways = new JRadioButton("Greenways"); - - optimizeTypeGrp = new ButtonGroup(); - optimizeTypeGrp.add(opQuick); - optimizeTypeGrp.add(opSafe); - optimizeTypeGrp.add(opFlat); - optimizeTypeGrp.add(opGreenways); - - JPanel optimizeTypePane = new JPanel(); - optimizeTypePane.add(opQuick); - optimizeTypePane.add(opSafe); - optimizeTypePane.add(opFlat); - optimizeTypePane.add(opGreenways); - - pane.add(optimizeTypePane); - - return pane; - } - - private void initRightPanel(Container pane) { - /* right panel holds trip pattern and stop metadata */ - JPanel rightPanel = new JPanel(); - rightPanel.setLayout(new BorderLayout()); - pane.add(rightPanel, BorderLayout.LINE_END); - - JTabbedPane rightPanelTabs = new JTabbedPane(); - - rightPanel.add(rightPanelTabs, BorderLayout.LINE_END); - - // a place to print out the details of a path - pathStates = new JList<>(); - JScrollPane stScrollPane = new JScrollPane(pathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("path states", stScrollPane); - - // when you select a path component state, it prints the backedge's metadata - pathStates.addListSelectionListener( - new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - outgoingEdges.clearSelection(); - incomingEdges.clearSelection(); - - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - State st = (State) theList.getSelectedValue(); - Edge edge = st.getBackEdge(); - reactToEdgeSelection(edge, false); - } - } - ); - - metadataList = new JList<>(); - metadataModel = new DefaultListModel<>(); - metadataList.setModel(metadataModel); - JScrollPane mdScrollPane = new JScrollPane(metadataList); - mdScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("metadata", mdScrollPane); - - // This is where matched issues from an issue search go - issueMatches = new JList<>(); - issueMatches.addListSelectionListener(e -> { - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - - DataImportIssue issue = theList.getSelectedValue(); - if (issue == null) { - return; - } - showGraph.drawIssue(issue); - }); - - issueMatchesModel = new DefaultListModel<>(); - issueMatches.setModel(issueMatchesModel); - JScrollPane imScrollPane = new JScrollPane(issueMatches); - imScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("issues", imScrollPane); - - Dimension size = new Dimension(200, 1600); - - imScrollPane.setMaximumSize(size); - imScrollPane.setPreferredSize(size); - stScrollPane.setMaximumSize(size); - stScrollPane.setPreferredSize(size); - mdScrollPane.setMaximumSize(size); - mdScrollPane.setPreferredSize(size); - rightPanelTabs.setMaximumSize(size); - rightPanel.setMaximumSize(size); - } - - private void initControlButtons() { - /* buttons at bottom */ - JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new GridLayout(0, 3)); - leftPanel.add(buttonPanel, BorderLayout.PAGE_END); - - JButton zoomDefaultButton = new JButton("Zoom to default"); - zoomDefaultButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.zoomToDefault(); - } - } - ); - buttonPanel.add(zoomDefaultButton); - - final JFrame frame = this; - - JButton zoomToNodeButton = new JButton("Zoom to node"); - zoomToNodeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String nodeName = JOptionPane.showInputDialog( - frame, - "Node id", - JOptionPane.PLAIN_MESSAGE - ); - Vertex v = getGraph().getVertex(VertexLabel.string(nodeName)); - if (v == null) { - System.out.println("no such node " + nodeName); - } else { - showGraph.zoomToVertex(v); - } - } - } - ); - buttonPanel.add(zoomToNodeButton); - - JButton zoomToLocationButton = new JButton("Zoom to location"); - zoomToLocationButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String result = JOptionPane.showInputDialog("Enter the location (lat lon)"); - if (result == null || result.length() == 0) { - return; - } - String[] tokens = result.split("[\\s,]+"); - double lat = Double.parseDouble(tokens[0]); - double lon = Double.parseDouble(tokens[1]); - Coordinate c = new Coordinate(lon, lat); - showGraph.zoomToLocation(c); - } - } - ); - buttonPanel.add(zoomToLocationButton); - - JButton zoomOutButton = new JButton("Zoom out"); - zoomOutButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.zoomOut(); - } - } - ); - buttonPanel.add(zoomOutButton); - - JButton routeButton2 = new JButton("Route"); - routeButton2.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - // String initialFrom = ""; - // Object selected = nearbyVertices.getSelectedValue(); - // if (selected != null) { - // initialFrom = selected.toString(); - // } - // RouteDialog dlg = new RouteDialog(frame, initialFrom); // modal - String from = sourceVertex.getText(); - String to = sinkVertex.getText(); - route(from, to); - } - } - ); - buttonPanel.add(routeButton2); - - JButton findButton = new JButton("Find node"); - findButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String nodeName = (String) JOptionPane.showInputDialog( - frame, - "Node id", - JOptionPane.PLAIN_MESSAGE - ); - Vertex v = getGraph().getVertex(VertexLabel.string(nodeName)); - if (v == null) { - System.out.println("no such node " + nodeName); - } else { - showGraph.highlightVertex(v); - ArrayList l = new ArrayList<>(); - l.add(v); - verticesSelected(l); - } - } - } - ); - buttonPanel.add(findButton); - - JButton findEdgeButton = new JButton("Find edge"); - findEdgeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String edgeName = (String) JOptionPane.showInputDialog( - frame, - "Edge name like", - JOptionPane.PLAIN_MESSAGE - ); - for (Vertex gv : getGraph().getVertices()) { - for (Edge edge : gv.getOutgoing()) { - if (edge.getDefaultName() != null && edge.getDefaultName().contains(edgeName)) { - showGraph.highlightVertex(gv); - ArrayList l = new ArrayList<>(); - l.add(gv); - verticesSelected(l); - } - } - } - } - } - ); - buttonPanel.add(findEdgeButton); - - JButton checkButton = new JButton("Check graph"); - checkButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - checkGraph(); - } - } - ); - buttonPanel.add(checkButton); - - JButton traceButton = new JButton("Trace"); - traceButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - trace(); - } - } - ); - buttonPanel.add(traceButton); - - JButton findEdgeByIdButton = new JButton("Find edge ID"); - findEdgeByIdButton.addActionListener(e -> { - throw new UnsupportedOperationException("Edges no longer have integer IDs."); - }); - buttonPanel.add(findEdgeByIdButton); - - JButton snapButton = new JButton("Snap location"); - snapButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - LOG.error("StreetIndex.getClosestPointOnStreet no longer exists."); - } - } - ); - buttonPanel.add(snapButton); - } - - private void getMetadata(Object selected) { - Class c = selected.getClass(); - Field[] fields; - while (c != null && c != Object.class) { - metadataModel.addElement("Class:" + c); - fields = c.getDeclaredFields(); - for (Field field : fields) { - int modifiers = field.getModifiers(); - if ((modifiers & Modifier.STATIC) != 0) { - continue; - } - field.setAccessible(true); - String name = field.getName(); - - String value = "(unknown -- see console for stack trace)"; - try { - value = "" + field.get(selected); - } catch (IllegalArgumentException e1) { - LOG.error("IllegalArgumentException", e1); - } catch (IllegalAccessException e1) { - LOG.error("IllegalAccessException", e1); - } - metadataModel.addElement(name + ": " + value); - } - c = c.getSuperclass(); - } - } - - private void reactToEdgeSelection(Edge selected, boolean outgoing) { - if (selected == null) { - return; - } - showGraph.highlightEdge(selected); - - /* for turns, highlight the outgoing street's ends */ - if (selected instanceof StreetEdge) { - List vertices = new ArrayList<>(); - List edges = new ArrayList<>(); - Vertex tov = selected.getToVertex(); - for (Edge og : tov.getOutgoing()) { - if (og instanceof StreetEdge) { - edges.add(og); - vertices.add(og.getToVertex()); - break; - } - } - Vertex fromv = selected.getFromVertex(); - for (Edge ic : fromv.getIncoming()) { - if (ic instanceof StreetEdge) { - edges.add(ic); - vertices.add(ic.getFromVertex()); - break; - } - } - // showGraph.setHighlightedVertices(vertices); - showGraph.setHighlightedEdges(edges); - } - - /* add the connected vertices to the list of vertices */ - VertexList nearbyModel = (VertexList) nearbyVertices.getModel(); - List vertices = nearbyModel.selected; - - Vertex v; - if (outgoing) { - v = selected.getToVertex(); - } else { - v = selected.getFromVertex(); - } - if (!vertices.contains(v)) { - vertices.add(v); - nearbyModel = new VertexList(vertices); - nearbyVertices.setModel(nearbyModel); - // this should just be an event, but for some reason, JList doesn't implement the right event. - } - - /* set up metadata tab */ - metadataModel.clear(); - getMetadata(selected); - // fromv - Vertex fromv = selected.getFromVertex(); - getMetadata(fromv); - if (selected instanceof StreetEdge) { - //TODO ElevationProfileSegment do not exist anymore - //getMetadata(((StreetEdge) selected).getElevationProfileSegment()); - } - metadataList.revalidate(); - } - - private void initVertexInfoSubpanel() { - JPanel vertexDataPanel = new JPanel(); - vertexDataPanel.setLayout(new BoxLayout(vertexDataPanel, BoxLayout.PAGE_AXIS)); - vertexDataPanel.setPreferredSize(new Dimension(300, 600)); - leftPanel.add(vertexDataPanel, BorderLayout.CENTER); - - // nearby vertices - JLabel nvLabel = new JLabel("Vertices"); - vertexDataPanel.add(nvLabel); - nearbyVertices = new JList<>(); - nearbyVertices.setVisibleRowCount(4); - JScrollPane nvScrollPane = new JScrollPane(nearbyVertices); - vertexDataPanel.add(nvScrollPane); - nearbyVertices.addListSelectionListener( - new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - outgoingEdges.removeAll(); - incomingEdges.removeAll(); - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected != null) { - Vertex nowSelected = selected.vertex; - showGraph.highlightVertex(nowSelected); - outgoingEdges.setModel(new EdgeListModel(nowSelected.getOutgoing())); - incomingEdges.setModel(new EdgeListModel(nowSelected.getIncoming())); - } - } - } - ); - - // listener useful for both incoming and outgoing edge list panes - // when a different edge is selected, change up the pattern pane and list of nearby nodes - ListSelectionListener edgeChanged = new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - @SuppressWarnings("unchecked") - JList edgeList = (JList) e.getSource(); - - Edge selected = (Edge) edgeList.getSelectedValue(); - - boolean outgoing = (edgeList == outgoingEdges); - reactToEdgeSelection(selected, outgoing); - } - }; - - // outgoing edges - JLabel ogeLabel = new JLabel("Outgoing edges"); - vertexDataPanel.add(ogeLabel); - outgoingEdges = new JList<>(); - outgoingEdges.setVisibleRowCount(4); - JScrollPane ogeScrollPane = new JScrollPane(outgoingEdges); - vertexDataPanel.add(ogeScrollPane); - outgoingEdges.addListSelectionListener(edgeChanged); - - // incoming edges - JLabel iceLabel = new JLabel("Incoming edges"); - vertexDataPanel.add(iceLabel); - incomingEdges = new JList<>(); - JScrollPane iceScrollPane = new JScrollPane(incomingEdges); - vertexDataPanel.add(iceScrollPane); - incomingEdges.addListSelectionListener(edgeChanged); - - // paths list - JLabel pathsLabel = new JLabel("Paths"); - vertexDataPanel.add(pathsLabel); - pathsList = new JList<>(); - - popup = new JPopupMenu(); - JMenuItem compareMenuItem = new JMenuItem("compare"); - compareMenuItem.addActionListener(new OnPopupMenuClickListener()); - popup.add(compareMenuItem); - - // make paths list right-clickable - pathsList.addMouseListener( - new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - @SuppressWarnings("unchecked") - JList list = (JList) e.getSource(); - int row = list.locationToIndex(e.getPoint()); - list.setSelectedIndex(row); - - popup.show(list, e.getX(), e.getY()); - } - } - - @Override - public void mousePressed(MouseEvent e) {} - - @Override - public void mouseReleased(MouseEvent e) {} - - @Override - public void mouseEntered(MouseEvent e) {} - - @Override - public void mouseExited(MouseEvent e) {} - } - ); - pathsList.addListSelectionListener( - new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent ev) { - PathPrinter pp = ((PathPrinter) pathsList.getSelectedValue()); - if (pp == null) { - return; - } - var path = pp.sp; - - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : path.states()) { - pathModel.addElement(st); - } - pathStates.setModel(pathModel); - - showGraph.highlightStreetPath(path); - } - } - ); - JScrollPane pathsScrollPane = new JScrollPane(pathsList); - vertexDataPanel.add(pathsScrollPane); - } - - private void initRoutingSubpanel() { - /* ROUTING SUBPANEL */ - JPanel routingPanel = new JPanel(); - routingPanel.setLayout(new GridLayout(0, 2)); - leftPanel.add(routingPanel, BorderLayout.NORTH); - - // row: source vertex - JButton setSourceVertexButton = new JButton("set source"); - setSourceVertexButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - Object selected = nearbyVertices.getSelectedValue(); - if (selected != null) { - sourceVertex.setText(selected.toString()); - } - } - } - ); - routingPanel.add(setSourceVertexButton); - sourceVertex = new JTextField(); - routingPanel.add(sourceVertex); - - // row: sink vertex - JButton setSinkVertexButton = new JButton("set sink"); - setSinkVertexButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - Object selected = nearbyVertices.getSelectedValue(); - if (selected != null) { - sinkVertex.setText(selected.toString()); - } - } - } - ); - routingPanel.add(setSinkVertexButton); - sinkVertex = new JTextField(); - routingPanel.add(sinkVertex); - - // row: set date - JButton resetSearchDateButton = new JButton("now ->"); - resetSearchDateButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - searchDate.setText(DATE_FORMAT.format(ZonedDateTime.now())); - } - } - ); - routingPanel.add(resetSearchDateButton); - searchDate = new JTextField(); - searchDate.setText(DATE_FORMAT.format(ZonedDateTime.now())); - routingPanel.add(searchDate); - - // row: launch, continue, and clear path search - JButton routeButton = new JButton("path search"); - routeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String from = sourceVertex.getText(); - String to = sinkVertex.getText(); - route(from, to); - } - } - ); - routingPanel.add(routeButton); - JButton continueButton = new JButton("continue"); - continueButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - //TODO continue search - } - } - ); - routingPanel.add(continueButton); - JButton clearRouteButton = new JButton("clear path"); - clearRouteButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.highlightStreetPath(null); - showGraph.clearHighlights(); - showGraph.resetSPT(); - } - } - ); - routingPanel.add(clearRouteButton); - - //label: search time elapsed - searchTimeElapsedLabel = new JLabel("search time elapsed:"); - routingPanel.add(searchTimeElapsedLabel); - - //option: don't use graphical callback. useful for doing a quick profile - dontUseGraphicalCallbackCheckBox = new JCheckBox("no graphics"); - routingPanel.add(dontUseGraphicalCallbackCheckBox); - } - - private void showPathsInPanel(List paths) { - // show paths in a list panel - DefaultListModel data = new DefaultListModel<>(); - for (var gp : paths) { - data.addElement(new PathPrinter(gp)); - } - pathsList.setModel(data); - } - - static class PathPrinter { - - StreetPath sp; - - PathPrinter(StreetPath sp) { - this.sp = sp; - } - - public String toString() { - String startTime = TIME_FORMAT.format(sp.startTime()); - String endTime = TIME_FORMAT.format(sp.endTime()); - return ( - "Path (" + - startTime + - "-" + - endTime + - ") weight:" + - sp.weight() + - " dur:" + - (sp.duration().toSeconds() / 60.0) - // " walk:" + - // gp.getWalkDistance() - ); - } - } - - private final class ComparePathStatesClickListener implements ListSelectionListener { - - private final JList outputList; - - ComparePathStatesClickListener(JList outputList) { - this.outputList = outputList; - } - - @Override - public void valueChanged(ListSelectionEvent e) { - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - State st = (State) theList.getSelectedValue(); - if (st == null) { - return; - } - - DefaultListModel stateListModel = new DefaultListModel<>(); - stateListModel.addElement("weight:" + st.getWeight()); - stateListModel.addElement("weightdelta:" + st.getWeightDelta()); - stateListModel.addElement("rentingVehicle:" + st.isRentingVehicle()); - stateListModel.addElement("vehicleParked:" + st.isVehicleParked()); - stateListModel.addElement("traversalDistance:" + st.getTraversalDistanceMeters()); - stateListModel.addElement("elapsedTime:" + st.getElapsedTimeSeconds()); - outputList.setModel(stateListModel); - - lastStateClicked = st; - } - } - - private final class OnPopupMenuClickListener implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - PathPrinter pp = ((PathPrinter) pathsList.getSelectedValue()); - if (pp == null) { - return; - } - var path = pp.sp; - - firstComparePath = secondComparePath; - secondComparePath = path; - - if (firstComparePath != null) { - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : firstComparePath.states()) { - pathModel.addElement(st); - } - firstComparePathStates.setModel(pathModel); - } - if (secondComparePath != null) { - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : secondComparePath.states()) { - pathModel.addElement(st); - } - secondComparePathStates.setModel(pathModel); - } - - int[] diff = diffPaths(); - final int diverge = diff[0]; - final int converge = diff[1]; - if (diff[0] >= 0) { - firstComparePathStates.setCellRenderer( - new DiffListCellRenderer(diverge, firstComparePath.states().size() - converge - 1) - ); - secondComparePathStates.setCellRenderer( - new DiffListCellRenderer(diverge, secondComparePath.states().size() - converge - 1) - ); - } - } - - private int[] diffPaths() { - if (firstComparePath == null || secondComparePath == null) { - return new int[] { -2, -2 }; - } - - int l1 = firstComparePath.states().size(); - int l2 = secondComparePath.states().size(); - int minlen = l1 < l2 ? l1 : l2; - - int divergence = -1; - int convergence = -1; - - // find divergence - for (int i = 0; i < minlen; i++) { - Vertex v1 = firstComparePath.states().get(i).getVertex(); - Vertex v2 = secondComparePath.states().get(i).getVertex(); - if (!v1.equals(v2)) { - divergence = i - 1; - break; - } - } - - // find convergence - for (int i = 0; i < minlen; i++) { - Vertex v1 = firstComparePath.states().get(l1 - i - 1).getVertex(); - Vertex v2 = secondComparePath.states().get(l2 - i - 1).getVertex(); - if (!v1.equals(v2)) { - convergence = i - 1; - break; - } - } - - return new int[] { divergence, convergence }; - } - - private final class DiffListCellRenderer extends DefaultListCellRenderer { - - private final int diverge; - private final int converge; - - private DiffListCellRenderer(int diverge, int converge) { - this.diverge = diverge; - this.converge = converge; - } - - @Override - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus - ) { - Component c = super.getListCellRendererComponent( - list, - value, - index, - isSelected, - cellHasFocus - ); - if (isSelected) { - return c; - } - - if (index <= diverge) { - c.setBackground(new Color(196, 201, 255)); - } - if (index >= converge) { - c.setBackground(new Color(255, 196, 196)); - } - - return c; - } - } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java b/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java deleted file mode 100644 index 04c343d2b30..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.Container; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JTextField; - -/** - * A dialog box to plan a route. - */ -public class RouteDialog extends JDialog { - - private final JTextField fromField; - private final JTextField toField; - private final JButton goButton; - - public String from; - public String to; - - public RouteDialog(JFrame owner, String initialFrom) { - super(owner, true); - fromField = new JTextField(initialFrom, 30); - toField = new JTextField(30); - goButton = new JButton("Go"); - - Container pane = getContentPane(); - - pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); - pane.add(new JLabel("From")); - pane.add(fromField); - pane.add(new JLabel("To")); - pane.add(toField); - pane.add(goButton); - pack(); - final RouteDialog outer = this; - goButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - from = fromField.getText().trim(); - to = toField.getText().trim(); - outer.setVisible(false); - } - } - ); - setVisible(true); - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java b/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java index 26d567b6903..e69de29bb2d 100644 --- a/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java +++ b/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java @@ -1,1222 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.Point; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.text.DecimalFormat; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.index.strtree.STRtree; -import org.opentripplanner.astar.model.ShortestPathTree; -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; -import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; -import org.opentripplanner.street.geometry.GeometryUtils; -import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.street.model.StreetTraversalPermission; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.ElevatorAlightEdge; -import org.opentripplanner.street.model.edge.ElevatorBoardEdge; -import org.opentripplanner.street.model.edge.FreeEdge; -import org.opentripplanner.street.model.edge.PathwayEdge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.edge.StreetTransitEntityLink; -import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; -import org.opentripplanner.street.model.edge.VehicleParkingEdge; -import org.opentripplanner.street.model.path.StreetPath; -import org.opentripplanner.street.model.vertex.ElevatorHopVertex; -import org.opentripplanner.street.model.vertex.ExitVertex; -import org.opentripplanner.street.model.vertex.IntersectionVertex; -import org.opentripplanner.street.model.vertex.SplitterVertex; -import org.opentripplanner.street.model.vertex.StreetLocation; -import org.opentripplanner.street.model.vertex.TemporaryVertex; -import org.opentripplanner.street.model.vertex.TransitBoardingAreaVertex; -import org.opentripplanner.street.model.vertex.TransitEntranceVertex; -import org.opentripplanner.street.model.vertex.TransitPathwayNodeVertex; -import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.state.State; -import processing.core.PApplet; -import processing.core.PFont; - -/** - * Processing applet to show a map of the graph. The user can: - Use mouse wheel to zoom (or right - * drag, or ctrl-drag) - Left drag to pan around the map - Left click to send a list of nearby - * vertices to the associated VertexSelectionListener. - */ -public class ShowGraph extends PApplet implements MouseWheelListener { - - private static final int FRAME_RATE = 30; - private static final boolean VIDEO = false; - private static final String VIDEO_PATH = "/home/syncopate/pathimage/"; - private static final DecimalFormat LAT_FORMATTER = new DecimalFormat("00.0000°N ; 00.0000°S"); - private static final DecimalFormat LON_FORMATTER = new DecimalFormat("000.0000°E ; 000.0000°W"); - /* Layer constants */ - // XY coordinates - static final int DRAW_MINIMAL = 0; - static final int DRAW_HIGHLIGHTED = 1; - static final int DRAW_SPT = 2; - static final int DRAW_VERTICES = 3; - static final int DRAW_TRANSIT = 4; - static final int DRAW_LINKS = 5; - static final int DRAW_STREETS = 6; - static final int DRAW_ALL = 7; - static final int DRAW_PARTIAL = 8; - private static double lastLabelY; - // how many edges to draw before checking whether we need to move on to the next frame - private final int BLOCK_SIZE = 1000; - // how many edges to skip over (to ensure a sampling of edges throughout the visible area) - private final long DECIMATE = 40; - // 800 instead of 1000 msec, leaving 20% of the time for work other than drawing. - private final int FRAME_TIME = 800 / FRAME_RATE; - private final ArrayList selectors; - private final List visibleStreetEdges = new ArrayList<>(1000); - private final List visibleLinkEdges = new ArrayList<>(1000); - private final List visibleTransitEdges = new ArrayList<>(1000); - // these queues are filled by a search in another thread, so must be threadsafe - private final Queue newHighlightedVertices = new LinkedBlockingQueue<>(); - private final Queue newHighlightedEdges = new LinkedBlockingQueue<>(); - private static final DateTimeFormatter SHORT_DATE_FORMAT = DateTimeFormatter.ofPattern( - "HH:mm:ss z" - ); - private final LinkedBlockingQueue newSPTEdges = new LinkedBlockingQueue<>(); - private final boolean drawEdges = true; - private int videoFrameNumber = 0; - Graph graph; - STRtree vertexIndex; - STRtree edgeIndex; - Envelope modelOuterBounds; - Envelope modelBounds = new Envelope(); - VertexSelectionListener selector; - private List visibleVertices; - private List highlightedVertices = new ArrayList<>(1000); - private List highlightedEdges = new ArrayList<>(1000); - private Coordinate highlightedCoordinate; - private Edge highlightedEdge; - private StreetPath highlightedStreetPath; - protected double mouseModelX; - protected double mouseModelY; - private Point startDrag = null; - private int dragX; - private int dragY; - private boolean ctrlPressed = false; - boolean drawFast = false; - boolean drawStreetEdges = true; - boolean drawTransitEdges = true; - boolean drawLinkEdges = true; - boolean drawStreetVertices = true; - boolean drawTransitStopVertices = true; - boolean drawExtraVertices = true; - private int drawLevel = DRAW_ALL; - private int drawOffset = 0; - private boolean drawHighlighted = true; - public SimpleSPT simpleSPT = new SimpleSPT(); - private LinkedBlockingQueue sptEdgeQueue; - private boolean sptVisible = true; - private float sptFlattening = 0.3f; - private float sptThickness = 0.1f; - private boolean drawMultistateVertices = true; - private ShortestPathTree spt; - - /* - * Constructor. Call processing constructor, and register the listener to notify when the user selects vertices. - */ - public ShowGraph(VertexSelectionListener selector, Graph graph) { - super(); - this.graph = graph; - this.spt = null; - this.selector = selector; - this.selectors = new ArrayList<>(); - } - - /* - * Setup Processing applet - */ - public void setup() { - size(getSize().width, getSize().height, JAVA2D); - - /* Build spatial index of vertices and edges */ - buildSpatialIndex(); - - /* Set model bounds to encompass all vertices in the index, and then some */ - modelBounds = (Envelope) (vertexIndex.getRoot().getBounds()); - modelBounds.expandBy(0.02); - matchAspect(); - /* save this zoom level to allow returning to default later */ - modelOuterBounds = new Envelope(modelBounds); - - /* find and set up the appropriate font */ - String[] fonts = PFont.list(); - String[] preferredFonts = { "Mono", "Courier" }; - PFont font = null; - for (String preferredFontName : preferredFonts) { - for (String fontName : fonts) { - if (fontName.contains(preferredFontName)) { - font = createFont(fontName, 16); - break; - } - } - if (font != null) { - break; - } - } - textFont(font); - textMode(SCREEN); - addMouseWheelListener(this); - addMouseMotionListener( - new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - Point p = e.getPoint(); - mouseModelX = toModelX(p.x); - mouseModelY = toModelY(p.y); - } - } - ); - addComponentListener( - new ComponentAdapter() { - public void componentResized(ComponentEvent e) { - matchAspect(); - drawLevel = DRAW_PARTIAL; - } - } - ); - frameRate(FRAME_RATE); - } - - public synchronized void draw() { - smooth(); - int startMillis = millis(); - if (drawLevel == DRAW_PARTIAL) { - drawPartial(startMillis); - } else if (drawLevel == DRAW_ALL) { - boolean finished = drawAll(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_LINKS) { - boolean finished = drawLinks(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_TRANSIT) { - boolean finished = drawTransit(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_VERTICES) { - drawVertices(); - } else if (drawLevel == DRAW_SPT) { - boolean finished = drawSPT(); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_HIGHLIGHTED) { - drawHighlighted(); - } else if (drawLevel == DRAW_MINIMAL) { - if (!newHighlightedEdges.isEmpty()) { - handleNewHighlights(); - } - drawNewEdges(); - drawCoords(); - } - drawOffset = 0; - if (drawLevel > DRAW_MINIMAL) { - // move to next layer - drawLevel -= 1; - } - } - - public void redraw() { - drawLevel = DRAW_ALL; - } - - public void mouseReleased(MouseEvent e) { - startDrag = null; - } - - public void mouseDragged(MouseEvent e) { - Point c = e.getPoint(); - if (startDrag == null) { - startDrag = c; - dragX = c.x; - dragY = c.y; - } - double dx = dragX - c.x; - double dy = c.y - dragY; - if (ctrlPressed || mouseButton == RIGHT) { - zoom(dy * 0.01, startDrag); - } else { - double tx = (modelBounds.getWidth() * dx) / getWidth(); - double ty = (modelBounds.getHeight() * dy) / getHeight(); - modelBounds.translate(tx, ty); - } - dragX = c.x; - dragY = c.y; - drawLevel = DRAW_PARTIAL; - } - - /* - * Zoom in/out proportional to the number of clicks of the mouse wheel. - */ - public void mouseWheelMoved(MouseWheelEvent e) { - double f = e.getWheelRotation() * 0.2; - zoom(f, e.getPoint()); - } - - @SuppressWarnings("unchecked") - public void mouseClicked() { - Envelope screenEnv = new Envelope(new Coordinate(mouseX, mouseY)); - screenEnv.expandBy(4, 4); - Envelope env = new Envelope( - toModelX(screenEnv.getMinX()), - toModelX(screenEnv.getMaxX()), - toModelY(screenEnv.getMinY()), - toModelY(screenEnv.getMaxY()) - ); - - List nearby = (List) vertexIndex.query(env); - selector.verticesSelected(nearby); - drawLevel = DRAW_ALL; - } - - public void keyPressed() { - if (key == CODED && keyCode == CONTROL) { - ctrlPressed = true; - } - } - - public void keyReleased() { - if (key == CODED && keyCode == CONTROL) { - ctrlPressed = false; - } - } - - public void zoomToDefault() { - modelBounds = new Envelope(modelOuterBounds); - drawLevel = DRAW_ALL; - } - - public void zoomOut() { - modelBounds.expandBy(modelBounds.getWidth(), modelBounds.getHeight()); - drawLevel = DRAW_ALL; - } - - public void zoomToLocation(Coordinate c) { - Envelope e = new Envelope(); - e.expandToInclude(c); - e.expandBy(0.002); - modelBounds = e; - matchAspect(); - drawLevel = DRAW_ALL; - } - - public void zoomToVertex(Vertex v) { - Envelope e = new Envelope(); - e.expandToInclude(v.getCoordinate()); - e.expandBy(0.002); - modelBounds = e; - drawLevel = DRAW_ALL; - } - - /** - * Zoom to an envelope. Used for issue zoom. - * - * @author mattwigway - */ - public void zoomToEnvelope(Envelope e) { - modelBounds = e; - matchAspect(); - drawLevel = DRAW_ALL; - } - - /* - * Iterate through all vertices and their (outgoing) edges. If they are of 'interesting' types, - * add them to the corresponding spatial index. - */ - public synchronized void buildSpatialIndex() { - vertexIndex = new STRtree(); - edgeIndex = new STRtree(); - Envelope env; - - // int xminx, xmax, ymin, ymax; - for (Vertex v : graph.getVertices()) { - Coordinate c = v.getCoordinate(); - env = new Envelope(c); - vertexIndex.insert(env, v); - for (Edge e : v.getOutgoing()) { - var edgeGeometry = e.getGeometry(); - if (edgeGeometry == null) { - edgeIndex.insert( - new Envelope(e.getFromVertex().getCoordinate(), e.getToVertex().getCoordinate()), - e - ); - } else { - edgeIndex.insert(edgeGeometry.getEnvelopeInternal(), e); - } - } - } - vertexIndex.build(); - edgeIndex.build(); - } - - /** - * Set the Vertex selector to newSelector, and store the old selector on the stack of selectors - */ - public void pushSelector(VertexSelectionListener newSelector) { - selectors.add(selector); - selector = newSelector; - } - - /** - * Restore the previous vertexSelector - */ - public void popSelector() { - selector = selectors.get(selectors.size() - 1); - selectors.remove(selectors.size() - 1); - } - - public void highlightCoordinate(Coordinate c) { - double xd = 0; - double yd = 0; - while (!modelBounds.contains(c)) { - xd = modelBounds.getWidth() / 100; - yd = modelBounds.getHeight() / 100; - modelBounds.expandBy(xd, yd); - } - modelBounds.expandBy(xd, yd); - highlightedCoordinate = c; - drawLevel = DRAW_ALL; - } - - public void highlightVertex(Vertex v) { - highlightCoordinate(v.getCoordinate()); - } - - public void enqueueHighlightedEdge(Edge de) { - newHighlightedEdges.add(de); - } - - public void clearHighlights() { - highlightedEdges.clear(); - highlightedVertices.clear(); - drawLevel = DRAW_ALL; - } - - public void highlightEdge(Edge selected) { - highlightedEdge = selected; - drawLevel = DRAW_ALL; - } - - public void highlightStreetPath(StreetPath sp) { - highlightedStreetPath = sp; - // drawLevel = DRAW_ALL; - // leave streets in grey - drawLevel = DRAW_TRANSIT; - } - - public void setHighlightedVertices(Set vertices) { - highlightedVertices = new ArrayList<>(vertices); - drawLevel = DRAW_ALL; - } - - public void setHighlightedVertices(List vertices) { - highlightedVertices = vertices; - drawLevel = DRAW_ALL; - } - - public void setHighlightedEdges(List edges) { - highlightedEdges = edges; - drawLevel = DRAW_ALL; - } - - public void drawIssue(DataImportIssue anno) { - Envelope env = new Envelope(); - - Edge e = anno.getReferencedEdge(); - if (e != null) { - this.enqueueHighlightedEdge(e); - env.expandToInclude(e.getFromVertex().getCoordinate()); - env.expandToInclude(e.getToVertex().getCoordinate()); - } - - ArrayList vertices = new ArrayList<>(); - Vertex v = anno.getReferencedVertex(); - if (v != null) { - env.expandToInclude(v.getCoordinate()); - vertices.add(v); - } - - if (e == null && v == null) { - return; - } - - // make it a little bigger, especially needed for STOP_UNLINKED - env.expandBy(0.02); - - // highlight relevant things - this.clearHighlights(); - this.setHighlightedVertices(vertices); - - // zoom the graph display - this.zoomToEnvelope(env); - - // and draw - this.draw(); - } - - public void setShowTransit(boolean selected) { - drawTransitEdges = selected; - drawTransitStopVertices = selected; - } - - public void setShowStreets(boolean selected) { - drawStreetEdges = selected; - drawStreetVertices = selected; - } - - public void setShowHightlights(boolean selected) { - drawHighlighted = selected; - } - - public void addNewSPTEdge(State state) { - this.newSPTEdges.add(state); - this.simpleSPT.add(state); - } - - public void resetSPT() { - this.simpleSPT = new SimpleSPT(); - } - - public void setShowSPT(boolean selected) { - sptVisible = selected; - } - - public void setSPTFlattening(float sptFlattening) { - this.sptFlattening = sptFlattening; - } - - public void setSPTThickness(float sptThickness) { - this.sptThickness = sptThickness; - } - - public void setShowMultistateVertices(boolean selected) { - this.drawMultistateVertices = selected; - } - - public void setSPT(ShortestPathTree spt) { - this.spt = spt; - } - - /* - * Zoom in/out. Translate the viewing window such that the place under the mouse pointer is a fixed point. If p is null, zoom around the center of - * the viewport. - */ - void zoom(double f, Point p) { - double ex = modelBounds.getWidth() * f; - double ey = modelBounds.getHeight() * f; - modelBounds.expandBy(ex / 2, ey / 2); - if (p != null) { - // Note: Graphics Y coordinates increase down the screen, hence the opposite signs. - double tx = ex * -((p.getX() / this.width) - 0.5); - double ty = ey * +((p.getY() / this.height) - 0.5); - modelBounds.translate(tx, ty); - } - // update the display - drawLevel = DRAW_PARTIAL; - } - - void matchAspect() { - /* Basic sinusoidal projection of lat/lon data to square pixels */ - double yCenter = modelBounds.centre().y; - float xScale = cos(radians((float) yCenter)); - double newX = - modelBounds.getHeight() * (1 / xScale) * ((float) this.getWidth() / this.getHeight()); - modelBounds.expandBy((newX - modelBounds.getWidth()) / 2f, 0); - } - - private static LineString getOrCreateGeometry(Edge edge) { - var edgeGeometry = edge.getGeometry(); - if (edgeGeometry != null) { - return edgeGeometry; - } - - Coordinate[] coordinates = new Coordinate[] { - edge.getFromVertex().getCoordinate(), - edge.getToVertex().getCoordinate(), - }; - return GeometryUtils.getGeometryFactory().createLineString(coordinates); - } - - @SuppressWarnings("unchecked") - private synchronized void findVisibleElements() { - visibleVertices = (List) vertexIndex.query(modelBounds); - visibleStreetEdges.clear(); - visibleLinkEdges.clear(); - visibleTransitEdges.clear(); - for (Edge de : (Iterable) edgeIndex.query(modelBounds)) { - if ( - de instanceof PathwayEdge || - de instanceof VehicleParkingEdge || - de instanceof StreetTransitEntityLink || - de instanceof FreeEdge || - de instanceof StreetVehicleParkingLink || - de instanceof StreetVehicleRentalLink - ) { - visibleLinkEdges.add(de); - } else if ( - de instanceof StreetEdge || - de instanceof ElevatorAlightEdge || - de instanceof ElevatorBoardEdge - ) { - visibleStreetEdges.add(de); - } - } - } - - private int drawEdge(Edge e) { - var geometry = getOrCreateGeometry(e); - Coordinate[] coords = geometry.getCoordinates(); - beginShape(); - for (Coordinate coord : coords) { - vertex((float) toScreenX(coord.x), (float) toScreenY(coord.y)); - } - endShape(); - // should be used to count segments, not edges drawn - return coords.length; - } - - /* use endpoints instead of geometry for quick updating */ - private void drawEdgeFast(Edge e) { - Coordinate[] coords = getOrCreateGeometry(e).getCoordinates(); - Coordinate c0 = coords[0]; - Coordinate c1 = coords[coords.length - 1]; - line( - (float) toScreenX(c0.x), - (float) toScreenY(c0.y), - (float) toScreenX(c1.x), - (float) toScreenY(c1.y) - ); - } - - private void drawStreetPath(StreetPath sp) { - // draw edges in different colors according to mode - for (State s : sp.states()) { - Edge e = s.getBackEdge(); - if (e == null) { - continue; - } - - // TODO Add support for crating transit edges on the fly - // if (mode != null && mode.isTransit()) { - // stroke(200, 050, 000); - // strokeWeight(6); - // drawEdge(e); - // } - if (e instanceof StreetEdge) { - StreetTraversalPermission stp = ((StreetEdge) e).getPermission(); - if (stp == StreetTraversalPermission.PEDESTRIAN) { - stroke(000, 200, 000); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.BICYCLE) { - stroke(000, 000, 200); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE) { - stroke(000, 200, 200); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.ALL) { - stroke(200, 200, 200); - strokeWeight(6); - drawEdge(e); - } else { - stroke(64, 64, 64); - strokeWeight(6); - drawEdge(e); - } - } - } - // mark key vertices - lastLabelY = -999; - labelState(sp.states().getFirst(), "begin"); - labelState(sp.states().getLast(), "end"); - - if (VIDEO) { - // freeze on final path for a few frames - for (int i = 0; i < 10; i++) { - saveVideoFrame(); - } - resetVideoFrameNumber(); - } - } - - private void labelState(State s, String str) { - fill(240, 240, 240); - Vertex v = s.getVertex(); - drawVertex(v, 8); - str += " " + SHORT_DATE_FORMAT.format(Instant.ofEpochSecond(s.getTimeSeconds())); - str += " [" + (int) s.getWeight() + "]"; - double x = toScreenX(v.getX()) + 10; - double y = toScreenY(v.getY()); - double dy = y - lastLabelY; - if (dy == 0) { - y = lastLabelY + 20; - } else if (Math.abs(dy) < 20) { - y = lastLabelY + Math.signum(dy) * 20; - } - text(str, (float) x, (float) y); - lastLabelY = y; - } - - private void drawCoordinate(Coordinate c, double r) { - noStroke(); - ellipse(toScreenX(c.x), toScreenY(c.y), r, r); - } - - private void drawVertex(Vertex v, double r) { - drawCoordinate(v.getCoordinate(), r); - } - - private boolean drawSPT() { - if (!sptVisible) { - return true; - } - - noFill(); - // if(sptEdgeQueue==null){ - // sptEdgeQueue = simpleSPT.getEdgeQueue(); - // } - - // colorOverlappingBranches(sptEdgeQueue); - // - // int i=0; - // while(!sptEdgeQueue.isEmpty()){ - // SPTNode node = sptEdgeQueue.poll(); - // i++; - // node.draw(sptBranchColors); - // if ((i%BLOCK_SIZE==0) && (millis() - startMillis > FRAME_TIME)) - // return false; - // } - // sptEdgeQueue=null; - - simpleSPT.draw(); - - return true; - } - - private void colorOverlappingBranches(LinkedBlockingQueue queue) { - HashMap stateHeight = new HashMap<>(); - - for (SPTNode node : queue) { - Integer height = stateHeight.get(node.state.getVertex()); - if (height == null) { - height = 0; - } else { - height += 1; - } - stateHeight.put(node.state.getVertex(), height); - - node.setHeight(height); - } - } - - private void drawNewEdges() { - if (drawEdges) { - strokeWeight(1); - // white - stroke(255, 255, 255); - noFill(); - while (!newSPTEdges.isEmpty()) { - State leaf = newSPTEdges.poll(); - - if (leaf != null) { - if (leaf.getBackEdge() != null) { - drawEdge(leaf.getBackEdge()); - } - } - } - } - } - - private void drawCoords() { - // Black background box - fill(0, 0, 0); - stroke(30, 128, 30); - // noStroke(); - strokeWeight(1); - rect(3, 3, 303, textAscent() + textDescent() + 6); - // Print lat & lon coordinates - fill(128, 128, 256); - // noStroke(); - String output = LON_FORMATTER.format(mouseModelX) + " " + LAT_FORMATTER.format(mouseModelY); - textAlign(LEFT, TOP); - text(output, 6, 6); - } - - private void drawVertices() { - /* turn off vertex display when zoomed out */ - final double METERS_PER_DEGREE_LAT = 111111.111111; - boolean closeEnough = ((modelBounds.getHeight() * METERS_PER_DEGREE_LAT) / this.width < 5); - /* Draw selected visible vertices */ - for (Vertex v : visibleVertices) { - if ( - drawTransitStopVertices && - closeEnough && - (v instanceof TransitStopVertex || - v instanceof TransitPathwayNodeVertex || - v instanceof TransitEntranceVertex || - v instanceof TransitBoardingAreaVertex) - ) { - // Make transit stops blue dots - fill(60, 60, 200); - drawVertex(v, 7); - } - if ( - drawExtraVertices && - closeEnough && - (v instanceof VehicleParkingEntranceVertex || v instanceof VehicleRentalPlace) - ) { - // Make B+R/P+R pink - fill(255, 70, 255); - drawVertex(v, 7); - } - if ( - drawStreetVertices && - ((v instanceof IntersectionVertex && ((IntersectionVertex) v).hasCyclingTrafficLight()) || - (v instanceof ElevatorHopVertex || - v instanceof ExitVertex || - v instanceof TemporaryVertex || - v instanceof SplitterVertex || - v instanceof StreetLocation)) - ) { - if (v instanceof IntersectionVertex && ((IntersectionVertex) v).hasCyclingTrafficLight()) { - // Make traffic lights red dots - fill(120, 60, 60); - drawVertex(v, 5); - } - } - if (drawMultistateVertices && spt != null) { - List states = spt.getStates(v); - if (states != null) { - fill(100, 60, 100); - drawVertex(v, states.size() * 2); - } - } - } - } - - private void drawHighlighted() { - /* Draw highlighted edges in another color */ - noFill(); - // yellow transparent edge highlight - stroke(200, 200, 000, 16); - strokeWeight(8); - if (drawHighlighted && highlightedEdges != null) { - try { - for (Edge e : highlightedEdges) { - drawEdge(e); - } - } catch (ConcurrentModificationException cme) { - // The edge list was cleared or added to while it was being drawn, no harm done. - } - } - /* Draw highlighted street path in another color */ - if (highlightedStreetPath != null) { - drawStreetPath(highlightedStreetPath); - } - /* Draw (single) highlighted edge in highlight color */ - if (highlightedEdge != null) { - stroke(10, 200, 10, 128); - strokeWeight(12); - drawEdge(highlightedEdge); - } - /* Draw highlighted vertices */ - // orange fill - fill(255, 127, 0); - noStroke(); - if (highlightedVertices != null) { - for (Vertex v : highlightedVertices) { - drawVertex(v, 8); - } - } - /* Draw (single) highlighed coordinate in a different color */ - if (highlightedCoordinate != null) { - fill(255, 255, 30); - drawCoordinate(highlightedCoordinate, 7); - } - noFill(); - } - - private boolean drawTransit(int startMillis) { - if (drawTransitEdges) { - // transparent blue - stroke(40, 40, 128, 30); - strokeWeight(4); - noFill(); - // for (Edge e : visibleTransitEdges) { - while (drawOffset < visibleTransitEdges.size()) { - Edge e = visibleTransitEdges.get(drawOffset); - drawEdge(e); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private boolean drawLinks(int startMillis) { - if (drawLinkEdges) { - // transparent orange - stroke(256, 165, 0, 30); - strokeWeight(3); - noFill(); - // for (Edge e : visibleTransitEdges) { - while (drawOffset < visibleLinkEdges.size()) { - Edge e = visibleLinkEdges.get(drawOffset); - drawEdge(e); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private boolean drawAll(int startMillis) { - if (drawOffset == 0) { - findVisibleElements(); - background(15); - } - if (drawStreetEdges) { - // dark green - stroke(30, 128, 30); - strokeWeight(1); - noFill(); - while (drawOffset < visibleStreetEdges.size()) { - drawEdge(visibleStreetEdges.get(drawOffset)); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private void drawPartial(int startMillis) { - background(15); - stroke(30, 128, 30); - strokeWeight(1); - noFill(); - // noSmooth(); - int drawIndex = 0; - int drawStart = 0; - int drawCount = 0; - while (drawStart < DECIMATE && drawStart < visibleStreetEdges.size()) { - if (drawFast) { - drawEdgeFast(visibleStreetEdges.get(drawIndex)); - } else { - drawEdge(visibleStreetEdges.get(drawIndex)); - } - drawIndex += DECIMATE; - drawCount += 1; - if (drawCount % BLOCK_SIZE == 0 && millis() - startMillis > FRAME_TIME) { - // ran out of time to draw this frame. - // enable fast-drawing when too few edges were drawn: - // drawFast = drawCount < visibleStreetEdges.size() / 10; - // leave edge drawing loop to let other work happen. - break; - } - if (drawIndex >= visibleStreetEdges.size()) { - // start over drawing every DECIMATEth edge, offset by 1 - drawStart += 1; - drawIndex = drawStart; - } - } - } - - private void handleNewHighlights() { - // fill(0, 0, 0, 1); - // rect(0,0,this.width, this.height); - desaturate(); - noFill(); - stroke(256, 0, 0, 128); - strokeWeight(6); - while (!newHighlightedEdges.isEmpty()) { - Edge de = newHighlightedEdges.poll(); - if (de != null) { - drawEdge(de); - highlightedEdges.add(de); - } - } - if (VIDEO) { - saveVideoFrame(); - } - } - - private void saveVideoFrame() { - save(VIDEO_PATH + "/" + videoFrameNumber++ + ".bmp"); - } - - private void resetVideoFrameNumber() { - videoFrameNumber = 0; - } - - private void desaturate() { - final float f = 8; - loadPixels(); - for (int i = 0; i < width * height; i++) { - int c = pixels[i]; - float r = red(c); - float g = green(c); - float b = blue(c); - float avg = (r + g + b) / 3; - r += (avg - r) / f; - g += (avg - g) / f; - b += (avg - b) / f; - pixels[i] = color(r, g, b); - } - updatePixels(); - } - - private double toScreenY(double y) { - return map( - (float) y, - (float) modelBounds.getMinY(), - (float) modelBounds.getMaxY(), - getSize().height, - 0 - ); - } - - private double toScreenX(double x) { - return map( - (float) x, - (float) modelBounds.getMinX(), - (float) modelBounds.getMaxX(), - 0, - getSize().width - ); - } - - private double toModelY(double y) { - return map( - (float) y, - 0, - getSize().height, - (float) modelBounds.getMaxY(), - (float) modelBounds.getMinY() - ); - } - - private double toModelX(double x) { - return map( - (float) x, - 0, - getSize().width, - (float) modelBounds.getMinX(), - (float) modelBounds.getMaxX() - ); - } - - /** - * A version of ellipse that takes double args, because apparently Java is too stupid to downgrade - * automatically. - */ - private void ellipse(double d, double e, double f, double g) { - ellipse((float) d, (float) e, (float) f, (float) g); - } - - static class Trunk { - - public Edge edge; - public Double trunkiness; - - Trunk(Edge edge, Double trunkiness) { - this.edge = edge; - this.trunkiness = trunkiness; - } - } - - class SimpleSPT { - - private final HashMap nodes; - SPTNode root; - - SimpleSPT() { - nodes = new HashMap<>(); - } - - public void add(State state) { - // create simpleSPT entry - SPTNode curNode = new SPTNode(state); - SPTNode parentNode = this.nodes.get(state.getBackState()); - if (parentNode != null) { - parentNode.children.add(curNode); - } else { - root = curNode; - } - curNode.parent = parentNode; - this.nodes.put(state, curNode); - } - - public void draw() { - if (root == null) { - return; - } - - HashMap vertexHeight = new HashMap<>(); - - root.drawRecursive(0, vertexHeight); - } - - public LinkedBlockingQueue getEdgeQueue() { - LinkedBlockingQueue ret = new LinkedBlockingQueue<>(); - if (root != null) { - root.addToEdgeQueue(ret); - } - return ret; - } - - void setWeights() { - if (root == null) { - return; - } - root.setWeight(); - } - } - - class SPTNode { - - // this is a tool for the traverse visitor to build a very simple - // shortest path tree, which we can use to come up with the trunkiness - // of every SPT edge. - - State state; - SPTNode parent; - List children; - double weight = 0.0; - public Integer height; - - SPTNode(State state) { - this.state = state; - this.height = null; - this.children = new ArrayList<>(); - } - - public void addToEdgeQueue(LinkedBlockingQueue ret) { - ret.add(this); - for (SPTNode child : children) { - child.addToEdgeQueue(ret); - } - } - - public void drawRecursive(int height, HashMap vertexStatesEncountered) { - colorMode(HSB); - - // get the number of states we've already drawn from this vertex - Integer vertexHeight = vertexStatesEncountered.get(this.state.getVertex()); - if (vertexHeight == null) { - vertexHeight = 0; - } - - // if it's larger than the 'height' of the state we're about to draw, bump the state's visual height - // up to the number of states it has to climb over - if (vertexHeight > height) { - height = vertexHeight; - } - - // increment the counter of the number of times we've encountered this vertex - vertexStatesEncountered.put(this.state.getVertex(), vertexHeight + 1); - - if (state.getBackEdge() != null) { - //stroke( colorRamp( (int)(state.getWeight()/10.0) ) ); - stroke(color((height * 10) % 255, 255, 255)); - - strokeWeight((float) (sptThickness * Math.pow(weight, sptFlattening))); - drawEdge(state.getBackEdge()); - } - - for (SPTNode child : children) { - child.drawRecursive(height, vertexStatesEncountered); - } - - colorMode(RGB); - } - - public void draw(List colors) { - colorMode(HSB); - - if (state.getBackEdge() != null) { - //stroke( colorRamp( (int)(state.getWeight()/10.0) ) ); - strokeWeight((float) (sptThickness * Math.pow(weight, sptFlattening))); - - stroke(colors.get(this.height)); - - drawEdge(state.getBackEdge()); - } - - colorMode(RGB); - } - - public void setWeight() { - weight = state.getWeight(); - for (SPTNode child : children) { - child.setWeight(); - weight += child.weight; - } - } - - public void setHeight(Integer height) { - this.height = height; - } - - void addChild(SPTNode child) { - this.children.add(child); - } - - private int colorRamp(int aa) { - int NHUES = 6; - int HUELEN = 256; - int RAMPLEN = NHUES * HUELEN; - int BRIGHTNESS = 220; - - // make sure aa fits within the color ramp - aa = aa % RAMPLEN; - // establish the hue - int hueIndex = aa / HUELEN; - // convert that to a hue value - int hue = hueIndex * (HUELEN / NHUES); - int saturation = HUELEN - (aa % HUELEN); - - return color(hue, saturation, BRIGHTNESS); - } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java b/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java deleted file mode 100644 index 6e91494b4d6..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.util.List; -import org.opentripplanner.street.model.vertex.Vertex; - -/** - * An interface allowing a map UI element to report that the user has selected vertices. - */ -public interface VertexSelectionListener { - void verticesSelected(List selected); -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java b/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java deleted file mode 100644 index 04add0fe46f..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.opentripplanner.visualizer; - -import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.search.state.State; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class VisualTraverseVisitor implements TraverseVisitor { - - private static final Logger LOG = LoggerFactory.getLogger(VisualTraverseVisitor.class); - - private final ShowGraph gui; - - private final int SLEEP_AFTER = 50; - private final int SLEEP_LEN = 2; - - private int sleepAfter = SLEEP_AFTER; - - public VisualTraverseVisitor(ShowGraph gui) { - this.gui = gui; - } - - @Override - public void visitEdge(Edge edge) { - gui.enqueueHighlightedEdge(edge); - //gui.highlightVertex(state.getVertex()); - } - - @Override - public void visitVertex(State state) { - // every SLEEP_AFTER visits of a vertex, sleep for SLEEP_LEN - // this slows down the search so it animates prettily - if (--sleepAfter <= 0) { - sleepAfter = SLEEP_AFTER; - try { - Thread.sleep(SLEEP_LEN); - } catch (InterruptedException e) { - LOG.warn("interrupted", e); - } - } - gui.addNewSPTEdge(state); - } - - @Override - public void visitEnqueue() { - // Edge e = state.getBackEdge(); - // if (e instanceof Edge) { - // gui.enqueueHighlightedEdge((Edge) e); - // } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/package-info.md b/application/src/main/java/org/opentripplanner/visualizer/package-info.md deleted file mode 100644 index f1b60dde32f..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/package-info.md +++ /dev/null @@ -1,5 +0,0 @@ -# Graph visualizer - -This package contains classes used for visualizing OpenTripPlanner graphs. This graph visualizer -is intended for debugging purposes and may therefore have arcane developer-oriented features and -grow new UI components as needed. diff --git a/application/src/test/java/org/opentripplanner/TestServerContext.java b/application/src/test/java/org/opentripplanner/TestServerContext.java index 97cf1cbc881..1b16288bdcf 100644 --- a/application/src/test/java/org/opentripplanner/TestServerContext.java +++ b/application/src/test/java/org/opentripplanner/TestServerContext.java @@ -151,7 +151,6 @@ public static OtpServerRequestContext createServerContext( null, null, null, - null, null ); } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java index cd55843943a..ee60230ff47 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java @@ -277,7 +277,7 @@ private void testBuildingAreas(boolean skipVisibility) { Vertex bottomV = graph.getVertex(VertexLabel.osm(580290955)); Vertex topV = graph.getVertex(VertexLabel.osm(559271124)); - GraphPathFinder graphPathFinder = new GraphPathFinder(null); + GraphPathFinder graphPathFinder = new GraphPathFinder(); var pathList = graphPathFinder.graphPathFinderEntryPoint( request, Set.of(bottomV), diff --git a/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java index f28f4412b1e..c889b184f7c 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java @@ -196,7 +196,7 @@ private static String computePolyline( var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java index e9f28b66a9e..46abc64884f 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java @@ -97,7 +97,7 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java index c09f67b50c4..843f0209c68 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java @@ -146,7 +146,7 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java b/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java index 8def307cdd1..5d168642719 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java @@ -178,7 +178,7 @@ private static String computeCarPolyline(Graph graph, GenericLocation from, Gene var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java index fab37f07aa5..8029da967ac 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java @@ -99,7 +99,7 @@ private static List route( var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); return gpf.graphPathFinderEntryPoint(request, linkingContext); } } diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index b62778c4ff4..9006c7528d1 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -161,7 +161,6 @@ public SpeedTest( null, null, null, - null, null ); // Creating raptor transit data should be integrated into the TimetableRepository, but for now diff --git a/doc/user/Frontends.md b/doc/user/Frontends.md index 1482a2a229c..51887042fa1 100644 --- a/doc/user/Frontends.md +++ b/doc/user/Frontends.md @@ -22,12 +22,6 @@ OpenTripPlanner comes with an integrated debug web frontend. The frontend source The current debug client is a React/TypeScript Single Page App (SPA) that can be served locally or accessed over a content delivery network (CDN). Unlike the original debug client, it connects to the OTP Java backend via the GraphQL API using the Transmodel vocabulary. By default, it is available at the root URL (`http://localhost:8080/` in local operation). -There is another of software that might qualify as an OTP client: a Java Swing application making use of the Processing visualization library, -located in the [GraphVisualizer class](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java). -While it would not be accurate to call this a "native" desktop application (as it's cross-platform Java) it is not a web app. This very developer-centric -UI is also over a decade old and has been very sparsely maintained, but continues to exist because it can visualize the progress of searches through the -street network, providing some insight into the internals of the routing algorithms that are not otherwise visible. - ## Working with the Debug Frontend While the debug frontend is enabled by default as of this writing, it may not be in the future, and you may wish to disable it if you've chosen to use a different frontend. From 48bec5ecee37affc406ae8559550dbfc91e5fed3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 11:28:01 +0200 Subject: [PATCH 044/177] Remove inefficient methods from ShortestPathTree --- .../astar/model/ShortestPathTree.java | 46 ------------------- .../astar/model/ShortestPathTreeTest.java | 11 ----- 2 files changed, 57 deletions(-) diff --git a/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java b/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java index 20320d5b93b..c6a5f8fef23 100644 --- a/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java +++ b/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java @@ -2,9 +2,7 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.AStarEdge; @@ -45,29 +43,6 @@ public ShortestPathTree(DominanceFunction dominanceFunction) { stateSets = new SegmentedIdentityMap<>(25_000); } - /** @return a list of GraphPaths, sometimes empty but never null. */ - @SuppressWarnings("unchecked") - public List> getPaths(Vertex dest) { - Object existing = stateSets.get(dest); - if (existing == null) { - return Collections.emptyList(); - } - List> ret = new LinkedList<>(); - if (!(existing instanceof List)) { - State s = (State) existing; - if (s.isFinal()) { - ret.add(new GraphPath<>(s)); - } - } else { - for (State s : (List) existing) { - if (s.isFinal()) { - ret.add(new GraphPath<>(s)); - } - } - } - return ret; - } - /** @return a single optimal, optionally back-optimized path to the given vertex. */ public GraphPath getPath(Vertex dest) { State s = getState(dest); @@ -162,27 +137,6 @@ public State getState(Vertex dest) { return ret; } - /** - * Returns a collection of 'interesting' states for the given Vertex. Depending on the - * implementation, this could contain a single optimal state, a set of Pareto-optimal states, or - * even states that are not known to be optimal but are judged interesting by some other - * criteria. - * - * @param dest the vertex of interest - * @return a collection of 'interesting' states at that vertex - */ - @SuppressWarnings("unchecked") - public List getStates(Vertex dest) { - Object existing = stateSets.get(dest); - if (existing == null) { - return null; - } - if (existing instanceof List) { - return (List) existing; - } - return List.of((State) existing); - } - /** * The visit method should be called upon extracting a State from a priority queue. It checks * whether the State is still worth visiting (i.e. whether it has been dominated since it was diff --git a/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java b/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java index 37c97fd2370..148f1311496 100644 --- a/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java +++ b/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java @@ -7,7 +7,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; -import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.TestState; import org.opentripplanner.astar.TestVertex; @@ -31,7 +30,6 @@ void singleState() { assertTrue(spt.add(s)); assertTrue(spt.visit(s)); assertEquals(s, spt.getState(v)); - assertEquals(List.of(s), spt.getStates(v)); } @Test @@ -46,7 +44,6 @@ void dominatedStateReplacement() { assertFalse(spt.visit(worse)); assertTrue(spt.visit(better)); assertEquals(better, spt.getState(v)); - assertEquals(List.of(better), spt.getStates(v)); } @Test @@ -58,7 +55,6 @@ void dominatedStateRejected() { assertTrue(spt.add(better)); assertFalse(spt.add(worse)); - assertEquals(List.of(better), spt.getStates(v)); } @Test @@ -72,7 +68,6 @@ void coDominantStates() { assertTrue(spt.add(s2)); assertTrue(spt.visit(s1)); assertTrue(spt.visit(s2)); - assertEquals(List.of(s1, s2), spt.getStates(v)); } @Test @@ -88,12 +83,6 @@ void visitReturnsFalseForAbsentVertex() { assertFalse(spt.visit(s2)); } - @Test - void getStatesNullForAbsentVertex() { - var spt = new ShortestPathTree<>(BY_WEIGHT); - assertNull(spt.getStates(new TestVertex())); - } - @Test void getStateNullForAbsentVertex() { var spt = new ShortestPathTree<>(BY_WEIGHT); From b37c4e3b2fa1424ef872d639dd9bd613c0b605ca Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 16:43:13 +0200 Subject: [PATCH 045/177] Remove empty files --- .../main/java/org/opentripplanner/visualizer/GraphVisualizer.java | 0 .../src/main/java/org/opentripplanner/visualizer/ShowGraph.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java delete mode 100644 application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java diff --git a/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java b/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java b/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java deleted file mode 100644 index e69de29bb2d..00000000000 From 249b6413d5671e5f4944f2983e1ebc092f854c41 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Apr 2026 17:23:54 +0200 Subject: [PATCH 046/177] Remove imports --- .../org/opentripplanner/routing/impl/GraphPathFinder.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index fd39e783543..3bf8a27b1ba 100644 --- a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -4,9 +4,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; -import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.routing.api.request.RouteRequest; @@ -14,13 +11,11 @@ import org.opentripplanner.routing.error.PathNotFoundException; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.street.model.StreetConstants; -import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.ExtensionRequestContext; import org.opentripplanner.street.model.path.StreetPath; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.EuclideanRemainingWeightHeuristic; import org.opentripplanner.street.search.StreetSearchBuilder; -import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.streetadapter.StreetSearchRequestMapper; import org.slf4j.Logger; From 16e3bff8d34867c512816871e76fa62412204919 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 15 Apr 2026 11:51:35 +0200 Subject: [PATCH 047/177] Remove unused method --- .../issue/api/DataImportIssue.java | 20 ------------------- .../graph_builder/issues/IsolatedStop.java | 6 ------ .../issues/StopNotLinkedForTransfers.java | 6 ------ .../module/islandpruning/GraphIsland.java | 5 ----- .../islandpruning/PrunedStopIsland.java | 6 ------ 5 files changed, 43 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java b/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java index 45a61cf8a3f..b3b74179e3a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java @@ -1,8 +1,6 @@ package org.opentripplanner.graph_builder.issue.api; import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.Vertex; /** * Represents noteworthy data import issues that occur during the graph building process. These @@ -47,22 +45,4 @@ default String getHTMLMessage() { default Geometry getGeometry() { return null; } - - /** - * @deprecated This is used in the {@link org.opentripplanner.visualizer.ShowGraph} only, which - * status is unclear. Is anyone still using it? - */ - @Deprecated - default Edge getReferencedEdge() { - return null; - } - - /** - * @deprecated This is used in the {@link org.opentripplanner.visualizer.ShowGraph} only, which - * status is unclear. Is anyone still using it? - */ - @Deprecated - default Vertex getReferencedVertex() { - return null; - } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java b/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java index 005b143a3fb..2533904c465 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java @@ -7,7 +7,6 @@ import org.opentripplanner.graph_builder.issue.api.OsmUrlGenerator; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.utils.time.DurationUtils; public record IsolatedStop(TransitStopVertex vertex, Duration maxWalk) implements DataImportIssue { @@ -28,11 +27,6 @@ public int getPriority() { return (int) maxWalk.toSeconds(); } - @Override - public Vertex getReferencedVertex() { - return vertex; - } - @Override public Geometry getGeometry() { return GeometryUtils.getGeometryFactory().createPoint(vertex.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java b/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java index ed2bdb06e61..ad3b93b2477 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java @@ -4,7 +4,6 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.Vertex; public record StopNotLinkedForTransfers(TransitStopVertex stop) implements DataImportIssue { private static final String FMT = "Stop %s not near any other stops; no transfers are possible."; @@ -28,11 +27,6 @@ public String getHTMLMessage() { ); } - @Override - public Vertex getReferencedVertex() { - return this.stop; - } - @Override public Geometry getGeometry() { return GeometryUtils.getGeometryFactory().createPoint(stop.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java index e484a6fc185..8390bad3420 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java @@ -65,11 +65,6 @@ public int getPriority() { return island.streetSize() + island.stopSize(); } - @Override - public Vertex getReferencedVertex() { - return island.getRepresentativeVertex(); - } - @Override public Geometry getGeometry() { return island.getGeometry(); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java index eb7961c3865..d9bb15f958f 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java @@ -2,7 +2,6 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.model.vertex.VertexLabel; public record PrunedStopIsland( @@ -57,11 +56,6 @@ public int getPriority() { return island.streetSize() + island.stopSize(); } - @Override - public Vertex getReferencedVertex() { - return island.getRepresentativeVertex(); - } - @Override public Geometry getGeometry() { return island.getGeometry(); From ef787672a529559852d01253c855e1b65d083635 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 10 Apr 2026 16:43:19 +0300 Subject: [PATCH 048/177] Add startupRetryPeriod to VehicleRentalUpdaterConfig. --- .../VehicleRentalParameters.java | 3 ++- .../VehicleRentalServiceDirectoryFetcher.java | 2 ++ .../config/framework/json/OtpVersion.java | 3 ++- .../updaters/VehicleRentalUpdaterConfig.java | 15 +++++++++++++++ .../vehicle_rental/VehicleRentalUpdater.java | 16 ++++++++++++++-- .../VehicleRentalUpdaterParameters.java | 10 ++++++++++ .../vehicle_rental/VehicleRentalUpdaterTest.java | 1 + doc/user/GBFS-Config.md | 13 +++++++++++++ 8 files changed, 59 insertions(+), 4 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java index 4fb1a06cda2..85cdae8a817 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java +++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java @@ -9,8 +9,9 @@ public class VehicleRentalParameters extends VehicleRentalUpdaterParameters { public VehicleRentalParameters( String configRef, Duration frequency, + Duration startupRetryPeriod, VehicleRentalDataSourceParameters sourceParameters ) { - super(configRef, frequency, sourceParameters); + super(configRef, frequency, startupRetryPeriod, sourceParameters); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java index 18b574d8c0e..90513d0adcf 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java +++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java @@ -39,6 +39,7 @@ public class VehicleRentalServiceDirectoryFetcher { VehicleRentalServiceDirectoryFetcher.class ); private static final Duration DEFAULT_FREQUENCY = Duration.ofSeconds(15); + private static final Duration DEFAULT_STARTUP_RETRY_PERIOD = Duration.ofSeconds(0); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -170,6 +171,7 @@ private VehicleRentalUpdater fetchAndCreateUpdater( VehicleRentalParameters vehicleRentalParameters = new VehicleRentalParameters( "vehicle-rental-service-directory:" + parameters.network(), DEFAULT_FREQUENCY, + DEFAULT_STARTUP_RETRY_PERIOD, parameters ); diff --git a/application/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java b/application/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java index 9e4bff7ecf7..1a07c9ff605 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java @@ -14,7 +14,8 @@ public enum OtpVersion { V2_6("2.6"), V2_7("2.7"), V2_8("2.8"), - V2_9("2.9"); + V2_9("2.9"), + V2_10("2.10"); private final String text; diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java index 81416ac7d78..b40c41064b9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V1_5; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -23,6 +24,20 @@ public static VehicleRentalUpdaterParameters create(String configRef, NodeAdapte .since(V1_5) .summary("How often the data should be updated.") .asDuration(Duration.ofMinutes(1)), + c + .of("startupRetryPeriod") + .since(V2_10) + .summary( + "How long to retry loading the vehicle rental data source on startup if it initially fails." + ) + .description( + """ + The first time the data source is loaded, OTP will retry for this duration every + 5 seconds before giving up. This is useful to handle temporary network failures during + OTP startup. Set to `PT0S` to disable retries. + """ + ) + .asDuration(Duration.ofSeconds(15)), VehicleRentalSourceFactory.create(sourceType, c) ); } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java index 59325c67636..e677cb14a27 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java @@ -12,6 +12,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.framework.retry.OtpRetry; +import org.opentripplanner.framework.retry.OtpRetryBuilder; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.vehiclerental.model.GeofencingZone; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; @@ -46,6 +48,8 @@ public class VehicleRentalUpdater extends PollingGraphUpdater { private static final Logger LOG = LoggerFactory.getLogger(VehicleRentalUpdater.class); + private static final Duration RETRY_INTERVAL = Duration.ofSeconds(5); + private static final int RETRY_BACKOFF_MULTIPLIER = 1; private final Throttle unlinkedPlaceThrottle; @@ -83,10 +87,18 @@ public VehicleRentalUpdater( // Adding a vehicle rental station service needs a graph writer runnable this.service = repository; + OtpRetry retry = new OtpRetryBuilder() + .withName("%s updater setup".formatted(nameForLogging)) + .withMaxAttempts((int) parameters.startupRetryPeriod().dividedBy(RETRY_INTERVAL)) + .withInitialRetryInterval(RETRY_INTERVAL) + .withBackoffMultiplier(RETRY_BACKOFF_MULTIPLIER) + .withRetryableException(UpdaterConstructionException.class::isInstance) + .build(); + try { // Do any setup if needed - source.setup(); - } catch (UpdaterConstructionException e) { + retry.execute(source::setup); + } catch (InterruptedException e) { LOG.warn("Unable to setup updater: {}", nameForLogging, e); } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java index d21f3d2dcef..6e0d5f55647 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java @@ -8,15 +8,18 @@ public class VehicleRentalUpdaterParameters implements PollingGraphUpdaterParame private final String configRef; private final Duration frequency; + private final Duration startupRetryPeriod; private final VehicleRentalDataSourceParameters source; public VehicleRentalUpdaterParameters( String configRef, Duration frequency, + Duration startupRetryPeriod, VehicleRentalDataSourceParameters source ) { this.configRef = configRef; this.frequency = frequency; + this.startupRetryPeriod = startupRetryPeriod; this.source = source; } @@ -33,6 +36,13 @@ public String configRef() { return configRef; } + /** + * How long to retry loading the vehicle rental data source on startup if it initially fails. + */ + public Duration startupRetryPeriod() { + return startupRetryPeriod; + } + public VehicleRentalDataSourceParameters sourceParameters() { return source; } diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java index 47f13569b30..de6f4a330fe 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java @@ -30,6 +30,7 @@ class VehicleRentalUpdaterTest { public static final VehicleRentalUpdaterParameters PARAMS = new VehicleRentalUpdaterParameters( "A", Duration.ofMinutes(1), + Duration.ofSeconds(0), new FakeParams() ); public static final DefaultVehicleRentalService SERVICE = new DefaultVehicleRentalService(); diff --git a/doc/user/GBFS-Config.md b/doc/user/GBFS-Config.md index 1b68fffba9a..709ccf75421 100644 --- a/doc/user/GBFS-Config.md +++ b/doc/user/GBFS-Config.md @@ -30,6 +30,7 @@ Furthermore, support is limited to the following form factors: | [network](#u_1_network) | `string` | The name of the network to override the one derived from the source data. | *Optional* | | 1.5 | | overloadingAllowed | `boolean` | Allow leaving vehicles at a station even though there are no free slots. | *Optional* | `false` | 2.2 | | [sourceType](#u_1_sourceType) | `enum` | What source of vehicle rental updater to use. | *Required* | | 1.5 | +| [startupRetryPeriod](#u_1_startupRetryPeriod) | `duration` | How long to retry loading the vehicle rental data source on startup if it initially fails. | *Optional* | `"PT15S"` | 2.10 | | url | `string` | The URL to download the data from. | *Required* | | 1.5 | | [headers](#u_1_headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 1.5 | | [rentalPickupTypes](#u_1_rentalPickupTypes) | `enum set` | This is temporary and will be removed in a future version of OTP. Use this to specify the type of rental data that is allowed to be read from the data source. | *Optional* | | 2.7 | @@ -84,6 +85,18 @@ GBFS feeds must include a system_id which will be used as the default `network`. What source of vehicle rental updater to use. +

startupRetryPeriod

+ +**Since version:** `2.10` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT15S"` +**Path:** /updaters/[1] + +How long to retry loading the vehicle rental data source on startup if it initially fails. + +The first time the data source is loaded, OTP will retry for this duration every +5 seconds before giving up. This is useful to handle temporary network failures during +OTP startup. Set to `PT0S` to disable retries. + +

headers

**Since version:** `1.5` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` From 0ec5bebf5c90cd4e1b1f7b2db95d2a1951016f8d Mon Sep 17 00:00:00 2001 From: Max Gosau Date: Wed, 15 Apr 2026 17:52:03 +0200 Subject: [PATCH 049/177] Remove skip condition for already-linked transit stops in `OsmBoardingLocationsModule` --- .../graph_builder/module/OsmBoardingLocationsModule.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index 948904d6e45..82ba3a947cf 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -31,7 +31,6 @@ import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.edge.StreetEdgeBuilder; -import org.opentripplanner.street.model.edge.StreetTransitStopLink; import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; @@ -104,11 +103,6 @@ public void buildGraph() { int successes = 0; for (TransitStopVertex ts : graph.getVerticesOfType(TransitStopVertex.class)) { - // if the street is already linked there is no need to link it again, - // could happen if using the prune isolated island - if (ts.getOutgoing().stream().anyMatch(StreetTransitStopLink.class::isInstance)) { - continue; - } // only connect transit stops that are not part of a pathway network if (!ts.hasPathways()) { var stop = stopResolver.getStop(ts.getId()); From f630a9a606e3269cc2278aef62916257d5c92657 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 16 Apr 2026 11:12:46 +0200 Subject: [PATCH 050/177] Add application warmup feature to run routing queries during startup Add a configurable warmup feature that runs GraphQL transit routing queries in a background daemon thread during OTP startup. This warms up JIT compilation, GraphQL schema caches, Raptor transfer cache, and other lazily initialized components before production traffic arrives. - WarmupConfig parses the "warmup" section in router-config.json with configurable API (TransModel/GTFS), origin/destination coordinates, and parallel access/egress mode lists (validated for equal length) - WarmupWorker runs sequential queries in a daemon thread, alternating depart-at/arrive-by and cycling through mode combinations in config order, stopping when all updaters are primed or after 20 queries - TransmodelWarmupQueryExecutor and GtfsWarmupQueryExecutor implement the WarmupQueryExecutor strategy interface, using GraphQL variables - WarmupConfigTest covers config parsing and validation - WarmupQueryValidationTest validates queries against actual schemas --- .../standalone/config/RouterConfig.java | 9 + .../config/routerconfig/WarmupConfig.java | 183 +++++++++++++ .../configure/ConstructApplication.java | 8 + .../warmup/GtfsWarmupQueryExecutor.java | 123 +++++++++ .../warmup/TransmodelWarmupQueryExecutor.java | 133 ++++++++++ .../configure/warmup/WarmupQueryExecutor.java | 12 + .../configure/warmup/WarmupWorker.java | 164 ++++++++++++ .../standalone/configure/warmup/package.md | 28 ++ .../config/routerconfig/WarmupConfigTest.java | 55 ++++ .../warmup/WarmupQueryValidationTest.java | 56 ++++ .../standalone/config/router-config.json | 7 + doc/user/RouterConfiguration.md | 244 ++++++++++++++---- 12 files changed, 977 insertions(+), 45 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md create mode 100644 application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java create mode 100644 application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java diff --git a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java index 5f88433c771..e92b10849c9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.node.MissingNode; import java.io.Serializable; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GtfsApiParameters; import org.opentripplanner.ext.flex.FlexParameters; import org.opentripplanner.ext.ojp.config.OjpApiConfig; @@ -22,6 +23,7 @@ import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; import org.opentripplanner.standalone.config.routerconfig.UpdatersConfig; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; +import org.opentripplanner.standalone.config.routerconfig.WarmupConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.standalone.config.sandbox.GtfsApiConfig; import org.opentripplanner.standalone.config.sandbox.TransmodelAPIConfig; @@ -58,6 +60,7 @@ public class RouterConfig implements Serializable { private final VectorTileConfig vectorTileConfig; private final TriasApiParameters triasApiParameters; private final OjpApiParameters ojpApiParameters; + private final WarmupConfig warmupConfig; public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this(new NodeAdapter(node, source), logUnusedParams); @@ -88,6 +91,7 @@ public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this.triasApiParameters = TriasApiConfig.mapParameters("triasApi", root); this.ojpApiParameters = OjpApiConfig.mapParameters("ojpApi", root); this.flexConfig = new FlexConfig(root, "flex"); + this.warmupConfig = WarmupConfig.mapWarmupConfig("warmup", root); if (logUnusedParams && LOG.isWarnEnabled()) { root.logAllWarnings(LOG::warn); @@ -158,6 +162,11 @@ public GtfsApiParameters gtfsApiParameters() { return gtfsApi; } + @Nullable + public WarmupConfig warmupConfig() { + return warmupConfig; + } + public NodeAdapter asNodeAdapter() { return root; } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java new file mode 100644 index 00000000000..84ed5efa945 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -0,0 +1,183 @@ +package org.opentripplanner.standalone.config.routerconfig; + +import static org.opentripplanner.standalone.config.framework.json.EnumMapper.docEnumValueList; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; + +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.core.model.doc.DocumentedEnum; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; + +/** + * Configuration for application warmup during startup. When configured, OTP runs transit routing + * queries between the given locations during startup to warm up the application (JIT compilation, + * GraphQL schema caches, routing data structures, etc.) before production traffic arrives. + *

+ * Queries start after the Raptor transit data is created and stop when all updaters are + * primed (the health endpoint would return "UP"). If no updaters are configured, no warmup + * queries are run. + */ +public final class WarmupConfig { + + public enum Api implements DocumentedEnum { + TRANSMODEL("Use the TransModel GraphQL API for warmup queries."), + GTFS("Use the GTFS GraphQL API for warmup queries."); + + private final String description; + + Api(String description) { + this.description = description; + } + + @Override + public String typeDescription() { + return "Which GraphQL API to use for warmup queries."; + } + + @Override + public String enumValueDescription() { + return description; + } + } + + private static final List DEFAULT_ACCESS_MODES = List.of( + StreetMode.WALK, + StreetMode.CAR_TO_PARK + ); + private static final List DEFAULT_EGRESS_MODES = List.of( + StreetMode.WALK, + StreetMode.WALK + ); + + private final Api api; + private final WgsCoordinate from; + private final WgsCoordinate to; + private final List accessModes; + private final List egressModes; + + private WarmupConfig( + Api api, + WgsCoordinate from, + WgsCoordinate to, + List accessModes, + List egressModes + ) { + this.api = api; + this.from = from; + this.to = to; + this.accessModes = accessModes; + this.egressModes = egressModes; + } + + @Nullable + public static WarmupConfig mapWarmupConfig(String parameterName, NodeAdapter root) { + if (!root.exist(parameterName)) { + return null; + } + + var c = root + .of(parameterName) + .since(V2_10) + .summary("Configure application warmup by running transit searches during startup.") + .description( + """ + When configured, OTP runs transit routing queries between the given locations + during startup. This warms up the application (JIT compilation, GraphQL schema + caches, routing data structures, etc.) before production traffic arrives. + Queries start after the Raptor transit data is created and stop when all updaters + are primed (the health endpoint would return "UP"). + If no updaters are configured, no warmup queries are run. + """ + ) + .asObject(); + + Api api = c + .of("api") + .since(V2_10) + .summary("Which GraphQL API to use for warmup queries.") + .description(docEnumValueList(Api.values())) + .asEnum(Api.TRANSMODEL); + + var from = c.of("from").since(V2_10).summary("Origin location for warmup searches.").asObject(); + double fromLat = from.of("lat").since(V2_10).summary("Latitude of the origin.").asDouble(); + double fromLng = from.of("lon").since(V2_10).summary("Longitude of the origin.").asDouble(); + + var to = c + .of("to") + .since(V2_10) + .summary("Destination location for warmup searches.") + .asObject(); + double toLat = to.of("lat").since(V2_10).summary("Latitude of the destination.").asDouble(); + double toLng = to.of("lon").since(V2_10).summary("Longitude of the destination.").asDouble(); + + var accessModeStrings = c + .of("accessModes") + .since(V2_10) + .summary("Access modes to cycle through in warmup queries.") + .description( + "Ordered list of `StreetMode` values used as access modes. " + + "Each entry is paired with the egress mode at the same index. " + + docEnumValueList(StreetMode.values()) + ) + .asStringList(DEFAULT_ACCESS_MODES.stream().map(Enum::name).toList()); + List accessModes = accessModeStrings + .stream() + .map(s -> StreetMode.valueOf(s)) + .toList(); + + var egressModeStrings = c + .of("egressModes") + .since(V2_10) + .summary("Egress modes to cycle through in warmup queries.") + .description( + "Ordered list of `StreetMode` values used as egress modes. " + + "Each entry is paired with the access mode at the same index. " + + docEnumValueList(StreetMode.values()) + ) + .asStringList(DEFAULT_EGRESS_MODES.stream().map(Enum::name).toList()); + List egressModes = egressModeStrings + .stream() + .map(s -> StreetMode.valueOf(s)) + .toList(); + + if (accessModes.size() != egressModes.size()) { + throw new IllegalArgumentException( + "warmup.accessModes and warmup.egressModes must have the same number of entries, " + + "got %d access modes and %d egress modes.".formatted( + accessModes.size(), + egressModes.size() + ) + ); + } + + return new WarmupConfig( + api, + new WgsCoordinate(fromLat, fromLng), + new WgsCoordinate(toLat, toLng), + accessModes, + egressModes + ); + } + + public Api api() { + return api; + } + + public WgsCoordinate from() { + return from; + } + + public WgsCoordinate to() { + return to; + } + + public List accessModes() { + return accessModes; + } + + public List egressModes() { + return egressModes; + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 73cc75f1a1c..594d7734bbd 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -34,6 +34,7 @@ import org.opentripplanner.standalone.config.DebugUiConfig; import org.opentripplanner.standalone.config.OtpConfig; import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.standalone.configure.warmup.WarmupWorker; import org.opentripplanner.standalone.server.GrizzlyServer; import org.opentripplanner.standalone.server.OTPWebApplication; import org.opentripplanner.street.StreetRepository; @@ -212,6 +213,13 @@ private void setupTransitRoutingServer() { routerConfig().updaterConfig() ); + // Start application warmup — runs routing queries to warm up the application + WarmupWorker.start( + routerConfig().warmupConfig(), + () -> factory.createServerContext(), + () -> timetableRepository().getUpdaterManager() + ); + initEllipsoidToGeoidDifference(); initializeTransferCache(routerConfig().transitTuningConfig(), timetableRepository()); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java new file mode 100644 index 00000000000..f92d927db47 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java @@ -0,0 +1,123 @@ +package org.opentripplanner.standalone.configure.warmup; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.mapping.routerequest.AccessModeMapper; +import org.opentripplanner.apis.gtfs.mapping.routerequest.EgressModeMapper; +import org.opentripplanner.apis.support.graphql.OtpDataFetcherExceptionHandler; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class GtfsWarmupQueryExecutor implements WarmupQueryExecutor { + + private static final Logger LOG = LoggerFactory.getLogger(GtfsWarmupQueryExecutor.class); + + static final String QUERY = """ + query( + $fromLat: CoordinateValue!, $fromLon: CoordinateValue!, + $toLat: CoordinateValue!, $toLon: CoordinateValue!, + $dateTime: PlanDateTimeInput!, + $accessMode: PlanAccessMode!, $egressMode: PlanEgressMode! + ) { + planConnection( + origin: { location: { coordinate: { latitude: $fromLat, longitude: $fromLon } } } + destination: { location: { coordinate: { latitude: $toLat, longitude: $toLon } } } + dateTime: $dateTime + modes: { transit: { access: [$accessMode], egress: [$egressMode] } } + ) { + edges { + node { + start + end + legs { + mode + duration + from { name lat lon } + to { name lat lon } + route { shortName } + legGeometry { points } + } + } + } + } + } + """; + + private final GraphQL graphQL; + private final GraphQLRequestContext requestContext; + private final List accessModes; + private final List egressModes; + + GtfsWarmupQueryExecutor( + OtpServerRequestContext context, + List accessModes, + List egressModes + ) { + this.requestContext = GraphQLRequestContext.ofServerContext(context); + this.graphQL = GraphQL.newGraphQL(context.gtfsSchema()) + .defaultDataFetcherExceptionHandler(new OtpDataFetcherExceptionHandler()) + .build(); + this.accessModes = accessModes + .stream() + .map(m -> AccessModeMapper.map(m).name()) + .toList(); + this.egressModes = egressModes + .stream() + .map(m -> EgressModeMapper.map(m).name()) + .toList(); + } + + @Override + public int modeCombinationCount() { + return accessModes.size(); + } + + @Override + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex) { + var variables = buildVariables(from, to, arriveBy, modeIndex); + + var input = ExecutionInput.newExecutionInput() + .query(QUERY) + .context(requestContext) + .variables(variables) + .locale(Locale.US) + .build(); + + var result = graphQL.execute(input); + if (!result.getErrors().isEmpty()) { + LOG.warn("Warmup query had GraphQL errors: {}", result.getErrors()); + return false; + } + return true; + } + + Map buildVariables( + WgsCoordinate from, + WgsCoordinate to, + boolean arriveBy, + int modeIndex + ) { + var now = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + var dateTime = arriveBy ? Map.of("latestArrival", now) : Map.of("earliestDeparture", now); + + return Map.ofEntries( + Map.entry("fromLat", from.latitude()), + Map.entry("fromLon", from.longitude()), + Map.entry("toLat", to.latitude()), + Map.entry("toLon", to.longitude()), + Map.entry("dateTime", dateTime), + Map.entry("accessMode", accessModes.get(modeIndex % accessModes.size())), + Map.entry("egressMode", egressModes.get(modeIndex % egressModes.size())) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java new file mode 100644 index 00000000000..b31781775a3 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java @@ -0,0 +1,133 @@ +package org.opentripplanner.standalone.configure.warmup; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import graphql.schema.GraphQLSchema; +import java.util.List; +import java.util.Map; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.AbortOnUnprocessableRequestExecutionStrategy; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class TransmodelWarmupQueryExecutor implements WarmupQueryExecutor { + + private static final Logger LOG = LoggerFactory.getLogger(TransmodelWarmupQueryExecutor.class); + + static final String QUERY = """ + query( + $fromLat: Float!, $fromLon: Float!, + $toLat: Float!, $toLon: Float!, + $arriveBy: Boolean!, + $accessMode: StreetMode!, $egressMode: StreetMode! + ) { + trip( + from: { coordinates: { latitude: $fromLat, longitude: $fromLon } } + to: { coordinates: { latitude: $toLat, longitude: $toLon } } + arriveBy: $arriveBy + modes: { accessMode: $accessMode, egressMode: $egressMode } + ) { + tripPatterns { + duration + legs { + mode + duration + fromPlace { name } + toPlace { name } + line { publicCode } + pointsOnLink { points } + } + } + } + } + """; + + private final OtpServerRequestContext serverContext; + private final TransmodelRequestContext requestContext; + private final GraphQLSchema schema; + private final List accessModes; + private final List egressModes; + + TransmodelWarmupQueryExecutor( + OtpServerRequestContext context, + List accessModes, + List egressModes + ) { + this.serverContext = context; + this.schema = context.transmodelSchema(); + this.requestContext = new TransmodelRequestContext( + context, + context.routingService(), + context.transitService(), + context.empiricalDelayService() + ); + this.accessModes = accessModes.stream().map(this::toGraphQLName).toList(); + this.egressModes = egressModes.stream().map(this::toGraphQLName).toList(); + } + + @Override + public int modeCombinationCount() { + return accessModes.size(); + } + + @Override + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex) { + var variables = buildVariables(from, to, arriveBy, modeIndex); + + var input = ExecutionInput.newExecutionInput() + .query(QUERY) + .context(requestContext) + .root(serverContext) + .variables(variables) + .build(); + + // The AbortOnUnprocessableRequestExecutionStrategy has per-query state + // (ProgressTracker) and must be created fresh for each execution. + try (var strategy = new AbortOnUnprocessableRequestExecutionStrategy()) { + var graphQL = GraphQL.newGraphQL(schema).queryExecutionStrategy(strategy).build(); + var result = graphQL.execute(input); + if (!result.getErrors().isEmpty()) { + LOG.warn("Warmup query had GraphQL errors: {}", result.getErrors()); + return false; + } + return true; + } + } + + private String toGraphQLName(StreetMode mode) { + return EnumTypes.STREET_MODE.getValues() + .stream() + .filter(v -> v.getValue() == mode) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No TransModel mapping for " + mode)) + .getName(); + } + + Map buildVariables( + WgsCoordinate from, + WgsCoordinate to, + boolean arriveBy, + int modeIndex + ) { + return Map.of( + "fromLat", + from.latitude(), + "fromLon", + from.longitude(), + "toLat", + to.latitude(), + "toLon", + to.longitude(), + "arriveBy", + arriveBy, + "accessMode", + accessModes.get(modeIndex % accessModes.size()), + "egressMode", + egressModes.get(modeIndex % egressModes.size()) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java new file mode 100644 index 00000000000..4b762ba7873 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java @@ -0,0 +1,12 @@ +package org.opentripplanner.standalone.configure.warmup; + +import org.opentripplanner.street.geometry.WgsCoordinate; + +/** Strategy for executing warmup queries against a specific GraphQL API. */ +interface WarmupQueryExecutor { + /** The number of distinct access/egress mode combinations to cycle through. */ + int modeCombinationCount(); + + /** @return true if the query executed without GraphQL errors. */ + boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex); +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java new file mode 100644 index 00000000000..cd3366a5ced --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java @@ -0,0 +1,164 @@ +package org.opentripplanner.standalone.configure.warmup; + +import java.time.Duration; +import java.time.Instant; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.standalone.config.routerconfig.WarmupConfig; +import org.opentripplanner.updater.GraphUpdaterStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runs GraphQL trip queries in a background thread during OTP startup to warm up the + * application before production traffic arrives. + *

+ * The worker sends sequential queries through the configured GraphQL API (TransModel or GTFS), + * exercising the full stack: GraphQL parsing, data fetchers, routing (Raptor + A*), itinerary + * filtering, and response serialization. This warms up JIT compilation, GraphQL schema caches, + * routing data structures, and other lazily initialized components. It alternates between + * depart-at / arrive-by and cycles through access/egress modes (walk, bike, car-to-park). + *

+ * It starts after Raptor transit data is created and stops when the health probe + * reports "UP" (all updaters primed). + */ +public class WarmupWorker implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(WarmupWorker.class); + private static final int MAX_QUERIES = 20; + + private final WarmupConfig config; + private final WarmupQueryExecutor queryExecutor; + private final Supplier updaterStatusProvider; + + WarmupWorker( + WarmupConfig config, + OtpServerRequestContext serverContext, + Supplier updaterStatusProvider + ) { + this.config = config; + this.updaterStatusProvider = updaterStatusProvider; + this.queryExecutor = switch (config.api()) { + case TRANSMODEL -> new TransmodelWarmupQueryExecutor( + serverContext, + config.accessModes(), + config.egressModes() + ); + case GTFS -> new GtfsWarmupQueryExecutor( + serverContext, + config.accessModes(), + config.egressModes() + ); + }; + } + + /** + * Start the application warmup thread if configured and applicable. + *

+ * No warmup is started if the config is null (section absent in router-config.json) + * or if no updaters are configured (health probe would immediately return "UP"). + */ + public static void start( + @Nullable WarmupConfig config, + Supplier serverContextProvider, + Supplier updaterStatusProvider + ) { + if (config == null) { + return; + } + if (updaterStatusProvider.get() == null) { + LOG.info("Application warmup configured but no updaters found. Skipping warmup."); + return; + } + var serverContext = serverContextProvider.get(); + var schema = switch (config.api()) { + case TRANSMODEL -> serverContext.transmodelSchema(); + case GTFS -> serverContext.gtfsSchema(); + }; + if (schema == null) { + LOG.warn( + "Application warmup configured for {} API, but the schema is not available. " + + "Is the corresponding API feature enabled?", + config.api() + ); + return; + } + var worker = new WarmupWorker(config, serverContext, updaterStatusProvider); + var thread = new Thread(worker, "app-warmup"); + thread.setDaemon(true); + thread.start(); + LOG.info("Application warmup thread started."); + } + + @Override + public void run() { + LOG.info( + "Application warmup started. Sending {} GraphQL trip queries from {} to {}.", + config.api(), + config.from(), + config.to() + ); + + var startTime = Instant.now(); + int queryCount = 0; + int failureCount = 0; + + try { + while (queryCount < MAX_QUERIES) { + if (isHealthy()) { + LOG.info( + "Application warmup complete: {} queries ({} failures) in {} ms. All updaters primed.", + queryCount, + failureCount, + Duration.between(startTime, Instant.now()).toMillis() + ); + return; + } + + queryCount++; + boolean arriveBy = queryCount % 2 == 0; + int modeIndex = (queryCount - 1) % queryExecutor.modeCombinationCount(); + if (!executeQuery(queryCount, arriveBy, modeIndex)) { + failureCount++; + } + } + + LOG.info( + "Application warmup reached maximum of {} queries ({} failures) in {} ms" + + " before all updaters were primed.", + MAX_QUERIES, + failureCount, + Duration.between(startTime, Instant.now()).toMillis() + ); + } catch (Throwable e) { + LOG.error("Application warmup terminated by error after {} queries.", queryCount, e); + } + } + + /** @return true if the query succeeded without errors, false otherwise. */ + private boolean executeQuery(int queryCount, boolean arriveBy, int modeIndex) { + var queryStart = Instant.now(); + try { + boolean success = queryExecutor.execute(config.from(), config.to(), arriveBy, modeIndex); + var elapsed = Duration.between(queryStart, Instant.now()); + LOG.info("Warmup query #{} completed in {} ms.", queryCount, elapsed.toMillis()); + return success; + } catch (Exception e) { + var elapsed = Duration.between(queryStart, Instant.now()); + LOG.info( + "Warmup query #{} failed in {} ms: {}", + queryCount, + elapsed.toMillis(), + e.getMessage() + ); + LOG.debug("Warmup query #{} exception detail", queryCount, e); + return false; + } + } + + private boolean isHealthy() { + var status = updaterStatusProvider.get(); + return status == null || status.listUnprimedUpdaters().isEmpty(); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md new file mode 100644 index 00000000000..239e603ccf1 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md @@ -0,0 +1,28 @@ +# Application Warmup + +Runs GraphQL trip queries in a background thread during OTP startup to warm up the application +(JIT compilation, GraphQL schema caches, routing data structures, etc.) before production traffic +arrives. + +## Lifecycle + +1. `WarmupWorker.start()` is called from `ConstructApplication` after Raptor transit data is + created and updaters are configured. +2. A daemon thread sends sequential queries through the configured GraphQL API (TransModel or GTFS), + exercising the full stack: GraphQL parsing, data fetchers, routing (Raptor + A*), itinerary + filtering, and response serialization. +3. It alternates between depart-at / arrive-by and cycles through access/egress modes. +4. The thread stops when the health probe reports "UP" (all updaters primed). + +## Design + +`WarmupQueryExecutor` is the strategy interface with two implementations: +- `TransmodelWarmupQueryExecutor` -- builds and executes TransModel `trip` queries. +- `GtfsWarmupQueryExecutor` -- builds and executes GTFS `planConnection` queries. + +Each executor receives configurable access/egress mode lists (`StreetMode` values) and maps them +to the API-specific GraphQL enum names. + +## Configuration + +Configured via the `warmup` section in `router-config.json`. See `WarmupConfig` for details. diff --git a/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java b/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java new file mode 100644 index 00000000000..9e690a991f4 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.standalone.config.routerconfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeForTest; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.street.model.StreetMode; + +class WarmupConfigTest { + + @Test + void defaultModesWhenAbsent() { + var root = createNodeAdapter( + """ + { + warmup: { + from: { lat: 59.91, lon: 10.75 }, + to: { lat: 59.95, lon: 10.76 } + } + } + """ + ); + var config = WarmupConfig.mapWarmupConfig("warmup", root); + assertNotNull(config); + assertEquals(List.of(StreetMode.WALK, StreetMode.CAR_TO_PARK), config.accessModes()); + assertEquals(List.of(StreetMode.WALK, StreetMode.WALK), config.egressModes()); + } + + @Test + void mismatchedModeListSizesThrows() { + var root = createNodeAdapter( + """ + { + warmup: { + from: { lat: 59.91, lon: 10.75 }, + to: { lat: 59.95, lon: 10.76 }, + accessModes: ["WALK", "BIKE", "CAR_TO_PARK"], + egressModes: ["WALK", "BIKE"] + } + } + """ + ); + assertThrows(IllegalArgumentException.class, () -> + WarmupConfig.mapWarmupConfig("warmup", root) + ); + } + + private static NodeAdapter createNodeAdapter(String jsonText) { + return new NodeAdapter(jsonNodeForTest(jsonText), "Test"); + } +} diff --git a/application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java b/application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java new file mode 100644 index 00000000000..6c4dc98dc2f --- /dev/null +++ b/application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java @@ -0,0 +1,56 @@ +package org.opentripplanner.standalone.configure.warmup; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.parser.Parser; +import graphql.schema.GraphQLSchema; +import graphql.validation.Validator; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.api.model.transit.DefaultFeedIdMapper; +import org.opentripplanner.apis.gtfs.SchemaFactory; +import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile; +import org.opentripplanner.apis.transmodel.TransmodelGraphQLSchemaFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParametersTestFactory; +import org.opentripplanner.routing.api.request.RouteRequest; + +/** + * Validates that the warmup GraphQL queries are syntactically and structurally valid + * against the actual schemas. This catches field renames, removed arguments, or + * invalid enum values at build time rather than at runtime. + */ +class WarmupQueryValidationTest { + + private static final GraphQLSchema GTFS_SCHEMA = SchemaFactory.createSchemaWithDefaultInjection( + RouteRequest.defaultValue() + ); + + private static final GraphQLSchema TRANSMODEL_SCHEMA = new TransmodelGraphQLSchemaFactory( + RouteRequest.defaultValue(), + ZoneIds.OSLO, + TransitTuningParametersTestFactory.forTest(), + new DefaultFeedIdMapper(), + ApiDocumentationProfile.DEFAULT + ).create(); + + @Test + void transmodelQueryIsValid() { + var errors = new Validator().validateDocument( + TRANSMODEL_SCHEMA, + Parser.parse(TransmodelWarmupQueryExecutor.QUERY), + Locale.ROOT + ); + assertEquals(0, errors.size(), errors.toString()); + } + + @Test + void gtfsQueryIsValid() { + var errors = new Validator().validateDocument( + GTFS_SCHEMA, + Parser.parse(GtfsWarmupQueryExecutor.QUERY), + Locale.ROOT + ); + assertEquals(0, errors.size(), errors.toString()); + } +} diff --git a/application/src/test/resources/standalone/config/router-config.json b/application/src/test/resources/standalone/config/router-config.json index fd3d00cbf25..b454e817954 100644 --- a/application/src/test/resources/standalone/config/router-config.json +++ b/application/src/test/resources/standalone/config/router-config.json @@ -473,6 +473,13 @@ "maxPrimingIdleTime": "1s" } ], + "warmup": { + "api": "transmodel", + "from": { "lat": 59.9139, "lon": 10.7522 }, + "to": { "lat": 59.95, "lon": 10.76 }, + "accessModes": ["WALK", "CAR_TO_PARK"], + "egressModes": ["WALK", "WALK"] + }, "rideHailingServices": [ { "type": "uber-car-hailing", diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index ba4a481e6b4..b0083600435 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -31,51 +31,61 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md). -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|-------------------------------------------------------------------------------------------|:---------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:| -| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 | -| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 | -| gtfsApi | `object` | Configuration for the GTFS GraphQL API. | *Optional* | | 2.8 | -|    [tracingTags](#gtfsApi_tracingTags) | `string[]` | Used to group requests based on headers or query parameters when monitoring OTP. | *Optional* | | na | -| [ojpApi](sandbox/OjpApi.md) | `object` | Configuration for the OJP API. | *Optional* | | 2.9 | -| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 | -| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 | -| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 | -|    [apiDocumentationProfile](#server_apiDocumentationProfile) | `enum` | List of available custom documentation profiles. A profile is used to inject custom documentation like type and field description or a deprecated reason. Currently, ONLY the Transmodel API supports this feature. | *Optional* | `"default"` | 2.7 | -|    [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 | -|    [httpResponseTimeMetrics](#server_httpResponseTimeMetrics) | `object` | Configuration for HTTP response time metrics. | *Optional* | | 2.9 | -|    [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 | -|          generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 | -|          httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 | -|          httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 | -|          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | -| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | -|    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | -|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | -| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | -|    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | -|    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | -|    [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 | -|    [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na | -|    [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na | -|    [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na | -|    [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 | -|       [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 | -|       [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 | -|       [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 | -|       [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 | -|       [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 | -|    [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na | -|    [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 | -|    [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 | -| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 | -|    [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na | -|    [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 | -|    [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na | -| [triasApi](sandbox/TriasApi.md) | `object` | Configuration for the TRIAS API. | *Optional* | | 2.8 | -| [updaters](Realtime-Updaters.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 | -| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na | -| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory using GBFS v3 manifest. | *Optional* | | 2.0 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-------------------------------------------------------------------------------------------|:---------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|----------------|:-----:| +| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 | +| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 | +| gtfsApi | `object` | Configuration for the GTFS GraphQL API. | *Optional* | | 2.8 | +|    [tracingTags](#gtfsApi_tracingTags) | `string[]` | Used to group requests based on headers or query parameters when monitoring OTP. | *Optional* | | na | +| [ojpApi](sandbox/OjpApi.md) | `object` | Configuration for the OJP API. | *Optional* | | 2.9 | +| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 | +| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 | +| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 | +|    [apiDocumentationProfile](#server_apiDocumentationProfile) | `enum` | List of available custom documentation profiles. A profile is used to inject custom documentation like type and field description or a deprecated reason. Currently, ONLY the Transmodel API supports this feature. | *Optional* | `"default"` | 2.7 | +|    [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 | +|    [httpResponseTimeMetrics](#server_httpResponseTimeMetrics) | `object` | Configuration for HTTP response time metrics. | *Optional* | | 2.9 | +|    [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 | +|          generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 | +|          httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 | +|          httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 | +|          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | +| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | +|    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | +|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | +| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | +|    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | +|    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | +|    [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 | +|    [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na | +|    [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na | +|    [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na | +|    [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 | +|       [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 | +|       [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 | +|       [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 | +|       [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 | +|       [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 | +|    [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na | +|    [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 | +|    [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 | +| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 | +|    [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na | +|    [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 | +|    [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na | +| [triasApi](sandbox/TriasApi.md) | `object` | Configuration for the TRIAS API. | *Optional* | | 2.8 | +| [updaters](Realtime-Updaters.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 | +| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na | +| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory using GBFS v3 manifest. | *Optional* | | 2.0 | +| [warmup](#warmup) | `object` | Configure application warmup by running transit searches during startup. | *Optional* | | 2.10 | +|    [api](#warmup_api) | `enum` | Which GraphQL API to use for warmup queries. | *Optional* | `"transmodel"` | 2.10 | +|    [accessModes](#warmup_accessModes) | `string[]` | Access modes to cycle through in warmup queries. | *Optional* | | 2.10 | +|    [egressModes](#warmup_egressModes) | `string[]` | Egress modes to cycle through in warmup queries. | *Optional* | | 2.10 | +|    from | `object` | Origin location for warmup searches. | *Optional* | | 2.10 | +|       lat | `double` | Latitude of the origin. | *Required* | | 2.10 | +|       lon | `double` | Longitude of the origin. | *Required* | | 2.10 | +|    to | `object` | Destination location for warmup searches. | *Optional* | | 2.10 | +|       lat | `double` | Latitude of the destination. | *Required* | | 2.10 | +|       lon | `double` | Longitude of the destination. | *Required* | | 2.10 | @@ -482,6 +492,131 @@ Enforce rate limiting based on query complexity; Queries that return too much da Used to group requests when monitoring OTP. +

warmup

+ +**Since version:** `2.10` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** / + +Configure application warmup by running transit searches during startup. + +When configured, OTP runs transit routing queries between the given locations +during startup. This warms up the application (JIT compilation, GraphQL schema +caches, routing data structures, etc.) before production traffic arrives. +Queries start after the Raptor transit data is created and stop when all updaters +are primed (the health endpoint would return "UP"). +If no updaters are configured, no warmup queries are run. + + +

api

+ +**Since version:** `2.10` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"transmodel"` +**Path:** /warmup +**Enum values:** `transmodel` | `gtfs` + +Which GraphQL API to use for warmup queries. + + - `transmodel` Use the TransModel GraphQL API for warmup queries. + - `gtfs` Use the GTFS GraphQL API for warmup queries. + + +

accessModes

+ +**Since version:** `2.10` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /warmup + +Access modes to cycle through in warmup queries. + +Ordered list of `StreetMode` values used as access modes. Each entry is paired with the egress mode at the same index. - `not-set` + - `walk` Walking some or all of the way of the route. + - `bike` Cycling for the entirety of the route or taking a bicycle onto the public transport and cycling from the arrival station to the destination. + + Taking a bicycle onto transit is only possible if information about the permission to do so is supplied in the source data. In GTFS this field + is called `bikesAllowed`. + - `bike-to-park` Leaving the bicycle at the departure station and walking from the arrival station to the destination. + This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. + - `bike-rental` Taking a rented, shared-mobility bike for part or the entirety of the route. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `scooter-rental` Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way. + This can include scooter rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car` Driving your own car the entirety of the route. + This can be combined with transit, where will return routes with a [Kiss & Ride](https://en.wikipedia.org/wiki/Park_and_ride#Kiss_and_ride_/_kiss_and_fly) component. + This means that the car is not parked in a permanent parking area but rather the passenger is dropped off (for example, at an airport) and the driver continues driving the car away from the drop off location. + - `car-to-park` Driving a car to the park-and-ride facilities near a station and taking publictransport. + This mode needs to be combined with at least one transit mode otherwise, it behaves like an ordinary car journey. + _Prerequisite:_ Park-and-ride areas near the stations need to be present in the OSM input file. + - `car-pickup` Walking to a pickup point along the road, driving to a drop-off point along the road, and walking the rest of the way.
This can include various taxi-services or kiss & ride. + - `car-rental` Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way. + This can include car rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car-hailing` Using a car hailing app like Uber or Lyft to get to a train station or all the way to the destination. + + See [the sandbox documentation](sandbox/RideHailing.md) on how to configure it. + - `carpool` Carpool or rideshare with other passengers going in the same direction. + + This is the request mode for enabling carpooling in street route searches. + + Use this _street_ mode, if your data source for trips is SIRI, not GTFS static. + - `flexible` Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places. + + +

egressModes

+ +**Since version:** `2.10` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /warmup + +Egress modes to cycle through in warmup queries. + +Ordered list of `StreetMode` values used as egress modes. Each entry is paired with the access mode at the same index. - `not-set` + - `walk` Walking some or all of the way of the route. + - `bike` Cycling for the entirety of the route or taking a bicycle onto the public transport and cycling from the arrival station to the destination. + + Taking a bicycle onto transit is only possible if information about the permission to do so is supplied in the source data. In GTFS this field + is called `bikesAllowed`. + - `bike-to-park` Leaving the bicycle at the departure station and walking from the arrival station to the destination. + This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. + - `bike-rental` Taking a rented, shared-mobility bike for part or the entirety of the route. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `scooter-rental` Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way. + This can include scooter rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car` Driving your own car the entirety of the route. + This can be combined with transit, where will return routes with a [Kiss & Ride](https://en.wikipedia.org/wiki/Park_and_ride#Kiss_and_ride_/_kiss_and_fly) component. + This means that the car is not parked in a permanent parking area but rather the passenger is dropped off (for example, at an airport) and the driver continues driving the car away from the drop off location. + - `car-to-park` Driving a car to the park-and-ride facilities near a station and taking publictransport. + This mode needs to be combined with at least one transit mode otherwise, it behaves like an ordinary car journey. + _Prerequisite:_ Park-and-ride areas near the stations need to be present in the OSM input file. + - `car-pickup` Walking to a pickup point along the road, driving to a drop-off point along the road, and walking the rest of the way.
This can include various taxi-services or kiss & ride. + - `car-rental` Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way. + This can include car rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car-hailing` Using a car hailing app like Uber or Lyft to get to a train station or all the way to the destination. + + See [the sandbox documentation](sandbox/RideHailing.md) on how to configure it. + - `carpool` Carpool or rideshare with other passengers going in the same direction. + + This is the request mode for enabling carpooling in street route searches. + + Use this _street_ mode, if your data source for trips is SIRI, not GTFS static. + - `flexible` Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places. + + @@ -961,6 +1096,25 @@ Used to group requests when monitoring OTP. "maxPrimingIdleTime" : "1s" } ], + "warmup" : { + "api" : "transmodel", + "from" : { + "lat" : 59.9139, + "lon" : 10.7522 + }, + "to" : { + "lat" : 59.95, + "lon" : 10.76 + }, + "accessModes" : [ + "WALK", + "CAR_TO_PARK" + ], + "egressModes" : [ + "WALK", + "WALK" + ] + }, "rideHailingServices" : [ { "type" : "uber-car-hailing", From d8ad9ac78940a9eb4446dd87293f5f8e9d708800 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 16 Apr 2026 15:12:13 +0100 Subject: [PATCH 051/177] Use a set in OsmDatabase to mark entrances in stop areas instead of mutating the entrance themselves. --- .../graph_builder/module/osm/OsmDatabase.java | 13 ++-- .../graph_builder/module/osm/OsmModule.java | 5 +- .../module/osm/VertexGenerator.java | 27 +++++++- .../module/osm/OsmDatabaseTest.java | 38 ----------- .../module/osm/VertexGeneratorTest.java | 63 +++++++++++++++++++ 5 files changed, 102 insertions(+), 44 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 e847fee6478..2706a14f183 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 @@ -80,6 +80,9 @@ public class OsmDatabase { /* All bike parking areas */ private final List bikeParkingAreas = new ArrayList<>(); + /* All stop area relations */ + private final List stopAreas = new ArrayList<>(); + /* Map of all area OSMWay for a given node */ private final TLongObjectMap> areasForNode = new TLongObjectHashMap<>(); @@ -174,6 +177,10 @@ public Collection getBikeParkingAreas() { return Collections.unmodifiableCollection(bikeParkingAreas); } + public Collection getStopAreas() { + return Collections.unmodifiableCollection(stopAreas); + } + public Collection getTurnRestrictionWayIds() { return Collections.unmodifiableCollection(turnRestrictionsByFromWay.keySet()); } @@ -982,6 +989,8 @@ private void processRoute(OsmRelation relation) { * @see "http://wiki.openstreetmap.org/wiki/Tag:public_transport%3Dstop_area" */ private void processPublicTransportStopArea(OsmRelation relation) { + stopAreas.add(relation); + Set platformAreas = new HashSet<>(); Set platformNodes = new HashSet<>(); for (OsmRelationMember member : relation.getMembers()) { @@ -990,10 +999,6 @@ private void processPublicTransportStopArea(OsmRelation relation) { var node = nodesById.get(member.getRef()); if (node != null && node.isPlatformAccess()) { platformNodes.add(node); - if (node.isEntrance()) { - // mark it as a station entrance so it can be returned in walk step descriptions. - node.addTag("public_transport", "entrance"); - } } } case WAY -> { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index e0eb4dcdefa..890c389fdd4 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -193,6 +193,9 @@ private void build(OsmDatabase osmdb, VertexGenerator vertexGenerator) { vertexGenerator.initIntersectionNodes(); vertexGenerator.initNodesInBarrierWays(); + // figure out entrances in stop areas, which should be treated as station entrances + vertexGenerator.initEntrancesInStopAreas(); + ElevatorProcessor elevatorProcessor = new ElevatorProcessor( issueStore, osmdb, @@ -433,7 +436,7 @@ private void buildBasicGraph( osmEndNode.hasTag("ele") || osmEndNode.isBoardingLocation() || osmEndNode.isBarrier() || - osmEndNode.isStationEntrance() || + osmEndNode.isEntrance() || vertexGenerator.nodesInBarrierWays().containsKey(osmEndNode) ) { segmentCoordinates.add(osmEndNode.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 4602979bf89..88c5c270dff 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -20,6 +20,7 @@ import org.opentripplanner.graph_builder.issues.DifferentLevelsSharingBarrier; import org.opentripplanner.osm.model.OsmEntity; import org.opentripplanner.osm.model.OsmLevel; +import org.opentripplanner.osm.model.OsmMemberType; import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.graph.Graph; @@ -61,6 +62,12 @@ class VertexGenerator { * The map from node to the split vertex for each routable entity connecting it. */ private final Map> splitVerticesOnBarriers = new HashMap<>(); + + /** + * + */ + private final Set entrancesInStopAreas = new HashSet<>(); + private final OsmDatabase osmdb; private final Set boardingAreaRefTags; private final Boolean includeOsmStationEntrances; @@ -134,7 +141,10 @@ IntersectionVertex getVertexForOsmNode( } } - if (includeOsmStationEntrances && node.isStationEntrance()) { + if ( + includeOsmStationEntrances && + (node.isStationEntrance() || entrancesInStopAreas.contains(node)) + ) { String ref = node.getTag("ref"); iv = vertexFactory.stationEntrance( nid, @@ -310,6 +320,21 @@ void initNodesInBarrierWays() { } } + void initEntrancesInStopAreas() { + if (includeOsmStationEntrances) { + for (var relation : osmdb.getStopAreas()) { + for (var member : relation.getMembers()) { + if (member.getType() == OsmMemberType.NODE) { + var node = osmdb.getNode(member.getRef()); + if (node != null && node.isEntrance()) { + entrancesInStopAreas.add(node); + } + } + } + } + } + } + /** * Track OSM nodes that will become graph vertices because they appear in multiple OSM ways */ diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java index a3fd63f1782..dc5058303d0 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmDatabaseTest.java @@ -223,42 +223,4 @@ void testWayIsntKeptForAreas() { // innerRingWithBarrier should not be polluted with the highway tag when fetched from the way assertFalse(osmdb.getWay(innerRingWithBarrier.getId()).hasTag("highway")); } - - @Test - void testEntranceTagAdded() { - var entrance1 = new OsmNode(0, 0); - entrance1.setId(1); - entrance1.addTag("entrance", "yes"); - - var entrance2 = new OsmNode(0, 0); - entrance2.setId(2); - entrance2.addTag("entrance", "yes"); - - var footway = new OsmWay(); - footway.addNodeRef(1); - footway.addNodeRef(2); - footway.addTag("highway", "footway"); - - var stopArea = new OsmRelation(); - stopArea.addTag("type", "public_transport"); - stopArea.addTag("public_transport", "stop_area"); - - var member = new OsmRelationMember(); - member.setType(OsmMemberType.NODE); - member.setRef(1); - stopArea.addMember(member); - - var osmdb = new OsmDatabase(DataImportIssueStore.NOOP); - osmdb.addRelation(stopArea); - osmdb.doneFirstPhaseRelations(); - osmdb.addWay(footway); - osmdb.doneSecondPhaseWays(); - osmdb.addNode(entrance1); - osmdb.addNode(entrance2); - osmdb.doneThirdPhaseNodes(); - osmdb.postLoad(); - - assertTrue(entrance1.isTag("public_transport", "entrance")); - assertFalse(entrance2.isTag("public_transport", "entrance")); - } } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java index a459b74bf71..bf9a7b4bf5c 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java @@ -16,12 +16,16 @@ import org.opentripplanner.graph_builder.issues.BarrierIntersectingHighway; import org.opentripplanner.graph_builder.issues.DifferentLevelsSharingBarrier; import org.opentripplanner.osm.model.OsmEntity; +import org.opentripplanner.osm.model.OsmMemberType; import org.opentripplanner.osm.model.OsmNode; +import org.opentripplanner.osm.model.OsmRelation; +import org.opentripplanner.osm.model.OsmRelationMember; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.street.model.vertex.BarrierPassThroughVertex; import org.opentripplanner.street.model.vertex.BarrierVertex; import org.opentripplanner.street.model.vertex.OsmVertex; +import org.opentripplanner.street.model.vertex.StationEntranceVertex; class VertexGeneratorTest { @@ -153,6 +157,65 @@ void testBarrierGenerator() { assertEquals(4, issues[1].node().getId()); } + @Test + void testEntranceInStopAreas() { + var entranceInStopArea = new OsmNode(0, 0); + entranceInStopArea.setId(1); + entranceInStopArea.addTag("entrance", "yes"); + + var entranceOutsideStopArea = new OsmNode(0, 0); + entranceOutsideStopArea.setId(2); + entranceOutsideStopArea.addTag("entrance", "yes"); + + var subwayEntranceOutsideStopArea = new OsmNode(0, 0); + subwayEntranceOutsideStopArea.setId(3); + subwayEntranceOutsideStopArea.addTag("railway", "subway_entrance"); + subwayEntranceOutsideStopArea.addTag("entrance", "yes"); + + var footway = new OsmWay(); + footway.addNodeRef(1); + footway.addNodeRef(2); + footway.addNodeRef(3); + footway.addTag("highway", "footway"); + + var stopArea = new OsmRelation(); + stopArea.addTag("type", "public_transport"); + stopArea.addTag("public_transport", "stop_area"); + + var member = new OsmRelationMember(); + member.setType(OsmMemberType.NODE); + member.setRef(1); + stopArea.addMember(member); + + var osmdb = new OsmDatabase(DataImportIssueStore.NOOP); + osmdb.addRelation(stopArea); + osmdb.doneFirstPhaseRelations(); + osmdb.addWay(footway); + osmdb.doneSecondPhaseWays(); + osmdb.addNode(entranceInStopArea); + osmdb.addNode(entranceOutsideStopArea); + osmdb.doneThirdPhaseNodes(); + osmdb.postLoad(); + + Graph graph = new Graph(); + + var subject = new VertexGenerator(osmdb, graph, Set.of(), true, DataImportIssueStore.NOOP); + subject.initEntrancesInStopAreas(); + + assertInstanceOf( + StationEntranceVertex.class, + subject.getVertexForOsmNode(entranceInStopArea, footway, NORMAL) + ); + assertFalse( + subject.getVertexForOsmNode(entranceOutsideStopArea, footway, NORMAL) instanceof + StationEntranceVertex + ); + assertInstanceOf( + StationEntranceVertex.class, + subject.getVertexForOsmNode(subwayEntranceOutsideStopArea, footway, NORMAL) + ); + } + static DifferentLevelsSharingBarrier[] getBarrierLevelIssues(DataImportIssueStore issueStore) { return issueStore .listIssues() From 3013b01d50666a3e1f16d7bcddaf03187460fea6 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 16 Apr 2026 15:25:26 +0100 Subject: [PATCH 052/177] extract railway station entrance tags constant --- .../main/java/org/opentripplanner/osm/model/OsmNode.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java index 19de99a2ea0..c429cebb2ba 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java @@ -9,6 +9,11 @@ public class OsmNode extends OsmEntity { + private static final Set RAILWAY_STATION_ENTRANCE_TAGS = Set.of( + "subway_entrance", + "train_station_entrance" + ); + public double lat; public double lon; @@ -51,8 +56,7 @@ public boolean isBarrier() { */ public boolean isStationEntrance() { return ( - isOneOfTags("railway", Set.of("subway_entrance", "train_station_entrance")) || - isTag("public_transport", "entrance") + isOneOfTags("railway", RAILWAY_STATION_ENTRANCE_TAGS) || isTag("public_transport", "entrance") ); } From 18016c386e2243189e3437abddab90f8573eefca Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 16 Apr 2026 15:39:55 +0100 Subject: [PATCH 053/177] make OsmNodeTest.isEntrance a parameterized test --- .../osm/model/OsmNodeTest.java | 72 +++++++++++++------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java index ec0093265f8..0b65d08d049 100644 --- a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java +++ b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java @@ -1,12 +1,56 @@ package org.opentripplanner.osm.model; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import 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; public class OsmNodeTest { + public static Stream entranceTestCases() { + var mainEntrance = new OsmNode(); + mainEntrance.addTag("entrance", "main"); + + var trainStationEntrance = new OsmNode(); + trainStationEntrance.addTag("railway", "train_station_entrance"); + + var subwayEntrance = new OsmNode(); + subwayEntrance.addTag("railway", "subway_entrance"); + + var publicTransportEntrance = new OsmNode(); + publicTransportEntrance.addTag("public_transport", "entrance"); + + var trainStationEntranceWithEntranceTag = new OsmNode(); + trainStationEntranceWithEntranceTag.addTag("railway", "train_station_entrance"); + trainStationEntranceWithEntranceTag.addTag("entrance", "main"); + + var notAnEntrance = new OsmNode(); + notAnEntrance.addTag("entrance", "no"); + + var emergencyEntrance = new OsmNode(); + notAnEntrance.addTag("entrance", "emergency"); + + return Stream.of( + Arguments.argumentSet("main entrance", mainEntrance, true, false), + Arguments.argumentSet("train station entrance", trainStationEntrance, true, true), + Arguments.argumentSet("subway entrance", subwayEntrance, true, true), + Arguments.argumentSet("public transport entrance", publicTransportEntrance, true, true), + Arguments.argumentSet( + "train station entrance with entrance tag", + trainStationEntranceWithEntranceTag, + true, + true + ), + Arguments.argumentSet("not an entrance", notAnEntrance, false, false), + Arguments.argumentSet("emergency entrance", emergencyEntrance, false, false) + ); + } + @Test public void isBarrier() { OsmNode node = new OsmNode(); @@ -55,28 +99,10 @@ public void isTaggedBarrierCrossing() { assertTrue(node.isTaggedBarrierCrossing()); } - @Test - public void isEntrance() { - OsmNode node = new OsmNode(); - node.addTag("entrance", "main"); - assertTrue(node.isEntrance()); - assertFalse(node.isStationEntrance()); - node.addTag("railway", "train_station_entrance"); - assertTrue(node.isEntrance()); - - node = new OsmNode(); - node.addTag("railway", "train_station_entrance"); - assertTrue(node.isEntrance()); - assertTrue(node.isStationEntrance()); - - node = new OsmNode(); - node.addTag("railway", "subway_entrance"); - assertTrue(node.isEntrance()); - assertTrue(node.isStationEntrance()); - - node = new OsmNode(); - node.addTag("public_transport", "entrance"); - assertTrue(node.isEntrance()); - assertTrue(node.isStationEntrance()); + @ParameterizedTest + @MethodSource("entranceTestCases") + public void isEntrance(OsmNode node, boolean entrance, boolean stationEntrance) { + assertEquals(entrance, node.isEntrance()); + assertEquals(stationEntrance, node.isStationEntrance()); } } From 9838893b2792b8cd423e29c915dfae6fee29782d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Apr 2026 16:41:56 +0200 Subject: [PATCH 054/177] Add API GraphQL convention for small input Value Objects Clarified pagination details and added conventions for input value objects. --- doc/dev/decisionrecords/APIGraphQLDesign.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/dev/decisionrecords/APIGraphQLDesign.md b/doc/dev/decisionrecords/APIGraphQLDesign.md index a5d353002df..3f842bd80f3 100644 --- a/doc/dev/decisionrecords/APIGraphQLDesign.md +++ b/doc/dev/decisionrecords/APIGraphQLDesign.md @@ -8,7 +8,8 @@ of the OTP developers is the Production Ready GraphQL book by Marc-André Giroux ## Pagination We use the [pagination](https://graphql.org/learn/pagination/) (a.k. Relay) specification for paging over entities like stations, -stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. +stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. The route request has a OTP custom pagination +feature - it is not finate and very complex. ## Refetching @@ -43,3 +44,12 @@ We allow breaking API changes in these cases: > Use feature xyz instead. > Note! This feature is subject for removal in September 2027! If you want this to stay, please > notify the OTP community. + +## Conventions + +### Small `input` Value Objects should have requered feilds, no default field values + +An input representing a single consept or thing should cary all relevant fields even if some of the +values are common or have a natural default. Example fo such input types are `InputCoordinate(lat, long)` +and `InputLinearFunction(constant, coefisient)`. Such types could be scalars, but if they have multiple +fields input types are a better match. From 5bce55b3019d743f9f42146afc45c412b503f306 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Apr 2026 16:54:55 +0200 Subject: [PATCH 055/177] Fix typos and enhance clarity in API GraphQL design doc Corrected typos and improved clarity in the documentation regarding pagination and input value objects. --- doc/dev/decisionrecords/APIGraphQLDesign.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/dev/decisionrecords/APIGraphQLDesign.md b/doc/dev/decisionrecords/APIGraphQLDesign.md index 3f842bd80f3..8cdae20b1d4 100644 --- a/doc/dev/decisionrecords/APIGraphQLDesign.md +++ b/doc/dev/decisionrecords/APIGraphQLDesign.md @@ -8,8 +8,8 @@ of the OTP developers is the Production Ready GraphQL book by Marc-André Giroux ## Pagination We use the [pagination](https://graphql.org/learn/pagination/) (a.k. Relay) specification for paging over entities like stations, -stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. The route request has a OTP custom pagination -feature - it is not finate and very complex. +stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. The route request has an OTP custom pagination +feature - it is not finite and very complex. ## Refetching @@ -47,9 +47,10 @@ We allow breaking API changes in these cases: ## Conventions -### Small `input` Value Objects should have requered feilds, no default field values +### Small `input` value-objects should have required fields, no default field values -An input representing a single consept or thing should cary all relevant fields even if some of the -values are common or have a natural default. Example fo such input types are `InputCoordinate(lat, long)` -and `InputLinearFunction(constant, coefisient)`. Such types could be scalars, but if they have multiple -fields input types are a better match. +An input representing a single concept or thing should carry all relevant fields even if +some of the values are common or have a natural default. Examples of such input types are +`InputCoordinate(lat, long)` and `InputLinearFunction(constant, coefficient)`. Small input +value-object types could be scalars, but if they have multiple fields using an input type +is a better match. From 76a6f7255f9ed784626542a4fb53fa22e28da47f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Apr 2026 22:06:31 +0200 Subject: [PATCH 056/177] Improve SpeedTest warm-up Make sure to run a warm-up case for each mode combination and at least 3 test-cases if the set is bigger than 3. This is to properly warm up the JIT compiler. --- .../api/parameter/QualifiedModeSet.java | 15 ++++++++ .../transit/speed_test/SpeedTest.java | 4 +- .../speed_test/model/testcase/TestCases.java | 38 +++++++++++++++++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java index 9393f5f0006..d1aaf9c7860 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java @@ -3,6 +3,7 @@ import java.io.Serializable; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import org.opentripplanner.routing.api.request.RequestModes; @@ -164,4 +165,18 @@ public String toString() { } return sb.toString(); } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifiedModeSet that = (QualifiedModeSet) o; + return Objects.equals(qModes, that.qModes); + } + + @Override + public int hashCode() { + return Objects.hashCode(qModes); + } } diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index b62778c4ff4..2f203e71fcb 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -257,7 +257,9 @@ private void runSampleTest(SpeedTestProfile profile, int sample, int nSamples) { // We assume we are debugging and not measuring performance if we only run 1 test-case // one time; Hence skip JIT compiler warm-up. if (testCases.runJitWarmUp() || opts.profiles().length > 1) { - performRouting(testCases.getJitWarmUpCase()); + for (var tc : testCases.getJitWarmUpCases()) { + performRouting(tc); + } } ResultPrinter.logSingleTestHeader(profile); diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java index a11ffc3cfc6..deea10eaec4 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java @@ -1,8 +1,11 @@ package org.opentripplanner.transit.speed_test.model.testcase; import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -35,11 +38,38 @@ public boolean runJitWarmUp() { } /** - * If we need to warm up the JIT compiler, we run the last test-case. This avoids repeating the - * same test case twice if more than one test-case exist. + * Select test-cases to use for JIT warm-up. + *

+ * If there are three or fewer cases, all cases are returned. Otherwise, a small set of cases + * is selected. When there are multiple mode combinations, the first test-case for each mode + * combination is selected first. Additional cases are then sampled from across the list until + * at least three cases have been selected. */ - public TestCase getJitWarmUpCase() { - return cases.get(numberOfTestCases() - 1); + public List getJitWarmUpCases() { + if (numberOfTestCases() <= 3) { + return List.copyOf(cases); + } + var samples = new HashSet(); + + var groupByModes = cases.stream().collect(Collectors.groupingBy(tc -> tc.definition().modes())); + + // If more than 1 mode combination exists, return the first test-case for each + // mode combination + if (groupByModes.keySet().size() > 1) { + for (var it : groupByModes.values()) { + samples.add(it.getFirst()); + } + } + // There are at least 4 cases, so we split the set in 4 groups and pick the first element in + // the 3 last groups. + int step = numberOfTestCases() / 4; + int index = step; + + while (samples.size() < 3) { + samples.add(cases.get(index)); + index += step; + } + return samples.stream().sorted(Comparator.comparing(TestCase::id)).toList(); } public Iterable iterable() { From 10f789b05b85db10d13c0b10d9c6ec8424164807 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 17 Apr 2026 08:14:51 +0200 Subject: [PATCH 057/177] Remove TimetableRepositoryForTest.id --- .../v2/CostedTransferAcrossNetworksTest.java | 9 +++++---- .../opentripplanner/ext/flex/FlexIndexTest.java | 2 +- .../ext/flex/FlexibleTransitLegTest.java | 2 +- .../ext/flex/filter/FilterMapperTest.java | 2 +- .../ScheduledFlexPathCalculatorTest.java | 2 +- .../ext/flex/template/ClosestTripTest.java | 2 +- .../flex/trip/ScheduledDeviatedTripTest.java | 2 +- .../trip/UnscheduledDrivingDurationTest.java | 2 +- .../ext/flex/trip/UnscheduledTripTest.java | 2 +- .../ext/geocoder/SerializationTest.java | 2 +- .../ext/ojp/resource/OjpMapperTest.java | 2 +- .../ext/ojp/service/CallAtStopServiceTest.java | 2 +- .../DefaultStopConsolidationRepositoryTest.java | 2 +- .../model/ConsolidatedStopLegBuilderTest.java | 2 +- .../DigitransitStationPropertyMapperTest.java | 2 +- .../layers/stops/StopsLayerTest.java | 2 +- .../VehicleParkingGroupsLayerTest.java | 4 ++-- .../VehicleParkingsLayerTest.java | 4 ++-- .../ext/fares/model/FareModelForTest.java | 2 +- .../ext/fares/model/FareTestConstants.java | 2 +- .../street/model/StreetModelForTest.java | 2 +- .../apis/gtfs/GraphQLIntegrationTest.java | 5 +++-- .../gtfs/service/ApiTransitServiceTest.java | 2 +- .../ext/flex/FlexAccessEgressBookingTest.java | 3 ++- .../gbfs/v2/GbfsStationStatusMapperTest.java | 2 +- .../gbfs/v3/GbfsStationStatusMapperTest.java | 2 +- .../module/StreetLinkerModuleTest.java | 2 +- .../module/VehicleParkingLinkingTest.java | 2 +- .../module/geometry/GeometryProcessorTest.java | 2 +- .../DirectTransferGeneratorTestData.java | 11 ++++++----- .../gtfs/GenerateTripPatternsOperationTest.java | 11 ++++++----- .../interlining/InterlineProcessorTest.java | 3 ++- .../CalendarServiceDataFactoryImplTest.java | 2 +- .../impl/DefaultTransitDataImportTest.java | 2 +- ...TransitDataImportBuilderLimitPeriodTest.java | 13 +++++++------ .../impl/TransitDataImportBuilderTest.java | 3 ++- .../model/plan/ItineraryTest.java | 5 +++-- .../model/plan/TestItineraryBuilder.java | 2 +- .../LegReferenceSerializerTest.java | 8 ++++---- .../ScheduledTransitLegReferenceTest.java | 9 +++++---- .../netex/mapping/TripMapperTest.java | 3 ++- .../netex/mapping/TripPatternMapperTest.java | 4 ++-- .../calendar/CalendarServiceBuilderTest.java | 12 ++++++------ .../routing/algorithm/FilterTest.java | 2 +- .../routing/algorithm/GraphRoutingTest.java | 2 +- .../routing/algorithm/TestBanning.java | 2 +- .../transit/RaptorTransitDataTest.java | 3 ++- .../transit/TripPatternForDateTest.java | 3 ++- .../transit/cost/PatternCostCalculatorTest.java | 2 +- .../GeneralizedCostParametersMapperTest.java | 2 +- .../DefaultTransitDataProviderFilterTest.java | 17 +++++++++-------- ...torRoutingRequestTransitDataCreatorTest.java | 2 +- .../transit/request/TestRouteData.java | 5 +++-- .../request/TripPatternForDatesTest.java | 3 ++- .../routing/core/ItineraryFareTest.java | 2 +- .../PlaceFinderTraverseVisitorTest.java | 2 +- .../graphfinder/StreetGraphFinderTest.java | 5 +++-- .../linking/LinkingContextFactoryTest.java | 2 +- .../routing/trippattern/FrequencyEntryTest.java | 3 ++- .../standalone/server/AlertMetricsTest.java | 2 +- .../StreetSearchRequestMapperTest.java | 6 +++--- .../ConstrainedBoardingSearchTest.java | 4 ++-- .../transit/model/_data/PatternTestModel.java | 2 +- .../model/_data/TimetableRepositoryForTest.java | 6 ++---- .../transit/model/basic/NoticeTest.java | 7 ++++--- .../filter/transit/TripMatcherFactoryTest.java | 2 +- .../transit/model/framework/EntityByIdTest.java | 6 +++--- .../model/network/GroupOfRoutesTest.java | 6 +++--- .../transit/model/network/RouteTest.java | 5 +++-- .../transit/model/network/TripPatternTest.java | 2 +- .../transit/model/organization/AgencyTest.java | 6 +++--- .../model/organization/BrandingTest.java | 6 +++--- .../model/organization/OperatorTest.java | 6 +++--- .../transit/model/site/AreaStopTest.java | 5 +++-- .../transit/model/site/BoardingAreaTest.java | 5 +++-- .../transit/model/site/EntranceTest.java | 5 +++-- .../transit/model/site/FareZoneTest.java | 6 +++--- .../transit/model/site/GroupOfStationsTest.java | 5 +++-- .../transit/model/site/GroupStopTest.java | 9 +++++---- .../model/site/MultiModalStationTest.java | 5 +++-- .../transit/model/site/PathwayNodeTest.java | 5 +++-- .../transit/model/site/PathwayTest.java | 7 ++++--- .../transit/model/site/RegularStopTest.java | 7 ++++--- .../transit/model/site/StationTest.java | 5 +++-- .../model/timetable/RealTimeTripTimesTest.java | 2 +- .../model/timetable/ScheduledTripTimesTest.java | 2 +- .../model/timetable/StopTimeKeyTest.java | 6 +++--- .../model/timetable/TripOnServiceDateTest.java | 9 +++++---- .../transit/model/timetable/TripTest.java | 7 ++++--- .../service/DefaultTransitServiceTest.java | 2 +- .../transit/service/SiteRepositoryTest.java | 4 ++-- .../service/TimetableRepositoryTest.java | 2 +- .../updater/trip/siri/AddedTripBuilderTest.java | 15 ++++++++------- .../trip/siri/ModifiedTripBuilderTest.java | 3 ++- .../VehicleParkingAvailabilityUpdaterTest.java | 2 +- .../RealtimeVehicleMatcherTest.java | 9 +++++---- .../GeofencingVertexUpdaterTest.java | 2 +- 97 files changed, 223 insertions(+), 190 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java index 194618bba6d..512b62efd9c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.ext.fares.model.FareTransferRule; import org.opentripplanner.model.fare.FareOffer; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.network.Route; @@ -38,26 +39,26 @@ class CostedTransferAcrossNetworksTest implements PlanTestConstants, FareTestCon List.of( // transferring from A to A is free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t1")) + .withId(FeedScopedIdForTestFactory.id("t1")) .withFromLegGroup(LEG_GROUP_A) .withToLegGroup(LEG_GROUP_A) .build(), // transferring from B to B is also free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t2")) + .withId(FeedScopedIdForTestFactory.id("t2")) .withFromLegGroup(LEG_GROUP_B) .withToLegGroup(LEG_GROUP_B) .build(), // transferring from A to B costs one EUR FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t3")) + .withId(FeedScopedIdForTestFactory.id("t3")) .withFromLegGroup(LEG_GROUP_A) .withToLegGroup(LEG_GROUP_B) .withFareProducts(TRANSFER_1) .build(), // transferring from B to A is free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t4")) + .withId(FeedScopedIdForTestFactory.id("t4")) .withFromLegGroup(LEG_GROUP_B) .withToLegGroup(LEG_GROUP_A) .build() diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java index f0ee672b247..35135235e4e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.Collection; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java index cd843b3d18e..b95ddeebd7f 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.LocalDate; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java index e2ebad57302..4f6a4ebc654 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.flex.filter; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java index d35ee6436d0..64caa954a74 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java @@ -5,7 +5,7 @@ import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java index d0213119f0a..3295b39c187 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Instant; import java.time.LocalDate; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index 43f7c3e1451..f51ff32e8dc 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -7,7 +7,7 @@ import static org.opentripplanner.ext.flex.FlexStopTimesForTest.areaWithContinuousStopping; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopWithContinuousStopping; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java index 61ea07c20bd..1666396451d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d5760946a8e..1f40a266e33 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -7,7 +7,7 @@ import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc; import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Collections; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java index 220566888ac..1255feee514 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java @@ -2,7 +2,7 @@ import static org.opentripplanner.ext.geocoder.StopCluster.LocationType.STOP; import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java index 2846522b2e0..f65ba2f15dd 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.ojp.resource; import static jakarta.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import de.vdv.ojp20.OJP; import jakarta.xml.bind.JAXBContext; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java index e70c0dd21db..cab20f2b59d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java index 7bf87b4c89c..2c2b250d163 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java index b3a983850bd..de98204c542 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java index 3c9a3fb5a59..fed3c256f31 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stations; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java index faf6f8766df..a39b35b4d3f 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stops; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java index 0f8d40c7d76..b67e5619547 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java @@ -28,11 +28,11 @@ import org.opentripplanner.service.vehicleparking.model.VehicleParkingState; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class VehicleParkingGroupsLayerTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("id"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("id"); private VehicleParkingGroup vehicleParkingGroup; private VehicleParking vehicleParking; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java index de4d16f799d..3c9069685af 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java @@ -30,12 +30,12 @@ import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.model.openinghours.OpeningHoursCalendarService; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.framework.Deduplicator; public class VehicleParkingsLayerTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("id"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("id"); private VehicleParking vehicleParking; diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java index f7f13cead65..d7950fb0d61 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.model; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.OTHER_FEED_AGENCY; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.ZonedDateTime; import org.opentripplanner.core.model.i18n.I18NString; diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java index ce487a23af0..c2cf7e6b9e9 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.model; import static org.opentripplanner.ext.fares.model.FareModelForTest.fareProduct; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.groupOfRoutes; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.LocalTime; import org.opentripplanner.core.model.id.FeedScopedId; diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java index 54b9c81d64d..cd9fb3c197d 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java @@ -1,6 +1,6 @@ package org.opentripplanner.street.model; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.HashSet; import java.util.Set; diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 91762d72db3..54b7a407f13 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -12,7 +12,7 @@ import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import static org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus.IN_TRANSIT_TO; import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; import static org.opentripplanner.transit.model.timetable.OccupancyStatus.FEW_SEATS_AVAILABLE; @@ -103,6 +103,7 @@ import org.opentripplanner.street.search.state.TestStateBuilder; import org.opentripplanner.test.support.FilePatternSource; import org.opentripplanner.transfer.regular.TransferServiceTestFactory; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.basic.TransitMode; @@ -208,7 +209,7 @@ static void setup() { var siteRepository = siteRepositoryBuilder.build(); var timetableRepository = new TimetableRepository(siteRepository); - var cal_id = TimetableRepositoryForTest.id("CAL_1"); + var cal_id = FeedScopedIdForTestFactory.id("CAL_1"); var trip = TimetableRepositoryForTest.trip("123") .withHeadsign(I18NString.of("Trip Headsign")) .withServiceId(cal_id) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java index c6d159402b7..cb4f34332f6 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.FULL_DATASET; diff --git a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java index bc34dbeadc1..cc7cc8d16e2 100644 --- a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java +++ b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; @@ -42,7 +43,7 @@ private static FlexAccessEgress buildFlexAccessEgress( int alightPos, int requestedBookingTime ) { - var trip = UnscheduledTrip.of(TimetableRepositoryForTest.id("flex")) + var trip = UnscheduledTrip.of(FeedScopedIdForTestFactory.id("flex")) .withTrip(TimetableRepositoryForTest.trip("t1").build()) .withStopTimes(stopTimes) .build(); diff --git a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java index 285f7e77147..84b73934d65 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java @@ -10,7 +10,7 @@ import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Map; diff --git a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java index 8cf3db9cd10..49120fc80f1 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java @@ -9,7 +9,7 @@ import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static shadow.org.assertj.core.api.Assertions.assertThat; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java index 4a1e13c25ac..216f3fb06de 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java @@ -8,7 +8,7 @@ import static org.opentripplanner.street.linking.VisibilityMode.TRAVERSE_AREA_EDGES; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Arrays; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java index 1bbc5178d80..024ae806247 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.BeforeEach; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java index 1c34e86ba66..07d8390dbc5 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java @@ -6,7 +6,7 @@ import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.distance; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.moveMeters; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.stream.IntStream; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java index 608d515b4c1..dde09cab64f 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java @@ -12,6 +12,7 @@ import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.transfer.regular.TransferRepository; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.BikeAccess; @@ -153,13 +154,13 @@ public void build() { var agency = TimetableRepositoryForTest.agency("Agency"); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP0")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP0")) .withRoute(route("R0", TransitMode.RAIL, agency)) .withStopPattern(new StopPattern(List.of(st(S_FAR_AWAY), st(S0)))) .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP1")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP1")) .withRoute(route("R1", TransitMode.BUS, agency)) .withStopPattern( new StopPattern(List.of(st(S11, !withBoardingConstraint, true), st(S12))) @@ -167,7 +168,7 @@ public void build() { .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP2")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP2")) .withRoute(route("R2", TransitMode.BUS, agency)) .withStopPattern(new StopPattern(List.of(st(S21), st(S22), st(S_FAR_AWAY)))) .withScheduledTimeTableBuilder(builder -> @@ -187,7 +188,7 @@ public void build() { if (includeCarFerryTrips) { tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP4")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP4")) .withRoute(route("R4", TransitMode.FERRY, agency)) .withStopPattern(new StopPattern(List.of(st(S_FAR_AWAY), st(S0), st(S12)))) .withScheduledTimeTableBuilder(b -> @@ -196,7 +197,7 @@ public void build() { .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP5")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP5")) .withRoute(route("R5", TransitMode.FERRY, agency)) .withStopPattern(new StopPattern(List.of(st(S22), st(S23)))) .withScheduledTimeTableBuilder(b -> diff --git a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java index 1ed2c270615..8eedcd287ad 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java @@ -20,6 +20,7 @@ import org.opentripplanner.graph_builder.module.geometry.GeometryProcessor; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.impl.TransitDataImportBuilder; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -68,7 +69,7 @@ static void setupClass() { stopTimeC = new StopTime(); stopTimeC.setStop(stopC); - FeedScopedId serviceId1 = TimetableRepositoryForTest.id("SERVICE_ID_1"); + FeedScopedId serviceId1 = FeedScopedIdForTestFactory.id("SERVICE_ID_1"); trip1 = trip("TRIP_ID_1") .withServiceId(serviceId1) .withMode(TransitMode.RAIL) @@ -77,7 +78,7 @@ static void setupClass() { .build(); // same route, mode, submode and direction as trip1 - FeedScopedId serviceId2 = TimetableRepositoryForTest.id("SERVICE_ID_2"); + FeedScopedId serviceId2 = FeedScopedIdForTestFactory.id("SERVICE_ID_2"); trip2 = trip("TRIP_ID_2") .withServiceId(serviceId2) .withRoute(trip1.getRoute()) @@ -87,7 +88,7 @@ static void setupClass() { .build(); // same route, direction as trip1, different mode - FeedScopedId serviceId3 = TimetableRepositoryForTest.id("SERVICE_ID_3"); + FeedScopedId serviceId3 = FeedScopedIdForTestFactory.id("SERVICE_ID_3"); trip3 = trip("TRIP_ID_3") .withServiceId(serviceId3) .withRoute(trip1.getRoute()) @@ -96,7 +97,7 @@ static void setupClass() { .build(); // same route, mode, direction as trip1, different submode - FeedScopedId serviceId4 = TimetableRepositoryForTest.id("SERVICE_ID_4"); + FeedScopedId serviceId4 = FeedScopedIdForTestFactory.id("SERVICE_ID_4"); trip4 = trip("TRIP_ID_4") .withServiceId(serviceId4) .withRoute(trip1.getRoute()) @@ -106,7 +107,7 @@ static void setupClass() { .build(); // same route, mode as trip1, different direction - FeedScopedId serviceId5 = TimetableRepositoryForTest.id("SERVICE_ID_5"); + FeedScopedId serviceId5 = FeedScopedIdForTestFactory.id("SERVICE_ID_5"); trip5 = trip("TRIP_ID_5") .withServiceId(serviceId5) .withRoute(trip1.getRoute()) diff --git a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java index 81ce4881097..a294440de94 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java @@ -17,6 +17,7 @@ import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.transfer.constrained.internal.DefaultConstrainedTransferService; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.StopPattern; @@ -163,7 +164,7 @@ private static TripPattern tripPattern(String tripId, String blockId, String ser var stopPattern = new StopPattern(stopTimes); var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); - return TripPattern.of(TimetableRepositoryForTest.id(tripId)) + return TripPattern.of(FeedScopedIdForTestFactory.id(tripId)) .withRoute(trip.getRoute()) .withStopPattern(stopPattern) .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) diff --git a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java index 84c3c9473f3..429c9b6c2fc 100644 --- a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java +++ b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java @@ -8,7 +8,7 @@ import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; import static org.opentripplanner.model.calendar.ServiceCalendarDate.EXCEPTION_TYPE_REMOVE; import static org.opentripplanner.model.calendar.impl.CalendarServiceDataFactoryImpl.merge; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.io.IOException; import java.time.LocalDate; diff --git a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java index 2ac1f02db0f..bb11b595ed2 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.io.IOException; import java.util.ArrayList; diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java index ee860fea58e..1053f657795 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java @@ -17,6 +17,7 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.ServiceCalendar; import org.opentripplanner.model.calendar.ServiceCalendarDate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.EntityById; @@ -43,10 +44,10 @@ public class TransitDataImportBuilderLimitPeriodTest { private static final LocalDate D1 = LocalDate.of(2020, 1, 8); private static final LocalDate D2 = LocalDate.of(2020, 1, 15); private static final LocalDate D3 = LocalDate.of(2020, 1, 31); - private static final FeedScopedId SERVICE_C_IN = TimetableRepositoryForTest.id("CalSrvIn"); - private static final FeedScopedId SERVICE_D_IN = TimetableRepositoryForTest.id("CalSrvDIn"); - private static final FeedScopedId SERVICE_C_OUT = TimetableRepositoryForTest.id("CalSrvOut"); - private static final FeedScopedId SERVICE_D_OUT = TimetableRepositoryForTest.id("CalSrvDOut"); + private static final FeedScopedId SERVICE_C_IN = FeedScopedIdForTestFactory.id("CalSrvIn"); + private static final FeedScopedId SERVICE_D_IN = FeedScopedIdForTestFactory.id("CalSrvDIn"); + private static final FeedScopedId SERVICE_C_OUT = FeedScopedIdForTestFactory.id("CalSrvOut"); + private static final FeedScopedId SERVICE_D_OUT = FeedScopedIdForTestFactory.id("CalSrvDOut"); private static final Deduplicator DEDUPLICATOR = new Deduplicator(); private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final RegularStop STOP_1 = TEST_MODEL.stop("Stop-1").build(); @@ -190,11 +191,11 @@ private static StopTime createStopTime(RegularStop stop, int time) { } private static FeedScopedId newId() { - return TimetableRepositoryForTest.id(Integer.toString(++SEQ_NR)); + return FeedScopedIdForTestFactory.id(Integer.toString(++SEQ_NR)); } private TripPattern createTripPattern(Collection trips) { - FeedScopedId patternId = TimetableRepositoryForTest.id( + FeedScopedId patternId = FeedScopedIdForTestFactory.id( trips .stream() .map(t -> t.getId().getId()) diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java index 66d051dfc90..fb7300d08f2 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java @@ -18,13 +18,14 @@ import org.opentripplanner.model.Frequency; import org.opentripplanner.model.calendar.ServiceCalendar; import org.opentripplanner.model.calendar.ServiceCalendarDate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.network.Route; public class TransitDataImportBuilderTest { private static final String FEED_ID = TimetableRepositoryForTest.FEED_ID; - private static final FeedScopedId SERVICE_WEEKDAYS_ID = TimetableRepositoryForTest.id("weekdays"); + private static final FeedScopedId SERVICE_WEEKDAYS_ID = FeedScopedIdForTestFactory.id("weekdays"); private static TransitDataImportBuilder subject; diff --git a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index 25196df35aa..e79b5f45c93 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -19,6 +19,7 @@ import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; import org.opentripplanner.street.search.TraverseMode; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; @@ -74,7 +75,7 @@ void testDerivedFieldsWithBusAllTheWay() { assertEquals(newTime(T11_00), firstLeg.startTime()); assertEquals(newTime(T11_10), firstLeg.endTime()); assertEquals(TransitMode.BUS, result.transitLeg(0).mode()); - assertEquals(TimetableRepositoryForTest.id("55"), firstLeg.trip().getId()); + assertEquals(FeedScopedIdForTestFactory.id("55"), firstLeg.trip().getId()); assertEquals(A_TO_B_DISTANCE, firstLeg.distanceMeters(), DISTANCE_DELTA); assertEquals("A ~ BUS 55 11:00 11:10 ~ B [C₁720]", result.toStr()); @@ -99,7 +100,7 @@ void testDerivedFieldsWithTrainAllTheWay() { assertEquals(newTime(T11_05), firstLeg.startTime()); assertEquals(newTime(T11_15), firstLeg.endTime()); assertEquals(TransitMode.RAIL, result.transitLeg(0).mode()); - assertEquals(TimetableRepositoryForTest.id("20"), firstLeg.trip().getId()); + assertEquals(FeedScopedIdForTestFactory.id("20"), firstLeg.trip().getId()); assertEquals(A_TO_B_DISTANCE, firstLeg.distanceMeters(), DISTANCE_DELTA); assertEquals("A ~ RAIL R2 11:05 11:15 ~ B [C₁720]", result.toStr()); diff --git a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 05d75e29eb5..7558550912c 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -4,7 +4,7 @@ import static org.opentripplanner.street.search.TraverseMode.BICYCLE; import static org.opentripplanner.street.search.TraverseMode.CAR; import static org.opentripplanner.street.search.TraverseMode.WALK; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import java.time.Duration; diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java index 42d3b304847..03c52579d2f 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java @@ -7,17 +7,17 @@ import java.time.LocalDate; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class LegReferenceSerializerTest { - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("Trip"); + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("Trip"); private static final LocalDate SERVICE_DATE = LocalDate.of(2022, 1, 31); private static final int FROM_STOP_POS = 1; - private static final FeedScopedId FROM_STOP_ID = TimetableRepositoryForTest.id("Boarding Stop"); + private static final FeedScopedId FROM_STOP_ID = FeedScopedIdForTestFactory.id("Boarding Stop"); private static final int TO_STOP_POS = 3; - private static final FeedScopedId TO_STOP_ID = TimetableRepositoryForTest.id("Alighting Stop"); + private static final FeedScopedId TO_STOP_ID = FeedScopedIdForTestFactory.id("Alighting Stop"); /** * Token based on the latest format, including stop ids and TripOnServiceDate id. diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index edfe07ca60f..c0d9dafc3d9 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; @@ -15,6 +15,7 @@ import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; import org.opentripplanner.street.graph.Graph; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.TripPattern; @@ -178,7 +179,7 @@ void getLegFromReferenceMismatchOnBoardingStop() { SERVICE_DATE, 0, 1, - TimetableRepositoryForTest.id("invalid stop id"), + FeedScopedIdForTestFactory.id("invalid stop id"), STOP_2_ID, null ); @@ -345,7 +346,7 @@ void legReferenceCannotReferToBothTripAndTripOnServiceDate() { NUMBER_OF_STOPS, STOP_1_ID, STOP_2_ID, - TimetableRepositoryForTest.id("trip on date id") + FeedScopedIdForTestFactory.id("trip on date id") ) ); } @@ -388,7 +389,7 @@ void getLegFromReferenceWithUnknownTripOnDate() { NUMBER_OF_STOPS, STOP_1_ID, STOP_2_ID, - TimetableRepositoryForTest.id("unknown trip on date id") + FeedScopedIdForTestFactory.id("unknown trip on date id") ); assertNull(scheduledTransitLegReference.getLeg(transitService)); } diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java index 196f65362a3..22a30d4698c 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java @@ -14,6 +14,7 @@ import org.opentripplanner.model.impl.TransitDataImportBuilder; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; import org.opentripplanner.netex.index.hierarchy.HierarchicalMapById; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.SiteRepository; @@ -33,7 +34,7 @@ public class TripMapperTest { private static final String ROUTE_ID = "RUT:Route:1"; private static final String SERVICE_JOURNEY_ID = NetexTestDataSample.SERVICE_JOURNEY_ID; private static final String JOURNEY_PATTERN_ID = "RUT:JourneyPattern:1"; - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("S001"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("S001"); private static final DataImportIssueStore ISSUE_STORE = DataImportIssueStore.NOOP; private static final JAXBElement LINE_REF = MappingSupport.createWrappedRef( diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java index eca7e842234..c656405851b 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java @@ -14,7 +14,7 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; import org.opentripplanner.netex.index.hierarchy.HierarchicalMapById; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DefaultEntityById; import org.opentripplanner.transit.model.timetable.Trip; @@ -28,7 +28,7 @@ class TripPatternMapperTest { - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("S01"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("S01"); @Test void testMapTripPattern() { diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java index bbd9c791570..8f1595751b9 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java @@ -11,16 +11,16 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.calendar.ServiceCalendarDate; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class CalendarServiceBuilderTest { private static final LocalDate D1 = LocalDate.of(2020, 11, 1); private static final LocalDate D2 = LocalDate.of(2020, 11, 2); - private static final FeedScopedId EXP_SID_1 = TimetableRepositoryForTest.id("S000001"); - private static final FeedScopedId EXP_SID_2 = TimetableRepositoryForTest.id("S000002"); - private static final FeedScopedId EXP_SID_3 = TimetableRepositoryForTest.id("S000003"); + private static final FeedScopedId EXP_SID_1 = FeedScopedIdForTestFactory.id("S000001"); + private static final FeedScopedId EXP_SID_2 = FeedScopedIdForTestFactory.id("S000002"); + private static final FeedScopedId EXP_SID_3 = FeedScopedIdForTestFactory.id("S000003"); @Test public void addDatesForAGivenService() { @@ -64,8 +64,8 @@ public void createServiceCalendar() { @Test public void createServiceId() { CalendarServiceBuilder subject = new CalendarServiceBuilder(new FeedScopedIdFactory(FEED_ID)); - assertEquals(TimetableRepositoryForTest.id("S000001"), subject.createServiceId()); - assertEquals(TimetableRepositoryForTest.id("S000002"), subject.createServiceId()); + assertEquals(FeedScopedIdForTestFactory.id("S000001"), subject.createServiceId()); + assertEquals(FeedScopedIdForTestFactory.id("S000002"), subject.createServiceId()); } private void assertServiceDateExistInList( diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java index 510ca5c8463..a3dff8d35e9 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java index 8be85ef6ffa..6ddd5a8e9de 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.routing.linking.TransitStopVertexBuilderFactory.ofStop; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.ArrayList; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java index f3d023d032e..ce800c887c6 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java index 30d1654b0db..5122702c5e0 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java @@ -10,6 +10,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.RoutingTripPattern; @@ -31,7 +32,7 @@ class RaptorTransitDataTest { stopTime.setStop(stop); var stopPattern = new StopPattern(List.of(stopTime)); var route = TimetableRepositoryForTest.route("1").build(); - TRIP_PATTERN = TripPattern.of(TimetableRepositoryForTest.id("P1")) + TRIP_PATTERN = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java index 87926384a5c..2f6d03ca739 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -48,7 +49,7 @@ void shouldExcludeAndIncludeBasedOnFrequency(List freqs) { var stopTime = new StopTime(); stopTime.setStop(STOP); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java index 0c0dfa103b3..2ca9fc7694a 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java @@ -4,8 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.raptorlegacy._data.transit.TestRoute.route; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.Duration; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java index 0bd2b6a3a01..7031ed5d165 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import com.google.common.collect.ArrayListMultimap; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java index 84a6569d9fe..36ec8360148 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java @@ -31,6 +31,7 @@ import org.opentripplanner.routing.api.request.request.filter.TransitFilter; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.SubMode; @@ -56,7 +57,7 @@ class DefaultTransitDataProviderFilterTest { private static final Route ROUTE = TimetableRepositoryForTest.route("1").build(); - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("T1"); + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("T1"); private static final RegularStop STOP_FOR_TEST = TEST_MODEL.stop("TEST:STOP", 0, 0).build(); @@ -99,7 +100,7 @@ void testWheelchairAccess(Accessibility wheelchair, WheelchairPreferences access stopTimeEnd.setStop(lastStop); var stopPattern = new StopPattern(List.of(stopTimeStart, stopTimeEnd)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(TimetableRepositoryForTest.route("1").build()) .withStopPattern(stopPattern) .build() @@ -148,7 +149,7 @@ void testRealtimeCancelledStops(boolean includeRealtimeCancellations) { var stopTime3 = getStopTime("TEST:3", PickDrop.NONE); var stopTime4 = getStopTime("TEST:4", PickDrop.SCHEDULED); var stopPattern = new StopPattern(List.of(stopTime1, stopTime2, stopTime3, stopTime4)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(TimetableRepositoryForTest.route("1").build()) .withStopPattern(stopPattern) .build() @@ -720,7 +721,7 @@ void includeRealtimeCancellationsTest() { @Test void testBikesAllowed() { RouteBuilder routeBuilder = TimetableRepositoryForTest.route("1"); - TripBuilder trip = Trip.of(TimetableRepositoryForTest.id("T1")).withRoute(routeBuilder.build()); + TripBuilder trip = Trip.of(FeedScopedIdForTestFactory.id("T1")).withRoute(routeBuilder.build()); assertEquals( BikeAccess.UNKNOWN, @@ -896,7 +897,7 @@ private TripPatternForDate createTestTripPatternForDate() { var stopTime = new StopTime(); stopTime.setStop(STOP_FOR_TEST); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .build() @@ -1005,7 +1006,7 @@ private PatternAndTimes createPatternAndTimes( stopTime.setStop(STOP_FOR_TEST); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .withMode(mode) @@ -1042,7 +1043,7 @@ private PatternAndTimes createPatternAndTimes( TransitMode tripMode, boolean containsMultipleModes ) { - Trip trip = Trip.of(TimetableRepositoryForTest.id(tripIdSuffix)) + Trip trip = Trip.of(FeedScopedIdForTestFactory.id(tripIdSuffix)) .withRoute(ROUTE) .withMode(tripMode) .withBikesAllowed(BikeAccess.NOT_ALLOWED) @@ -1058,7 +1059,7 @@ private PatternAndTimes createPatternAndTimes( StopPattern stopPattern = new StopPattern(List.of(stopTime)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .withMode(patternMode) diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 5913b42b29c..78e4c57f00b 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -3,7 +3,7 @@ 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.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.LocalTime; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index ec681707f62..f106a322939 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -16,6 +16,7 @@ import org.opentripplanner.raptor.spi.RaptorTimeTable; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -56,7 +57,7 @@ public TestRouteData(Route route, List stops, List times) { .map(tripTimesByTrip::get) .collect(Collectors.toList()); - tripPattern = TripPattern.of(TimetableRepositoryForTest.id("TP:" + route)) + tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("TP:" + route)) .withRoute(this.route) .withStopPattern(new StopPattern(stopTimesFistTrip)) .withScheduledTimeTableBuilder(builder -> builder.addAllTripTimes(tripTimes)) @@ -163,7 +164,7 @@ private Trip parseTripInfo( List stops, Deduplicator deduplicator ) { - var trip = Trip.of(TimetableRepositoryForTest.id(route + "-" + stopTimesByTrip.size() + 1)) + var trip = Trip.of(FeedScopedIdForTestFactory.id(route + "-" + stopTimesByTrip.size() + 1)) .withRoute(this.route) .build(); var stopTimes = stopTimes(trip, stops, tripTimes); diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java index 882dbc979b1..1b94e804c23 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.SearchDirection; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -79,7 +80,7 @@ private static TripPatternForDates getTestSubjectWithExactFrequency() { stopTime2.setDepartureTime(300); stopTime2.setStopSequence(1); StopPattern stopPattern = new StopPattern(List.of(stopTime1, stopTime2)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java index f85dc481c12..d6dbc4ebe54 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java @@ -12,7 +12,7 @@ import static org.opentripplanner.model.plan.PlanTestConstants.T11_30; import static org.opentripplanner.model.plan.PlanTestConstants.T11_50; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java index 3c593756604..06f5d942544 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java @@ -4,7 +4,7 @@ import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; import static org.opentripplanner.model.plan.PlanTestConstants.T11_10; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index de364988b6b..8f395aef8d3 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -17,6 +17,7 @@ import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.vertex.IntersectionVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.Route; @@ -109,13 +110,13 @@ public void build() { street(C, D, 100, StreetTraversalPermission.ALL); tripPattern( - TP1 = TripPattern.of(TimetableRepositoryForTest.id("TP1")) + TP1 = TripPattern.of(FeedScopedIdForTestFactory.id("TP1")) .withRoute(R1) .withStopPattern(new StopPattern(List.of(st(S1), st(S2)))) .build() ); tripPattern( - TP2 = TripPattern.of(TimetableRepositoryForTest.id("TP2")) + TP2 = TripPattern.of(FeedScopedIdForTestFactory.id("TP2")) .withRoute(R2) .withStopPattern(new StopPattern(List.of(st(S1), st(S3)))) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java index e8e5c522947..b9d04060aba 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.Arrays; diff --git a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java index 05728e492cf..1482d879453 100644 --- a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java @@ -8,6 +8,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.site.RegularStop; @@ -28,7 +29,7 @@ public class FrequencyEntryTest { int time = 0; for (int i = 0; i < STOP_NUM; ++i) { - FeedScopedId id = TimetableRepositoryForTest.id(i + ""); + FeedScopedId id = FeedScopedIdForTestFactory.id(i + ""); RegularStop stop = TimetableRepositoryForTest.of().stop(id.getId(), 0.0, 0.0).build(); diff --git a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java index 5fc22e55e05..32069b692b7 100644 --- a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java +++ b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.routing.alertpatch.AlertEffect.DETOUR; import static org.opentripplanner.routing.alertpatch.AlertSeverity.INFO; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java index 4db18162e69..71cdc0f512a 100644 --- a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.core.model.basic.Cost.costOfSeconds; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.Instant; @@ -25,7 +25,7 @@ import org.opentripplanner.street.search.intersection_model.DrivingDirection; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalModel; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class StreetSearchRequestMapperTest { @@ -332,7 +332,7 @@ void mapCarRentalArrivalRequest() { var dateTime = Instant.parse("2022-11-10T10:00:00Z"); var rentalDuration = Duration.ofHours(2); builder.withDateTime(dateTime); - var from = new GenericLocation(null, TimetableRepositoryForTest.id("STOP"), null, null); + var from = new GenericLocation(null, FeedScopedIdForTestFactory.id("STOP"), null, null); builder.withFrom(from); var to = GenericLocation.fromCoordinate(60.0, 20.0); builder.withTo(to); diff --git a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java index 0300126bd77..1a0a64e70ad 100644 --- a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java +++ b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java @@ -27,7 +27,7 @@ import org.opentripplanner.transfer.constrained.model.StopTransferPoint; import org.opentripplanner.transfer.constrained.model.TransferConstraint; import org.opentripplanner.transfer.constrained.model.TripTransferPoint; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.TripPattern; @@ -35,7 +35,7 @@ public class ConstrainedBoardingSearchTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("ID"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("ID"); private static final TransferConstraint GUARANTEED_CONSTRAINT = TransferConstraint.of() .guaranteed() .build(); diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java index 8c872b80147..d9f50aa6551 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java index b7f1df8587b..03f7ace0bc5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java @@ -1,5 +1,7 @@ package org.opentripplanner.transit.model._data; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; + import java.time.LocalTime; import java.util.Arrays; import java.util.List; @@ -104,10 +106,6 @@ public static TimetableRepositoryForTest of() { return new TimetableRepositoryForTest(SiteRepository.of()); } - public static FeedScopedId id(String id) { - return new FeedScopedId(FEED_ID, id); - } - public static Agency agency(String name) { return AGENCY.copy().withId(id(name)).withName(name).build(); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java index fbac179e9e7..a8e8bb7eaac 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java @@ -5,9 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class NoticeTest { @@ -15,7 +16,7 @@ class NoticeTest { private static final String TEXT = "text"; private static final String PUBLIC_CODE = "public code"; - private static final Notice SUBJECT = Notice.of(TimetableRepositoryForTest.id(ID)) + private static final Notice SUBJECT = Notice.of(id(ID)) .withPublicCode(PUBLIC_CODE) .withText(TEXT) .build(); @@ -44,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withPublicCode("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withText("X").build())); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java index 89c16e73f6a..d73afa2b426 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java index 23168453bd8..e1f12ae4479 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java @@ -7,16 +7,16 @@ import java.util.Collections; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class EntityByIdTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("99"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("99"); private static final TestEntity E = TestEntity.of(ID).build(); private static final String E_TO_STRING = E.toString(); private static final String LIST_OF_E_TO_STRING = String.format("[%s]", E_TO_STRING); private static final String MAP_OF_E_TO_STRING = String.format("{%s=%s}", ID, E_TO_STRING); - private static final FeedScopedId FAKE_ID = TimetableRepositoryForTest.id("77"); + private static final FeedScopedId FAKE_ID = FeedScopedIdForTestFactory.id("77"); private final EntityById subject = new DefaultEntityById<>(); @Test diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java index f3d2b053e1d..94e178a82b5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class GroupOfRoutesTest { @@ -17,7 +17,7 @@ public class GroupOfRoutesTest { private static final String NAME = "test_name"; private static final String DESCRIPTION = "description"; - private static final GroupOfRoutes SUBJECT = GroupOfRoutes.of(TimetableRepositoryForTest.id(ID)) + private static final GroupOfRoutes SUBJECT = GroupOfRoutes.of(FeedScopedIdForTestFactory.id(ID)) .withPrivateCode(PRIVATE_CODE) .withShortName(SHORT_NAME) .withName(NAME) @@ -51,7 +51,7 @@ public void sameValue() { // Make a copy, and set the same name (nothing is changed) var other = SUBJECT.copy().build(); assertTrue(SUBJECT.sameAs(other)); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withDescription("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java index aaa5cbcdfad..e912a939866 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -39,7 +40,7 @@ class RouteTest { private static final Integer GTFS_SORT_ORDER = 0; private static final String URL = "url"; public static final Agency AGENCY = TimetableRepositoryForTest.AGENCY; - private static final Route SUBJECT = Route.of(TimetableRepositoryForTest.id(ID)) + private static final Route SUBJECT = Route.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withLongName(LONG_NAME) .withDescription(DESCRIPTION) @@ -94,7 +95,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withLongName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withDescription("X").build())); 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 8243030c439..a76b411e2e0 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 @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Assertions; diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java index de7c3295009..9119d23ac84 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class AgencyTest { @@ -19,7 +19,7 @@ class AgencyTest { private static final String FARE_URL = "http://fare.aaa.com"; private static final String LANG = "image"; - private static final Agency SUBJECT = Agency.of(TimetableRepositoryForTest.id(ID)) + private static final Agency SUBJECT = Agency.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withUrl(URL) .withTimezone(TIMEZONE) @@ -56,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withUrl("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withTimezone("CET").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java index c609195e55b..f85a013b4bd 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class BrandingTest { @@ -18,7 +18,7 @@ public class BrandingTest { private static final String DESCRIPTION = "test_description"; private static final String IMAGE = "test_image"; - Branding subject = Branding.of(TimetableRepositoryForTest.id(ID)) + Branding subject = Branding.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withName(NAME) .withUrl(URL) @@ -52,7 +52,7 @@ void copy() { @Test void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); - assertFalse(subject.sameAs(subject.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(subject.sameAs(subject.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withName("X").build())); assertFalse(subject.sameAs(subject.copy().withShortName("X").build())); assertFalse(subject.sameAs(subject.copy().withUrl("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java index e3fdb62b6a7..cc485d509b0 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class OperatorTest { @@ -16,7 +16,7 @@ class OperatorTest { private static final String URL = "http://info.aaa.com"; private static final String PHONE = "+47 95566333"; - private static final Operator SUBJECT = Operator.of(TimetableRepositoryForTest.id(ID)) + private static final Operator SUBJECT = Operator.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withUrl(URL) .withPhone(PHONE) @@ -45,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withUrl("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withPhone("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java index bea8842364e..8146e3c762b 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java @@ -13,6 +13,7 @@ import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.service.SiteRepository; @@ -34,7 +35,7 @@ class AreaStopTest { private static AreaStopBuilder areaStopBuilder() { return SiteRepository.of() - .areaStop(TimetableRepositoryForTest.id(ID)) + .areaStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withUrl(URL) @@ -69,7 +70,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java index d552ff731dd..b1580f5afae 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java @@ -10,6 +10,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class BoardingAreaTest { @@ -21,7 +22,7 @@ class BoardingAreaTest { .stop("stopId") .build(); - private static final BoardingArea SUBJECT = BoardingArea.of(TimetableRepositoryForTest.id(ID)) + private static final BoardingArea SUBJECT = BoardingArea.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withParentStop(PARENT_STOP) @@ -53,7 +54,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java index bc7673f2455..e39da91043b 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class EntranceTest { @@ -26,7 +27,7 @@ class EntranceTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final Station PARENT_STATION = TEST_MODEL.station("stationId").build(); - private static final Entrance SUBJECT = Entrance.of(TimetableRepositoryForTest.id(ID)) + private static final Entrance SUBJECT = Entrance.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -66,7 +67,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java index 3e699a10b8f..97f6cab8cdb 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java @@ -7,13 +7,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class FareZoneTest { private static final String ID = "1"; private static final String NAME = "name"; - private static final FareZone SUBJECT = FareZone.of(TimetableRepositoryForTest.id(ID)) + private static final FareZone SUBJECT = FareZone.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .build(); @@ -40,7 +40,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); } } diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java index bce3d429a40..9144ab7c284 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class GroupOfStationsTest { @@ -25,7 +26,7 @@ class GroupOfStationsTest { .build(); private static final GroupOfStations SUBJECT = GroupOfStations.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withName(NAME) .addChildStation(STATION) @@ -55,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs( diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java index ee44afcb842..e12951d30fd 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java @@ -14,6 +14,7 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.service.SiteRepository; @@ -30,7 +31,7 @@ class GroupStopTest { Coordinates.BERLIN.getY() ).build(); private static final GroupStop SUBJECT = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(STOP_LOCATION) .build(); @@ -49,7 +50,7 @@ void testGroupStopGeometry() { ).build(); GroupStop groupStop = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(stopLocation1) .addLocation(stopLocation2) @@ -71,7 +72,7 @@ void testGroupStopEncompassingAreaGeometry() { ).build(); GroupStop groupStop = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(stopLocation) .withEncompassingAreaGeometries(List.of(Polygons.BERLIN)) @@ -110,7 +111,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs( diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java index 116843a2b70..0c2f79de9f1 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class MultiModalStationTest { @@ -24,7 +25,7 @@ class MultiModalStationTest { public static final Set CHILD_STATIONS = Set.of(STATION_1, STATION_2); private static final MultiModalStation SUBJECT = MultiModalStation.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withName(NAME) .withChildStations(CHILD_STATIONS) @@ -55,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withChildStations(Set.of(STATION_1)).build())); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java index da54e83d080..3b05922382d 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class PathwayNodeTest { @@ -25,7 +26,7 @@ class PathwayNodeTest { public static final WgsCoordinate COORDINATE = new WgsCoordinate(0, 0); private static final StopLevel LEVEL = new StopLevel("level", 0); private static final Accessibility WHEELCHAIR_ACCESSIBILITY = Accessibility.POSSIBLE; - private static final PathwayNode SUBJECT = PathwayNode.of(TimetableRepositoryForTest.id(ID)) + private static final PathwayNode SUBJECT = PathwayNode.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -65,7 +66,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java index 61f8a653ec2..12556b1bca5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class PathwayTest { @@ -16,13 +17,13 @@ class PathwayTest { private static final String ID = "1:pathway"; private static final String NAME = "name"; private static final PathwayMode MODE = PathwayMode.ESCALATOR; - private static final PathwayNode FROM = PathwayNode.of(TimetableRepositoryForTest.id("1:node")) + private static final PathwayNode FROM = PathwayNode.of(FeedScopedIdForTestFactory.id("1:node")) .withCoordinate(new WgsCoordinate(20, 30)) .build(); private static final RegularStop TO = TimetableRepositoryForTest.of().stop("1:stop").build(); public static final int TRAVERSAL_TIME = 120; - private final Pathway subject = Pathway.of(TimetableRepositoryForTest.id(ID)) + private final Pathway subject = Pathway.of(FeedScopedIdForTestFactory.id(ID)) .withPathwayMode(MODE) .withSignpostedAs(NAME) .withFromStop(FROM) @@ -64,7 +65,7 @@ void copy() { @Test void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); - assertFalse(subject.sameAs(subject.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(subject.sameAs(subject.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withSignpostedAs("X").build())); assertFalse(subject.sameAs(subject.copy().withReverseSignpostedAs("X").build())); assertFalse(subject.sameAs(subject.copy().withPathwayMode(PathwayMode.ELEVATOR).build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java index a7b76de1aa5..8751f4ae7ef 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java @@ -14,6 +14,7 @@ import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -38,7 +39,7 @@ class RegularStopTest { private static final String PLATFORM_CODE = "platformCode"; private static final RegularStop SUBJECT = SiteRepository.of() - .regularStop(TimetableRepositoryForTest.id(ID)) + .regularStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -87,7 +88,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) @@ -113,7 +114,7 @@ void fareZonesAreSortedById() { var zoneB = FareZone.of(new FeedScopedId("F", "B")).build(); var stop = SiteRepository.of() - .regularStop(TimetableRepositoryForTest.id("sortTest")) + .regularStop(FeedScopedIdForTestFactory.id("sortTest")) .withName(NAME) .withCoordinate(COORDINATE) .addFareZone(zoneC) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java index 599b17321fd..70337b4e6e7 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java @@ -11,6 +11,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class StationTest { @@ -27,7 +28,7 @@ class StationTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final Station PARENT_STATION = TEST_MODEL.station("stationId").build(); - private static final Station SUBJECT = Station.of(TimetableRepositoryForTest.id(ID)) + private static final Station SUBJECT = Station.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -66,7 +67,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withCode("X").build())); assertFalse( diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index 7398de93ca4..1ccfc7b5722 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java index fbf61d27435..bef235cc153 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -5,7 +5,7 @@ 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 static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.BitSet; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java index 60ff095ba9c..acde72734e5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class StopTimeKeyTest { @@ -13,7 +13,7 @@ class StopTimeKeyTest { private static final int STOP_SEQUENCE_NUMBER = 1; private static final StopTimeKey SUBJECT = StopTimeKey.of( - TimetableRepositoryForTest.id(ID), + FeedScopedIdForTestFactory.id(ID), STOP_SEQUENCE_NUMBER ).build(); @@ -30,6 +30,6 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); } } diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java index 5b1c5a2eca3..fe71f7b4ae9 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class TripOnServiceDateTest { @@ -14,12 +15,12 @@ class TripOnServiceDateTest { private static final String ID = "1"; private static final TripAlteration TRIP_ALTERATION = TripAlteration.CANCELLATION; private static final List REPLACEMENT_FOR = List.of( - TripOnServiceDate.of(TimetableRepositoryForTest.id("id1")).build() + TripOnServiceDate.of(FeedScopedIdForTestFactory.id("id1")).build() ); public static final LocalDate SERVICE_DATE = LocalDate.now(); public static final String TRIP_ID = "tripId"; private static final TripOnServiceDate SUBJECT = TripOnServiceDate.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withTrip(TimetableRepositoryForTest.trip(TRIP_ID).build()) .withServiceDate(SERVICE_DATE) @@ -44,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withServiceDate(LocalDate.now().plusDays(1)).build()) ); @@ -53,7 +54,7 @@ void sameAs() { SUBJECT.sameAs( SUBJECT.copy() .withReplacementFor( - List.of(TripOnServiceDate.of(TimetableRepositoryForTest.id("id2")).build()) + List.of(TripOnServiceDate.of(FeedScopedIdForTestFactory.id("id2")).build()) ) .build() ) diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java index 0a306acd161..3c29734c9ff 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java @@ -8,6 +8,7 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -37,7 +38,7 @@ class TripTest { .build(); private static final FeedScopedId SERVICE_ID = FeedScopedId.of("x", "serviceId"); private static final FeedScopedId SHAPE_ID = FeedScopedId.of("x", "shapeId"); - private static final Trip SUBJECT = Trip.of(TimetableRepositoryForTest.id(ID)) + private static final Trip SUBJECT = Trip.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withRoute(ROUTE) .withDirection(DIRECTION) @@ -63,7 +64,7 @@ void shouldCopyFieldsFromRoute() { .withBikesAllowed(BIKE_ACCESS) .build(); - var SUBJECT = Trip.of(TimetableRepositoryForTest.id(ID)).withRoute(routeWithModes).build(); + var SUBJECT = Trip.of(FeedScopedIdForTestFactory.id(ID)).withRoute(routeWithModes).build(); assertEquals(TRANSIT_MODE, SUBJECT.getMode()); assertEquals(NETEX_SUBMODE, SUBJECT.getNetexSubMode()); @@ -98,7 +99,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withWheelchairBoarding(Accessibility.NOT_POSSIBLE).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java index 3c6e477cec3..592e9089475 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java @@ -4,7 +4,7 @@ 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.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; import static org.opentripplanner.transit.model.basic.TransitMode.RAIL; diff --git a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java index d45a9281b16..d446f3c5993 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java @@ -12,7 +12,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.GroupOfStations; import org.opentripplanner.transit.model.site.GroupStop; @@ -28,7 +28,7 @@ class SiteRepositoryTest { COOR_A.asJtsCoordinate() ); public static final NonLocalizedString NAME = NonLocalizedString.ofNullable("Name"); - private static final FeedScopedId ID = TimetableRepositoryForTest.id("A"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("A"); private static final Station STATION = Station.of(ID) .withName(NAME) .withCoordinate(COOR_B) diff --git a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java index bef23268dd6..c64dce7b1e9 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.framework.application.OtpFileNames.BUILD_CONFIG_FILENAME; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.stopPattern; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java index 9993609a282..a8ae07033ae 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java @@ -20,6 +20,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -45,7 +46,7 @@ class AddedTripBuilderTest { private static final Agency AGENCY = TimetableRepositoryForTest.AGENCY; private static final ZoneId TIME_ZONE = AGENCY.getTimezone(); - private static final Operator OPERATOR = Operator.of(TimetableRepositoryForTest.id("OPERATOR_ID")) + private static final Operator OPERATOR = Operator.of(FeedScopedIdForTestFactory.id("OPERATOR_ID")) .withName("OPERATOR_NAME") .build(); private static final Route REPLACED_ROUTE = TimetableRepositoryForTest.route("REPLACED_ROUTE") @@ -53,8 +54,8 @@ class AddedTripBuilderTest { .withOperator(OPERATOR) .build(); private static final String LINE_REF = "ROUTE_ID"; - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("TRIP_ID"); - private static final FeedScopedId DATED_SERVICE_JOURNEY_ID = TimetableRepositoryForTest.id( + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("TRIP_ID"); + private static final FeedScopedId DATED_SERVICE_JOURNEY_ID = FeedScopedIdForTestFactory.id( "DATED_SERVICE_JOURNEY_ID" ); private static final LocalDate SERVICE_DATE = LocalDate.of(2023, 2, 17); @@ -96,7 +97,7 @@ void setUp() { // Crate a scheduled calendar, to have the SERVICE_DATE be within the transit feed coverage CalendarServiceData calendarServiceData = new CalendarServiceData(); - var cal_id = TimetableRepositoryForTest.id("CAL_1"); + var cal_id = FeedScopedIdForTestFactory.id("CAL_1"); calendarServiceData.putServiceDatesForServiceId( cal_id, List.of(SERVICE_DATE.minusDays(1), SERVICE_DATE, SERVICE_DATE.plusDays(1)) @@ -264,8 +265,8 @@ void testAddedTripOnAddedRoute() { var firstTrip = firstAddedTrip.tripTimes().getTrip(); - var tripId2 = TimetableRepositoryForTest.id("TRIP_ID_2"); - var datedServiceJourneyId2 = TimetableRepositoryForTest.id("DATED_SERVICE_JOURNEY_ID_2"); + var tripId2 = FeedScopedIdForTestFactory.id("TRIP_ID_2"); + var datedServiceJourneyId2 = FeedScopedIdForTestFactory.id("DATED_SERVICE_JOURNEY_ID_2"); var secondAddedTrip = new AddedTripBuilder( transitService, @@ -570,7 +571,7 @@ void testGetTransportMode( String subMode ) { // Arrange - var route = Route.of(TimetableRepositoryForTest.id(LINE_REF)) + var route = Route.of(FeedScopedIdForTestFactory.id(LINE_REF)) .withShortName(SHORT_NAME) .withAgency(AGENCY) .withMode(TransitMode.valueOf(replacedRouteMode)) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java index fd595c9d11d..2a4ab7cb599 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java @@ -15,6 +15,7 @@ import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -69,7 +70,7 @@ class ModifiedTripBuilderTest { .withStopPattern(TimetableRepositoryForTest.stopPattern(STOP_A_1, STOP_B_1, STOP_C_1)) .build(); - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("CAL_1"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("CAL_1"); private static final Trip TRIP = TimetableRepositoryForTest.trip("TRIP") .withRoute(ROUTE) diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 78b033ce175..b6ff21c4777 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.util.concurrent.Futures; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 7554426a665..5eaf0ac657c 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -31,6 +31,7 @@ import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; @@ -53,7 +54,7 @@ public class RealtimeVehicleMatcherTest { ); ZoneId zoneId = ZoneIds.BERLIN; String tripId = "trip1"; - FeedScopedId scopedTripId = TimetableRepositoryForTest.id(tripId); + FeedScopedId scopedTripId = FeedScopedIdForTestFactory.id(tripId); @Test public void matchRealtimeVehiclesToTrip() { @@ -126,7 +127,7 @@ public void sequenceId() { var service = new DefaultRealtimeVehicleService(null); var tripId = "trip1"; - var scopedTripId = TimetableRepositoryForTest.id(tripId); + var scopedTripId = FeedScopedIdForTestFactory.id(tripId); var trip1 = TimetableRepositoryForTest.trip(tripId).build(); var stopTimes = List.of( @@ -275,8 +276,8 @@ public void clearOldTrips() { var tripId1 = "trip1"; var tripId2 = "trip2"; - var scopedTripId1 = TimetableRepositoryForTest.id(tripId1); - var scopedTripId2 = TimetableRepositoryForTest.id(tripId2); + var scopedTripId1 = FeedScopedIdForTestFactory.id(tripId1); + var scopedTripId2 = FeedScopedIdForTestFactory.id(tripId2); var trip1 = TimetableRepositoryForTest.trip(tripId1).build(); var trip2 = TimetableRepositoryForTest.trip(tripId2).build(); diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java index 46b65252a6e..0f34a3eb652 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.opentripplanner.street.model.StreetModelForTest.intersectionVertex; import static org.opentripplanner.street.model.StreetModelForTest.streetEdge; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; From 1482ca9978ed33a15e316fa69bd2351ef541c1b2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 17 Apr 2026 08:20:42 +0200 Subject: [PATCH 058/177] Move factory to test fixtures --- .../java/org/opentripplanner/ext/fares/FaresFilterTest.java | 2 +- .../ext/fares/service/gtfs/GtfsFaresServiceTest.java | 2 +- .../HighestFareInFreeTransferWindowFareServiceTest.java | 2 +- .../opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java | 2 +- .../service/gtfs/v2/CostedTransferAcrossNetworksTest.java | 2 +- .../fares/service/gtfs/v2/CostedTransferInNetworkTest.java | 2 +- .../service/gtfs/v2/DepartureToArrivalTimeLimitTest.java | 2 +- .../service/gtfs/v2/DepartureToDepartureTimeLimitTest.java | 2 +- .../ext/fares/service/gtfs/v2/FlexLegTest.java | 2 +- .../fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java | 2 +- .../ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java | 2 +- .../ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java | 2 +- .../ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java | 2 +- .../fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java | 2 +- .../ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java | 2 +- .../ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java | 2 +- .../gtfs/v2/OverlappingTimeframesWithPriorityTest.java | 2 +- .../ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java | 2 +- .../ext/fares/service/gtfs/v2/TimeframeMatcherTest.java | 4 ++-- .../ext/fares/service/gtfs/v2/TimeframeTest.java | 2 +- .../ext/fares/service/gtfs/v2/TransferCountTest.java | 2 +- .../fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java | 2 +- .../fares/service/gtfs/v2/WildcardNetworkTransferTest.java | 2 +- .../service/gtfs/v2/custom/OregonHopFareFactoryTest.java | 2 +- .../java/org/opentripplanner/ext/flex/FlexIndexTest.java | 2 +- .../org/opentripplanner/ext/flex/FlexTransferIndexTest.java | 2 +- .../org/opentripplanner/ext/flex/FlexibleTransitLegTest.java | 2 +- .../org/opentripplanner/ext/flex/filter/FilterMapperTest.java | 2 +- .../flexpathcalculator/ScheduledFlexPathCalculatorTest.java | 2 +- .../opentripplanner/ext/flex/template/ClosestTripTest.java | 2 +- .../ext/flex/trip/ScheduledDeviatedTripTest.java | 2 +- .../ext/flex/trip/UnscheduledDrivingDurationTest.java | 2 +- .../opentripplanner/ext/flex/trip/UnscheduledTripTest.java | 2 +- .../org/opentripplanner/ext/geocoder/LuceneIndexTest.java | 2 +- .../org/opentripplanner/ext/geocoder/SerializationTest.java | 2 +- .../ext/ojp/mapping/RouteRequestMapperTest.java | 2 +- .../org/opentripplanner/ext/ojp/resource/OjpMapperTest.java | 2 +- .../ext/ojp/service/CallAtStopServiceTest.java | 2 +- .../ext/ojp/service/StopEventParamsMapperTest.java | 2 +- .../internal/DefaultStopConsolidationRepositoryTest.java | 2 +- .../model/ConsolidatedStopLegBuilderTest.java | 2 +- .../layers/stations/DigitransitStationPropertyMapperTest.java | 2 +- .../ext/vectortiles/layers/stops/StopsLayerTest.java | 2 +- .../layers/vehicleparkings/VehicleParkingGroupsLayerTest.java | 2 +- .../layers/vehicleparkings/VehicleParkingsLayerTest.java | 2 +- .../core/model/id}/FeedScopedIdForTestFactory.java | 4 +--- .../org/opentripplanner/ext/fares/model/FareModelForTest.java | 2 +- .../opentripplanner/ext/fares/model/FareTestConstants.java | 4 ++-- .../org/opentripplanner/model/plan/TestTransitLegBuilder.java | 2 +- .../routing/linking/TransitStopVertexBuilderFactory.java | 2 +- .../org/opentripplanner/street/model/StreetModelForTest.java | 2 +- .../org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java | 4 ++-- .../mapping/routerequest/LegacyRouteRequestMapperTest.java | 2 +- .../apis/gtfs/service/ApiTransitServiceTest.java | 2 +- .../opentripplanner/ext/flex/FlexAccessEgressBookingTest.java | 2 +- .../opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java | 2 +- .../opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java | 2 +- .../graph_builder/module/StreetLinkerModuleTest.java | 2 +- .../graph_builder/module/VehicleParkingLinkingTest.java | 2 +- .../graph_builder/module/geometry/GeometryProcessorTest.java | 2 +- .../module/stopconnectivity/StopConnectivityModuleTest.java | 2 +- .../module/transfer/DirectTransferGeneratorTestData.java | 2 +- .../gtfs/GenerateTripPatternsOperationTest.java | 2 +- .../gtfs/interlining/InterlineProcessorTest.java | 2 +- .../calendar/impl/CalendarServiceDataFactoryImplTest.java | 2 +- .../model/impl/DefaultTransitDataImportTest.java | 2 +- .../model/impl/TransitDataImportBuilderLimitPeriodTest.java | 2 +- .../model/impl/TransitDataImportBuilderTest.java | 2 +- .../java/org/opentripplanner/model/plan/ItineraryTest.java | 2 +- .../org/opentripplanner/model/plan/TestItineraryBuilder.java | 2 +- .../model/plan/leg/ScheduledTransitLegTest.java | 2 +- .../model/plan/legreference/LegReferenceSerializerTest.java | 2 +- .../plan/legreference/ScheduledTransitLegReferenceTest.java | 4 ++-- .../org/opentripplanner/netex/mapping/TripMapperTest.java | 2 +- .../opentripplanner/netex/mapping/TripPatternMapperTest.java | 2 +- .../netex/mapping/calendar/CalendarServiceBuilderTest.java | 2 +- .../org/opentripplanner/routing/algorithm/FilterTest.java | 2 +- .../opentripplanner/routing/algorithm/GraphRoutingTest.java | 2 +- .../org/opentripplanner/routing/algorithm/TestBanning.java | 2 +- .../raptoradapter/transit/RaptorTransitDataTest.java | 2 +- .../raptoradapter/transit/TripPatternForDateTest.java | 2 +- .../raptoradapter/transit/cost/PatternCostCalculatorTest.java | 2 +- .../transit/mappers/GeneralizedCostParametersMapperTest.java | 2 +- .../transit/request/DefaultTransitDataProviderFilterTest.java | 2 +- .../request/RaptorRoutingRequestTransitDataCreatorTest.java | 2 +- .../raptoradapter/transit/request/TestRouteData.java | 2 +- .../transit/request/TripPatternForDatesTest.java | 2 +- .../org/opentripplanner/routing/core/ItineraryFareTest.java | 2 +- .../routing/graphfinder/PlaceFinderTraverseVisitorTest.java | 2 +- .../routing/graphfinder/StreetGraphFinderTest.java | 2 +- .../routing/linking/LinkingContextFactoryTest.java | 2 +- .../routing/trippattern/FrequencyEntryTest.java | 2 +- .../opentripplanner/standalone/server/AlertMetricsTest.java | 2 +- .../streetadapter/StreetSearchRequestMapperTest.java | 4 ++-- .../raptoradaptor/ConstrainedBoardingSearchTest.java | 2 +- .../opentripplanner/transit/model/_data/PatternTestModel.java | 2 +- .../transit/model/_data/SiteRepositoryTestBuilder.java | 2 +- .../transit/model/_data/TimetableRepositoryForTest.java | 2 +- .../transit/model/_data/TimetableRepositoryTestBuilder.java | 2 +- .../transit/model/_data/TransitTestEnvironment.java | 3 ++- .../org/opentripplanner/transit/model/basic/NoticeTest.java | 4 ++-- .../transit/model/filter/transit/TripMatcherFactoryTest.java | 2 +- .../transit/model/framework/EntityByIdTest.java | 2 +- .../transit/model/network/GroupOfRoutesTest.java | 2 +- .../org/opentripplanner/transit/model/network/RouteTest.java | 2 +- .../transit/model/network/TripPatternTest.java | 2 +- .../transit/model/organization/AgencyTest.java | 2 +- .../transit/model/organization/BrandingTest.java | 2 +- .../transit/model/organization/OperatorTest.java | 2 +- .../org/opentripplanner/transit/model/site/AreaStopTest.java | 2 +- .../opentripplanner/transit/model/site/BoardingAreaTest.java | 2 +- .../org/opentripplanner/transit/model/site/EntranceTest.java | 2 +- .../org/opentripplanner/transit/model/site/FareZoneTest.java | 2 +- .../transit/model/site/GroupOfStationsTest.java | 2 +- .../org/opentripplanner/transit/model/site/GroupStopTest.java | 2 +- .../transit/model/site/MultiModalStationTest.java | 2 +- .../opentripplanner/transit/model/site/PathwayNodeTest.java | 2 +- .../org/opentripplanner/transit/model/site/PathwayTest.java | 2 +- .../opentripplanner/transit/model/site/RegularStopTest.java | 2 +- .../org/opentripplanner/transit/model/site/StationTest.java | 2 +- .../transit/model/timetable/RealTimeTripTimesTest.java | 2 +- .../transit/model/timetable/ScheduledTripTimesTest.java | 2 +- .../transit/model/timetable/StopTimeKeyTest.java | 2 +- .../transit/model/timetable/TimetableTest.java | 2 +- .../transit/model/timetable/TripOnServiceDateTest.java | 2 +- .../org/opentripplanner/transit/model/timetable/TripTest.java | 2 +- .../transit/service/DefaultTransitServiceTest.java | 2 +- .../transit/service/ReplacementHelperTest.java | 2 +- .../opentripplanner/transit/service/SiteRepositoryTest.java | 2 +- .../transit/service/TimetableRepositoryTest.java | 2 +- .../updater/GtfsRealtimeFuzzyTripMatcherTest.java | 2 +- .../updater/trip/gtfs/moduletests/addition/AddedTest.java | 2 +- .../trip/gtfs/moduletests/addition/ReplacementTest.java | 2 +- .../cancellation/CancelAfterPatternChangeTest.java | 2 +- .../trip/gtfs/moduletests/cancellation/CanceledTripTest.java | 2 +- .../moduletests/cancellation/CancellationDeletionTest.java | 2 +- .../updater/trip/gtfs/moduletests/delay/SkippedTest.java | 2 +- .../updater/trip/siri/AddedTripBuilderTest.java | 2 +- .../updater/trip/siri/ModifiedTripBuilderTest.java | 2 +- .../updater/trip/siri/SiriFuzzyTripMatcherTest.java | 2 +- .../trip/siri/moduletests/extrajourney/ExtraJourneyTest.java | 2 +- .../VehicleParkingAvailabilityUpdaterTest.java | 2 +- .../updater/vehicle_position/RealtimeVehicleMatcherTest.java | 2 +- .../updater/vehicle_rental/GeofencingVertexUpdaterTest.java | 2 +- 144 files changed, 151 insertions(+), 152 deletions(-) rename application/src/{test/java/org/opentripplanner/transit/model/_data => test-fixtures/java/org/opentripplanner/core/model/id}/FeedScopedIdForTestFactory.java (71%) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java index bb2e2014530..fc79e22ae73 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java index e534fe1404c..ff7db8c76ee 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java index 5a275126330..0645a9530aa 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java index 5d4e9f8af03..244c9014a2e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.collect.Multimaps; import java.util.Map; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java index 512b62efd9c..03bfaf576fc 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java @@ -6,12 +6,12 @@ import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.ext.fares.model.FareTestConstants; import org.opentripplanner.ext.fares.model.FareTransferRule; import org.opentripplanner.model.fare.FareOffer; import org.opentripplanner.model.plan.PlanTestConstants; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java index 8330460cd39..0801c35b9a4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java index 783b6df108b..da80500a699 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java index 57fc5b89ac4..56e8d3df822 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java index 860edb4b7bc..5c9927b93a8 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java index b814c074fa7..dfdb3ed2f13 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java index 0f6ee6a5449..31b512727bf 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.groupOfRoutes; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java index b21a0e28295..626e5c29604 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java index f292926ecbf..74139d94164 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java index 46494d6f5e3..2bd9c448731 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.time.LocalTime; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java index 6eb4fe5fe8e..17e6c4ef648 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java index 1f0c69c9604..39f69869b16 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java index 0324be08e10..44b57d05280 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java index 1ebb383b359..3bd8803e352 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java index ec19683355f..9d48c8a7721 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; @@ -10,10 +10,10 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.ext.fares.model.FareTestConstants; import org.opentripplanner.model.plan.TestTransitLeg; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class TimeframeMatcherTest implements FareTestConstants { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java index d7362cd62b0..243ce47d1c6 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java index e0912de9bce..5a9d1856dbb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java index d4843095991..94ce64c5713 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java index 893a46c3fb0..83cc4e8e460 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java index 3ddb47ff5c9..4dc5017193c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.service.gtfs.v2.custom; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.ADULT_REGIONAL_SINGLE_RIDE; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.CATEGORY_ADULT; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.HOP_FASTPASS; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.LG_CTRAN_REGIONAL; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.LG_TRIMET_TRIMET; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.TRIMET_ADULT_SINGLE_RIDE; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java index 35135235e4e..8783ba89b35 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.Collection; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java index 9b1d51c5fac..12f5c1ac799 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java index b95ddeebd7f..81dfca8a34e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.LocalDate; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java index 4f6a4ebc654..6836a2ce493 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.flex.filter; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java index 64caa954a74..5b67266ea70 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java @@ -1,11 +1,11 @@ package org.opentripplanner.ext.flex.flexpathcalculator; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java index 3295b39c187..4aaf46fc3aa 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Instant; import java.time.LocalDate; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index f51ff32e8dc..e5d6c0b9c54 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -3,11 +3,11 @@ 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.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.areaWithContinuousStopping; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopWithContinuousStopping; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java index 1666396451d..bceaf934a80 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java @@ -1,9 +1,9 @@ package org.opentripplanner.ext.flex.trip; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 1f40a266e33..33f891afa41 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -3,11 +3,11 @@ 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.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.trip.UnscheduledTrip.isUnscheduledTrip; import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc; import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Collections; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 33f8c29f948..54ee43e8f85 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java index 1255feee514..bedfd6c7f96 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.geocoder; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.geocoder.StopCluster.LocationType.STOP; import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java index 069a059cacf..1c1980b1bb6 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import de.vdv.ojp20.LineDirectionFilterStructure; import de.vdv.ojp20.ModeAndModeOfOperationFilterStructure; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java index f65ba2f15dd..4fc06b20fff 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.ojp.resource; import static jakarta.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import de.vdv.ojp20.OJP; import jakarta.xml.bind.JAXBContext; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java index cab20f2b59d..8135ce14274 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java index 4511aca913f..fc9d47aae5a 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java index 2c2b250d163..98cc695228e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java index de98204c542..288f568b8e5 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.stopconsolidation.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java index fed3c256f31..00eb659805c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stations; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java index a39b35b4d3f..eb10dc6fa03 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stops; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java index b67e5619547..8e3dec8266a 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java @@ -16,6 +16,7 @@ import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.i18n.TranslatedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.inspector.vector.KeyValue; import org.opentripplanner.inspector.vector.LayerParameters; @@ -28,7 +29,6 @@ import org.opentripplanner.service.vehicleparking.model.VehicleParkingState; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class VehicleParkingGroupsLayerTest { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java index 3c9069685af..994373288ed 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java @@ -22,6 +22,7 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.core.model.i18n.TranslatedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository; import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingService; import org.opentripplanner.service.vehicleparking.model.VehicleParking; @@ -30,7 +31,6 @@ import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.model.openinghours.OpeningHoursCalendarService; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.framework.Deduplicator; public class VehicleParkingsLayerTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java b/application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java similarity index 71% rename from application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java rename to application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java index 5f416471331..1703c80dc8a 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java @@ -1,6 +1,4 @@ -package org.opentripplanner.transit.model._data; - -import org.opentripplanner.core.model.id.FeedScopedId; +package org.opentripplanner.core.model.id; public class FeedScopedIdForTestFactory { diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java index d7950fb0d61..33b41b9fa7b 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java @@ -1,6 +1,6 @@ package org.opentripplanner.ext.fares.model; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.OTHER_FEED_AGENCY; import java.time.ZonedDateTime; diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java index c2cf7e6b9e9..a160f0568ca 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.model; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.fareProduct; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.groupOfRoutes; import java.time.LocalTime; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.fare.FareProduct; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.GroupOfRoutes; diff --git a/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java index eacd09ec8c3..530c38a849a 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.model.plan; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.LocalTime; diff --git a/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java b/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java index c1e90ad6a4e..5109d129aef 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.routing.linking; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.street.geometry.GeometryUtils; diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java index cd9fb3c197d..28de61e5595 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java @@ -1,6 +1,6 @@ package org.opentripplanner.street.model; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashSet; import java.util.Set; diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 54b7a407f13..534505e4a4c 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.opentripplanner._support.time.ZoneIds.BERLIN; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.D10_m; import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_01; @@ -12,7 +13,6 @@ import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import static org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus.IN_TRANSIT_TO; import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; import static org.opentripplanner.transit.model.timetable.OccupancyStatus.FEW_SEATS_AVAILABLE; @@ -48,6 +48,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.ItineraryFaresDecorator; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; import org.opentripplanner.model.FeedInfoTestFactory; @@ -103,7 +104,6 @@ import org.opentripplanner.street.search.state.TestStateBuilder; import org.opentripplanner.test.support.FilePatternSource; import org.opentripplanner.transfer.regular.TransferServiceTestFactory; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.basic.TransitMode; 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 c0cb7629c30..ef53d24662a 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 @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.params.provider.Arguments.of; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.VehicleRoutingOptimizeType.SAFE_STREETS; import static org.opentripplanner.street.model.VehicleRoutingOptimizeType.TRIANGLE; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java index cb4f34332f6..4a5516859ff 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.FULL_DATASET; diff --git a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java index cc7cc8d16e2..7b232c23330 100644 --- a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java +++ b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java @@ -7,11 +7,11 @@ import java.time.LocalTime; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.flex.trip.UnscheduledTrip; import org.opentripplanner.model.StopTime; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; diff --git a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java index 84b73934d65..7e3ff7e6dfb 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java @@ -5,12 +5,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.ANY_TYPE; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.SPECIFIC_TYPES; import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Map; diff --git a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java index 49120fc80f1..ba402be379e 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java @@ -4,12 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.ANY_TYPE; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.SPECIFIC_TYPES; import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static shadow.org.assertj.core.api.Assertions.assertThat; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java index 216f3fb06de..ed59bddfad3 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java @@ -5,10 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner._support.geometry.Coordinates.KONGSBERG_PLATFORM_1; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.linking.VisibilityMode.TRAVERSE_AREA_EDGES; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.Arrays; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java index 024ae806247..bf7a2a16854 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.BeforeEach; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java index 07d8390dbc5..65339ee59dc 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java @@ -2,11 +2,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP; import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.distance; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.moveMeters; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.stream.IntStream; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java index b1e00b192f1..91d72f3a52a 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.graph_builder.module.stopconnectivity; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.intersectionVertex; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java index dde09cab64f..cdca41ffe19 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.transfer.api.RegularTransferParameters; import org.opentripplanner.graph_builder.module.transfer.api.TransferParametersForMode; @@ -12,7 +13,6 @@ import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.transfer.regular.TransferRepository; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.BikeAccess; diff --git a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java index 8eedcd287ad..ff6f430d360 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; import org.opentripplanner.graph_builder.issues.TripDegenerate; @@ -20,7 +21,6 @@ import org.opentripplanner.graph_builder.module.geometry.GeometryProcessor; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.impl.TransitDataImportBuilder; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; diff --git a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java index a294440de94..369152059f5 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java @@ -12,12 +12,12 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.StaySeatedNotAllowed; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.PlanTestConstants; import org.opentripplanner.transfer.constrained.internal.DefaultConstrainedTransferService; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.StopPattern; diff --git a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java index 429c9b6c2fc..bc29d49f12b 100644 --- a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java +++ b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java @@ -5,10 +5,10 @@ import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; import static org.opentripplanner.model.calendar.ServiceCalendarDate.EXCEPTION_TYPE_REMOVE; import static org.opentripplanner.model.calendar.impl.CalendarServiceDataFactoryImpl.merge; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.io.IOException; import java.time.LocalDate; diff --git a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java index bb11b595ed2..9a2b3f6d5b3 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java @@ -4,8 +4,8 @@ import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; import java.io.IOException; diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java index 1053f657795..cdda8e2fc02 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java @@ -11,13 +11,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.ServiceCalendar; import org.opentripplanner.model.calendar.ServiceCalendarDate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.EntityById; diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java index fb7300d08f2..9a64b4cb0f8 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java @@ -14,11 +14,11 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.FeedInfoTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.calendar.ServiceCalendar; import org.opentripplanner.model.calendar.ServiceCalendarDate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index e79b5f45c93..bbee09d96d4 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -15,11 +15,11 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; import org.opentripplanner.street.search.TraverseMode; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; diff --git a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 7558550912c..3794b3324f5 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -1,10 +1,10 @@ package org.opentripplanner.model.plan; import static java.time.ZoneOffset.UTC; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.search.TraverseMode.BICYCLE; import static org.opentripplanner.street.search.TraverseMode.CAR; import static org.opentripplanner.street.search.TraverseMode.WALK; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import java.time.Duration; diff --git a/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java b/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java index f85f867a8db..902e698a040 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.OffsetDateTime; diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java index 03c52579d2f..a3c781403cc 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java @@ -7,7 +7,7 @@ import java.time.LocalDate; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class LegReferenceSerializerTest { diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index c0d9dafc3d9..12c90233468 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; @@ -12,10 +12,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.TripPattern; diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java index 22a30d4698c..ec851d14e16 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java @@ -10,11 +10,11 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.TransitDataImportBuilder; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; import org.opentripplanner.netex.index.hierarchy.HierarchicalMapById; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.SiteRepository; diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java index c656405851b..2c84b88e7f2 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java @@ -11,10 +11,10 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; import org.opentripplanner.netex.index.hierarchy.HierarchicalMapById; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DefaultEntityById; import org.opentripplanner.transit.model.timetable.Trip; diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java index 8f1595751b9..c226b2cc78d 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java @@ -9,9 +9,9 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.calendar.ServiceCalendarDate; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; public class CalendarServiceBuilderTest { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java index a3dff8d35e9..82359653d85 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java index 6ddd5a8e9de..b0b91442e2e 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.routing.algorithm; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.routing.linking.TransitStopVertexBuilderFactory.ofStop; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.ArrayList; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java index ce800c887c6..e89d33fe6fa 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java index 5122702c5e0..85ba53e3565 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java @@ -9,8 +9,8 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.RoutingTripPattern; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java index 2f6d03ca739..86ff036e319 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java @@ -9,9 +9,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java index 2ca9fc7694a..484fd08d832 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java @@ -3,8 +3,8 @@ import static graphql.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.raptorlegacy._data.transit.TestRoute.route; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; import java.time.Duration; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java index 7031ed5d165..8411cbdc288 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java index 36ec8360148..58baadf15ac 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java @@ -20,6 +20,7 @@ import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; @@ -31,7 +32,6 @@ import org.opentripplanner.routing.api.request.request.filter.TransitFilter; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.SubMode; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 78e4c57f00b..cf0eecd2516 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -3,7 +3,7 @@ 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.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.LocalTime; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index f106a322939..d0cdfdf3437 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -12,11 +12,11 @@ import java.util.Map; import java.util.stream.Collectors; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.RaptorTimeTable; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java index 1b94e804c23..f7c3e235100 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java @@ -7,11 +7,11 @@ import java.util.BitSet; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.SearchDirection; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java index d6dbc4ebe54..644684ccfb6 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.A; import static org.opentripplanner.model.plan.PlanTestConstants.B; import static org.opentripplanner.model.plan.PlanTestConstants.C; @@ -12,7 +13,6 @@ import static org.opentripplanner.model.plan.PlanTestConstants.T11_30; import static org.opentripplanner.model.plan.PlanTestConstants.T11_50; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java index 06f5d942544..d98c407d7fd 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java @@ -1,10 +1,10 @@ package org.opentripplanner.routing.graphfinder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; import static org.opentripplanner.model.plan.PlanTestConstants.T11_10; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index 8f395aef8d3..e73eccd3670 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.linking.LinkingContextFactory; import org.opentripplanner.routing.linking.VertexLinkerTestFactory; @@ -17,7 +18,6 @@ import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.vertex.IntersectionVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java index b9d04060aba..5853b7f8f08 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.Arrays; diff --git a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java index 1482d879453..1e61b73b3e8 100644 --- a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java @@ -6,9 +6,9 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.site.RegularStop; diff --git a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java index 32069b692b7..b0a0145543d 100644 --- a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java +++ b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java @@ -1,9 +1,9 @@ package org.opentripplanner.standalone.server; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.routing.alertpatch.AlertEffect.DETOUR; import static org.opentripplanner.routing.alertpatch.AlertSeverity.INFO; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java index 71cdc0f512a..fe838307944 100644 --- a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.core.model.basic.Cost.costOfSeconds; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.Instant; @@ -15,6 +15,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RequestModes; import org.opentripplanner.routing.api.request.RouteRequest; @@ -25,7 +26,6 @@ import org.opentripplanner.street.search.intersection_model.DrivingDirection; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalModel; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class StreetSearchRequestMapperTest { diff --git a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java index 1a0a64e70ad..bbe54de6c81 100644 --- a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java +++ b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.spi.RaptorBoardOrAlightEvent; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; @@ -27,7 +28,6 @@ import org.opentripplanner.transfer.constrained.model.StopTransferPoint; import org.opentripplanner.transfer.constrained.model.TransferConstraint; import org.opentripplanner.transfer.constrained.model.TripTransferPoint; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.TripPattern; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java index d9f50aa6551..0c67dcd7469 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java b/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java index a90734efdab..5f342685d15 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Optional; import java.util.function.Consumer; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java index 03f7ace0bc5..f632bd988ed 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalTime; import java.util.Arrays; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java index 903d218fd63..b5cc7a426b1 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.ZoneId; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java index dc251229e99..f64c1773739 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java @@ -1,10 +1,11 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.ZoneId; import org.opentripplanner.LocalTimeParser; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorTransitDataMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RealTimeRaptorTransitDataUpdater; diff --git a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java index a8e8bb7eaac..9dd4ac9c4a6 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java @@ -5,10 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class NoticeTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java index d73afa2b426..dfbd06d9675 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java index e1f12ae4479..88bd876e96e 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java @@ -7,7 +7,7 @@ import java.util.Collections; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class EntityByIdTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java index 94e178a82b5..9a890f7b21a 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class GroupOfRoutesTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java index e912a939866..0ccb3b47c51 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; 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 a76b411e2e0..0037701691a 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 @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Assertions; diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java index 9119d23ac84..1811d8c7d78 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class AgencyTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java index f85a013b4bd..e7d5cd8b242 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class BrandingTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java index cc485d509b0..67414a86a87 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class OperatorTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java index 8146e3c762b..f2a23d51892 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java @@ -11,9 +11,9 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.service.SiteRepository; diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java index b1580f5afae..b99b2ae18ec 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java @@ -9,8 +9,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class BoardingAreaTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java index e39da91043b..cb9fd5dc1c5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java @@ -10,8 +10,8 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class EntranceTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java index 97f6cab8cdb..536ded7c482 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class FareZoneTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java index 9144ab7c284..e5d689ffb94 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class GroupOfStationsTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java index e12951d30fd..93d640e332d 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java @@ -14,7 +14,7 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.service.SiteRepository; diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java index 0c2f79de9f1..ac5b965fe97 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java @@ -10,8 +10,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class MultiModalStationTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java index 3b05922382d..209a7761db0 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java @@ -10,8 +10,8 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class PathwayNodeTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java index 12556b1bca5..6c922943da9 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java @@ -8,8 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class PathwayTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java index 8751f4ae7ef..8e107379643 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java @@ -13,8 +13,8 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java index 70337b4e6e7..29cd50621b4 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java @@ -10,8 +10,8 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class StationTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index 1ccfc7b5722..020ca003820 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java index bef235cc153..f457213b1e8 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -5,7 +5,7 @@ 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 static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.BitSet; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java index acde72734e5..708bfc926ec 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class StopTimeKeyTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java index 6d4193aebc8..39fb2e673b1 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java index fe71f7b4ae9..48b5e81743f 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java @@ -7,7 +7,7 @@ import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class TripOnServiceDateTest { diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java index 3c29734c9ff..ce6b151ad05 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java @@ -8,7 +8,7 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; diff --git a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java index 592e9089475..44c4f94d36c 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java @@ -4,7 +4,7 @@ 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.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; import static org.opentripplanner.transit.model.basic.TransitMode.RAIL; diff --git a/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java b/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java index f71eb5b40d5..c805d6f1bf6 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java @@ -5,7 +5,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import java.util.stream.StreamSupport; diff --git a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java index d446f3c5993..19997c3be38 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java @@ -10,9 +10,9 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.GroupOfStations; import org.opentripplanner.transit.model.site.GroupStop; diff --git a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java index c64dce7b1e9..e8008a87570 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.framework.application.OtpFileNames.BUILD_CONFIG_FILENAME; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.stopPattern; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; diff --git a/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java index 0fe87c9c150..287675e86c2 100644 --- a/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java @@ -7,7 +7,7 @@ import java.time.LocalDate; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TransitTestEnvironment; import org.opentripplanner.transit.model._data.TransitTestEnvironmentBuilder; import org.opentripplanner.transit.model._data.TripInput; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java index 5bf796cee26..868cc626f98 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java index e473b84a276..279f7281ff6 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java index 6d40541ae63..68481023cea 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java index 0d47bae9ee4..847ed6b09c9 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.CANCELED; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import org.junit.jupiter.api.Test; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java index 004545dc76d..7945a920b11 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java index 4de05d4e6d2..d2c04ad8688 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java index a8ae07033ae..1f10b3b24af 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java @@ -18,9 +18,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.calendar.CalendarServiceData; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java index 2a4ab7cb599..5ba43aefe29 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java @@ -12,10 +12,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.CalendarServiceData; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java index 324da7e5189..93974cc0e16 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.updater.trip.siri; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateErrorType.MULTIPLE_FUZZY_TRIP_MATCHES; import static org.opentripplanner.updater.spi.UpdateErrorType.NO_FUZZY_TRIP_MATCH; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java index 49d588120ac..63cbae11541 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index b6ff21c4777..797a030c1e2 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.util.concurrent.Futures; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 5eaf0ac657c..cdc5bf45d0a 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -27,11 +27,11 @@ import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java index 0f34a3eb652..a61219e5290 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.intersectionVertex; import static org.opentripplanner.street.model.StreetModelForTest.streetEdge; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; From 11e69a82f1c41b2ee1406b5f9b9c9b271d6544d9 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 17 Apr 2026 10:31:21 +0300 Subject: [PATCH 059/177] Change VehicleRentalUpdater error handling. --- .../updater/vehicle_rental/VehicleRentalUpdater.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java index e677cb14a27..ae13a84622f 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java @@ -14,6 +14,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.framework.retry.OtpRetry; import org.opentripplanner.framework.retry.OtpRetryBuilder; +import org.opentripplanner.framework.retry.OtpRetryException; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.vehiclerental.model.GeofencingZone; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; @@ -99,6 +100,9 @@ public VehicleRentalUpdater( // Do any setup if needed retry.execute(source::setup); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.warn("Updater setup interrupted: {}", nameForLogging, e); + } catch (OtpRetryException e) { LOG.warn("Unable to setup updater: {}", nameForLogging, e); } From e244ef986888a20d1d297c046261b2e29111e206 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 17 Apr 2026 11:01:57 +0300 Subject: [PATCH 060/177] Set all default values to zero. --- .../VehicleRentalServiceDirectoryFetcher.java | 2 +- .../routerconfig/updaters/VehicleRentalUpdaterConfig.java | 2 +- doc/user/GBFS-Config.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java index 90513d0adcf..52f7b7f3db3 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java +++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java @@ -39,7 +39,7 @@ public class VehicleRentalServiceDirectoryFetcher { VehicleRentalServiceDirectoryFetcher.class ); private static final Duration DEFAULT_FREQUENCY = Duration.ofSeconds(15); - private static final Duration DEFAULT_STARTUP_RETRY_PERIOD = Duration.ofSeconds(0); + private static final Duration DEFAULT_STARTUP_RETRY_PERIOD = Duration.ZERO; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java index b40c41064b9..b852fd7e534 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java @@ -37,7 +37,7 @@ public static VehicleRentalUpdaterParameters create(String configRef, NodeAdapte OTP startup. Set to `PT0S` to disable retries. """ ) - .asDuration(Duration.ofSeconds(15)), + .asDuration(Duration.ZERO), VehicleRentalSourceFactory.create(sourceType, c) ); } diff --git a/doc/user/GBFS-Config.md b/doc/user/GBFS-Config.md index 709ccf75421..6727a35aa0a 100644 --- a/doc/user/GBFS-Config.md +++ b/doc/user/GBFS-Config.md @@ -30,7 +30,7 @@ Furthermore, support is limited to the following form factors: | [network](#u_1_network) | `string` | The name of the network to override the one derived from the source data. | *Optional* | | 1.5 | | overloadingAllowed | `boolean` | Allow leaving vehicles at a station even though there are no free slots. | *Optional* | `false` | 2.2 | | [sourceType](#u_1_sourceType) | `enum` | What source of vehicle rental updater to use. | *Required* | | 1.5 | -| [startupRetryPeriod](#u_1_startupRetryPeriod) | `duration` | How long to retry loading the vehicle rental data source on startup if it initially fails. | *Optional* | `"PT15S"` | 2.10 | +| [startupRetryPeriod](#u_1_startupRetryPeriod) | `duration` | How long to retry loading the vehicle rental data source on startup if it initially fails. | *Optional* | `"PT0S"` | 2.10 | | url | `string` | The URL to download the data from. | *Required* | | 1.5 | | [headers](#u_1_headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 1.5 | | [rentalPickupTypes](#u_1_rentalPickupTypes) | `enum set` | This is temporary and will be removed in a future version of OTP. Use this to specify the type of rental data that is allowed to be read from the data source. | *Optional* | | 2.7 | @@ -87,7 +87,7 @@ What source of vehicle rental updater to use.

startupRetryPeriod

-**Since version:** `2.10` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT15S"` +**Since version:** `2.10` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` **Path:** /updaters/[1] How long to retry loading the vehicle rental data source on startup if it initially fails. From 4da9ab499f7e4f37b134bb26aa93c58160e31cb4 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 17 Apr 2026 11:16:42 +0300 Subject: [PATCH 061/177] Change Duration.ofSeconds(0) to Duration.ZERO. --- .../updater/vehicle_rental/VehicleRentalUpdaterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java index de6f4a330fe..a638747f9db 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java @@ -30,7 +30,7 @@ class VehicleRentalUpdaterTest { public static final VehicleRentalUpdaterParameters PARAMS = new VehicleRentalUpdaterParameters( "A", Duration.ofMinutes(1), - Duration.ofSeconds(0), + Duration.ZERO, new FakeParams() ); public static final DefaultVehicleRentalService SERVICE = new DefaultVehicleRentalService(); From 616a08cf4c347c302a8d481d7c781125b0af0426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Fri, 27 Mar 2026 14:02:24 +0100 Subject: [PATCH 062/177] Remove CapacityFilter, capacity is checked per-position during insertion --- .../carpooling/filter/CapacityFilterTest.java | 105 ------------------ .../carpooling/filter/FilterChainTest.java | 16 --- .../ext/carpooling/Architecture.md | 1 - .../opentripplanner/ext/carpooling/README.md | 22 ++-- .../ext/carpooling/filter/CapacityFilter.java | 48 -------- .../ext/carpooling/filter/FilterChain.java | 8 +- 6 files changed, 10 insertions(+), 190 deletions(-) delete mode 100644 application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java delete mode 100644 application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java deleted file mode 100644 index ca741e20b73..00000000000 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_SOUTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createDestinationStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createOriginStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithCapacity; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithStops; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class CapacityFilterTest { - - private CapacityFilter filter; - - @BeforeEach - void setup() { - filter = new CapacityFilter(); - } - - @Test - void accepts_tripWithCapacity_returnsTrue() { - var trip = createTripWithStops(OSLO_CENTER, List.of(), OSLO_NORTH); - - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripAtFullCapacity_returnsTrue() { - // CapacityFilter only checks configured capacity, not actual occupancy - // Detailed capacity validation happens in the validator layer - // All 4 seats taken - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Filter accepts because trip has capacity configured (even if currently full) - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripWithOneOpenSeat_returnsTrue() { - // 3 of 4 seats taken - var stop1 = createStop(0, 3); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_zeroCapacityTrip_returnsFalse() { - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var trip = createTripWithCapacity(0, stops); - - assertFalse(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_passengerCoordinatesIgnored() { - // Filter only checks if ANY capacity exists, not position-specific - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var trip = createTripWithCapacity(2, stops); - - // Should accept regardless of passenger coordinates - assertTrue(filter.accepts(trip, OSLO_SOUTH, OSLO_EAST)); - assertTrue(filter.accepts(trip, OSLO_NORTH, OSLO_SOUTH)); - } - - @Test - void accepts_tripWithFluctuatingCapacity_checksOverallAvailability() { - // 2 passengers - var stop1 = createStop(0, 2); - // Dropoff 2 - var stop2 = createStop(1, -2); - // Pickup 1 - var stop3 = createStop(2, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // At some point there's capacity (positions 0, 2+) - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripAlwaysAtCapacity_returnsTrue() { - // CapacityFilter only checks configured capacity, not actual occupancy - // Fill to capacity - var stop1 = createStop(0, 4); - // Drop 1 - var stop2 = createStop(1, -1); - // Pick 1 (back to full) - var stop3 = createStop(2, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // Filter accepts because trip has capacity configured - // The validator will determine if there's actual room for insertion - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } -} diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java index b36b690fe46..c37532160a4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java @@ -6,10 +6,7 @@ import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createDestinationStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createOriginStop; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithCapacity; import java.util.List; import org.junit.jupiter.api.Test; @@ -73,19 +70,6 @@ void accepts_firstFilterRejects_doesNotCallOthers() { assertFalse(filter2Called[0], "Filter2 should not have been called due to short-circuit"); } - @Test - void standard_includesAllStandardFilters() { - var chain = FilterChain.standard(); - - // Should contain CapacityFilter and DirectionalCompatibilityFilter - // Verify by testing behavior with a trip that has no capacity - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var emptyTrip = createTripWithCapacity(0, stops); - - // Should reject due to capacity filter - assertFalse(chain.accepts(emptyTrip, OSLO_EAST, OSLO_WEST)); - } - @Test void standard_checksDirectionalCompatibility() { var chain = FilterChain.standard(); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md index 7ffa3b64ee7..447fb65ee76 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md @@ -18,7 +18,6 @@ org.opentripplanner.ext.carpooling/ │ └── CarpoolStreetRouter # Street routing for carpooling ├── filter/ # Trip pre-filtering │ ├── TripFilter # Filter interface -│ ├── CapacityFilter # Checks available capacity │ ├── TimeBasedFilter # Time window filtering │ ├── DistanceBasedFilter # Geographic distance checks │ └── DirectionalCompatibilityFilter # Directional alignment diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index dff8c5d4461..6abcc4f7e75 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -84,7 +84,6 @@ org.opentripplanner.ext.carpooling/ │ ├── filter/ # Pre-screening filters │ ├── FilterChain.java # Composite filter -│ ├── CapacityFilter.java # Seat availability check │ ├── TimeBasedFilter.java # Time window check │ ├── DirectionalCompatibilityFilter.java # Direction check │ └── DistanceBasedFilter.java # Distance check @@ -124,10 +123,9 @@ org.opentripplanner.ext.carpooling/ Filters eliminate obviously incompatible trips **without any street routing**: -1. **CapacityFilter**: Does the vehicle have available seats? -2. **TimeBasedFilter**: Is the trip timing compatible with passenger request? -3. **DirectionalCompatibilityFilter**: Are driver and passenger heading the same direction? -4. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? +1. **TimeBasedFilter**: Is the trip timing compatible with passenger request? +2. **DirectionalCompatibilityFilter**: Are driver and passenger heading the same direction? +3. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? **Performance**: O(n) where n = number of active trips. @@ -374,7 +372,6 @@ public class CustomFilter implements TripFilter { // Add to filter chain FilterChain chain = FilterChain.of( - new CapacityFilter(), new TimeBasedFilter(), new CustomFilter() ); @@ -405,17 +402,12 @@ Test individual components in isolation: ```java @Test -void testCapacityFilter() { - var filter = new CapacityFilter(); - var trip = createTripWithSeats(2); // 2 available seats +void testTimeBasedFilter() { + var filter = new TimeBasedFilter(); + var trip = createSimpleTrip(origin, destination); - // Should pass - within capacity + // Should pass - within time window assertTrue(filter.accepts(trip, pickup, dropoff, now())); - - var fullTrip = createTripWithSeats(0); // No seats - - // Should fail - no capacity - assertFalse(filter.accepts(fullTrip, pickup, dropoff, now())); } ``` diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java deleted file mode 100644 index 1d296cc0956..00000000000 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import java.time.Duration; -import java.time.Instant; -import org.opentripplanner.ext.carpooling.model.CarpoolTrip; -import org.opentripplanner.street.geometry.WgsCoordinate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filters trips based on available capacity. - *

- * This is a fast pre-filter that checks if the trip has any capacity at all. - * More detailed per-position capacity checking happens during insertion validation. - */ -public class CapacityFilter implements TripFilter { - - private static final Logger LOG = LoggerFactory.getLogger(CapacityFilter.class); - - private boolean accepts(CarpoolTrip trip) { - boolean hasCapacity = trip.availableSeats() > 0; - - if (!hasCapacity) { - LOG.debug("Trip {} rejected by capacity filter: no available seats", trip.getId()); - } - - return hasCapacity; - } - - @Override - public boolean accepts( - CarpoolTrip trip, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - return accepts(trip); - } - - @Override - public boolean acceptsAccessEgress( - CarpoolTrip trip, - WgsCoordinate coordinateOfPassenger, - Instant passengerDepartureTime, - Duration searchWindow - ) { - return accepts(trip); - } -} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java index 8031328095c..663f9263cb9 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java @@ -13,10 +13,9 @@ * as soon as one filter rejects a trip, evaluation stops. *

* The standard filter chain includes (in order of performance impact): - * 1. CapacityFilter - Very fast (O(1)) - * 2. TimeBasedFilter - Very fast (O(1)) - * 3. DistanceBasedFilter - Fast (O(1) with 4 distance calculations) - * 4. DirectionalCompatibilityFilter - Medium (O(n) with n = number of stops) + * 1. TimeBasedFilter - Very fast (O(1)) + * 2. DistanceBasedFilter - Fast (O(1) with 4 distance calculations) + * 3. DirectionalCompatibilityFilter - Medium (O(n) with n = number of stops) */ public class FilterChain implements TripFilter { @@ -35,7 +34,6 @@ public FilterChain(List filters) { public static FilterChain standard() { return new FilterChain( List.of( - new CapacityFilter(), new TimeBasedFilter(), new DistanceBasedFilter(), new DirectionalCompatibilityFilter() From fee5f598deb9406668bb9538952a2f156a10db7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Fri, 27 Mar 2026 18:12:32 +0100 Subject: [PATCH 063/177] Changed passengerDelta to onboardCount and availableSeats to totalCapacity in carpooling trips --- .../ext/carpooling/CarpoolTripTestData.java | 26 +- .../model/CarpoolStopBuilderTest.java | 8 +- .../model/CarpoolTripBuilderTest.java | 6 +- .../model/CarpoolTripCapacityTest.java | 258 +++++++++--------- .../opentripplanner/ext/carpooling/README.md | 13 +- .../PassengerDelayConstraints.java | 4 +- .../ext/carpooling/model/CarpoolStop.java | 17 +- .../carpooling/model/CarpoolStopBuilder.java | 15 +- .../ext/carpooling/model/CarpoolTrip.java | 93 ++++--- .../carpooling/model/CarpoolTripBuilder.java | 15 +- .../routing/InsertionCandidate.java | 6 +- .../carpooling/routing/InsertionPosition.java | 13 +- .../routing/InsertionPositionFinder.java | 13 +- .../carpooling/updater/CarpoolSiriMapper.java | 7 +- 14 files changed, 257 insertions(+), 237 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 23f30b9dd71..f112fb438e6 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -69,7 +69,7 @@ public static CarpoolTrip createTripWithStops( .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) .withAimedArrivalTime(intermediate.getAimedDepartureTime()) .withSequenceNumber(intermediate.getSequenceNumber() + 1) - .withPassengerDelta(intermediate.getPassengerDelta()) + .withOnboardCount(intermediate.getOnboardCount()) .build() ); } @@ -110,7 +110,7 @@ public static CarpoolTrip createTripWithDeviationBudget( FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) ) .withStops(stops) - .withAvailableSeats(seats) + .withTotalCapacity(seats) .withStartTime(ZonedDateTime.now()) .withDeviationBudget(deviationBudget) .build(); @@ -129,7 +129,7 @@ public static CarpoolTrip createTripWithTime( FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) ) .withStops(stops) - .withAvailableSeats(seats) + .withTotalCapacity(seats) .withStartTime(startTime) .withEndTime(startTime.plusHours(1)) .withDeviationBudget(Duration.ofMinutes(10)) @@ -137,35 +137,35 @@ public static CarpoolTrip createTripWithTime( } /** - * Creates a CarpoolStop with specified sequence (0-based) and passenger delta. + * Creates a CarpoolStop with specified sequence (0-based) and onboard count. */ - public static CarpoolStop createStop(int zeroBasedSequence, int passengerDelta) { - return createStopAt(zeroBasedSequence, passengerDelta, CarpoolTestCoordinates.OSLO_CENTER); + public static CarpoolStop createStop(int zeroBasedSequence, int onboardCount) { + return createStopAt(zeroBasedSequence, onboardCount, CarpoolTestCoordinates.OSLO_CENTER); } /** - * Creates a CarpoolStop at a specific location. + * Creates a CarpoolStop at a specific location with onboardCount=1 (driver only). */ public static CarpoolStop createStopAt(int sequence, WgsCoordinate location) { - return createStopAt(sequence, 0, location); + return createStopAt(sequence, 1, location); } /** * Creates a CarpoolStop with all parameters. */ - public static CarpoolStop createStopAt(int sequence, int passengerDelta, WgsCoordinate location) { + public static CarpoolStop createStopAt(int sequence, int onboardCount, WgsCoordinate location) { return CarpoolStop.of( FeedScopedId.ofNullable("TEST", "area-" + AREA_STOP_COUNTER.incrementAndGet()), AREA_STOP_COUNTER::getAndIncrement ) .withCoordinate(location) .withSequenceNumber(sequence) - .withPassengerDelta(passengerDelta) + .withOnboardCount(onboardCount) .build(); } /** - * Creates an origin stop (first stop, PICKUP_ONLY, passengerDelta=0, departure times only). + * Creates an origin stop (first stop, PICKUP_ONLY, onboardCount=1 for driver, departure times only). */ public static CarpoolStop createOriginStop(WgsCoordinate location) { return createOriginStopWithTime(location, null, null); @@ -181,13 +181,14 @@ public static CarpoolStop createOriginStopWithTime( ) { return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-0"), () -> 0) .withCoordinate(location) + .withOnboardCount(1) .withExpectedDepartureTime(expectedDepartureTime) .withAimedDepartureTime(aimedDepartureTime) .build(); } /** - * Creates a destination stop (last stop, DROP_OFF_ONLY, passengerDelta=0, arrival times only). + * Creates a destination stop (last stop, DROP_OFF_ONLY, onboardCount=1 for driver, arrival times only). */ public static CarpoolStop createDestinationStop(WgsCoordinate location, int sequenceNumber) { return createDestinationStopWithTime(location, sequenceNumber, null, null); @@ -209,6 +210,7 @@ public static CarpoolStop createDestinationStopWithTime( .withCoordinate(location) .withCarpoolStopType(CarpoolStopType.DROP_OFF_ONLY) .withSequenceNumber(sequenceNumber) + .withOnboardCount(1) .withExpectedArrivalTime(expectedArrivalTime) .withAimedArrivalTime(aimedArrivalTime) .build(); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java index a7c3521331b..efecc20a3ef 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java @@ -60,7 +60,7 @@ void buildFromValues_usingWith_buildToCorrectValues() { var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id"), () -> -1); builder .withSequenceNumber(1) - .withPassengerDelta(2) + .withOnboardCount(2) .withCoordinate(OSLO_NORTH) .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) @@ -74,7 +74,7 @@ void buildFromValues_usingWith_buildToCorrectValues() { assertEquals(-1, stop.getIndex()); assertEquals(1, stop.getSequenceNumber()); - assertEquals(2, stop.getPassengerDelta()); + assertEquals(2, stop.getOnboardCount()); assertEquals(OSLO_NORTH, stop.getCoordinate()); assertEquals(DROP_OFF_ONLY, stop.getCarpoolStopType()); assertEquals(AIMED_ARRIVAL_TIME, stop.getAimedArrivalTime()); @@ -95,7 +95,7 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { ); originalBuilder .withSequenceNumber(2) - .withPassengerDelta(3) + .withOnboardCount(3) .withCoordinate(OSLO_CENTER) .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) @@ -112,7 +112,7 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { assertEquals(1, copy.getIndex()); assertEquals(original.getSequenceNumber(), copy.getSequenceNumber()); - assertEquals(original.getPassengerDelta(), copy.getPassengerDelta()); + assertEquals(original.getOnboardCount(), copy.getOnboardCount()); assertEquals(original.getCoordinate(), copy.getCoordinate()); assertEquals(original.getCarpoolStopType(), copy.getCarpoolStopType()); assertEquals(original.getAimedArrivalTime(), copy.getAimedArrivalTime()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java index 5a0fc8f7207..f0ed3633501 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java @@ -23,7 +23,7 @@ void buildFromValues_fromId_buildToCorrectValues() { var builder = new CarpoolTripBuilder(new FeedScopedId("feed", "id")); var trip = builder - .withAvailableSeats(2) + .withTotalCapacity(2) .withProvider("UNIT") .withDeviationBudget(Duration.of(8, ChronoUnit.MINUTES)) .withStartTime(startTime) @@ -31,7 +31,7 @@ void buildFromValues_fromId_buildToCorrectValues() { .withStops(List.of(stop)) .buildFromValues(); - assertEquals(2, trip.availableSeats()); + assertEquals(2, trip.totalCapacity()); assertEquals("UNIT", trip.provider()); assertEquals(Duration.of(8, ChronoUnit.MINUTES), trip.deviationBudget()); assertEquals(startTime, trip.startTime()); @@ -46,7 +46,7 @@ void buildFromValues_fromOriginal_buildToCorrectValues() { var builder = new CarpoolTripBuilder(original); var trip = builder.buildFromValues(); - assertEquals(original.availableSeats(), trip.availableSeats()); + assertEquals(original.totalCapacity(), trip.totalCapacity()); assertEquals(original.provider(), trip.provider()); assertEquals(original.deviationBudget(), trip.deviationBudget()); assertEquals(original.startTime(), trip.startTime()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java index 2f7c5c43469..c5af490c123 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java @@ -15,181 +15,179 @@ /** * Tests for capacity checking methods on {@link CarpoolTrip}. + *

+ * All trips created via {@code createTripWithStops} have totalCapacity=4. + * The method wraps intermediate stops with an Origin (onboard=1) at the front + * and a Destination (onboard=1) at the end. + *

+ * {@code pickupPosition} and {@code dropoffPosition} in {@code hasCapacityForInsertion} + * are 0-based indices of the passenger's stops in the modified route (the route after the + * passenger's pickup and dropoff have been inserted into the carpool trip). */ class CarpoolTripCapacityTest { + // -- getPassengerCountAtDepartureOfStop tests -- + @Test - void getPassengerCountAtPosition_noStops_allZeros() { + void getPassengerCountAtDepartureOfStop_driverOnly() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - // Boarding - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Beyond stops - assertEquals(0, trip.getPassengerCountAtPosition(1)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(0)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(1)); } @Test - void getPassengerCountAtPosition_onePickupStop_incrementsAtStop() { - // Pickup 1 passenger, then drop off 1 passenger - var stop1 = createStop(0, 1); - var stop2 = createStop(1, -1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Position 0: Before origin stop - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After pickup stop (passengerDelta=1) - assertEquals(1, trip.getPassengerCountAtPosition(2)); - // Position 3: After dropoff stop (passengerDelta=-1) - assertEquals(0, trip.getPassengerCountAtPosition(3)); - // Position 4: After destination stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(4)); + void getPassengerCountAtDepartureOfStop_withIntermediateStops() { + // Stops: [Origin(1), A(2), B(1), Destination(1)] + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(0, 2), createStop(1, 1)), + OSLO_NORTH + ); + + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(0)); + assertEquals(2, trip.getPassengerCountAtDepartureOfStop(1)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(2)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(3)); } @Test - void getPassengerCountAtPosition_pickupAndDropoff_incrementsThenDecrements() { - // Pickup 2 passengers - var stop1 = createStop(0, 2); - // Dropoff 1 passenger - var stop2 = createStop(1, -1); - // Dropoff remaining passenger - var stop3 = createStop(2, -1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // Position 0: Before origin stop - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After first intermediate stop (passengerDelta=2) - assertEquals(2, trip.getPassengerCountAtPosition(2)); - // Position 3: After second intermediate stop (passengerDelta=-1) - assertEquals(1, trip.getPassengerCountAtPosition(3)); - // Position 4: After third intermediate stop (passengerDelta=-1) - assertEquals(0, trip.getPassengerCountAtPosition(4)); - // Position 5: After destination stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(5)); + void getPassengerCountAtDepartureOfStop_negativeIndex_throwsException() { + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtDepartureOfStop(-1)); } @Test - void getPassengerCountAtPosition_multipleStops_cumulativeCount() { - var stop1 = createStop(0, 1); - var stop2 = createStop(1, 2); - var stop3 = createStop(2, -1); - var stop4 = createStop(3, 1); - var stop5 = createStop(4, -3); - var trip = createTripWithStops( - OSLO_CENTER, - List.of(stop1, stop2, stop3, stop4, stop5), - OSLO_NORTH - ); + void getPassengerCountAtDepartureOfStop_indexTooLarge_throwsException() { + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + // Trip has 2 stops (Origin, Destination), valid indices are 0 and 1 + assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtDepartureOfStop(2)); + } - // Position 0: Before origin - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After stop1 (0 + 1) - assertEquals(1, trip.getPassengerCountAtPosition(2)); - // Position 3: After stop2 (1 + 2) - assertEquals(3, trip.getPassengerCountAtPosition(3)); - // Position 4: After stop3 (3 - 1) - assertEquals(2, trip.getPassengerCountAtPosition(4)); - // Position 5: After stop4 (2 + 1) - assertEquals(3, trip.getPassengerCountAtPosition(5)); - // Position 6: After stop5 (3 - 3) - assertEquals(0, trip.getPassengerCountAtPosition(6)); - // Position 7: After destination (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(7)); + // -- hasCapacityForInsertion tests -- + // + // pickupPosition and dropoffPosition are 0-based indices of the passenger's + // stops in the modified route. The method checks capacity at the original stops + // where the passenger would be riding, from original index (pickupPosition - 1) + // to (dropoffPosition - 2) inclusive. + + @Test + void adjacentPositions_onlyChecksStopBeforePickup() { + // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=4 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); + + // Modified route: [Origin, Pickup, Dropoff, A, Destination] + // 0 1 2 3 4 + // Checked original stops: Origin (index 0, onboard=1). A is NOT checked. + assertTrue(trip.hasCapacityForInsertion(1, 2, 3)); } @Test - void getPassengerCountAtPosition_negativePosition_throwsException() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + void adjacentPositions_fullAtStopBeforePickup() { + // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=4 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(-1)); + // Modified route: [Origin, A, Pickup, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: A (index 1, onboard=4). No room. + assertFalse(trip.hasCapacityForInsertion(2, 3, 1)); } @Test - void getPassengerCountAtPosition_positionTooLarge_throwsException() { - var stop1 = createStop(0, 1); - var stop2 = createStop(1, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Trip has: origin (0), stop1 (1), stop2 (2), destination (3) = 4 stops total - // Valid positions are 0 to 4 (0 to stops.size()) - // Position 5 should throw - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(5)); - // Position 999 should also throw - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(999)); + void widerGap_checksAllOriginalStopsBetweenPickupAndDropoff() { + // Original stops: [Origin(1), A(2), B(3), C(1), Destination(1)] totalCapacity=4 + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(0, 2), createStop(1, 3), createStop(2, 1)), + OSLO_NORTH + ); + + // Modified route: [Origin, Pickup, A, B, Dropoff, C, Destination] + // 0 1 2 3 4 5 6 + // Checked original stops: Origin(1), A(2), B(3). Max is 3, room for 1. + assertTrue(trip.hasCapacityForInsertion(1, 4, 1)); + assertFalse(trip.hasCapacityForInsertion(1, 4, 2)); } @Test - void hasCapacityForInsertion_noPassengers_hasCapacity() { - var trip = createTripWithStops(OSLO_CENTER, List.of(), OSLO_NORTH); + void stopAfterDropoff_isNotChecked() { + // Original stops: [Origin(1), A(1), B(4), Destination(1)] totalCapacity=4 + // B is full, but the passenger is dropped off before B. + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(0, 1), createStop(1, 4)), + OSLO_NORTH + ); - assertTrue(trip.hasCapacityForInsertion(1, 2, 1)); - // Can fit all 4 seats - assertTrue(trip.hasCapacityForInsertion(1, 2, 4)); + // Modified route: [Origin, Pickup, A, Dropoff, B, Destination] + // 0 1 2 3 4 5 + // Checked original stops: Origin(1), A(1). B is after the dropoff. + assertTrue(trip.hasCapacityForInsertion(1, 3, 2)); } @Test - void hasCapacityForInsertion_fullCapacity_noCapacity() { - // Fill all 4 seats - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); + void pickupNearEnd_checksOnlyStopBeforePickup() { + // Original stops: [Origin(1), A(3), Destination(1)] totalCapacity=4 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 3)), OSLO_NORTH); - // No room for additional passenger after stop 1 - assertFalse(trip.hasCapacityForInsertion(2, 3, 1)); + // Modified route: [Origin, A, Pickup, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: A (index 1, onboard=3). Room for 1. + assertTrue(trip.hasCapacityForInsertion(2, 3, 1)); + assertFalse(trip.hasCapacityForInsertion(2, 3, 2)); } @Test - void hasCapacityForInsertion_partialCapacity_hasCapacityForOne() { - // 3 of 4 seats taken - var stop1 = createStop(0, 3); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); + void pickupNearEnd_limitedByStopBeforePickup() { + // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); - // Room for 1 + // Modified route: [Origin, A, Pickup, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: A (index 1, onboard=4). Room for 1. assertTrue(trip.hasCapacityForInsertion(2, 3, 1)); - // No room for 2 assertFalse(trip.hasCapacityForInsertion(2, 3, 2)); } @Test - void hasCapacityForInsertion_acrossMultiplePositions_checksAll() { - var stop1 = createStop(0, 2); - // Total 3 passengers at position 3 - var stop2 = createStop(1, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Trip positions: 0 (before origin), 1 (after origin=0), 2 (after stop1=2), 3 (after stop2=3), 4 (after dest=0) - // Range 2-4 includes position 3 with 3 passengers, so only 1 seat available - assertTrue(trip.hasCapacityForInsertion(2, 4, 1)); - assertFalse(trip.hasCapacityForInsertion(2, 4, 2)); + void fullRangeInsertion_checksAllOriginalStops() { + // Original stops: [Origin(2), A(2), Destination(2)] totalCapacity=4 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 2)), OSLO_NORTH); + + // Modified route: [Origin, Pickup, A, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: Origin(2), A(2). Both onboard=2, room for 2. + assertTrue(trip.hasCapacityForInsertion(1, 3, 2)); + assertFalse(trip.hasCapacityForInsertion(1, 3, 3)); } @Test - void hasCapacityForInsertion_rangeBeforeStop_usesInitialCapacity() { - // Fill capacity at position 1 - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Pickup at position 1, dropoff at position 1 - only checks capacity at boarding (position 0) - // At boarding there are no passengers yet, so we have full capacity - assertTrue(trip.hasCapacityForInsertion(1, 1, 4)); + void fullStopBeforePickup_notInCheckedRange() { + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + // A is full, but pickup and dropoff are inserted after A. + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); + + // Modified route: [Origin, A, Destination, Pickup, Dropoff] + // 0 1 2 3 4 + // firstOriginalStop = 3 - 1 = 2, lastOriginalStop = 4 - 2 = 2 + // Checked original stop: Destination (index 2, onboard=1). A is outside the range. + assertTrue(trip.hasCapacityForInsertion(3, 4, 4)); + assertFalse(trip.hasCapacityForInsertion(3, 4, 5)); } @Test - void hasCapacityForInsertion_capacityFreesUpInRange_checksMaxInRange() { - // 3 passengers - var stop1 = createStop(0, 3); - // 2 dropoff, leaving 1 - var stop2 = createStop(1, -2); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Range includes both positions - max passengers is 3 (at position 1) - // 4 total - 3 max = 1 available - assertTrue(trip.hasCapacityForInsertion(1, 3, 1)); - // Not enough - assertFalse(trip.hasCapacityForInsertion(1, 3, 2)); + void bottleneckInMiddle_limitsCapacity() { + // Original stops: [Origin(1), A(1), B(3), C(1), D(1), Destination(1)] totalCapacity=4 + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(0, 1), createStop(1, 4), createStop(2, 1), createStop(3, 1)), + OSLO_NORTH + ); + + // Modified route: [Origin, Pickup, A, B, C, Dropoff, D, Destination] + // 0 1 2 3 4 5 6 7 + // Checked original stops: Origin(1), A(1), B(3), C(1). Max is 3 at B, room for 1. + assertTrue(trip.hasCapacityForInsertion(1, 5, 1)); + assertFalse(trip.hasCapacityForInsertion(1, 5, 2)); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index 6abcc4f7e75..e7d12608f21 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -140,7 +140,7 @@ Fast heuristic checks eliminate impossible positions **before any A* routing**: ``` For each remaining trip: 1. Generate all position combinations (pickup, dropoff) where: - - Pickup: between any two consecutive stops (1-indexed) + - Pickup: between any two consecutive stops (0-based index in modified route) - Dropoff: after pickup position 2. For each position pair, check: @@ -295,7 +295,7 @@ Represents a driver's journey offering carpool seats: - **startTime**: When driver departs - **endTime**: When driver arrives (includes deviation budget) - **deviationBudget**: Extra time driver is willing to spend for passengers -- **availableSeats**: Current remaining capacity +- **totalCapacity**: Number of seats in the car, including the driver seat - **stops**: Ordered list of waypoints (includes booked passenger stops) - **provider**: Source system identifier @@ -306,17 +306,14 @@ Waypoint along a carpool route: - **coordinate**: Geographic location - **sequenceNumber**: Order in route (0-indexed) - **estimatedArrivalTime**: When driver expects to arrive -- **stopType**: PICKUP or DROPOFF -- **passengerDelta**: Change in passenger count (+1 for pickup, -1 for dropoff) +- **onboardCount**: Number of passengers onboard (including the driver) when departing this stop ### InsertionPosition Represents a viable pickup/dropoff position pair: -- **pickupPos**: Position to insert passenger pickup (1-indexed) -- **dropoffPos**: Position to insert passenger dropoff (1-indexed) - -Note: Positions are 1-indexed to match insertion semantics (insert between existing points). +- **pickupPos**: 0-based index of the passenger's pickup in the modified route +- **dropoffPos**: 0-based index of the passenger's dropoff in the modified route ### InsertionCandidate diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java index fe3ffa17b95..c626208bbe9 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java @@ -51,8 +51,8 @@ public PassengerDelayConstraints(Duration maxDelay) { * * @param originalCumulativeDurations Cumulative duration to each point in original route * @param modifiedCumulativeDurations Cumulative duration to each point in modified route - * @param pickupPos Position where passenger pickup is inserted (1-indexed) - * @param dropoffPos Position where passenger dropoff is inserted (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route * @return true if all existing passengers experience acceptable delays */ public boolean satisfiesConstraints( diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index d395025a740..c01f3df1da0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -14,8 +14,7 @@ import org.opentripplanner.transit.model.site.StopType; /** - * Represents a stop along a carpool trip route with passenger pickup/drop-off information. - * Each stop tracks the passenger delta (number of passengers picked up or dropped off). + * Represents a stop along a carpool trip route with occupancy and timing information. * Stops are ordered sequentially along the route. */ public class CarpoolStop @@ -34,7 +33,7 @@ public class CarpoolStop private final ZonedDateTime expectedDepartureTime; private final ZonedDateTime aimedDepartureTime; private final int sequenceNumber; - private final int passengerDelta; + private final int onboardCount; public CarpoolStop(CarpoolStopBuilder builder) { super(builder.getId()); @@ -55,7 +54,7 @@ public CarpoolStop(CarpoolStopBuilder builder) { this.expectedDepartureTime = builder.expectedDepartureTime(); this.aimedDepartureTime = builder.aimedDepartureTime(); this.sequenceNumber = builder.sequenceNumber(); - this.passengerDelta = builder.passengerDelta(); + this.onboardCount = builder.onboardCount(); } public static CarpoolStopBuilder of(FeedScopedId id, IntSupplier indexCounter) { @@ -162,6 +161,9 @@ public ZonedDateTime getExpectedDepartureTime() { return expectedDepartureTime; } + /** + * @return The 0-based position of this stop in the trip's stop sequence + */ public int getSequenceNumber() { return sequenceNumber; } @@ -185,8 +187,11 @@ public ZonedDateTime getEstimatedTime() { return aimedArrivalTime != null ? aimedArrivalTime : aimedDepartureTime; } - public int getPassengerDelta() { - return passengerDelta; + /** + * @return The number of passengers onboard (including the driver) when departing this stop + */ + public int getOnboardCount() { + return onboardCount; } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index 0bfc5518311..f1c58ae0cca 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -10,6 +10,9 @@ import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; +/** + * Builder for {@link CarpoolStop} instances. + */ public class CarpoolStopBuilder extends AbstractEntityBuilder { private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); @@ -26,7 +29,7 @@ public class CarpoolStopBuilder extends AbstractEntityBuilder * A carpool trip models a driver offering their vehicle journey for passengers to join. It includes - * the driver's planned route as a sequence of stops, available seating capacity, and timing + * the driver's planned route as a sequence of stops, total vehicle capacity, and timing * constraints including a deviation budget that allows the driver to slightly adjust their route * to accommodate passengers. * @@ -25,7 +25,7 @@ * can be picked up or dropped off. Stops are dynamically updated as bookings occur. *

  • Deviation Budget: Maximum additional time the driver is willing to spend * to pick up/drop off passengers (e.g., 5 minutes). This represents the driver's flexibility.
  • - *
  • Available Seats: Current passenger capacity remaining in the vehicle
  • + *
  • Total Capacity: Number of seats in the car, including the driver seat
  • * * *

    Data Source

    @@ -64,7 +64,7 @@ public class CarpoolTrip // The amount of time the trip can deviate from the scheduled time in order to pick up or drop off // a passenger. private final Duration deviationBudget; - private final int availableSeats; + private final int totalCapacity; // Ordered list of stops along the carpool route where passengers can be picked up or dropped off private final List stops; @@ -74,7 +74,7 @@ public CarpoolTrip(CarpoolTripBuilder builder) { this.startTime = builder.startTime(); this.endTime = builder.endTime(); this.provider = builder.provider(); - this.availableSeats = builder.availableSeats(); + this.totalCapacity = builder.totalCapacity(); this.deviationBudget = builder.deviationBudget(); this.stops = Collections.unmodifiableList(builder.stops()); } @@ -121,8 +121,11 @@ public Duration deviationBudget() { return deviationBudget; } - public int availableSeats() { - return availableSeats; + /** + * @return Total number of seats in the vehicle, including the driver seat + */ + public int totalCapacity() { + return totalCapacity; } /** @@ -152,65 +155,67 @@ public List routePoints() { } /** - * Calculates the number of passengers in the vehicle after visiting the specified position. - *

    - * Position semantics: - * - Position 0: Before any stops → 0 passengers - * - Position N: After Nth stop → cumulative passenger delta up to stop N + * Returns the number of passengers onboard the vehicle when departing the given stop. * - * @param position The position index (0 = before any stops, 1 = after first stop, etc.) - * @return Number of passengers after this position - * @throws IllegalArgumentException if position is negative or greater than stops.size() + * @param stopIndex The 0-based index of the stop in the stop list + * @return Number of passengers onboard when departing from this stop + * @throws IllegalArgumentException if stopIndex is out of bounds */ - public int getPassengerCountAtPosition(int position) { - if (position < 0) { - throw new IllegalArgumentException("Position must be non-negative, got: " + position); - } - - if (position > stops.size()) { + public int getPassengerCountAtDepartureOfStop(int stopIndex) { + if (stopIndex < 0 || stopIndex >= stops.size()) { throw new IllegalArgumentException( - "Position " + position + " exceeds valid range (0 to " + stops.size() + ")" + "Stop index " + stopIndex + " is out of bounds (0 to " + (stops.size() - 1) + ")" ); } - // Position 0 is before any stops - if (position == 0) { - return 0; - } - - // Accumulate passenger deltas up to this position - int count = 0; - for (int i = 0; i < position; i++) { - count += stops.get(i).getPassengerDelta(); - } - - return count; + return stops.get(stopIndex).getOnboardCount(); } /** - * Checks if there's capacity to add passengers throughout a range of positions. + * Checks if there's capacity to insert a passenger at the given pickup and dropoff positions + * in the modified route. + *

    + * The positions are 0-based indices of the passenger's pickup and dropoff stops in the + * modified route (the route after the passenger's stops have been inserted). For example, + * with original stops [Origin, A, B, Destination] and pickupPosition=1, dropoffPosition=3: + * the modified route is [Origin, Pickup, A, Dropoff, B, Destination]. + * All stops between (inclusive) pickupPosition - 1 and dropoffPosition - 2 are checked for capacity. + * In the example this is between stops 0 and 1, meaning that stops Origin and A need to have sufficient + * capacity for {@code additionalPassengers} extra passengers. *

    - * This validates that adding passengers won't exceed vehicle capacity at any point - * between pickup and dropoff positions. * - * @param pickupPosition The pickup position (1-indexed, inclusive) - * @param dropoffPosition The dropoff position (1-indexed, exclusive) + * @param pickupPosition 0-based index of the passenger's pickup in the modified route. + * Must be >= 1 (position 0 is the driver's origin). + * @param dropoffPosition 0-based index of the passenger's dropoff in the modified route. + * Must be > pickupPosition. * @param additionalPassengers Number of passengers to add (typically 1) * @return true if capacity is available throughout the entire range, false otherwise + * @throws IllegalArgumentException if pickupPosition < 1 or dropoffPosition <= pickupPosition */ public boolean hasCapacityForInsertion( int pickupPosition, int dropoffPosition, int additionalPassengers ) { - int pickupPassengers = getPassengerCountAtPosition(pickupPosition - 1); - if (pickupPassengers + additionalPassengers > availableSeats) { - return false; + if (pickupPosition < 1) { + throw new IllegalArgumentException( + "pickupPosition must be >= 1 (position 0 is the driver's origin), got: " + pickupPosition + ); } + if (dropoffPosition <= pickupPosition) { + throw new IllegalArgumentException( + "dropoffPosition must be > pickupPosition, got: pickupPosition=" + + pickupPosition + + ", dropoffPosition=" + + dropoffPosition + ); + } + + int firstOriginalStop = pickupPosition - 1; + int lastOriginalStop = dropoffPosition - 2; - for (int pos = pickupPosition; pos < dropoffPosition; pos++) { - int currentPassengers = getPassengerCountAtPosition(pos); - if (currentPassengers + additionalPassengers > availableSeats) { + for (int i = firstOriginalStop; i <= lastOriginalStop; i++) { + if (getPassengerCountAtDepartureOfStop(i) + additionalPassengers > totalCapacity) { return false; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java index 106232ff62e..805e0253fc0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java @@ -8,13 +8,16 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; +/** + * Builder for {@link CarpoolTrip} instances. + */ public class CarpoolTripBuilder extends AbstractEntityBuilder { private ZonedDateTime startTime; private ZonedDateTime endTime; private String provider; private Duration deviationBudget = Duration.ofMinutes(15); - private int availableSeats = 1; + private int totalCapacity; private List stops = new ArrayList<>(); public CarpoolTripBuilder(FeedScopedId id) { @@ -27,7 +30,7 @@ public CarpoolTripBuilder(CarpoolTrip original) { this.endTime = original.endTime(); this.provider = original.provider(); this.deviationBudget = original.deviationBudget(); - this.availableSeats = original.availableSeats(); + this.totalCapacity = original.totalCapacity(); this.stops = new ArrayList<>(original.stops()); } @@ -51,8 +54,8 @@ public CarpoolTripBuilder withDeviationBudget(Duration deviationBudget) { return this; } - public CarpoolTripBuilder withAvailableSeats(int availableSeats) { - this.availableSeats = availableSeats; + public CarpoolTripBuilder withTotalCapacity(int totalCapacity) { + this.totalCapacity = totalCapacity; return this; } @@ -72,8 +75,8 @@ public Duration deviationBudget() { return deviationBudget; } - public int availableSeats() { - return availableSeats; + public int totalCapacity() { + return totalCapacity; } public CarpoolTripBuilder withStops(List stops) { diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index 43f3472cc02..c8b2e9b9c35 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -14,9 +14,13 @@ *

    * Contains all information needed to construct an itinerary, including: * - The original trip - * - Insertion positions (where pickup and dropoff occur in the route) + * - Insertion positions (where pickup and dropoff occur in the modified route) * - Route segments (all GraphPaths forming the complete modified route) * - Timing information (baseline and total duration, deviation) + *

    + * {@code pickupPosition} and {@code dropoffPosition} are 0-based indices of the passenger's + * pickup and dropoff stops in the modified route (the route after the passenger's stops have + * been inserted into the carpool trip). */ public record InsertionCandidate( CarpoolTrip trip, diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java index 2471e39599a..16d875dd026 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java @@ -9,11 +9,12 @@ public class InsertionPosition { * Represents a pickup and dropoff position pair that passed heuristic validation. *

    * This is an intermediate value used between finding viable positions (via heuristics) - * and evaluating them (via A* routing). Positions are 1-indexed to match the insertion - * point semantics in the route modification algorithm. + * and evaluating them (via A* routing). Positions are 0-based indices of the passenger's + * pickup and dropoff stops in the modified route (the route after the passenger's stops + * have been inserted into the carpool trip). * - * @param pickupPos Position to insert passenger pickup (1-indexed) - * @param dropoffPos Position to insert passenger dropoff (1-indexed, always > pickupPos) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route (always > pickupPos) */ public InsertionPosition(int pickupPos, int dropoffPos) { if (dropoffPos <= pickupPos) { @@ -41,8 +42,8 @@ public int dropoffPos() { * indices shift. This method calculates the new index for an original route point. * * @param originalIndex Index in original route (before passenger insertion) - * @param pickupPos Position where pickup was inserted (1-indexed) - * @param dropoffPos Position where dropoff was inserted (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route * @return Corresponding index in modified route (after passenger insertion) */ public static int mapOriginalIndex(int originalIndex, int pickupPos, int dropoffPos) { diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index ffc69cd4591..54cc5921d67 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -76,9 +76,10 @@ public List findViablePositions( List viable = new ArrayList<>(); - // Pickup positions: 1 to routePoints.size()-1 (cannot pick up at position 0/origin) + // pickupPos/dropoffPos are 0-based indices of the passenger's stops in the modified route. + // Pickup cannot be at index 0 (that's the driver's origin). for (int pickupPos = 1; pickupPos < routePoints.size(); pickupPos++) { - // Dropoff positions: pickupPos+1 to routePoints.size() (can drop off up to and including destination) + // Dropoff must be after pickup. Max is routePoints.size() (appended after all original stops except the last). for (int dropoffPos = pickupPos + 1; dropoffPos <= routePoints.size(); dropoffPos++) { if (!trip.hasCapacityForInsertion(pickupPos, dropoffPos, 1)) { LOG.trace( @@ -139,8 +140,8 @@ public List findViablePositions( * to deviate too far from its intended direction. * * @param routePoints Current route points - * @param pickupPos Position to insert pickup (1-indexed) - * @param dropoffPos Position to insert dropoff (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route * @param passengerPickup Passenger pickup coordinate * @param passengerDropoff Passenger dropoff coordinate * @return true if insertion maintains forward progress @@ -232,8 +233,8 @@ private boolean maintainsForwardProgress( * @param originalBeelineTimes Beeline cumulative times for original route * @param passengerPickup Passenger pickup location * @param passengerDropoff Passenger dropoff location - * @param pickupPos Pickup insertion position (1-indexed) - * @param dropoffPos Dropoff insertion position (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route * @return true if insertion might satisfy delay constraints (proceed with A* routing) */ private boolean passesBeelineDelayCheck( diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 0f1536eb5b6..513749307f1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -30,7 +30,8 @@ public class CarpoolSiriMapper { private static final String FEED_ID = "ENT"; private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - private static final int DEFAULT_AVAILABLE_SEATS = 5; + private static final int DEFAULT_TOTAL_CAPACITY_SEATS = 5; + private static final int DEFAULT_ONBOARD_COUNT = 1; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); // INDEX is not relevant for our stop type. Also set index to a hard coded value to avoid // run-away memory use if it by error ends up in global repositories. @@ -77,7 +78,7 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { // TODO: Find a better way to exchange deviation budget with providers. .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) // TODO: Make available seats dynamic based on EstimatedVehicleJourney data - .withAvailableSeats(DEFAULT_AVAILABLE_SEATS) + .withTotalCapacity(DEFAULT_TOTAL_CAPACITY_SEATS) .withStops(stops) .build(); } @@ -231,7 +232,7 @@ private CarpoolStop toCarpoolStop( .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) .withExpectedArrivalTime(isFirst ? null : call.getExpectedArrivalTime()) - .withPassengerDelta(isLast ? 0 : calculatePassengerDelta(call, stopType)) + .withOnboardCount(DEFAULT_ONBOARD_COUNT) .build(); } From 94041bbf75b26653e08845a105199a3cacc770cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Fri, 27 Mar 2026 14:02:52 +0100 Subject: [PATCH 064/177] Extract capacity and occupancy in carpooling trip in carpooling trips from SIRI EstimatedCall data --- .../CarpoolEstimatedVehicleJourneyData.java | 42 +++++++ .../updater/CarpoolSiriMapperTest.java | 76 ++++++++++++ .../carpooling/updater/CarpoolSiriMapper.java | 109 ++++++++++++++---- 3 files changed, 206 insertions(+), 21 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java index ef15793c74f..765bfe9bebe 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java @@ -19,7 +19,9 @@ import uk.org.siri.siri21.EstimatedVehicleJourney; import uk.org.siri.siri21.NaturalLanguageStringStructure; import uk.org.siri.siri21.OperatorRefStructure; +import uk.org.siri.siri21.PassengerCapacityStructure; import uk.org.siri.siri21.StopAssignmentStructure; +import uk.org.siri.siri21.VehicleOccupancyStructure; public class CarpoolEstimatedVehicleJourneyData { @@ -174,6 +176,46 @@ static EstimatedCall forPolygon(String posList) { return call; } + public static EstimatedVehicleJourney journeyWithTotalCapacity(int capacity) { + var journey = minimalCompleteJourney(); + for (var call : journey.getEstimatedCalls().getEstimatedCalls()) { + addTotalCapacity(call, capacity); + } + return journey; + } + + public static EstimatedVehicleJourney journeyWithDifferentCapacitiesPerCall( + int firstCapacity, + int lastCapacity + ) { + var journey = minimalCompleteJourney(); + var calls = journey.getEstimatedCalls().getEstimatedCalls(); + addTotalCapacity(calls.getFirst(), firstCapacity); + addTotalCapacity(calls.getLast(), lastCapacity); + return journey; + } + + public static EstimatedVehicleJourney journeyWithOnboardCounts(int... onboardCounts) { + var journey = minimalCompleteJourney(); + var calls = journey.getEstimatedCalls().getEstimatedCalls(); + for (int i = 0; i < Math.min(onboardCounts.length, calls.size()); i++) { + setOnboardCount(calls.get(i), onboardCounts[i]); + } + return journey; + } + + private static void addTotalCapacity(EstimatedCall call, int totalCapacity) { + var capacity = new PassengerCapacityStructure(); + capacity.setTotalCapacity(BigInteger.valueOf(totalCapacity)); + call.getExpectedDepartureCapacities().add(capacity); + } + + private static void setOnboardCount(EstimatedCall call, int onboardCount) { + var occupancy = new VehicleOccupancyStructure(); + occupancy.setOnboardCount(BigInteger.valueOf(onboardCount)); + call.getExpectedDepartureOccupancies().add(occupancy); + } + static AimedFlexibleArea poslistToAimedFlexibleArea(String coordinates) { var gmlFactory = new ObjectFactory(); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java index 99686794eef..7c964980053 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java @@ -4,12 +4,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.arrivalIsAfterDepartureTime; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithDifferentCapacitiesPerCall; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithOnboardCounts; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithTotalCapacity; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.lessThanTwoStops; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.minimalCompleteJourney; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.minimalCompleteJourneyWithPolygon; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.stopTimesAreOutOfOrder; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasAimedTimesOnly; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasExpectedTimesOnly; +import static org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper.DEFAULT_ONBOARD_COUNT; +import static org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper.DEFAULT_TOTAL_CAPACITY; import org.junit.jupiter.api.Test; import uk.org.siri.siri21.EstimatedCall; @@ -108,4 +113,75 @@ void mapSiriToCarpoolTrip_stopTimesAreOutOfOrder_trowsIllegalArgumentException() mapper.mapSiriToCarpoolTrip(stopTimesAreOutOfOrder()) ); } + + // -- extractTotalCapacity tests -- + + @Test + void mapSiriToCarpoolTrip_noCapacityData_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_withCapacityData_usesProvidedCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(3)); + assertEquals(3, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_zeroCapacity_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(0)); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_negativeCapacity_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(-1)); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_differentCapacitiesPerCall_usesFirstValue() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithDifferentCapacitiesPerCall(3, 7)); + assertEquals(3, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_consistentCapacitiesPerCall_usesValue() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithDifferentCapacitiesPerCall(4, 4)); + assertEquals(4, mapped.totalCapacity()); + } + + // -- extractOnboardCount tests -- + + @Test + void mapSiriToCarpoolTrip_noOccupancyData_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } + + @Test + void mapSiriToCarpoolTrip_withOccupancyData_usesProvidedOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(2, 3)); + assertEquals(2, mapped.stops().getFirst().getOnboardCount()); + assertEquals(3, mapped.stops().getLast().getOnboardCount()); + } + + @Test + void mapSiriToCarpoolTrip_zeroOnboardCount_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(0, 0)); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } + + @Test + void mapSiriToCarpoolTrip_negativeOnboardCount_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(-1, -1)); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 513749307f1..239a7ee0902 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.carpooling.updater; +import java.math.BigInteger; import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -24,14 +25,20 @@ import uk.org.siri.siri21.EstimatedCall; import uk.org.siri.siri21.EstimatedVehicleJourney; +/** + * Maps SIRI EstimatedVehicleJourney messages to {@link CarpoolTrip} instances. + * Extracts stop geometry, timing, capacity and occupancy from the SIRI data. + */ public class CarpoolSiriMapper { private static final Logger LOG = LoggerFactory.getLogger(CarpoolSiriMapper.class); private static final String FEED_ID = "ENT"; private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - private static final int DEFAULT_TOTAL_CAPACITY_SEATS = 5; - private static final int DEFAULT_ONBOARD_COUNT = 1; + /** Default total capacity (including driver) when SIRI data has no capacity information. */ + static final int DEFAULT_TOTAL_CAPACITY = 5; + /** Default onboard count per stop (1 = driver only) when SIRI data has no occupancy information. */ + static final int DEFAULT_ONBOARD_COUNT = 1; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); // INDEX is not relevant for our stop type. Also set index to a hard coded value to avoid // run-away memory use if it by error ends up in global repositories. @@ -71,14 +78,15 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { ? lastStop.getExpectedArrivalTime() : lastStop.getAimedArrivalTime(); + int totalCapacity = extractTotalCapacity(tripId, calls); + return new CarpoolTripBuilder(new FeedScopedId(FEED_ID, tripId)) .withStartTime(startTime) .withEndTime(endTime) .withProvider(journey.getOperatorRef().getValue()) // TODO: Find a better way to exchange deviation budget with providers. .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) - // TODO: Make available seats dynamic based on EstimatedVehicleJourney data - .withTotalCapacity(DEFAULT_TOTAL_CAPACITY_SEATS) + .withTotalCapacity(totalCapacity) .withStops(stops) .build(); } @@ -106,7 +114,7 @@ private CarpoolStop buildCarpoolStopForPosition( ? tripId + "_trip_destination" : tripId + "_stop_" + sequenceNumber; - return toCarpoolStop(call, stopId, isFirst, isLast); + return toCarpoolStop(call, stopId, tripId, isFirst, isLast); } /** @@ -130,23 +138,81 @@ private CarpoolStopType determineCarpoolStopType(EstimatedCall call) { } /** - * Calculate the passenger delta (change in passenger count) from the EstimatedCall. + * Extracts the total capacity from the EstimatedCalls' ExpectedDepartureCapacities. + * Only the first element of each call's capacities list is inspected; additional + * entries are ignored. Uses the value from the first call that has it. Logs a warning + * if different calls report different capacity values. Returns + * {@link #DEFAULT_TOTAL_CAPACITY} if no call has capacity data or if the value is invalid. */ - private int calculatePassengerDelta(EstimatedCall call, CarpoolStopType stopType) { - // This is a placeholder implementation - adapt based on SIRI ET data structure - // SIRI ET may have passenger count changes, boarding/alighting numbers, etc. - - // For now, return a default value of 1 passenger pickup/dropoff - if (stopType == CarpoolStopType.DROP_OFF_ONLY) { - // Assume 1 passenger drop-off - return -1; - } else if (stopType == CarpoolStopType.PICKUP_ONLY) { - // Assume 1 passenger pickup - return 1; - } else { - // No net change for both pickup and drop-off - return 0; + private int extractTotalCapacity(String tripId, List calls) { + Integer firstCapacity = null; + int firstCapacityIndex = -1; + + for (int i = 0; i < calls.size(); i++) { + var capacities = calls.get(i).getExpectedDepartureCapacities(); + if (capacities == null || capacities.isEmpty()) { + continue; + } + BigInteger value = capacities.getFirst().getTotalCapacity(); + if (value == null) { + continue; + } + int intValue = value.intValue(); + if (firstCapacity == null) { + firstCapacity = intValue; + firstCapacityIndex = i; + } else if (intValue != firstCapacity) { + LOG.warn( + "Trip {}: totalCapacity differs between calls (call {} has {}, call {} has {})", + tripId, + firstCapacityIndex, + firstCapacity, + i, + intValue + ); + } } + + if (firstCapacity == null) { + return DEFAULT_TOTAL_CAPACITY; + } + if (firstCapacity <= 0) { + LOG.warn( + "Trip {}: invalid totalCapacity {} at call {}, using default {}", + tripId, + firstCapacity, + firstCapacityIndex, + DEFAULT_TOTAL_CAPACITY + ); + return DEFAULT_TOTAL_CAPACITY; + } + return firstCapacity; + } + + /** + * Extracts the onboard count from the EstimatedCall's ExpectedDepartureOccupancies. + * Only the first element of the occupancies list is inspected; additional entries are + * ignored. Returns {@link #DEFAULT_ONBOARD_COUNT} if not present or if the value is invalid. + */ + private int extractOnboardCount(String tripId, EstimatedCall call) { + var occupancies = call.getExpectedDepartureOccupancies(); + if (occupancies != null && !occupancies.isEmpty()) { + BigInteger onboardCount = occupancies.getFirst().getOnboardCount(); + if (onboardCount != null) { + int value = onboardCount.intValue(); + if (value <= 0) { + LOG.warn( + "Trip {}: invalid onboardCount {}, using default {}", + tripId, + value, + DEFAULT_ONBOARD_COUNT + ); + return DEFAULT_ONBOARD_COUNT; + } + return value; + } + } + return DEFAULT_ONBOARD_COUNT; } /** @@ -205,6 +271,7 @@ private void validateEstimatedCallOrder(List calls) { private CarpoolStop toCarpoolStop( EstimatedCall call, String id, + String tripId, boolean isFirst, boolean isLast ) { @@ -232,7 +299,7 @@ private CarpoolStop toCarpoolStop( .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) .withExpectedArrivalTime(isFirst ? null : call.getExpectedArrivalTime()) - .withOnboardCount(DEFAULT_ONBOARD_COUNT) + .withOnboardCount(extractOnboardCount(tripId, call)) .build(); } From fb4dc0a13060e3c39dfaf2e0febfdf543c51b9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 30 Mar 2026 13:14:56 +0200 Subject: [PATCH 065/177] set default total capacity of test carpooling trips to 5 --- .../ext/carpooling/CarpoolTripTestData.java | 10 ++-- .../model/CarpoolTripCapacityTest.java | 57 +++++++------------ .../carpooling/updater/CarpoolSiriMapper.java | 4 +- 3 files changed, 26 insertions(+), 45 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index f112fb438e6..c5b5919395c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -20,12 +20,12 @@ public class CarpoolTripTestData { private static final AtomicInteger AREA_STOP_COUNTER = new AtomicInteger(0); /** - * Creates a simple trip with origin and destination stops, default capacity of 4. + * Creates a simple trip with origin and destination stops, default capacity of 5. */ public static CarpoolTrip createSimpleTrip(WgsCoordinate boarding, WgsCoordinate alighting) { var origin = createOriginStop(boarding); var destination = createDestinationStop(alighting, 1); - return createTripWithCapacity(4, List.of(origin, destination)); + return createTripWithCapacity(5, List.of(origin, destination)); } /** @@ -43,7 +43,7 @@ public static CarpoolTrip createSimpleTripWithTime( startTime.plusHours(1), startTime.plusHours(1) ); - return createTripWithTime(startTime, 4, List.of(origin, destination)); + return createTripWithTime(startTime, 5, List.of(origin, destination)); } /** @@ -75,7 +75,7 @@ public static CarpoolTrip createTripWithStops( } allStops.add(createDestinationStop(alighting, allStops.size())); - return createTripWithCapacity(4, allStops); + return createTripWithCapacity(5, allStops); } /** @@ -95,7 +95,7 @@ public static CarpoolTrip createTripWithDeviationBudget( ) { var origin = createOriginStop(boarding); var destination = createDestinationStop(alighting, 1); - return createTripWithDeviationBudget(deviationBudget, 4, List.of(origin, destination)); + return createTripWithDeviationBudget(deviationBudget, 5, List.of(origin, destination)); } /** diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java index c5af490c123..25d8681785d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java @@ -16,7 +16,7 @@ /** * Tests for capacity checking methods on {@link CarpoolTrip}. *

    - * All trips created via {@code createTripWithStops} have totalCapacity=4. + * All trips created via {@code createTripWithStops} have totalCapacity=5. * The method wraps intermediate stops with an Origin (onboard=1) at the front * and a Destination (onboard=1) at the end. *

    @@ -64,77 +64,58 @@ void getPassengerCountAtDepartureOfStop_indexTooLarge_throwsException() { assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtDepartureOfStop(2)); } - // -- hasCapacityForInsertion tests -- - // - // pickupPosition and dropoffPosition are 0-based indices of the passenger's - // stops in the modified route. The method checks capacity at the original stops - // where the passenger would be riding, from original index (pickupPosition - 1) - // to (dropoffPosition - 2) inclusive. - @Test void adjacentPositions_onlyChecksStopBeforePickup() { - // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=4 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); // Modified route: [Origin, Pickup, Dropoff, A, Destination] // 0 1 2 3 4 // Checked original stops: Origin (index 0, onboard=1). A is NOT checked. - assertTrue(trip.hasCapacityForInsertion(1, 2, 3)); + assertTrue(trip.hasCapacityForInsertion(1, 2, 4)); } @Test void adjacentPositions_fullAtStopBeforePickup() { - // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=4 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); // Modified route: [Origin, A, Pickup, Dropoff, Destination] // 0 1 2 3 4 - // Checked original stops: A (index 1, onboard=4). No room. + // Checked original stops: A (index 1, onboard=5). No room. assertFalse(trip.hasCapacityForInsertion(2, 3, 1)); } @Test void widerGap_checksAllOriginalStopsBetweenPickupAndDropoff() { - // Original stops: [Origin(1), A(2), B(3), C(1), Destination(1)] totalCapacity=4 + // Original stops: [Origin(1), A(2), B(4), C(1), Destination(1)] totalCapacity=5 var trip = createTripWithStops( OSLO_CENTER, - List.of(createStop(0, 2), createStop(1, 3), createStop(2, 1)), + List.of(createStop(0, 2), createStop(1, 4), createStop(2, 1)), OSLO_NORTH ); // Modified route: [Origin, Pickup, A, B, Dropoff, C, Destination] // 0 1 2 3 4 5 6 - // Checked original stops: Origin(1), A(2), B(3). Max is 3, room for 1. + // Checked original stops: Origin(1), A(2), B(4). Max is 4, room for 1. assertTrue(trip.hasCapacityForInsertion(1, 4, 1)); assertFalse(trip.hasCapacityForInsertion(1, 4, 2)); } @Test void stopAfterDropoff_isNotChecked() { - // Original stops: [Origin(1), A(1), B(4), Destination(1)] totalCapacity=4 + // Original stops: [Origin(1), A(1), B(5), Destination(1)] totalCapacity=5 // B is full, but the passenger is dropped off before B. var trip = createTripWithStops( OSLO_CENTER, - List.of(createStop(0, 1), createStop(1, 4)), + List.of(createStop(0, 1), createStop(1, 5)), OSLO_NORTH ); // Modified route: [Origin, Pickup, A, Dropoff, B, Destination] // 0 1 2 3 4 5 // Checked original stops: Origin(1), A(1). B is after the dropoff. - assertTrue(trip.hasCapacityForInsertion(1, 3, 2)); - } - - @Test - void pickupNearEnd_checksOnlyStopBeforePickup() { - // Original stops: [Origin(1), A(3), Destination(1)] totalCapacity=4 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 3)), OSLO_NORTH); - - // Modified route: [Origin, A, Pickup, Dropoff, Destination] - // 0 1 2 3 4 - // Checked original stops: A (index 1, onboard=3). Room for 1. - assertTrue(trip.hasCapacityForInsertion(2, 3, 1)); - assertFalse(trip.hasCapacityForInsertion(2, 3, 2)); + assertTrue(trip.hasCapacityForInsertion(1, 3, 3)); } @Test @@ -151,14 +132,14 @@ void pickupNearEnd_limitedByStopBeforePickup() { @Test void fullRangeInsertion_checksAllOriginalStops() { - // Original stops: [Origin(2), A(2), Destination(2)] totalCapacity=4 + // Original stops: [Origin(2), A(2), Destination(2)] totalCapacity=5 var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 2)), OSLO_NORTH); // Modified route: [Origin, Pickup, A, Dropoff, Destination] // 0 1 2 3 4 - // Checked original stops: Origin(2), A(2). Both onboard=2, room for 2. - assertTrue(trip.hasCapacityForInsertion(1, 3, 2)); - assertFalse(trip.hasCapacityForInsertion(1, 3, 3)); + // Checked original stops: Origin(2), A(2). Both onboard=2, room for 3. + assertTrue(trip.hasCapacityForInsertion(1, 3, 3)); + assertFalse(trip.hasCapacityForInsertion(1, 3, 4)); } @Test @@ -177,7 +158,7 @@ void fullStopBeforePickup_notInCheckedRange() { @Test void bottleneckInMiddle_limitsCapacity() { - // Original stops: [Origin(1), A(1), B(3), C(1), D(1), Destination(1)] totalCapacity=4 + // Original stops: [Origin(1), A(1), B(4), C(1), D(1), Destination(1)] totalCapacity=5 var trip = createTripWithStops( OSLO_CENTER, List.of(createStop(0, 1), createStop(1, 4), createStop(2, 1), createStop(3, 1)), @@ -186,7 +167,7 @@ void bottleneckInMiddle_limitsCapacity() { // Modified route: [Origin, Pickup, A, B, C, Dropoff, D, Destination] // 0 1 2 3 4 5 6 7 - // Checked original stops: Origin(1), A(1), B(3), C(1). Max is 3 at B, room for 1. + // Checked original stops: Origin(1), A(1), B(4), C(1). Max is 4 at B, room for 1. assertTrue(trip.hasCapacityForInsertion(1, 5, 1)); assertFalse(trip.hasCapacityForInsertion(1, 5, 2)); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 239a7ee0902..0751c8779f6 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -36,9 +36,9 @@ public class CarpoolSiriMapper { private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); /** Default total capacity (including driver) when SIRI data has no capacity information. */ - static final int DEFAULT_TOTAL_CAPACITY = 5; + public static final int DEFAULT_TOTAL_CAPACITY = 5; /** Default onboard count per stop (1 = driver only) when SIRI data has no occupancy information. */ - static final int DEFAULT_ONBOARD_COUNT = 1; + public static final int DEFAULT_ONBOARD_COUNT = 1; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); // INDEX is not relevant for our stop type. Also set index to a hard coded value to avoid // run-away memory use if it by error ends up in global repositories. From 0f02b61ccbf36f1e56bd172bb61f21ae87653c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 30 Mar 2026 14:35:16 +0200 Subject: [PATCH 066/177] simplify carpoolTripTestData, fixed bug with arrivalTime getting value of departureTime --- .../ext/carpooling/CarpoolTripTestData.java | 104 +++++++----------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index c5b5919395c..5bd2bc1b1ab 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -9,6 +9,8 @@ import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.ext.carpooling.model.CarpoolStopType; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; +import org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder; +import org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper; import org.opentripplanner.street.geometry.WgsCoordinate; /** @@ -19,13 +21,15 @@ public class CarpoolTripTestData { private static final AtomicInteger ID_COUNTER = new AtomicInteger(0); private static final AtomicInteger AREA_STOP_COUNTER = new AtomicInteger(0); + private static final int DEFAULT_TOTAL_CAPACITY = CarpoolSiriMapper.DEFAULT_TOTAL_CAPACITY; + private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(10); + /** - * Creates a simple trip with origin and destination stops, default capacity of 5. + * Creates a simple trip with origin and destination stops. */ public static CarpoolTrip createSimpleTrip(WgsCoordinate boarding, WgsCoordinate alighting) { - var origin = createOriginStop(boarding); - var destination = createDestinationStop(alighting, 1); - return createTripWithCapacity(5, List.of(origin, destination)); + var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting, 1)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, stops); } /** @@ -36,14 +40,11 @@ public static CarpoolTrip createSimpleTripWithTime( WgsCoordinate alighting, ZonedDateTime startTime ) { - var origin = createOriginStopWithTime(boarding, startTime, startTime); - var destination = createDestinationStopWithTime( - alighting, - 1, - startTime.plusHours(1), - startTime.plusHours(1) + var stops = List.of( + createOriginStopWithTime(boarding, startTime, startTime), + createDestinationStopWithTime(alighting, 1, startTime.plusHours(1), startTime.plusHours(1)) ); - return createTripWithTime(startTime, 5, List.of(origin, destination)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, startTime, stops); } /** @@ -57,7 +58,6 @@ public static CarpoolTrip createTripWithStops( List allStops = new ArrayList<>(); allStops.add(createOriginStop(boarding)); - // Renumber intermediate stops to account for origin at position 0 for (int i = 0; i < intermediateStops.size(); i++) { CarpoolStop intermediate = intermediateStops.get(i); allStops.add( @@ -65,9 +65,9 @@ public static CarpoolTrip createTripWithStops( .withCoordinate(intermediate.getCoordinate()) .withCarpoolStopType(intermediate.getCarpoolStopType()) .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) - .withAimedArrivalTime(intermediate.getAimedArrivalTime()) + .withAimedDepartureTime(intermediate.getAimedDepartureTime()) .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) - .withAimedArrivalTime(intermediate.getAimedDepartureTime()) + .withAimedArrivalTime(intermediate.getAimedArrivalTime()) .withSequenceNumber(intermediate.getSequenceNumber() + 1) .withOnboardCount(intermediate.getOnboardCount()) .build() @@ -75,14 +75,14 @@ public static CarpoolTrip createTripWithStops( } allStops.add(createDestinationStop(alighting, allStops.size())); - return createTripWithCapacity(5, allStops); + return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, allStops); } /** * Creates a trip with specified capacity and all stops (including origin/destination). */ - public static CarpoolTrip createTripWithCapacity(int seats, List stops) { - return createTripWithDeviationBudget(Duration.ofMinutes(10), seats, stops); + public static CarpoolTrip createTripWithCapacity(int capacity, List stops) { + return buildTrip(capacity, DEFAULT_DEVIATION_BUDGET, null, stops); } /** @@ -93,47 +93,19 @@ public static CarpoolTrip createTripWithDeviationBudget( WgsCoordinate boarding, WgsCoordinate alighting ) { - var origin = createOriginStop(boarding); - var destination = createDestinationStop(alighting, 1); - return createTripWithDeviationBudget(deviationBudget, 5, List.of(origin, destination)); + var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting, 1)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, deviationBudget, null, stops); } /** - * Creates a trip with all parameters specified. - */ - public static CarpoolTrip createTripWithDeviationBudget( - Duration deviationBudget, - int seats, - List stops - ) { - return new org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder( - FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) - ) - .withStops(stops) - .withTotalCapacity(seats) - .withStartTime(ZonedDateTime.now()) - .withDeviationBudget(deviationBudget) - .build(); - } - - /** - * Creates a trip with specific start time and all other parameters. - * End time is calculated as startTime + 1 hour. + * Creates a trip with specific start time. */ public static CarpoolTrip createTripWithTime( ZonedDateTime startTime, - int seats, + int capacity, List stops ) { - return new org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder( - FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) - ) - .withStops(stops) - .withTotalCapacity(seats) - .withStartTime(startTime) - .withEndTime(startTime.plusHours(1)) - .withDeviationBudget(Duration.ofMinutes(10)) - .build(); + return buildTrip(capacity, DEFAULT_DEVIATION_BUDGET, startTime, stops); } /** @@ -164,16 +136,10 @@ public static CarpoolStop createStopAt(int sequence, int onboardCount, WgsCoordi .build(); } - /** - * Creates an origin stop (first stop, PICKUP_ONLY, onboardCount=1 for driver, departure times only). - */ public static CarpoolStop createOriginStop(WgsCoordinate location) { return createOriginStopWithTime(location, null, null); } - /** - * Creates an origin stop with specific departure times. - */ public static CarpoolStop createOriginStopWithTime( WgsCoordinate location, ZonedDateTime expectedDepartureTime, @@ -187,16 +153,10 @@ public static CarpoolStop createOriginStopWithTime( .build(); } - /** - * Creates a destination stop (last stop, DROP_OFF_ONLY, onboardCount=1 for driver, arrival times only). - */ public static CarpoolStop createDestinationStop(WgsCoordinate location, int sequenceNumber) { return createDestinationStopWithTime(location, sequenceNumber, null, null); } - /** - * Creates a destination stop with specific arrival times. - */ public static CarpoolStop createDestinationStopWithTime( WgsCoordinate location, int sequenceNumber, @@ -215,4 +175,24 @@ public static CarpoolStop createDestinationStopWithTime( .withAimedArrivalTime(aimedArrivalTime) .build(); } + + private static CarpoolTrip buildTrip( + int capacity, + Duration deviationBudget, + ZonedDateTime startTime, + List stops + ) { + var actualStartTime = startTime != null ? startTime : ZonedDateTime.now(); + var builder = new CarpoolTripBuilder( + FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) + ) + .withStops(stops) + .withTotalCapacity(capacity) + .withStartTime(actualStartTime) + .withDeviationBudget(deviationBudget); + if (startTime != null) { + builder.withEndTime(startTime.plusHours(1)); + } + return builder.build(); + } } From bd1e6c9be1396606cfce266ce00b2b3bbe16dcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Fri, 10 Apr 2026 17:26:20 +0200 Subject: [PATCH 067/177] deletes type CarpoolStopType, as it is no longer in use --- .../CarpoolEstimatedVehicleJourneyData.java | 4 +-- .../ext/carpooling/CarpoolTripTestData.java | 3 -- .../model/CarpoolStopBuilderTest.java | 5 --- .../ext/carpooling/model/CarpoolStop.java | 9 ------ .../carpooling/model/CarpoolStopBuilder.java | 11 ------- .../ext/carpooling/model/CarpoolStopType.java | 13 -------- .../carpooling/updater/CarpoolSiriMapper.java | 31 ------------------- 7 files changed, 2 insertions(+), 74 deletions(-) delete mode 100644 application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopType.java diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java index 765bfe9bebe..4ffbc2dc2a4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java @@ -199,7 +199,7 @@ public static EstimatedVehicleJourney journeyWithOnboardCounts(int... onboardCou var journey = minimalCompleteJourney(); var calls = journey.getEstimatedCalls().getEstimatedCalls(); for (int i = 0; i < Math.min(onboardCounts.length, calls.size()); i++) { - setOnboardCount(calls.get(i), onboardCounts[i]); + addOnboardCount(calls.get(i), onboardCounts[i]); } return journey; } @@ -210,7 +210,7 @@ private static void addTotalCapacity(EstimatedCall call, int totalCapacity) { call.getExpectedDepartureCapacities().add(capacity); } - private static void setOnboardCount(EstimatedCall call, int onboardCount) { + private static void addOnboardCount(EstimatedCall call, int onboardCount) { var occupancy = new VehicleOccupancyStructure(); occupancy.setOnboardCount(BigInteger.valueOf(onboardCount)); call.getExpectedDepartureOccupancies().add(occupancy); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 5bd2bc1b1ab..245eda2f4a3 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -7,7 +7,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.model.CarpoolStop; -import org.opentripplanner.ext.carpooling.model.CarpoolStopType; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder; import org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper; @@ -63,7 +62,6 @@ public static CarpoolTrip createTripWithStops( allStops.add( CarpoolStop.of(intermediate.getId(), () -> intermediate.getIndex() + 1) .withCoordinate(intermediate.getCoordinate()) - .withCarpoolStopType(intermediate.getCarpoolStopType()) .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) .withAimedDepartureTime(intermediate.getAimedDepartureTime()) .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) @@ -168,7 +166,6 @@ public static CarpoolStop createDestinationStopWithTime( AREA_STOP_COUNTER::getAndIncrement ) .withCoordinate(location) - .withCarpoolStopType(CarpoolStopType.DROP_OFF_ONLY) .withSequenceNumber(sequenceNumber) .withOnboardCount(1) .withExpectedArrivalTime(expectedArrivalTime) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java index efecc20a3ef..d6314939f58 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.model.CarpoolStopType.DROP_OFF_ONLY; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -62,7 +61,6 @@ void buildFromValues_usingWith_buildToCorrectValues() { .withSequenceNumber(1) .withOnboardCount(2) .withCoordinate(OSLO_NORTH) - .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) @@ -76,7 +74,6 @@ void buildFromValues_usingWith_buildToCorrectValues() { assertEquals(1, stop.getSequenceNumber()); assertEquals(2, stop.getOnboardCount()); assertEquals(OSLO_NORTH, stop.getCoordinate()); - assertEquals(DROP_OFF_ONLY, stop.getCarpoolStopType()); assertEquals(AIMED_ARRIVAL_TIME, stop.getAimedArrivalTime()); assertEquals(EXPECTED_ARRIVAL_TIME, stop.getExpectedArrivalTime()); assertEquals(AIMED_DEPARTURE_TIME, stop.getAimedDepartureTime()); @@ -97,7 +94,6 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { .withSequenceNumber(2) .withOnboardCount(3) .withCoordinate(OSLO_CENTER) - .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) @@ -114,7 +110,6 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { assertEquals(original.getSequenceNumber(), copy.getSequenceNumber()); assertEquals(original.getOnboardCount(), copy.getOnboardCount()); assertEquals(original.getCoordinate(), copy.getCoordinate()); - assertEquals(original.getCarpoolStopType(), copy.getCarpoolStopType()); assertEquals(original.getAimedArrivalTime(), copy.getAimedArrivalTime()); assertEquals(original.getExpectedArrivalTime(), copy.getExpectedArrivalTime()); assertEquals(original.getAimedDepartureTime(), copy.getAimedDepartureTime()); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index c01f3df1da0..1f118862196 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -27,7 +27,6 @@ public class CarpoolStop private final I18NString url; private final WgsCoordinate coordinate; private final Geometry geometry; - private final CarpoolStopType carpoolStopType; private final ZonedDateTime expectedArrivalTime; private final ZonedDateTime aimedArrivalTime; private final ZonedDateTime expectedDepartureTime; @@ -48,7 +47,6 @@ public CarpoolStop(CarpoolStopBuilder builder) { this.url = builder.url(); this.coordinate = Objects.requireNonNull(builder.coordinate()); this.geometry = builder.geometry(); - this.carpoolStopType = builder.carpoolStopType(); this.expectedArrivalTime = builder.expectedArrivalTime(); this.aimedArrivalTime = builder.aimedArrivalTime(); this.expectedDepartureTime = builder.expectedDepartureTime(); @@ -130,13 +128,6 @@ public boolean isPartOfSameStationAs(StopLocation alternativeStop) { // Carpool-specific methods - /** - * @return The type of carpool operation allowed at this stop - */ - public CarpoolStopType getCarpoolStopType() { - return carpoolStopType; - } - /** * @return The expected arrival time, or null if not applicable (e.g., origin stop) */ diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index f1c58ae0cca..f0af9799cf5 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -23,7 +23,6 @@ public class CarpoolStopBuilder extends AbstractEntityBuilder CARPOOLING_DUMMY_INDEX) .withName(I18NString.of(call.getStopPointNames().getFirst().getValue())) .withCoordinate(centroid) - .withCarpoolStopType(stopType) .withAimedDepartureTime(isLast ? null : call.getAimedDepartureTime()) .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) From 24252407444f059a8206f79350464443af3a81d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Thu, 16 Apr 2026 13:48:29 +0200 Subject: [PATCH 068/177] Move DEFAULT_TOTAL_CAPACITY and DEFAULT_ONBOARD_COUNT to CarpoolTrip and CarpoolStop respectively --- .../ext/carpooling/CarpoolTripTestData.java | 3 +-- .../ext/carpooling/updater/CarpoolSiriMapperTest.java | 10 +++++----- .../ext/carpooling/model/CarpoolStop.java | 3 +++ .../ext/carpooling/model/CarpoolStopBuilder.java | 2 +- .../ext/carpooling/model/CarpoolTrip.java | 3 +++ .../ext/carpooling/model/CarpoolTripBuilder.java | 2 +- .../ext/carpooling/updater/CarpoolSiriMapper.java | 11 +++++------ 7 files changed, 19 insertions(+), 15 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 245eda2f4a3..d08b6098cb3 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -9,7 +9,6 @@ import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder; -import org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper; import org.opentripplanner.street.geometry.WgsCoordinate; /** @@ -20,7 +19,7 @@ public class CarpoolTripTestData { private static final AtomicInteger ID_COUNTER = new AtomicInteger(0); private static final AtomicInteger AREA_STOP_COUNTER = new AtomicInteger(0); - private static final int DEFAULT_TOTAL_CAPACITY = CarpoolSiriMapper.DEFAULT_TOTAL_CAPACITY; + private static final int DEFAULT_TOTAL_CAPACITY = CarpoolTrip.DEFAULT_TOTAL_CAPACITY; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(10); /** diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java index 7c964980053..9c9d54f8a06 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java @@ -13,8 +13,8 @@ import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.stopTimesAreOutOfOrder; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasAimedTimesOnly; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasExpectedTimesOnly; -import static org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper.DEFAULT_ONBOARD_COUNT; -import static org.opentripplanner.ext.carpooling.updater.CarpoolSiriMapper.DEFAULT_TOTAL_CAPACITY; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_ONBOARD_COUNT; +import static org.opentripplanner.ext.carpooling.model.CarpoolTrip.DEFAULT_TOTAL_CAPACITY; import org.junit.jupiter.api.Test; import uk.org.siri.siri21.EstimatedCall; @@ -24,14 +24,14 @@ public class CarpoolSiriMapperTest { private final CarpoolSiriMapper mapper = new CarpoolSiriMapper(); @Test - void mapSiriToCarpoolTrip_arrivalIsAfterDepartureTime_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_arrivalIsAfterDepartureTime_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(arrivalIsAfterDepartureTime()) ); } @Test - void mapSiriToCarpoolTrip_lessThanTwoStops_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_lessThanTwoStops_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(lessThanTwoStops()) ); @@ -108,7 +108,7 @@ void mapSiriToCarpoolTrip_tripHasOnlyExpectedTimes_mapsOk() { } @Test - void mapSiriToCarpoolTrip_stopTimesAreOutOfOrder_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_stopTimesAreOutOfOrder_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(stopTimesAreOutOfOrder()) ); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index 1f118862196..8e3b0447f07 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -21,6 +21,9 @@ public class CarpoolStop extends AbstractTransitEntity implements StopLocation { + /** Default onboard count per stop (1 = driver only) when no occupancy information is provided. */ + public static final int DEFAULT_ONBOARD_COUNT = 1; + private final int index; private final I18NString name; private final I18NString description; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index f0af9799cf5..307bc87a06a 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -28,7 +28,7 @@ public class CarpoolStopBuilder extends AbstractEntityBuilder implements LogInfo { + /** Default total capacity (including driver) when no capacity information is provided. */ + public static final int DEFAULT_TOTAL_CAPACITY = 5; + private final ZonedDateTime startTime; private final ZonedDateTime endTime; private final String provider; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java index 805e0253fc0..d7dd1db9485 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java @@ -17,7 +17,7 @@ public class CarpoolTripBuilder extends AbstractEntityBuilder stops = new ArrayList<>(); public CarpoolTripBuilder(FeedScopedId id) { diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index f228c14919a..accb242ff09 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -1,5 +1,8 @@ package org.opentripplanner.ext.carpooling.updater; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_ONBOARD_COUNT; +import static org.opentripplanner.ext.carpooling.model.CarpoolTrip.DEFAULT_TOTAL_CAPACITY; + import java.math.BigInteger; import java.time.Duration; import java.time.ZonedDateTime; @@ -34,10 +37,6 @@ public class CarpoolSiriMapper { private static final String FEED_ID = "ENT"; private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - /** Default total capacity (including driver) when SIRI data has no capacity information. */ - public static final int DEFAULT_TOTAL_CAPACITY = 5; - /** Default onboard count per stop (1 = driver only) when SIRI data has no occupancy information. */ - public static final int DEFAULT_ONBOARD_COUNT = 1; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); // INDEX is not relevant for our stop type. Also set index to a hard coded value to avoid // run-away memory use if it by error ends up in global repositories. @@ -121,7 +120,7 @@ private CarpoolStop buildCarpoolStopForPosition( * Only the first element of each call's capacities list is inspected; additional * entries are ignored. Uses the value from the first call that has it. Logs a warning * if different calls report different capacity values. Returns - * {@link #DEFAULT_TOTAL_CAPACITY} if no call has capacity data or if the value is invalid. + * {@link CarpoolTrip#DEFAULT_TOTAL_CAPACITY} if no call has capacity data or if the value is invalid. */ private int extractTotalCapacity(String tripId, List calls) { Integer firstCapacity = null; @@ -171,7 +170,7 @@ private int extractTotalCapacity(String tripId, List calls) { /** * Extracts the onboard count from the EstimatedCall's ExpectedDepartureOccupancies. * Only the first element of the occupancies list is inspected; additional entries are - * ignored. Returns {@link #DEFAULT_ONBOARD_COUNT} if not present or if the value is invalid. + * ignored. Returns {@link CarpoolStop#DEFAULT_ONBOARD_COUNT} if not present or if the value is invalid. */ private int extractOnboardCount(String tripId, EstimatedCall call) { var occupancies = call.getExpectedDepartureOccupancies(); From a0ae19e26b0279917d471d372d2b505c34e79328 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Fri, 17 Apr 2026 11:14:23 +0100 Subject: [PATCH 069/177] move the entrances in stop area field to osm database so we don't need to make a separate init method --- .../graph_builder/module/osm/OsmDatabase.java | 27 +++++++++++-------- .../graph_builder/module/osm/OsmModule.java | 3 --- .../module/osm/VertexGenerator.java | 25 +---------------- .../module/osm/VertexGeneratorTest.java | 1 - 4 files changed, 17 insertions(+), 39 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 2706a14f183..bd5d451dbd0 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 @@ -80,9 +80,6 @@ public class OsmDatabase { /* All bike parking areas */ private final List bikeParkingAreas = new ArrayList<>(); - /* All stop area relations */ - private final List stopAreas = new ArrayList<>(); - /* Map of all area OSMWay for a given node */ private final TLongObjectMap> areasForNode = new TLongObjectHashMap<>(); @@ -122,6 +119,11 @@ public class OsmDatabase { */ private final Multimap stopsInAreas = HashMultimap.create(); + /** + * Set of all entrance nodes in stop areas, which will be treated as station entrances. + */ + private final Set entrancesInStopAreas = new HashSet<>(); + /* * ID of the next virtual node we create during building phase. Negative to prevent conflicts * with existing ones. @@ -177,10 +179,6 @@ public Collection getBikeParkingAreas() { return Collections.unmodifiableCollection(bikeParkingAreas); } - public Collection getStopAreas() { - return Collections.unmodifiableCollection(stopAreas); - } - public Collection getTurnRestrictionWayIds() { return Collections.unmodifiableCollection(turnRestrictionsByFromWay.keySet()); } @@ -197,6 +195,10 @@ public Collection getStopsInArea(OsmEntity areaParent) { return stopsInAreas.get(areaParent); } + public boolean isEntranceInStopArea(OsmNode node) { + return entrancesInStopAreas.contains(node); + } + /** * @return If a single level is defined for an entity return that level, * otherwise the default level is returned. @@ -989,16 +991,19 @@ private void processRoute(OsmRelation relation) { * @see "http://wiki.openstreetmap.org/wiki/Tag:public_transport%3Dstop_area" */ private void processPublicTransportStopArea(OsmRelation relation) { - stopAreas.add(relation); - Set platformAreas = new HashSet<>(); Set platformNodes = new HashSet<>(); for (OsmRelationMember member : relation.getMembers()) { switch (member.getType()) { case NODE -> { var node = nodesById.get(member.getRef()); - if (node != null && node.isPlatformAccess()) { - platformNodes.add(node); + if (node != null) { + if (node.isPlatformAccess()) { + platformNodes.add(node); + } + if (node.isEntrance()) { + entrancesInStopAreas.add(node); + } } } case WAY -> { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 890c389fdd4..8a05dc96d36 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -193,9 +193,6 @@ private void build(OsmDatabase osmdb, VertexGenerator vertexGenerator) { vertexGenerator.initIntersectionNodes(); vertexGenerator.initNodesInBarrierWays(); - // figure out entrances in stop areas, which should be treated as station entrances - vertexGenerator.initEntrancesInStopAreas(); - ElevatorProcessor elevatorProcessor = new ElevatorProcessor( issueStore, osmdb, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 88c5c270dff..24198f57586 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -20,7 +20,6 @@ import org.opentripplanner.graph_builder.issues.DifferentLevelsSharingBarrier; import org.opentripplanner.osm.model.OsmEntity; import org.opentripplanner.osm.model.OsmLevel; -import org.opentripplanner.osm.model.OsmMemberType; import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.graph.Graph; @@ -62,12 +61,6 @@ class VertexGenerator { * The map from node to the split vertex for each routable entity connecting it. */ private final Map> splitVerticesOnBarriers = new HashMap<>(); - - /** - * - */ - private final Set entrancesInStopAreas = new HashSet<>(); - private final OsmDatabase osmdb; private final Set boardingAreaRefTags; private final Boolean includeOsmStationEntrances; @@ -142,8 +135,7 @@ IntersectionVertex getVertexForOsmNode( } if ( - includeOsmStationEntrances && - (node.isStationEntrance() || entrancesInStopAreas.contains(node)) + includeOsmStationEntrances && (node.isStationEntrance() || osmdb.isEntranceInStopArea(node)) ) { String ref = node.getTag("ref"); iv = vertexFactory.stationEntrance( @@ -320,21 +312,6 @@ void initNodesInBarrierWays() { } } - void initEntrancesInStopAreas() { - if (includeOsmStationEntrances) { - for (var relation : osmdb.getStopAreas()) { - for (var member : relation.getMembers()) { - if (member.getType() == OsmMemberType.NODE) { - var node = osmdb.getNode(member.getRef()); - if (node != null && node.isEntrance()) { - entrancesInStopAreas.add(node); - } - } - } - } - } - } - /** * Track OSM nodes that will become graph vertices because they appear in multiple OSM ways */ diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java index bf9a7b4bf5c..686a29d85b6 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java @@ -200,7 +200,6 @@ void testEntranceInStopAreas() { Graph graph = new Graph(); var subject = new VertexGenerator(osmdb, graph, Set.of(), true, DataImportIssueStore.NOOP); - subject.initEntrancesInStopAreas(); assertInstanceOf( StationEntranceVertex.class, From 22338e90c322a87aa3cf17398d1e5cf326526cf9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 17 Apr 2026 12:54:05 +0200 Subject: [PATCH 070/177] Doc review: Update pagination and input type descriptions Clarified pagination description and improved input type explanation. --- doc/dev/decisionrecords/APIGraphQLDesign.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/dev/decisionrecords/APIGraphQLDesign.md b/doc/dev/decisionrecords/APIGraphQLDesign.md index 8cdae20b1d4..1121046f2af 100644 --- a/doc/dev/decisionrecords/APIGraphQLDesign.md +++ b/doc/dev/decisionrecords/APIGraphQLDesign.md @@ -7,7 +7,7 @@ of the OTP developers is the Production Ready GraphQL book by Marc-André Giroux ## Pagination -We use the [pagination](https://graphql.org/learn/pagination/) (a.k. Relay) specification for paging over entities like stations, +We use the [pagination](https://graphql.org/learn/pagination/) (a.k.a. Relay) specification for paging over entities like stations, stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. The route request has an OTP custom pagination feature - it is not finite and very complex. @@ -53,4 +53,4 @@ An input representing a single concept or thing should carry all relevant fields some of the values are common or have a natural default. Examples of such input types are `InputCoordinate(lat, long)` and `InputLinearFunction(constant, coefficient)`. Small input value-object types could be scalars, but if they have multiple fields using an input type -is a better match. +is a better match. This simplifies validation and default value injection. From 697faf28811779b59ecaa1dc83d52cecf065d2cf Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Fri, 17 Apr 2026 12:04:39 +0100 Subject: [PATCH 071/177] change station entrance test to be a module test --- .../module/osm/VertexGeneratorTest.java | 62 ---------- .../osm/moduletests/StationEntrancesTest.java | 110 ++++++++++++++++++ 2 files changed, 110 insertions(+), 62 deletions(-) create mode 100644 application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java index 686a29d85b6..a459b74bf71 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/VertexGeneratorTest.java @@ -16,16 +16,12 @@ import org.opentripplanner.graph_builder.issues.BarrierIntersectingHighway; import org.opentripplanner.graph_builder.issues.DifferentLevelsSharingBarrier; import org.opentripplanner.osm.model.OsmEntity; -import org.opentripplanner.osm.model.OsmMemberType; import org.opentripplanner.osm.model.OsmNode; -import org.opentripplanner.osm.model.OsmRelation; -import org.opentripplanner.osm.model.OsmRelationMember; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.street.model.vertex.BarrierPassThroughVertex; import org.opentripplanner.street.model.vertex.BarrierVertex; import org.opentripplanner.street.model.vertex.OsmVertex; -import org.opentripplanner.street.model.vertex.StationEntranceVertex; class VertexGeneratorTest { @@ -157,64 +153,6 @@ void testBarrierGenerator() { assertEquals(4, issues[1].node().getId()); } - @Test - void testEntranceInStopAreas() { - var entranceInStopArea = new OsmNode(0, 0); - entranceInStopArea.setId(1); - entranceInStopArea.addTag("entrance", "yes"); - - var entranceOutsideStopArea = new OsmNode(0, 0); - entranceOutsideStopArea.setId(2); - entranceOutsideStopArea.addTag("entrance", "yes"); - - var subwayEntranceOutsideStopArea = new OsmNode(0, 0); - subwayEntranceOutsideStopArea.setId(3); - subwayEntranceOutsideStopArea.addTag("railway", "subway_entrance"); - subwayEntranceOutsideStopArea.addTag("entrance", "yes"); - - var footway = new OsmWay(); - footway.addNodeRef(1); - footway.addNodeRef(2); - footway.addNodeRef(3); - footway.addTag("highway", "footway"); - - var stopArea = new OsmRelation(); - stopArea.addTag("type", "public_transport"); - stopArea.addTag("public_transport", "stop_area"); - - var member = new OsmRelationMember(); - member.setType(OsmMemberType.NODE); - member.setRef(1); - stopArea.addMember(member); - - var osmdb = new OsmDatabase(DataImportIssueStore.NOOP); - osmdb.addRelation(stopArea); - osmdb.doneFirstPhaseRelations(); - osmdb.addWay(footway); - osmdb.doneSecondPhaseWays(); - osmdb.addNode(entranceInStopArea); - osmdb.addNode(entranceOutsideStopArea); - osmdb.doneThirdPhaseNodes(); - osmdb.postLoad(); - - Graph graph = new Graph(); - - var subject = new VertexGenerator(osmdb, graph, Set.of(), true, DataImportIssueStore.NOOP); - - assertInstanceOf( - StationEntranceVertex.class, - subject.getVertexForOsmNode(entranceInStopArea, footway, NORMAL) - ); - assertFalse( - subject.getVertexForOsmNode(entranceOutsideStopArea, footway, NORMAL) instanceof - StationEntranceVertex - ); - assertInstanceOf( - StationEntranceVertex.class, - subject.getVertexForOsmNode(subwayEntranceOutsideStopArea, footway, NORMAL) - ); - } - static DifferentLevelsSharingBarrier[] getBarrierLevelIssues(DataImportIssueStore issueStore) { return issueStore .listIssues() diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java new file mode 100644 index 00000000000..22de5df9e05 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java @@ -0,0 +1,110 @@ +package org.opentripplanner.graph_builder.module.osm.moduletests; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.module.osm.OsmModuleTestFactory; +import org.opentripplanner.osm.TestOsmProvider; +import org.opentripplanner.osm.model.OsmMemberType; +import org.opentripplanner.osm.model.OsmNode; +import org.opentripplanner.osm.model.OsmRelation; +import org.opentripplanner.osm.model.OsmRelationMember; +import org.opentripplanner.osm.model.OsmWay; +import org.opentripplanner.street.graph.Graph; +import org.opentripplanner.street.model.vertex.OsmVertex; +import org.opentripplanner.street.model.vertex.StationEntranceVertex; + +public class StationEntrancesTest { + + private static final Graph GRAPH = new Graph(); + private static final OsmNode ENTRANCE_IN_STOP_AREA = new OsmNode(0, 0); + + static { + ENTRANCE_IN_STOP_AREA.setId(1); + ENTRANCE_IN_STOP_AREA.addTag("entrance", "yes"); + } + + private static final OsmNode ENTRANCE_OUTSIDE_STOP_AREA = new OsmNode(1, 1); + + static { + ENTRANCE_OUTSIDE_STOP_AREA.setId(2); + ENTRANCE_OUTSIDE_STOP_AREA.addTag("entrance", "yes"); + } + + private static final OsmNode SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA = new OsmNode(2, 2); + + static { + SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.setId(3); + SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.addTag("railway", "subway_entrance"); + SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.addTag("entrance", "yes"); + } + + private static final OsmWay FOOTWAY = new OsmWay(); + + static { + FOOTWAY.addNodeRef(1); + FOOTWAY.addNodeRef(2); + FOOTWAY.addNodeRef(3); + FOOTWAY.addTag("highway", "footway"); + } + + private static final OsmRelation STOP_AREA = new OsmRelation(); + + static { + STOP_AREA.addTag("type", "public_transport"); + STOP_AREA.addTag("public_transport", "stop_area"); + + var member = new OsmRelationMember(); + member.setType(OsmMemberType.NODE); + member.setRef(1); + STOP_AREA.addMember(member); + } + + @BeforeAll + public static void setUp() { + var osmProvider = new TestOsmProvider( + List.of(STOP_AREA), + List.of(FOOTWAY), + List.of(ENTRANCE_IN_STOP_AREA, ENTRANCE_OUTSIDE_STOP_AREA, SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) + ); + + OsmModuleTestFactory.of(osmProvider) + .withGraph(GRAPH) + .builder() + .withIncludeOsmStationEntrances(true) + .withIssueStore(DataImportIssueStore.NOOP) + .build() + .buildGraph(); + } + + @Test + void entranceInStopArea() { + assertInstanceOf(StationEntranceVertex.class, getVertexForOsmNode(ENTRANCE_IN_STOP_AREA)); + } + + @Test + void entranceOutsideStopArea() { + assertFalse(getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA) instanceof StationEntranceVertex); + } + + @Test + void stationEntranceOutsideStopArea() { + assertInstanceOf( + StationEntranceVertex.class, + getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) + ); + } + + private OsmVertex getVertexForOsmNode(OsmNode node) { + var vertices = GRAPH.getVerticesOfType(OsmVertex.class); + return vertices + .stream() + .filter(v -> v.nodeId() == node.getId()) + .findFirst() + .orElseThrow(); + } +} From 5c30223f052e5c8746dff366f822e45ca734847c Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Fri, 17 Apr 2026 15:56:21 +0200 Subject: [PATCH 072/177] refactor: Rename to AccessEgressFetcher and add simple javadoc --- ...ctFetchAccessEgress.java => AccessEgressFetcher.java} | 9 +++++++-- .../algorithm/raptoradapter/router/TransitRouter.java | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) rename application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/{AbstractFetchAccessEgress.java => AccessEgressFetcher.java} (94%) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java similarity index 94% rename from application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java rename to application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java index f20c7427c07..e65a631df89 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AbstractFetchAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java @@ -24,7 +24,12 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.street.model.StreetMode; -class AbstractFetchAccessEgress { +/** + * This class exposes methods for fetching access and egress legs for passing to a route request. + * An access or egress may be e.g. a walking path to the first transit stop on a route, + * but could also include other modes such as bicycle, shared mobility, flex or carpooling. + */ +class AccessEgressFetcher { private final RouteRequest request; private final OtpServerRequestContext serverContext; @@ -35,7 +40,7 @@ class AbstractFetchAccessEgress { private final TransitServiceResolver transitServiceResolver; private final CarpoolingService carpoolingService; - public AbstractFetchAccessEgress( + public AccessEgressFetcher( RouteRequest request, OtpServerRequestContext serverContext, ZonedDateTime transitSearchTimeZero, diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 0a40aa35a48..93fafd0603f 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -52,7 +52,7 @@ public class TransitRouter { private final AdditionalSearchDays additionalSearchDays; private final ViaCoordinateTransferFactory viaTransferResolver; private final LinkingContext linkingContext; - private final AbstractFetchAccessEgress fetchAccessEgress; + private final AccessEgressFetcher fetchAccessEgress; private TransitRouter( RouteRequest request, @@ -73,7 +73,7 @@ private TransitRouter( this.viaTransferResolver = serverContext.viaTransferResolver(); this.linkingContext = linkingContext; - this.fetchAccessEgress = new AbstractFetchAccessEgress( + this.fetchAccessEgress = new AccessEgressFetcher( request, serverContext, transitSearchTimeZero, From d2cda8b46de8fc70b25055de8ef41dded86ce218 Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Fri, 17 Apr 2026 16:03:54 +0200 Subject: [PATCH 073/177] docs: Add more extensive javadoc to AccessEgressFetcher constructor --- .../raptoradapter/router/AccessEgressFetcher.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java index e65a631df89..3ffb993482e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java @@ -40,6 +40,19 @@ class AccessEgressFetcher { private final TransitServiceResolver transitServiceResolver; private final CarpoolingService carpoolingService; + /** + * Creates an {@code AccessEgressFetcher} for a single route request. + * + * @param request the route request, used to derive access/egress preferences and + * street mode + * @param serverContext server-level context providing transit services, extension + * request contexts, and flex/ride-hailing parameters + * @param transitSearchTimeZero the point in time all times in seconds are counted from + * @param additionalSearchDays extra search days beyond the departure day, required for flex + * routing + * @param linkingContext context for linking origin/destination to the street network + * @param carpoolingService service used to resolve carpool access and egress legs + */ public AccessEgressFetcher( RouteRequest request, OtpServerRequestContext serverContext, From 197f992aa2e22719ed5e81527a24b43916d0df4f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 17 Apr 2026 12:42:38 +0200 Subject: [PATCH 074/177] Move warmup feature into its own top-level package Extract a WarmupParameters SPI interface in org.opentripplanner.warmup and have WarmupConfig implement it. Wiring is done by a new WarmupModule in org.opentripplanner.warmup.configure. --- .../standalone/config/RouterConfig.java | 9 ++- .../config/routerconfig/WarmupConfig.java | 54 +++++--------- .../configure/ConstructApplication.java | 7 +- .../ConstructApplicationFactory.java | 5 ++ .../warmup/GtfsWarmupQueryExecutor.java | 2 +- .../warmup/TransmodelWarmupQueryExecutor.java | 2 +- .../org/opentripplanner/warmup/WarmupApi.java | 25 +++++++ .../warmup/WarmupLauncher.java | 72 ++++++++++++++++++ .../warmup/WarmupParameters.java | 34 +++++++++ .../warmup/WarmupQueryExecutor.java | 2 +- .../configure => }/warmup/WarmupWorker.java | 73 +++++-------------- .../warmup/configure/WarmupModule.java | 39 ++++++++++ .../configure => }/warmup/package.md | 9 ++- .../warmup/WarmupQueryValidationTest.java | 2 +- 14 files changed, 229 insertions(+), 106 deletions(-) rename application/src/main/java/org/opentripplanner/{standalone/configure => }/warmup/GtfsWarmupQueryExecutor.java (98%) rename application/src/main/java/org/opentripplanner/{standalone/configure => }/warmup/TransmodelWarmupQueryExecutor.java (98%) create mode 100644 application/src/main/java/org/opentripplanner/warmup/WarmupApi.java create mode 100644 application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java create mode 100644 application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java rename application/src/main/java/org/opentripplanner/{standalone/configure => }/warmup/WarmupQueryExecutor.java (88%) rename application/src/main/java/org/opentripplanner/{standalone/configure => }/warmup/WarmupWorker.java (65%) create mode 100644 application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java rename application/src/main/java/org/opentripplanner/{standalone/configure => }/warmup/package.md (74%) rename application/src/test/java/org/opentripplanner/{standalone/configure => }/warmup/WarmupQueryValidationTest.java (97%) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java index e92b10849c9..ccd3e079ee4 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java @@ -28,6 +28,7 @@ import org.opentripplanner.standalone.config.sandbox.GtfsApiConfig; import org.opentripplanner.standalone.config.sandbox.TransmodelAPIConfig; import org.opentripplanner.updater.UpdatersParameters; +import org.opentripplanner.warmup.WarmupParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +61,7 @@ public class RouterConfig implements Serializable { private final VectorTileConfig vectorTileConfig; private final TriasApiParameters triasApiParameters; private final OjpApiParameters ojpApiParameters; - private final WarmupConfig warmupConfig; + private final WarmupParameters warmupParameters; public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this(new NodeAdapter(node, source), logUnusedParams); @@ -91,7 +92,7 @@ public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this.triasApiParameters = TriasApiConfig.mapParameters("triasApi", root); this.ojpApiParameters = OjpApiConfig.mapParameters("ojpApi", root); this.flexConfig = new FlexConfig(root, "flex"); - this.warmupConfig = WarmupConfig.mapWarmupConfig("warmup", root); + this.warmupParameters = WarmupConfig.mapWarmupConfig("warmup", root); if (logUnusedParams && LOG.isWarnEnabled()) { root.logAllWarnings(LOG::warn); @@ -163,8 +164,8 @@ public GtfsApiParameters gtfsApiParameters() { } @Nullable - public WarmupConfig warmupConfig() { - return warmupConfig; + public WarmupParameters warmupParameters() { + return warmupParameters; } public NodeAdapter asNodeAdapter() { diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java index 84ed5efa945..70cc1db4365 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -5,42 +5,19 @@ import java.util.List; import javax.annotation.Nullable; -import org.opentripplanner.core.model.doc.DocumentedEnum; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.model.StreetMode; +import org.opentripplanner.warmup.WarmupApi; +import org.opentripplanner.warmup.WarmupParameters; /** - * Configuration for application warmup during startup. When configured, OTP runs transit routing - * queries between the given locations during startup to warm up the application (JIT compilation, - * GraphQL schema caches, routing data structures, etc.) before production traffic arrives. - *

    - * Queries start after the Raptor transit data is created and stop when all updaters are - * primed (the health endpoint would return "UP"). If no updaters are configured, no warmup - * queries are run. + * Maps the {@code warmup} section of {@code router-config.json} into {@link WarmupParameters} + * consumed by the warmup module. + * + * @see WarmupParameters for documentation of parameters */ -public final class WarmupConfig { - - public enum Api implements DocumentedEnum { - TRANSMODEL("Use the TransModel GraphQL API for warmup queries."), - GTFS("Use the GTFS GraphQL API for warmup queries."); - - private final String description; - - Api(String description) { - this.description = description; - } - - @Override - public String typeDescription() { - return "Which GraphQL API to use for warmup queries."; - } - - @Override - public String enumValueDescription() { - return description; - } - } +public final class WarmupConfig implements WarmupParameters { private static final List DEFAULT_ACCESS_MODES = List.of( StreetMode.WALK, @@ -51,14 +28,14 @@ public String enumValueDescription() { StreetMode.WALK ); - private final Api api; + private final WarmupApi api; private final WgsCoordinate from; private final WgsCoordinate to; private final List accessModes; private final List egressModes; private WarmupConfig( - Api api, + WarmupApi api, WgsCoordinate from, WgsCoordinate to, List accessModes, @@ -93,12 +70,12 @@ are primed (the health endpoint would return "UP"). ) .asObject(); - Api api = c + WarmupApi api = c .of("api") .since(V2_10) .summary("Which GraphQL API to use for warmup queries.") - .description(docEnumValueList(Api.values())) - .asEnum(Api.TRANSMODEL); + .description(docEnumValueList(WarmupApi.values())) + .asEnum(WarmupApi.TRANSMODEL); var from = c.of("from").since(V2_10).summary("Origin location for warmup searches.").asObject(); double fromLat = from.of("lat").since(V2_10).summary("Latitude of the origin.").asDouble(); @@ -161,22 +138,27 @@ are primed (the health endpoint would return "UP"). ); } - public Api api() { + @Override + public WarmupApi api() { return api; } + @Override public WgsCoordinate from() { return from; } + @Override public WgsCoordinate to() { return to; } + @Override public List accessModes() { return accessModes; } + @Override public List egressModes() { return egressModes; } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 594d7734bbd..24ef92ae35d 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -34,7 +34,6 @@ import org.opentripplanner.standalone.config.DebugUiConfig; import org.opentripplanner.standalone.config.OtpConfig; import org.opentripplanner.standalone.config.RouterConfig; -import org.opentripplanner.standalone.configure.warmup.WarmupWorker; import org.opentripplanner.standalone.server.GrizzlyServer; import org.opentripplanner.standalone.server.OTPWebApplication; import org.opentripplanner.street.StreetRepository; @@ -214,11 +213,7 @@ private void setupTransitRoutingServer() { ); // Start application warmup — runs routing queries to warm up the application - WarmupWorker.start( - routerConfig().warmupConfig(), - () -> factory.createServerContext(), - () -> timetableRepository().getUpdaterManager() - ); + factory.warmupLauncher().start(); initEllipsoidToGeoidDifference(); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 26f082ccced..56f7abb6be2 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -67,6 +67,8 @@ import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.trip.TimetableSnapshotManager; import org.opentripplanner.visualizer.GraphVisualizer; +import org.opentripplanner.warmup.WarmupLauncher; +import org.opentripplanner.warmup.configure.WarmupModule; /** * A Factory used by the Dagger dependency injection system to create the components of OTP, which @@ -100,6 +102,7 @@ VehicleRentalRepositoryModule.class, VehicleRentalServiceModule.class, ViaModule.class, + WarmupModule.class, WorldEnvelopeServiceModule.class, } ) @@ -170,6 +173,8 @@ public interface ConstructApplicationFactory { DeduplicatorService deduplicatorService(); + WarmupLauncher warmupLauncher(); + @Component.Builder interface Builder { @BindsInstance diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java similarity index 98% rename from application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java rename to application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java index f92d927db47..8b57077f794 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/GtfsWarmupQueryExecutor.java +++ b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java @@ -1,4 +1,4 @@ -package org.opentripplanner.standalone.configure.warmup; +package org.opentripplanner.warmup; import graphql.ExecutionInput; import graphql.GraphQL; diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java similarity index 98% rename from application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java rename to application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java index b31781775a3..d51bf71195b 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/TransmodelWarmupQueryExecutor.java +++ b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java @@ -1,4 +1,4 @@ -package org.opentripplanner.standalone.configure.warmup; +package org.opentripplanner.warmup; import graphql.ExecutionInput; import graphql.GraphQL; diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupApi.java b/application/src/main/java/org/opentripplanner/warmup/WarmupApi.java new file mode 100644 index 00000000000..91eb1e70749 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupApi.java @@ -0,0 +1,25 @@ +package org.opentripplanner.warmup; + +import org.opentripplanner.core.model.doc.DocumentedEnum; + +/** Which GraphQL API to use for warmup queries. */ +public enum WarmupApi implements DocumentedEnum { + TRANSMODEL("Use the TransModel GraphQL API for warmup queries."), + GTFS("Use the GTFS GraphQL API for warmup queries."); + + private final String description; + + WarmupApi(String description) { + this.description = description; + } + + @Override + public String typeDescription() { + return "Which GraphQL API to use for warmup queries."; + } + + @Override + public String enumValueDescription() { + return description; + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java new file mode 100644 index 00000000000..5189955a382 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java @@ -0,0 +1,72 @@ +package org.opentripplanner.warmup; + +import javax.annotation.Nullable; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.transit.service.TimetableRepository; +import org.opentripplanner.updater.GraphUpdaterManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Launches the application warmup background thread. + *

    + * Injected dependencies come from the Dagger {@link + * org.opentripplanner.warmup.configure.WarmupModule}. The launcher decides whether a warmup run + * is applicable (parameters present, updaters present, selected API enabled) and, when it is, + * starts a daemon thread running a {@link WarmupWorker}. + */ +public class WarmupLauncher { + + private static final Logger LOG = LoggerFactory.getLogger(WarmupLauncher.class); + + @Nullable + private final WarmupParameters parameters; + + private final OtpServerRequestContext serverContext; + private final TimetableRepository timetableRepository; + + public WarmupLauncher( + @Nullable WarmupParameters parameters, + OtpServerRequestContext serverContext, + TimetableRepository timetableRepository + ) { + this.parameters = parameters; + this.serverContext = serverContext; + this.timetableRepository = timetableRepository; + } + + /** + * Start the application warmup thread if configured and applicable. + *

    + * No warmup is started if parameters are null (warmup section absent in router-config.json), + * if no updaters are configured (health probe would immediately return "UP"), or if the + * selected API schema is not available. + */ + public void start() { + if (parameters == null) { + return; + } + GraphUpdaterManager updaterManager = timetableRepository.getUpdaterManager(); + if (updaterManager == null) { + LOG.info("Application warmup configured but no updaters found. Skipping warmup."); + return; + } + var schema = switch (parameters.api()) { + case TRANSMODEL -> serverContext.transmodelSchema(); + case GTFS -> serverContext.gtfsSchema(); + }; + if (schema == null) { + LOG.warn( + "Application warmup configured for {} API, but the schema is not available. " + + "Is the corresponding API feature enabled?", + parameters.api() + ); + return; + } + var worker = new WarmupWorker(parameters, serverContext, () -> updaterManager); + var thread = new Thread(worker, "app-warmup"); + thread.setDaemon(true); + thread.start(); + LOG.info("Application warmup thread started."); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java new file mode 100644 index 00000000000..83e922d017b --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java @@ -0,0 +1,34 @@ +package org.opentripplanner.warmup; + +import java.util.List; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; + +/** + * Parameters for the application warmup feature. + *

    + * Produced by the JSON config mapper and consumed by {@link + * org.opentripplanner.warmup.configure.WarmupModule} to construct the warmup worker. + */ +public interface WarmupParameters { + /** Which GraphQL API to use for warmup queries. */ + WarmupApi api(); + + /** Origin location for warmup searches. */ + WgsCoordinate from(); + + /** Destination location for warmup searches. */ + WgsCoordinate to(); + + /** + * Ordered list of access modes cycled through by warmup queries. Paired with {@link + * #egressModes()} by index: entry {@code i} of each list forms a pair. + */ + List accessModes(); + + /** + * Ordered list of egress modes cycled through by warmup queries. Paired with {@link + * #accessModes()} by index. + */ + List egressModes(); +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java similarity index 88% rename from application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java rename to application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java index 4b762ba7873..59ba2d7ba4b 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryExecutor.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java @@ -1,4 +1,4 @@ -package org.opentripplanner.standalone.configure.warmup; +package org.opentripplanner.warmup; import org.opentripplanner.street.geometry.WgsCoordinate; diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java similarity index 65% rename from application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java rename to application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java index cd3366a5ced..08a0a04f326 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/WarmupWorker.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java @@ -1,11 +1,9 @@ -package org.opentripplanner.standalone.configure.warmup; +package org.opentripplanner.warmup; import java.time.Duration; import java.time.Instant; import java.util.function.Supplier; -import javax.annotation.Nullable; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.standalone.config.routerconfig.WarmupConfig; import org.opentripplanner.updater.GraphUpdaterStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,81 +21,43 @@ * It starts after Raptor transit data is created and stops when the health probe * reports "UP" (all updaters primed). */ -public class WarmupWorker implements Runnable { +class WarmupWorker implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(WarmupWorker.class); private static final int MAX_QUERIES = 20; - private final WarmupConfig config; + private final WarmupParameters parameters; private final WarmupQueryExecutor queryExecutor; private final Supplier updaterStatusProvider; WarmupWorker( - WarmupConfig config, + WarmupParameters parameters, OtpServerRequestContext serverContext, Supplier updaterStatusProvider ) { - this.config = config; + this.parameters = parameters; this.updaterStatusProvider = updaterStatusProvider; - this.queryExecutor = switch (config.api()) { + this.queryExecutor = switch (parameters.api()) { case TRANSMODEL -> new TransmodelWarmupQueryExecutor( serverContext, - config.accessModes(), - config.egressModes() + parameters.accessModes(), + parameters.egressModes() ); case GTFS -> new GtfsWarmupQueryExecutor( serverContext, - config.accessModes(), - config.egressModes() + parameters.accessModes(), + parameters.egressModes() ); }; } - /** - * Start the application warmup thread if configured and applicable. - *

    - * No warmup is started if the config is null (section absent in router-config.json) - * or if no updaters are configured (health probe would immediately return "UP"). - */ - public static void start( - @Nullable WarmupConfig config, - Supplier serverContextProvider, - Supplier updaterStatusProvider - ) { - if (config == null) { - return; - } - if (updaterStatusProvider.get() == null) { - LOG.info("Application warmup configured but no updaters found. Skipping warmup."); - return; - } - var serverContext = serverContextProvider.get(); - var schema = switch (config.api()) { - case TRANSMODEL -> serverContext.transmodelSchema(); - case GTFS -> serverContext.gtfsSchema(); - }; - if (schema == null) { - LOG.warn( - "Application warmup configured for {} API, but the schema is not available. " + - "Is the corresponding API feature enabled?", - config.api() - ); - return; - } - var worker = new WarmupWorker(config, serverContext, updaterStatusProvider); - var thread = new Thread(worker, "app-warmup"); - thread.setDaemon(true); - thread.start(); - LOG.info("Application warmup thread started."); - } - @Override public void run() { LOG.info( "Application warmup started. Sending {} GraphQL trip queries from {} to {}.", - config.api(), - config.from(), - config.to() + parameters.api(), + parameters.from(), + parameters.to() ); var startTime = Instant.now(); @@ -140,7 +100,12 @@ public void run() { private boolean executeQuery(int queryCount, boolean arriveBy, int modeIndex) { var queryStart = Instant.now(); try { - boolean success = queryExecutor.execute(config.from(), config.to(), arriveBy, modeIndex); + boolean success = queryExecutor.execute( + parameters.from(), + parameters.to(), + arriveBy, + modeIndex + ); var elapsed = Duration.between(queryStart, Instant.now()); LOG.info("Warmup query #{} completed in {} ms.", queryCount, elapsed.toMillis()); return success; diff --git a/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java new file mode 100644 index 00000000000..d31803b7381 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java @@ -0,0 +1,39 @@ +package org.opentripplanner.warmup.configure; + +import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; +import javax.annotation.Nullable; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.transit.service.TimetableRepository; +import org.opentripplanner.warmup.WarmupLauncher; +import org.opentripplanner.warmup.WarmupParameters; + +/** + * Dagger wiring for the application warmup feature. + *

    + * Provides the {@link WarmupParameters} binding (mapped from the JSON config section by {@code + * WarmupConfig}) and the {@link WarmupLauncher} that {@link + * org.opentripplanner.standalone.configure.ConstructApplication} uses to start the warmup thread + * after Raptor transit data and updaters have been set up. + */ +@Module +public class WarmupModule { + + @Provides + @Nullable + static WarmupParameters provideWarmupParameters(RouterConfig routerConfig) { + return routerConfig.warmupParameters(); + } + + @Provides + @Singleton + static WarmupLauncher provideWarmupLauncher( + @Nullable WarmupParameters parameters, + OtpServerRequestContext serverContext, + TimetableRepository timetableRepository + ) { + return new WarmupLauncher(parameters, serverContext, timetableRepository); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md b/application/src/main/java/org/opentripplanner/warmup/package.md similarity index 74% rename from application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md rename to application/src/main/java/org/opentripplanner/warmup/package.md index 239e603ccf1..73756f72136 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/warmup/package.md +++ b/application/src/main/java/org/opentripplanner/warmup/package.md @@ -6,8 +6,9 @@ arrives. ## Lifecycle -1. `WarmupWorker.start()` is called from `ConstructApplication` after Raptor transit data is - created and updaters are configured. +1. `ConstructApplication` obtains a `WarmupLauncher` from the Dagger factory + (`configure.WarmupModule` wires it) and calls `start()` after Raptor transit data is created + and updaters are configured. 2. A daemon thread sends sequential queries through the configured GraphQL API (TransModel or GTFS), exercising the full stack: GraphQL parsing, data fetchers, routing (Raptor + A*), itinerary filtering, and response serialization. @@ -16,6 +17,10 @@ arrives. ## Design +`WarmupParameters` is the SPI consumed by the module. `WarmupConfig` +(in `standalone.config.routerconfig`) implements this interface and maps the JSON config section +into parameter values. + `WarmupQueryExecutor` is the strategy interface with two implementations: - `TransmodelWarmupQueryExecutor` -- builds and executes TransModel `trip` queries. - `GtfsWarmupQueryExecutor` -- builds and executes GTFS `planConnection` queries. diff --git a/application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java b/application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java similarity index 97% rename from application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java rename to application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java index 6c4dc98dc2f..a6857db3b89 100644 --- a/application/src/test/java/org/opentripplanner/standalone/configure/warmup/WarmupQueryValidationTest.java +++ b/application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.standalone.configure.warmup; +package org.opentripplanner.warmup; import static org.junit.jupiter.api.Assertions.assertEquals; From 8f24b937abf0615e5775904c4044f66da97bb76a Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 17 Apr 2026 16:25:15 +0200 Subject: [PATCH 075/177] Make WarmupParameters a record produced by WarmupConfig Convert the WarmupParameters SPI from an interface implemented by WarmupConfig to a concrete record. WarmupConfig becomes a pure JSON-mapping utility whose mapWarmupConfig factory returns a WarmupParameters instance directly, matching the pattern used in the updater subsystem (e.g. PollingTripUpdaterParameters + PollingTripUpdaterConfig). This makes WarmupParameters instantiable in unit tests without going through the JSON config mapper. --- .../config/routerconfig/WarmupConfig.java | 57 ++----------------- .../warmup/WarmupParameters.java | 42 +++++++------- 2 files changed, 26 insertions(+), 73 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java index 70cc1db4365..cd3a59f5571 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -12,12 +12,10 @@ import org.opentripplanner.warmup.WarmupParameters; /** - * Maps the {@code warmup} section of {@code router-config.json} into {@link WarmupParameters} - * consumed by the warmup module. - * - * @see WarmupParameters for documentation of parameters + * Maps the {@code warmup} section of {@code router-config.json} into a {@link WarmupParameters} + * record consumed by the warmup module. */ -public final class WarmupConfig implements WarmupParameters { +public final class WarmupConfig { private static final List DEFAULT_ACCESS_MODES = List.of( StreetMode.WALK, @@ -28,28 +26,10 @@ public final class WarmupConfig implements WarmupParameters { StreetMode.WALK ); - private final WarmupApi api; - private final WgsCoordinate from; - private final WgsCoordinate to; - private final List accessModes; - private final List egressModes; - - private WarmupConfig( - WarmupApi api, - WgsCoordinate from, - WgsCoordinate to, - List accessModes, - List egressModes - ) { - this.api = api; - this.from = from; - this.to = to; - this.accessModes = accessModes; - this.egressModes = egressModes; - } + private WarmupConfig() {} @Nullable - public static WarmupConfig mapWarmupConfig(String parameterName, NodeAdapter root) { + public static WarmupParameters mapWarmupConfig(String parameterName, NodeAdapter root) { if (!root.exist(parameterName)) { return null; } @@ -129,7 +109,7 @@ are primed (the health endpoint would return "UP"). ); } - return new WarmupConfig( + return new WarmupParameters( api, new WgsCoordinate(fromLat, fromLng), new WgsCoordinate(toLat, toLng), @@ -137,29 +117,4 @@ are primed (the health endpoint would return "UP"). egressModes ); } - - @Override - public WarmupApi api() { - return api; - } - - @Override - public WgsCoordinate from() { - return from; - } - - @Override - public WgsCoordinate to() { - return to; - } - - @Override - public List accessModes() { - return accessModes; - } - - @Override - public List egressModes() { - return egressModes; - } } diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java index 83e922d017b..37846b72bde 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java @@ -7,28 +7,26 @@ /** * Parameters for the application warmup feature. *

    - * Produced by the JSON config mapper and consumed by {@link + * Produced by the JSON config mapper ({@code WarmupConfig}) and consumed by {@link * org.opentripplanner.warmup.configure.WarmupModule} to construct the warmup worker. + * + * @param api Which GraphQL API to use for warmup queries. + * @param from Origin location for warmup searches. + * @param to Destination location for warmup searches. + * @param accessModes Ordered list of access modes cycled through by warmup queries. Paired with + * {@link #egressModes()} by index: entry {@code i} of each list forms a pair. + * @param egressModes Ordered list of egress modes cycled through by warmup queries. Paired with + * {@link #accessModes()} by index. */ -public interface WarmupParameters { - /** Which GraphQL API to use for warmup queries. */ - WarmupApi api(); - - /** Origin location for warmup searches. */ - WgsCoordinate from(); - - /** Destination location for warmup searches. */ - WgsCoordinate to(); - - /** - * Ordered list of access modes cycled through by warmup queries. Paired with {@link - * #egressModes()} by index: entry {@code i} of each list forms a pair. - */ - List accessModes(); - - /** - * Ordered list of egress modes cycled through by warmup queries. Paired with {@link - * #accessModes()} by index. - */ - List egressModes(); +public record WarmupParameters( + WarmupApi api, + WgsCoordinate from, + WgsCoordinate to, + List accessModes, + List egressModes +) { + public WarmupParameters { + accessModes = List.copyOf(accessModes); + egressModes = List.copyOf(egressModes); + } } From 20709a0ac51e492dfaae6e81689705868c390eb6 Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Sun, 19 Apr 2026 18:21:10 +0200 Subject: [PATCH 076/177] docs: Apply PR feedback for AccessEgressFetcher --- .../raptoradapter/router/AccessEgressFetcher.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java index 81e42614d55..4674236d4ad 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java @@ -25,7 +25,7 @@ import org.opentripplanner.street.model.StreetMode; /** - * This class exposes methods for fetching access and egress legs for passing to a route request. + * This class exposes methods for fetching access and egress legs for a request. * An access or egress may be e.g. a walking path to the first transit stop on a route, * but could also include other modes such as bicycle, shared mobility, flex or carpooling. */ @@ -43,15 +43,10 @@ class AccessEgressFetcher { /** * Creates an {@code AccessEgressFetcher} for a single route request. * - * @param request the route request, used to derive access/egress preferences and - * street mode - * @param serverContext server-level context providing transit services, extension - * request contexts, and flex/ride-hailing parameters * @param transitSearchTimeZero the point in time all times in seconds are counted from * @param additionalSearchDays extra search days beyond the departure day, required for flex * routing * @param linkingContext context for linking origin/destination to the street network - * @param carpoolingService service used to resolve carpool access and egress legs */ public AccessEgressFetcher( RouteRequest request, From 49e315bc833a1e193eb549daa852c80da413598d Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 20 Apr 2026 07:47:58 +0000 Subject: [PATCH 077/177] Add changelog entry for #7534 [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 998addc9a59..c701f6de824 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -21,6 +21,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add replacement mode filter to the GTFS API's planConnection query [#7437](https://github.com/opentripplanner/OpenTripPlanner/pull/7437) - Fix default boarding/alighting rules for SIRI ET ExtraJourneys (#7506) [#7510](https://github.com/opentripplanner/OpenTripPlanner/pull/7510) - Reduce allocations when building linestrings [#7516](https://github.com/opentripplanner/OpenTripPlanner/pull/7516) +- Improve SpeedTest warm-up [#7534](https://github.com/opentripplanner/OpenTripPlanner/pull/7534) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 53b62360b5bc0ca332ebc40e439f6c3ef8895231 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 20 Apr 2026 11:16:48 +0200 Subject: [PATCH 078/177] Remove unused method --- .../ext/interactivelauncher/startup/OptionsView.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java index 3d52241b122..316ac2b8843 100644 --- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java +++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java @@ -34,7 +34,6 @@ class OptionsView { // Toggle [ ] save on/off buildStreetGraphChk.addActionListener(e -> onBuildGraphChkChanged()); buildTransitGraphChk.addActionListener(e -> onBuildGraphChkChanged()); - startOptServerChk.addActionListener(e -> onStartOptServerChkChanged()); //addSectionDoubleSpace(panel); bindCheckBoxesToModel(); @@ -90,6 +89,4 @@ private void onBuildGraphChkChanged() { saveGraphChk.setEnabled(buildStreet() || buildTransit()); startOptServerChk.setEnabled(buildTransit() || !buildStreet()); } - - private void onStartOptServerChkChanged() {} } From 2c6314ae93aa14e7c37d7d936ad7157042af226b Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 20 Apr 2026 12:26:44 +0200 Subject: [PATCH 079/177] review: Improve documentation and clarity in TripScheduleWithOffset and RaptorTripSchedule --- .../request/TripScheduleWithOffset.java | 8 +- .../raptor/spi/RaptorTripSchedule.java | 83 ++++++++++--------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java index b9f1a18ad2c..52449a25ea9 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java @@ -52,11 +52,9 @@ public int departure(int stopPosInPattern) { @Override public int relativeTravelDuration(int boardTime) { - // sortIndex is the arrival time at stop 0, which is fixed per trip. Since all trips in the - // same pattern have the same travel-time structure, (sortIndex - boardTime) satisfies both - // invariants of relativeTravelDuration: the difference between two boardings equals the actual - // transit time between the stops, and the value is identical across trips for the same - // boarding position. + // sortIndex is the arrival time at stop 0. This allow us to use it to compute the relative + // travel duration. It satisfies both invariants of relativeTravelDuration (see JavaDoc on this + // method). return sortIndex - boardTime; } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java index d62c4cc7ab6..fc290c9c0f2 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java @@ -8,7 +8,7 @@ * child of, and lives in the context of a trip pattern. *

    * The purpose of hiding these attributes behind an interface is to allow the implementation to - * chose the most efficient underlying implementation that suits its needs. + * choose the most efficient underlying representation that suits its needs. */ public interface RaptorTripSchedule { /** @@ -28,10 +28,10 @@ public interface RaptorTripSchedule { /** * Search for the arrival time for the given stopIndex. This is not optimized for performance. * - * @param startStopPos the stop position in pattern to start search(inclusive). - * @param stopIndex the stopIndex to find the arrival time for. + * @param startStopPos the stop position in pattern to start the search (inclusive). + * @param stopIndex the stop index to find the arrival time for. * @return the arrival time in seconds at the given stop - * @throws IndexOutOfBoundsException if stopIndex not found + * @throws IndexOutOfBoundsException if {@code stopIndex} is not found */ default int arrival(int startStopPos, int stopIndex) { return arrival(pattern().findStopPositionAfter(startStopPos, stopIndex)); @@ -41,38 +41,39 @@ default int arrival(int startStopPos, int stopIndex) { * The departure time at the given stop position in pattern. * * @param stopPosInPattern the stop position. - * @return the arrival time in seconds at the given stop + * @return the departure time in seconds at the given stop */ int departure(int stopPosInPattern); /** * Search for the departure time for the given stopIndex. This is not optimized for performance. * - * @param startStopPos the stop position in pattern to start the search(inclusive). - * @param stopIndex the stopIndex to find the departure time for. - * @return the arrival time in seconds at the given stop - * @throws IndexOutOfBoundsException if stopIndex not found + * @param startStopPos the stop position in pattern to start the search (inclusive). + * @param stopIndex the stop index to find the departure time for. + * @return the departure time in seconds at the given stop + * @throws IndexOutOfBoundsException if stopIndex is not found */ default int departure(int startStopPos, int stopIndex) { return departure(pattern().findStopPositionAfter(startStopPos, stopIndex)); } /// The relative-travel-duration is a proxy for time spent on transit from the boarding stop to - /// the last stop in the pattern. We do not know the alight-stop, so it is impossible to calculate - /// the "correct" time. But the only thing that matters is that the relative difference between - /// two boardings is correct. Compute a "relative-time" which can be used to compare the - /// travel-time cost for any two boardings in the same pattern. + /// the alight stop. We do not know the alight stop, so it is impossible to calculate the + /// "correct" time. The only thing that matters is that the relative difference between two + /// boardings is correct. Compute a relative-time that can be used to compare the travel-time + /// cost for any two boardings in the same pattern. /// /// Two invariants must hold: /// - /// - For two boardings at stops `i` and `j` on the same trip (where `i` comes before `j` in the - /// pattern), `relativeTravelDuration(boardAtI) - relativeTravelDuration(boardAtJ)` must equal - /// the actual transit duration from stop `i` to stop `j`. If the board time at position 3 is - /// 10:00 and at the next stop is 10:05, then the value at position 3 is larger by 5*60s = 300 - /// than at the next stop. - /// - All trips in the same pattern must return the same value for the last arrival stop. If the - /// value for one trip at the last stop is 56_000, then it must be 56_000 for the last stop in - /// all other trips in the same pattern. + /// - For two boardings at stop positions `i` and `j` on the same trip (where `i` comes before + /// `j` in the pattern), `relativeTravelDuration(boardAtI) - relativeTravelDuration(boardAtJ)` + /// must equal the actual transit duration from stop `i` to stop `j`. If the board time at + /// position 3 is 10:00 and at the next stop is 10:05, then the value at position 3 is larger + /// by 5*60s = 300 than at the next stop. + /// - All trips in the same pattern should return the same value for at least one stop. If you + /// choose to anchor the relative duration to the last stop, and the value for one trip at the + /// last stop is 56_000, then it must be 56_000 at the last stop for all other trips in the + /// same pattern. int relativeTravelDuration(int boardTime); /** @@ -81,17 +82,17 @@ default int departure(int startStopPos, int stopIndex) { RaptorTripPattern pattern(); /** - * Search for departure-stop-position for the given trip, earliest-departure-time and stop index. - * We need the time in addition to the stop in cases were the trip pattern visit the same stop - * twice. Also the time is not sufficient, since more than one stop could have the exact same - * departure time. + * Search for the arrival stop position for the given trip, latest arrival time, and stop index. + * We need the time in addition to the stop in cases where the trip pattern visits the same stop + * twice. Also, the time alone is not sufficient, since more than one stop could have the exact + * same arrival time. *

    - * Raptor save memory by NOT storing the board/arrival stop positions in pattern; Hence we need - * this method when mapping into a itinerary or raptor path. + * Raptor saves memory by NOT storing board/alight stop positions in the pattern; therefore, we + * need this method when mapping to an itinerary or Raptor path. *

    - * Avoid using this during routing, it is not optimized for performance. + * Avoid using this during routing, as it is not optimized for performance. * - * @return the stop-position in the trip pattern if found, if not -1 is returned. + * @return the stop position in the trip pattern if found; otherwise, -1 */ default int findArrivalStopPosition(int latestArrivalTime, int stop) { RaptorTripPattern p = pattern(); @@ -107,17 +108,17 @@ default int findArrivalStopPosition(int latestArrivalTime, int stop) { } /** - * Search for departure-stop-position for the given trip, earliest-departure-time and stop index. - * We need the time in addition to the stop in cases were the trip pattern visit the same stop - * twice. Also the time is not sufficient, since more than one stop could have the exact same - * departure time. + * Search for the departure stop position for the given trip, earliest departure time, and stop + * index. We need the time in addition to the stop in cases where the trip pattern visits the + * same stop twice. Also, the time alone is not sufficient, since more than one stop could have + * the exact same departure time. *

    - * Raptor save memory by NOT storing the board/arrival stop positions in pattern; Hence we need - * this method when mapping into a itinerary or raptor path. + * Raptor saves memory by NOT storing board/alight stop positions in the pattern; therefore, we + * need this method when mapping to an itinerary or Raptor path. *

    - * Avoid using this during routing, it is not optimized for performance. + * Avoid using this during routing, as it is not optimized for performance. * - * @return the stop-position in the trip pattern if found, if not -1 is returned. + * @return the stop position in the trip pattern if found; otherwise, -1 */ default int findDepartureStopPosition(int earliestDepartureTime, int stop) { var p = pattern(); @@ -134,10 +135,10 @@ default int findDepartureStopPosition(int earliestDepartureTime, int stop) { } /** - * Find all departure-stop-positions for a stop index after given earliest-departure-time. This is - * useful because trip can pass through the same stop more than once - if the stop pattern is circular. - * This method returns all stop positions, while the {@link #findDepartureStopPosition} only returns the first - * stop-position found. + * Find all departure stop positions for a stop index after the given earliest departure time. + * This is useful because a trip can pass through the same stop more than once if the stop pattern + * is circular. This method returns all stop positions, while + * {@link #findDepartureStopPosition} returns only the first stop position found. * * @return list of all valid stop positions for a given stop index */ From 3697c1cbc60d32d4ad05379aefae231eca15193b Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 20 Apr 2026 11:59:28 +0000 Subject: [PATCH 080/177] Add changelog entry for #7526 [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 c701f6de824..6fb93cb33fc 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -22,6 +22,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix default boarding/alighting rules for SIRI ET ExtraJourneys (#7506) [#7510](https://github.com/opentripplanner/OpenTripPlanner/pull/7510) - Reduce allocations when building linestrings [#7516](https://github.com/opentripplanner/OpenTripPlanner/pull/7516) - Improve SpeedTest warm-up [#7534](https://github.com/opentripplanner/OpenTripPlanner/pull/7534) +- Remove `GraphVisualizer` and inefficient methods in `ShortestPathTree` [#7526](https://github.com/opentripplanner/OpenTripPlanner/pull/7526) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 9b6c6725a76cea55779a62a9d141809f6e53eed1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 18 Apr 2026 08:51:47 +0200 Subject: [PATCH 081/177] Speed up tag lookup --- .../module/configure/GraphBuilderModules.java | 3 +- .../osm/DefaultOsmProvider.java | 30 ++++--------------- .../opentripplanner/osm/model/OsmEntity.java | 16 ++++++---- .../osm/model/TraverseDirection.java | 18 ++++++----- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 647f837194e..65d1b8d809e 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -84,8 +84,7 @@ static OsmModule provideOsmModule( osmConfiguredDataSource.dataSource(), osmConfiguredDataSource.config().osmTagMapper(), osmConfiguredDataSource.config().timeZone(), - config.osmCacheDataInMem, - issueStore + config.osmCacheDataInMem ) ); } diff --git a/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java b/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java index 2a514a8e400..382ac67dc6c 100644 --- a/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java +++ b/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java @@ -3,14 +3,12 @@ import crosby.binary.file.BlockInputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.time.ZoneId; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; import org.opentripplanner.framework.application.OtpFileNames; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.OsmDatabase; import org.opentripplanner.osm.tagmapping.OsmTagMapper; import org.opentripplanner.osm.tagmapping.OsmTagMapperSource; @@ -41,21 +39,14 @@ public class DefaultOsmProvider implements OsmProvider { /** For tests */ public DefaultOsmProvider(File file, boolean cacheDataInMem) { - this( - new FileDataSource(file, FileType.OSM), - OsmTagMapperSource.DEFAULT, - null, - cacheDataInMem, - DataImportIssueStore.NOOP - ); + this(new FileDataSource(file, FileType.OSM), OsmTagMapperSource.DEFAULT, null, cacheDataInMem); } public DefaultOsmProvider( DataSource dataSource, OsmTagMapperSource tagMapperSource, ZoneId zoneId, - boolean cacheDataInMem, - DataImportIssueStore issueStore + boolean cacheDataInMem ) { this.source = dataSource; this.zoneId = zoneId; @@ -102,21 +93,12 @@ private static InputStream track(OsmParserPhase phase, long size, InputStream in return ProgressTracker.track("Parse OSM " + phase, 1000, size, inputStream, m -> LOG.info(m)); } - private void parsePhase(OsmParser parser, OsmParserPhase phase) throws IOException { + private void parsePhase(OsmParser parser, OsmParserPhase phase) { parser.setPhase(phase); - BlockInputStream in = null; - try { - in = new BlockInputStream(createInputStream(phase), parser); + try (BlockInputStream in = new BlockInputStream(createInputStream(phase), parser)) { in.process(); - } finally { - // Close - try { - if (in != null) { - in.close(); - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - } + } catch (Exception e) { + LOG.error(e.getMessage(), e); } } diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index a5af1711504..59f4bfd4870 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -144,8 +144,9 @@ public abstract class OsmEntity { "bicycle", "vehicle" ); - public static final Set NO_ACCESS_TAGS = Set.of("no", "license", "dismount"); - public static final Map OSM_TAGS_FOR_TRAVERSAL_PERMISSION = + private static final Set WALK_ONLY_HIGHWAYS = Set.of("footway", "step", "corridor"); + private static final Set NO_ACCESS_TAGS = Set.of("no", "license", "dismount"); + private static final Map OSM_TAGS_FOR_TRAVERSAL_PERMISSION = Map.of( StreetTraversalPermission.CAR, "motorcar", @@ -341,7 +342,7 @@ protected boolean isExplicitlyAllowed(String key) { @Nullable public String getTag(String tag) { tag = tag.toLowerCase(); - if (tags != null && tags.containsKey(tag)) { + if (tags != null) { return tags.get(tag); } return null; @@ -516,7 +517,12 @@ public boolean isTag(String tag, String value) { * Takes a tag key and checks if the value is any of those in {@code oneOfTags}. */ public boolean isOneOfTags(String key, Set oneOfTags) { - return oneOfTags.stream().anyMatch(value -> isTag(key, value)); + var value = getTag(key); + if (value == null) { + return false; + } else { + return oneOfTags.contains(value); + } } /** @@ -658,7 +664,7 @@ public Optional isOneWay(@Nullable String mode) { return Optional.empty(); } - if ("foot".equals(mode) && !isOneOfTags("highway", Set.of("footway", "step", "corridor"))) { + if ("foot".equals(mode) && !isOneOfTags("highway", WALK_ONLY_HIGHWAYS)) { return Optional.empty(); } diff --git a/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java b/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java index c062e599375..6fbd16b7256 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java +++ b/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java @@ -10,22 +10,24 @@ public enum TraverseDirection { /** * Traverse in the direction the way is defined, from the beginning node to the end node. */ - FORWARD, + FORWARD(":forward"), /** * Traverse against the direction the way is defined, from the end node to the beginning node. */ - BACKWARD, + BACKWARD(":backward"), /** * Traverse not in a specific direction of the way, for example, across an area or through a node. */ - DIRECTIONLESS; + DIRECTIONLESS(""); - public String tagSuffix() { - if (this == DIRECTIONLESS) { - return ""; - } + private final String tagSuffix; + + TraverseDirection(String tagSuffix) { + this.tagSuffix = tagSuffix; + } - return ":" + name().toLowerCase(); + public String tagSuffix() { + return tagSuffix; } public TraverseDirection reverse() { From 4af353679199fbf870d3a8c2709a08cc224b1ebd Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Mon, 20 Apr 2026 14:57:38 +0200 Subject: [PATCH 082/177] Apply review feedback: DRY coordinate parsing and shorten WarmupParameters JavaDoc - Extract mapCoordinate() helper in WarmupConfig to avoid duplicating lat/lon reading between the from and to objects. - Replace per-field @param JavaDoc on WarmupParameters with a pointer to the configuration, which is the source of truth. --- .../config/routerconfig/WarmupConfig.java | 38 ++++++++++--------- .../warmup/WarmupParameters.java | 11 +----- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java index cd3a59f5571..41d8eaadd9f 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -57,17 +57,13 @@ are primed (the health endpoint would return "UP"). .description(docEnumValueList(WarmupApi.values())) .asEnum(WarmupApi.TRANSMODEL); - var from = c.of("from").since(V2_10).summary("Origin location for warmup searches.").asObject(); - double fromLat = from.of("lat").since(V2_10).summary("Latitude of the origin.").asDouble(); - double fromLng = from.of("lon").since(V2_10).summary("Longitude of the origin.").asDouble(); - - var to = c - .of("to") - .since(V2_10) - .summary("Destination location for warmup searches.") - .asObject(); - double toLat = to.of("lat").since(V2_10).summary("Latitude of the destination.").asDouble(); - double toLng = to.of("lon").since(V2_10).summary("Longitude of the destination.").asDouble(); + WgsCoordinate from = mapCoordinate(c, "from", "Origin location for warmup searches.", "origin"); + WgsCoordinate to = mapCoordinate( + c, + "to", + "Destination location for warmup searches.", + "destination" + ); var accessModeStrings = c .of("accessModes") @@ -109,12 +105,18 @@ are primed (the health endpoint would return "UP"). ); } - return new WarmupParameters( - api, - new WgsCoordinate(fromLat, fromLng), - new WgsCoordinate(toLat, toLng), - accessModes, - egressModes - ); + return new WarmupParameters(api, from, to, accessModes, egressModes); + } + + private static WgsCoordinate mapCoordinate( + NodeAdapter parent, + String name, + String summary, + String noun + ) { + var node = parent.of(name).since(V2_10).summary(summary).asObject(); + double lat = node.of("lat").since(V2_10).summary("Latitude of the " + noun + ".").asDouble(); + double lon = node.of("lon").since(V2_10).summary("Longitude of the " + noun + ".").asDouble(); + return new WgsCoordinate(lat, lon); } } diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java index 37846b72bde..63a550f53f9 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java @@ -7,16 +7,7 @@ /** * Parameters for the application warmup feature. *

    - * Produced by the JSON config mapper ({@code WarmupConfig}) and consumed by {@link - * org.opentripplanner.warmup.configure.WarmupModule} to construct the warmup worker. - * - * @param api Which GraphQL API to use for warmup queries. - * @param from Origin location for warmup searches. - * @param to Destination location for warmup searches. - * @param accessModes Ordered list of access modes cycled through by warmup queries. Paired with - * {@link #egressModes()} by index: entry {@code i} of each list forms a pair. - * @param egressModes Ordered list of egress modes cycled through by warmup queries. Paired with - * {@link #accessModes()} by index. + * See the configuration for documentation on the parameter fields. */ public record WarmupParameters( WarmupApi api, From 8daef5d7a7d4c1146438a56c99131e430855c7af Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 20 Apr 2026 13:03:04 +0000 Subject: [PATCH 083/177] Add changelog entry for #7504 [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 6fb93cb33fc..863b391d7e1 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -23,6 +23,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Reduce allocations when building linestrings [#7516](https://github.com/opentripplanner/OpenTripPlanner/pull/7516) - Improve SpeedTest warm-up [#7534](https://github.com/opentripplanner/OpenTripPlanner/pull/7534) - Remove `GraphVisualizer` and inefficient methods in `ShortestPathTree` [#7526](https://github.com/opentripplanner/OpenTripPlanner/pull/7526) +- Fix cost mismatch by anchoring pattern-ride pareto cost to last stop [#7504](https://github.com/opentripplanner/OpenTripPlanner/pull/7504) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 160d6188d06da2b6d6b90e26419918a3b732fa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 13 Apr 2026 11:00:20 +0200 Subject: [PATCH 084/177] removed StopLocation implementation from CarpoolStop --- .../ext/carpooling/CarpoolTripTestData.java | 22 ++-- .../model/CarpoolStopBuilderTest.java | 28 +---- .../ext/carpooling/model/CarpoolStop.java | 101 +----------------- .../carpooling/model/CarpoolStopBuilder.java | 60 +---------- .../carpooling/updater/CarpoolSiriMapper.java | 7 +- 5 files changed, 15 insertions(+), 203 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index d08b6098cb3..546fec8ee60 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -4,7 +4,6 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; @@ -16,8 +15,7 @@ */ public class CarpoolTripTestData { - private static final AtomicInteger ID_COUNTER = new AtomicInteger(0); - private static final AtomicInteger AREA_STOP_COUNTER = new AtomicInteger(0); + private static int idCounter = 0; private static final int DEFAULT_TOTAL_CAPACITY = CarpoolTrip.DEFAULT_TOTAL_CAPACITY; private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(10); @@ -59,7 +57,7 @@ public static CarpoolTrip createTripWithStops( for (int i = 0; i < intermediateStops.size(); i++) { CarpoolStop intermediate = intermediateStops.get(i); allStops.add( - CarpoolStop.of(intermediate.getId(), () -> intermediate.getIndex() + 1) + CarpoolStop.of(intermediate.getId()) .withCoordinate(intermediate.getCoordinate()) .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) .withAimedDepartureTime(intermediate.getAimedDepartureTime()) @@ -123,10 +121,7 @@ public static CarpoolStop createStopAt(int sequence, WgsCoordinate location) { * Creates a CarpoolStop with all parameters. */ public static CarpoolStop createStopAt(int sequence, int onboardCount, WgsCoordinate location) { - return CarpoolStop.of( - FeedScopedId.ofNullable("TEST", "area-" + AREA_STOP_COUNTER.incrementAndGet()), - AREA_STOP_COUNTER::getAndIncrement - ) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) .withSequenceNumber(sequence) .withOnboardCount(onboardCount) @@ -142,7 +137,7 @@ public static CarpoolStop createOriginStopWithTime( ZonedDateTime expectedDepartureTime, ZonedDateTime aimedDepartureTime ) { - return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-0"), () -> 0) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-0")) .withCoordinate(location) .withOnboardCount(1) .withExpectedDepartureTime(expectedDepartureTime) @@ -160,10 +155,7 @@ public static CarpoolStop createDestinationStopWithTime( ZonedDateTime expectedArrivalTime, ZonedDateTime aimedArrivalTime ) { - return CarpoolStop.of( - FeedScopedId.ofNullable("TEST", "area-" + AREA_STOP_COUNTER.incrementAndGet()), - AREA_STOP_COUNTER::getAndIncrement - ) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) .withSequenceNumber(sequenceNumber) .withOnboardCount(1) @@ -179,9 +171,7 @@ private static CarpoolTrip buildTrip( List stops ) { var actualStartTime = startTime != null ? startTime : ZonedDateTime.now(); - var builder = new CarpoolTripBuilder( - FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) - ) + var builder = new CarpoolTripBuilder(FeedScopedId.ofNullable("TEST", "trip-" + ++idCounter)) .withStops(stops) .withTotalCapacity(capacity) .withStartTime(actualStartTime) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java index d6314939f58..9455a32f579 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java @@ -6,9 +6,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; -import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; public class CarpoolStopBuilderTest { @@ -56,7 +54,7 @@ public class CarpoolStopBuilderTest { @Test void buildFromValues_usingWith_buildToCorrectValues() { - var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id"), () -> -1); + var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); builder .withSequenceNumber(1) .withOnboardCount(2) @@ -64,13 +62,9 @@ void buildFromValues_usingWith_buildToCorrectValues() { .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) - .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME) - .withName(new NonLocalizedString("name")) - .withDescription(new NonLocalizedString("description")) - .withUrl(new NonLocalizedString("http://url.value")); + .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME); var stop = builder.buildFromValues(); - assertEquals(-1, stop.getIndex()); assertEquals(1, stop.getSequenceNumber()); assertEquals(2, stop.getOnboardCount()); assertEquals(OSLO_NORTH, stop.getCoordinate()); @@ -78,18 +72,11 @@ void buildFromValues_usingWith_buildToCorrectValues() { assertEquals(EXPECTED_ARRIVAL_TIME, stop.getExpectedArrivalTime()); assertEquals(AIMED_DEPARTURE_TIME, stop.getAimedDepartureTime()); assertEquals(EXPECTED_DEPARTURE_TIME, stop.getExpectedDepartureTime()); - assertEquals("name", stop.getName().toString()); - assertEquals("description", stop.getDescription().toString()); - assertEquals("http://url.value", stop.getUrl().toString()); } @Test void buildFromValues_usingCarPoolStop_buildsCorrectValues() { - var i = new AtomicInteger(0); - var originalBuilder = new CarpoolStopBuilder( - new FeedScopedId("feed", "id"), - i::incrementAndGet - ); + var originalBuilder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); originalBuilder .withSequenceNumber(2) .withOnboardCount(3) @@ -97,16 +84,12 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) - .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME) - .withName(new NonLocalizedString("name value")) - .withDescription(new NonLocalizedString("description value")) - .withUrl(new NonLocalizedString("http://url.value")); + .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME); var original = originalBuilder.buildFromValues(); var copyBuilder = new CarpoolStopBuilder(original); var copy = copyBuilder.buildFromValues(); - assertEquals(1, copy.getIndex()); assertEquals(original.getSequenceNumber(), copy.getSequenceNumber()); assertEquals(original.getOnboardCount(), copy.getOnboardCount()); assertEquals(original.getCoordinate(), copy.getCoordinate()); @@ -114,8 +97,5 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { assertEquals(original.getExpectedArrivalTime(), copy.getExpectedArrivalTime()); assertEquals(original.getAimedDepartureTime(), copy.getAimedDepartureTime()); assertEquals(original.getExpectedDepartureTime(), copy.getExpectedDepartureTime()); - assertEquals(original.getName(), copy.getName()); - assertEquals(original.getDescription(), copy.getDescription()); - assertEquals(original.getUrl(), copy.getUrl()); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index 8e3b0447f07..aa11c75e109 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -2,34 +2,21 @@ import java.time.ZonedDateTime; import java.util.Objects; -import java.util.function.IntSupplier; import javax.annotation.Nullable; -import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.core.model.i18n.I18NString; -import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; -import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.model.site.StopType; /** * Represents a stop along a carpool trip route with occupancy and timing information. * Stops are ordered sequentially along the route. */ -public class CarpoolStop - extends AbstractTransitEntity - implements StopLocation { +public class CarpoolStop extends AbstractTransitEntity { /** Default onboard count per stop (1 = driver only) when no occupancy information is provided. */ public static final int DEFAULT_ONBOARD_COUNT = 1; - private final int index; - private final I18NString name; - private final I18NString description; - private final I18NString url; private final WgsCoordinate coordinate; - private final Geometry geometry; private final ZonedDateTime expectedArrivalTime; private final ZonedDateTime aimedArrivalTime; private final ZonedDateTime expectedDepartureTime; @@ -39,17 +26,7 @@ public class CarpoolStop public CarpoolStop(CarpoolStopBuilder builder) { super(builder.getId()); - this.index = builder.createIndex(); - // According to the spec, stop location names are optional for flex zones, so we set the ID as the dummy name. - if (builder.name() == null) { - this.name = new NonLocalizedString(builder.getId().toString()); - } else { - this.name = builder.name(); - } - this.description = builder.description(); - this.url = builder.url(); this.coordinate = Objects.requireNonNull(builder.coordinate()); - this.geometry = builder.geometry(); this.expectedArrivalTime = builder.expectedArrivalTime(); this.aimedArrivalTime = builder.aimedArrivalTime(); this.expectedDepartureTime = builder.expectedDepartureTime(); @@ -58,79 +35,18 @@ public CarpoolStop(CarpoolStopBuilder builder) { this.onboardCount = builder.onboardCount(); } - public static CarpoolStopBuilder of(FeedScopedId id, IntSupplier indexCounter) { - return new CarpoolStopBuilder(id, indexCounter); + public static CarpoolStopBuilder of(FeedScopedId id) { + return new CarpoolStopBuilder(id); } public static CarpoolStopBuilder of(CarpoolStop carpoolStop) { return new CarpoolStopBuilder(carpoolStop); } - // StopLocation interface implementation - delegate to the underlying AreaStop - - @Override - public int getIndex() { - return index; - } - - @Override - @Nullable - public I18NString getName() { - return name; - } - - @Override - @Nullable - public I18NString getDescription() { - return description; - } - - @Override - @Nullable - public I18NString getUrl() { - return url; - } - - @Override - public StopType getStopType() { - return StopType.REGULAR; - } - - @Override - @Nullable - public String getCode() { - return null; - } - - @Override - @Nullable - public String getPlatformCode() { - return null; - } - - @Override public WgsCoordinate getCoordinate() { return coordinate; } - @Override - @Nullable - public Geometry getGeometry() { - return geometry; - } - - @Override - public boolean isPartOfStation() { - return false; - } - - @Override - public boolean isPartOfSameStationAs(StopLocation alternativeStop) { - return false; - } - - // Carpool-specific methods - /** * @return The expected arrival time, or null if not applicable (e.g., origin stop) */ @@ -170,17 +86,6 @@ public ZonedDateTime getAimedDepartureTime() { return aimedDepartureTime; } - /** - * Returns the primary timing for this stop, preferring aimed arrival time. - * This provides backward compatibility for code that expects a single time value. - * - * @return The aimed arrival time if set, otherwise aimed departure time - */ - @Nullable - public ZonedDateTime getEstimatedTime() { - return aimedArrivalTime != null ? aimedArrivalTime : aimedDepartureTime; - } - /** * @return The number of passengers onboard (including the driver) when departing this stop */ diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index 307bc87a06a..95cd0cded70 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -1,11 +1,6 @@ package org.opentripplanner.ext.carpooling.model; import java.time.ZonedDateTime; -import java.util.Objects; -import java.util.function.IntSupplier; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryFactory; -import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; @@ -15,13 +10,7 @@ */ public class CarpoolStopBuilder extends AbstractEntityBuilder { - private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - private final IntSupplier indexCounter; - private I18NString name; - private I18NString description; - private I18NString url; private WgsCoordinate coordinate; - private Geometry geometry; private ZonedDateTime expectedArrivalTime; private ZonedDateTime aimedArrivalTime; @@ -30,20 +19,13 @@ public class CarpoolStopBuilder extends AbstractEntityBuilder CARPOOLING_DUMMY_INDEX) - .withName(I18NString.of(call.getStopPointNames().getFirst().getValue())) + return CarpoolStop.of(new FeedScopedId(FEED_ID, id)) .withCoordinate(centroid) .withAimedDepartureTime(isLast ? null : call.getAimedDepartureTime()) .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) From d427eeca6657b94397a3ba5f0686ad681e95f510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 13 Apr 2026 11:12:33 +0200 Subject: [PATCH 085/177] removed sequenceNumber from CarpoolStop --- .../ext/carpooling/CarpoolTripTestData.java | 28 ++++++++----------- .../DirectionalCompatibilityFilterTest.java | 10 +++---- .../filter/DistanceBasedFilterTest.java | 4 +-- .../model/CarpoolStopBuilderTest.java | 4 --- .../model/CarpoolTripBuilderTest.java | 2 +- .../model/CarpoolTripCapacityTest.java | 26 ++++++----------- .../routing/InsertionEvaluatorTest.java | 12 ++++---- .../routing/InsertionPositionFinderTest.java | 8 +++--- ...aultCarpoolingServiceAccessEgressTest.java | 5 ++-- .../DefaultCarpoolingServiceDirectTest.java | 10 +++---- .../opentripplanner/ext/carpooling/README.md | 1 - .../ext/carpooling/model/CarpoolStop.java | 9 ------ .../carpooling/model/CarpoolStopBuilder.java | 12 -------- .../ext/carpooling/model/CarpoolTrip.java | 8 +++--- .../carpooling/model/CarpoolTripBuilder.java | 13 --------- .../carpooling/updater/CarpoolSiriMapper.java | 6 ++-- 16 files changed, 52 insertions(+), 106 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 546fec8ee60..07ddf3ff479 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -24,7 +24,7 @@ public class CarpoolTripTestData { * Creates a simple trip with origin and destination stops. */ public static CarpoolTrip createSimpleTrip(WgsCoordinate boarding, WgsCoordinate alighting) { - var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting, 1)); + var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting)); return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, stops); } @@ -38,7 +38,7 @@ public static CarpoolTrip createSimpleTripWithTime( ) { var stops = List.of( createOriginStopWithTime(boarding, startTime, startTime), - createDestinationStopWithTime(alighting, 1, startTime.plusHours(1), startTime.plusHours(1)) + createDestinationStopWithTime(alighting, startTime.plusHours(1), startTime.plusHours(1)) ); return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, startTime, stops); } @@ -63,13 +63,12 @@ public static CarpoolTrip createTripWithStops( .withAimedDepartureTime(intermediate.getAimedDepartureTime()) .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) .withAimedArrivalTime(intermediate.getAimedArrivalTime()) - .withSequenceNumber(intermediate.getSequenceNumber() + 1) .withOnboardCount(intermediate.getOnboardCount()) .build() ); } - allStops.add(createDestinationStop(alighting, allStops.size())); + allStops.add(createDestinationStop(alighting)); return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, allStops); } @@ -88,7 +87,7 @@ public static CarpoolTrip createTripWithDeviationBudget( WgsCoordinate boarding, WgsCoordinate alighting ) { - var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting, 1)); + var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting)); return buildTrip(DEFAULT_TOTAL_CAPACITY, deviationBudget, null, stops); } @@ -104,26 +103,25 @@ public static CarpoolTrip createTripWithTime( } /** - * Creates a CarpoolStop with specified sequence (0-based) and onboard count. + * Creates a CarpoolStop with specified onboard count. */ - public static CarpoolStop createStop(int zeroBasedSequence, int onboardCount) { - return createStopAt(zeroBasedSequence, onboardCount, CarpoolTestCoordinates.OSLO_CENTER); + public static CarpoolStop createStop(int onboardCount) { + return createStopAt(onboardCount, CarpoolTestCoordinates.OSLO_CENTER); } /** * Creates a CarpoolStop at a specific location with onboardCount=1 (driver only). */ - public static CarpoolStop createStopAt(int sequence, WgsCoordinate location) { - return createStopAt(sequence, 1, location); + public static CarpoolStop createStopAt(WgsCoordinate location) { + return createStopAt(1, location); } /** * Creates a CarpoolStop with all parameters. */ - public static CarpoolStop createStopAt(int sequence, int onboardCount, WgsCoordinate location) { + public static CarpoolStop createStopAt(int onboardCount, WgsCoordinate location) { return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) - .withSequenceNumber(sequence) .withOnboardCount(onboardCount) .build(); } @@ -145,19 +143,17 @@ public static CarpoolStop createOriginStopWithTime( .build(); } - public static CarpoolStop createDestinationStop(WgsCoordinate location, int sequenceNumber) { - return createDestinationStopWithTime(location, sequenceNumber, null, null); + public static CarpoolStop createDestinationStop(WgsCoordinate location) { + return createDestinationStopWithTime(location, null, null); } public static CarpoolStop createDestinationStopWithTime( WgsCoordinate location, - int sequenceNumber, ZonedDateTime expectedArrivalTime, ZonedDateTime aimedArrivalTime ) { return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) - .withSequenceNumber(sequenceNumber) .withOnboardCount(1) .withExpectedArrivalTime(expectedArrivalTime) .withAimedArrivalTime(aimedArrivalTime) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java index d27b492490e..0216a4de21d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java @@ -57,8 +57,8 @@ void accepts_passengerOppositeDirection_returnsFalse() { @Test void accepts_tripAroundLake_passengerOnSegment_returnsTrue() { // Trip goes around a lake: North → East → South → West - var stop1 = createStopAt(0, LAKE_EAST); - var stop2 = createStopAt(1, LAKE_SOUTH); + var stop1 = createStopAt(LAKE_EAST); + var stop2 = createStopAt(LAKE_SOUTH); var trip = createTripWithStops(LAKE_NORTH, List.of(stop1, stop2), LAKE_WEST); // Passenger aligned with the southward segment (East → South) @@ -108,9 +108,9 @@ void accepts_passengerPerpendicularToTrip_returnsFalse() { void accepts_complexRoute_multipleSegments_findsCompatibleSegment() { // Trip with multiple segments going different directions // Go east first - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); // Then northeast - var stop2 = createStopAt(1, OSLO_NORTHEAST); + var stop2 = createStopAt(OSLO_NORTHEAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); // Passenger going northeast (aligns with second segment) @@ -122,7 +122,7 @@ void accepts_complexRoute_multipleSegments_findsCompatibleSegment() { @Test void accepts_tripWithSingleStop_checksAllSegments() { - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); // Passenger aligned with first segment (Center → East) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java index c2f8e43b4f8..ddcaf570ae4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java @@ -214,8 +214,8 @@ void accepts_horizontalRoute_passengerAlongRoute_returnsTrue() { @Test void accepts_tripWithMultipleStops_passengerNearAnySegment() { // Trip with multiple stops - filter checks ALL segments - var stop1 = createStopAt(0, LAKE_EAST); - var stop2 = createStopAt(1, LAKE_SOUTH); + var stop1 = createStopAt(LAKE_EAST); + var stop2 = createStopAt(LAKE_SOUTH); var trip = createTripWithStops(LAKE_NORTH, java.util.List.of(stop1, stop2), LAKE_WEST); // Passenger journey near the LAKE_SOUTH to LAKE_WEST segment diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java index 9455a32f579..a468587379c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java @@ -56,7 +56,6 @@ public class CarpoolStopBuilderTest { void buildFromValues_usingWith_buildToCorrectValues() { var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); builder - .withSequenceNumber(1) .withOnboardCount(2) .withCoordinate(OSLO_NORTH) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) @@ -65,7 +64,6 @@ void buildFromValues_usingWith_buildToCorrectValues() { .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME); var stop = builder.buildFromValues(); - assertEquals(1, stop.getSequenceNumber()); assertEquals(2, stop.getOnboardCount()); assertEquals(OSLO_NORTH, stop.getCoordinate()); assertEquals(AIMED_ARRIVAL_TIME, stop.getAimedArrivalTime()); @@ -78,7 +76,6 @@ void buildFromValues_usingWith_buildToCorrectValues() { void buildFromValues_usingCarPoolStop_buildsCorrectValues() { var originalBuilder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); originalBuilder - .withSequenceNumber(2) .withOnboardCount(3) .withCoordinate(OSLO_CENTER) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) @@ -90,7 +87,6 @@ void buildFromValues_usingCarPoolStop_buildsCorrectValues() { var copyBuilder = new CarpoolStopBuilder(original); var copy = copyBuilder.buildFromValues(); - assertEquals(original.getSequenceNumber(), copy.getSequenceNumber()); assertEquals(original.getOnboardCount(), copy.getOnboardCount()); assertEquals(original.getCoordinate(), copy.getCoordinate()); assertEquals(original.getAimedArrivalTime(), copy.getAimedArrivalTime()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java index f0ed3633501..dab358b4164 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java @@ -19,7 +19,7 @@ public class CarpoolTripBuilderTest { void buildFromValues_fromId_buildToCorrectValues() { var startTime = ZonedDateTime.now(); var endTime = ZonedDateTime.now().plusMinutes(45); - var stop = createStopAt(1, OSLO_EAST); + var stop = createStopAt(OSLO_EAST); var builder = new CarpoolTripBuilder(new FeedScopedId("feed", "id")); var trip = builder diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java index 25d8681785d..aadd58b84ab 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java @@ -39,11 +39,7 @@ void getPassengerCountAtDepartureOfStop_driverOnly() { @Test void getPassengerCountAtDepartureOfStop_withIntermediateStops() { // Stops: [Origin(1), A(2), B(1), Destination(1)] - var trip = createTripWithStops( - OSLO_CENTER, - List.of(createStop(0, 2), createStop(1, 1)), - OSLO_NORTH - ); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(2), createStop(1)), OSLO_NORTH); assertEquals(1, trip.getPassengerCountAtDepartureOfStop(0)); assertEquals(2, trip.getPassengerCountAtDepartureOfStop(1)); @@ -67,7 +63,7 @@ void getPassengerCountAtDepartureOfStop_indexTooLarge_throwsException() { @Test void adjacentPositions_onlyChecksStopBeforePickup() { // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); // Modified route: [Origin, Pickup, Dropoff, A, Destination] // 0 1 2 3 4 @@ -78,7 +74,7 @@ void adjacentPositions_onlyChecksStopBeforePickup() { @Test void adjacentPositions_fullAtStopBeforePickup() { // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); // Modified route: [Origin, A, Pickup, Dropoff, Destination] // 0 1 2 3 4 @@ -91,7 +87,7 @@ void widerGap_checksAllOriginalStopsBetweenPickupAndDropoff() { // Original stops: [Origin(1), A(2), B(4), C(1), Destination(1)] totalCapacity=5 var trip = createTripWithStops( OSLO_CENTER, - List.of(createStop(0, 2), createStop(1, 4), createStop(2, 1)), + List.of(createStop(2), createStop(4), createStop(1)), OSLO_NORTH ); @@ -106,11 +102,7 @@ void widerGap_checksAllOriginalStopsBetweenPickupAndDropoff() { void stopAfterDropoff_isNotChecked() { // Original stops: [Origin(1), A(1), B(5), Destination(1)] totalCapacity=5 // B is full, but the passenger is dropped off before B. - var trip = createTripWithStops( - OSLO_CENTER, - List.of(createStop(0, 1), createStop(1, 5)), - OSLO_NORTH - ); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(1), createStop(5)), OSLO_NORTH); // Modified route: [Origin, Pickup, A, Dropoff, B, Destination] // 0 1 2 3 4 5 @@ -121,7 +113,7 @@ void stopAfterDropoff_isNotChecked() { @Test void pickupNearEnd_limitedByStopBeforePickup() { // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=5 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 4)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(4)), OSLO_NORTH); // Modified route: [Origin, A, Pickup, Dropoff, Destination] // 0 1 2 3 4 @@ -133,7 +125,7 @@ void pickupNearEnd_limitedByStopBeforePickup() { @Test void fullRangeInsertion_checksAllOriginalStops() { // Original stops: [Origin(2), A(2), Destination(2)] totalCapacity=5 - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 2)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(2)), OSLO_NORTH); // Modified route: [Origin, Pickup, A, Dropoff, Destination] // 0 1 2 3 4 @@ -146,7 +138,7 @@ void fullRangeInsertion_checksAllOriginalStops() { void fullStopBeforePickup_notInCheckedRange() { // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 // A is full, but pickup and dropoff are inserted after A. - var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(0, 5)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); // Modified route: [Origin, A, Destination, Pickup, Dropoff] // 0 1 2 3 4 @@ -161,7 +153,7 @@ void bottleneckInMiddle_limitsCapacity() { // Original stops: [Origin(1), A(1), B(4), C(1), D(1), Destination(1)] totalCapacity=5 var trip = createTripWithStops( OSLO_CENTER, - List.of(createStop(0, 1), createStop(1, 4), createStop(2, 1), createStop(3, 1)), + List.of(createStop(1), createStop(4), createStop(1), createStop(1)), OSLO_NORTH ); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java index badc0b555bb..1c7ce6f1ba0 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java @@ -142,7 +142,7 @@ private WgsCoordinate getCoordinateBetween(WgsCoordinate coordinate1, WgsCoordin void findOptimalInsertion_onDeviationBudgetExceeded_returnsNull() { var trip = createTripWithStops( OSLO_SOUTH, - List.of(createStopAt(0, OSLO_CENTER), createStopAt(0, OSLO_NORTHEAST)), + List.of(createStopAt(OSLO_CENTER), createStopAt(OSLO_NORTHEAST)), OSLO_NORTH ); @@ -189,7 +189,7 @@ void findOptimalInsertion_oneValidPosition_returnsCandidate() { @Test void findOptimalInsertion_routingFails_skipsPosition() { // Use a trip with one stop to have multiple viable insertion positions - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); var mockPath = createGraphPath(Duration.ofMinutes(3)); @@ -241,8 +241,8 @@ void findOptimalInsertion_exceedsDeviationBudget_returnsNull() { @Test void findOptimalInsertion_tripWithStops_evaluatesAllPositions() { - var stop1 = createStopAt(0, OSLO_EAST); - var stop2 = createStopAt(1, OSLO_WEST); + var stop1 = createStopAt(OSLO_EAST); + var stop2 = createStopAt(OSLO_WEST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); var mockPath = createGraphPath(); @@ -399,7 +399,7 @@ void findOptimalInsertion_insertAtEnd_reusesMostSegments() { // Scenario: Trip A→B→C, insert passenger that allows some segment reuse // Expected: Segments that have matching endpoints should be REUSED - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); // Baseline has 2 segments: CENTER→EAST, EAST→NORTH @@ -444,7 +444,7 @@ void findOptimalInsertion_pickupAtExistingPoint_handlesCorrectly() { // Scenario: Trip A→B→C, passenger pickup at B (existing point), dropoff at new point // Expected: Segment A→B should be reused, B→dropoff and dropoff→C should be routed - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTHEAST); var mockPath = createGraphPath(Duration.ofMinutes(3)); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java index 0ae740b984b..b175950e9bc 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java @@ -62,7 +62,7 @@ void findViablePositions_incompatibleDirection_rejectsPosition() { @Test void findViablePositions_noCapacity_rejectsPosition() { // Create a trip with 0 available seats - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); + var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH)); var trip = createTripWithCapacity(0, stops); var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST); @@ -80,7 +80,7 @@ void findViablePositions_exceedsBeelineDelay_rejectsPosition() { new BeelineEstimator() ); - var trip = createTripWithStops(OSLO_CENTER, List.of(createStopAt(0, OSLO_EAST)), OSLO_NORTH); + var trip = createTripWithStops(OSLO_CENTER, List.of(createStopAt(OSLO_EAST)), OSLO_NORTH); // Try to insert passenger that would cause significant detour // Far from route @@ -96,8 +96,8 @@ void findViablePositions_exceedsBeelineDelay_rejectsPosition() { @Test void findViablePositions_multipleStops_checksAllCombinations() { - var stop1 = createStopAt(0, OSLO_EAST); - var stop2 = createStopAt(1, OSLO_WEST); + var stop1 = createStopAt(OSLO_EAST); + var stop2 = createStopAt(OSLO_WEST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java index a9fc0d84658..c29eae69219 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java @@ -502,11 +502,10 @@ void tripWithIntermediateStopsProducesResults() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(coordA, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( coordD, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 226b0662c85..ffbb51720ef 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -316,11 +316,10 @@ void tripWithIntermediateStopsProducesResults() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(tripStart, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( tripEnd, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) @@ -348,11 +347,10 @@ void routeFollowsIntermediateStopsInsteadOfDirectPath() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(tripStart, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( tripEnd, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index e7d12608f21..5994d55c17b 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -304,7 +304,6 @@ Represents a driver's journey offering carpool seats: Waypoint along a carpool route: - **coordinate**: Geographic location -- **sequenceNumber**: Order in route (0-indexed) - **estimatedArrivalTime**: When driver expects to arrive - **onboardCount**: Number of passengers onboard (including the driver) when departing this stop diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index aa11c75e109..957ba780e45 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -21,7 +21,6 @@ public class CarpoolStop extends AbstractTransitEntity * Stops include both the driver's originally planned stops and any dynamically added stops - * for passenger pickups and dropoffs. The list is ordered by sequence number, representing - * the order in which stops are visited along the route. + * for passenger pickups and dropoffs. The list is ordered by visit order along the route: + * the first element is the origin and the last is the destination. * - * @return an immutable list of stops along the carpool route, ordered by sequence number, - * never null but may be empty for trips with no intermediate stops + * @return an immutable list of stops along the carpool route, in visit order; never null, + * and always contains at least the origin and destination */ public List stops() { return stops; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java index d7dd1db9485..c5c9f4c7d45 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; @@ -84,18 +83,6 @@ public CarpoolTripBuilder withStops(List stops) { return this; } - public CarpoolTripBuilder addStop(CarpoolStop stop) { - this.stops.add(stop); - // Sort stops by sequence number to maintain order - this.stops.sort(Comparator.comparingInt(CarpoolStop::getSequenceNumber)); - return this; - } - - public CarpoolTripBuilder clearStops() { - this.stops.clear(); - return this; - } - public List stops() { return stops; } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 7d05b1990ca..4325359c90a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -90,7 +90,7 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { * * @param call The SIRI EstimatedCall containing stop information * @param tripId The trip ID for generating unique stop IDs - * @param sequenceNumber The 0-based sequence number of this stop + * @param stopIndex The 0-based index of this stop in the call list * @param isFirst true if this is the first stop (origin) * @param isLast true if this is the last stop (destination) * @return A CarpoolStop representing the stop @@ -98,7 +98,7 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { private CarpoolStop buildCarpoolStopForPosition( EstimatedCall call, String tripId, - int sequenceNumber, + int stopIndex, boolean isFirst, boolean isLast ) { @@ -106,7 +106,7 @@ private CarpoolStop buildCarpoolStopForPosition( ? tripId + "_trip_origin" : isLast ? tripId + "_trip_destination" - : tripId + "_stop_" + sequenceNumber; + : tripId + "_stop_" + stopIndex; return toCarpoolStop(call, stopId, tripId, isFirst, isLast); } From 7bec9497bb078a0b6110f90b67ac5c93e07057c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Thu, 9 Apr 2026 13:28:46 +0200 Subject: [PATCH 086/177] deviation budget of carpoolingTrip is now per stop instead of per trip --- .../ext/carpooling/CarpoolTripTestData.java | 98 +++- .../PassengerDelayConstraintsTest.java | 441 +++++++++--------- .../model/CarpoolTripBuilderTest.java | 5 - .../routing/InsertionCandidateTest.java | 67 --- .../routing/InsertionEvaluatorTest.java | 71 +-- .../routing/InsertionPositionFinderTest.java | 40 +- .../PassengerDelayConstraints.java | 77 +-- .../ext/carpooling/model/CarpoolStop.java | 20 + .../carpooling/model/CarpoolStopBuilder.java | 23 + .../ext/carpooling/model/CarpoolTrip.java | 12 - .../carpooling/model/CarpoolTripBuilder.java | 12 - .../routing/InsertionCandidate.java | 7 - .../routing/InsertionEvaluator.java | 29 +- .../routing/InsertionPositionFinder.java | 65 ++- .../service/DefaultCarpoolingService.java | 7 +- .../carpooling/updater/CarpoolSiriMapper.java | 4 +- 16 files changed, 442 insertions(+), 536 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 07ddf3ff479..11674add0d3 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -25,7 +25,7 @@ public class CarpoolTripTestData { */ public static CarpoolTrip createSimpleTrip(WgsCoordinate boarding, WgsCoordinate alighting) { var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting)); - return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, stops); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, stops); } /** @@ -40,7 +40,7 @@ public static CarpoolTrip createSimpleTripWithTime( createOriginStopWithTime(boarding, startTime, startTime), createDestinationStopWithTime(alighting, startTime.plusHours(1), startTime.plusHours(1)) ); - return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, startTime, stops); + return buildTrip(DEFAULT_TOTAL_CAPACITY, startTime, stops); } /** @@ -64,31 +64,65 @@ public static CarpoolTrip createTripWithStops( .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) .withAimedArrivalTime(intermediate.getAimedArrivalTime()) .withOnboardCount(intermediate.getOnboardCount()) + .withDeviationBudget(intermediate.getDeviationBudget()) .build() ); } allStops.add(createDestinationStop(alighting)); - return buildTrip(DEFAULT_TOTAL_CAPACITY, DEFAULT_DEVIATION_BUDGET, null, allStops); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, allStops); + } + + /** + * Creates a trip with origin, intermediate stops, and destination. The deviation budget is applied + * to the origin and destination stops, while intermediate stops retain their own deviation budget. + */ + public static CarpoolTrip createTripWithStops( + WgsCoordinate boarding, + List intermediateStops, + WgsCoordinate alighting, + Duration deviationBudget + ) { + List allStops = new ArrayList<>(); + allStops.add(createOriginStopWithDeviationBudget(boarding, deviationBudget)); + + for (int i = 0; i < intermediateStops.size(); i++) { + CarpoolStop intermediate = intermediateStops.get(i); + allStops.add( + CarpoolStop.of(intermediate.getId()) + .withCoordinate(intermediate.getCoordinate()) + .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) + .withAimedDepartureTime(intermediate.getAimedDepartureTime()) + .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) + .withAimedArrivalTime(intermediate.getAimedArrivalTime()) + .withOnboardCount(intermediate.getOnboardCount()) + .withDeviationBudget(intermediate.getDeviationBudget()) + .build() + ); + } + + allStops.add(createDestinationStopWithDeviationBudget(alighting, deviationBudget)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, allStops); } /** * Creates a trip with specified capacity and all stops (including origin/destination). */ public static CarpoolTrip createTripWithCapacity(int capacity, List stops) { - return buildTrip(capacity, DEFAULT_DEVIATION_BUDGET, null, stops); + return buildTrip(capacity, null, stops); } /** - * Creates a trip with specified deviation budget. + * Creates a trip with specified deviation budget on all stops. */ public static CarpoolTrip createTripWithDeviationBudget( Duration deviationBudget, WgsCoordinate boarding, WgsCoordinate alighting ) { - var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting)); - return buildTrip(DEFAULT_TOTAL_CAPACITY, deviationBudget, null, stops); + var origin = createOriginStopWithDeviationBudget(boarding, deviationBudget); + var destination = createDestinationStopWithDeviationBudget(alighting, deviationBudget); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, List.of(origin, destination)); } /** @@ -99,7 +133,7 @@ public static CarpoolTrip createTripWithTime( int capacity, List stops ) { - return buildTrip(capacity, DEFAULT_DEVIATION_BUDGET, startTime, stops); + return buildTrip(capacity, startTime, stops); } /** @@ -116,6 +150,17 @@ public static CarpoolStop createStopAt(WgsCoordinate location) { return createStopAt(1, location); } + /** + * Creates a CarpoolStop at a specific location with a specific deviation budget. + */ + public static CarpoolStop createStopAt(WgsCoordinate location, Duration deviationBudget) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) + .build(); + } + /** * Creates a CarpoolStop with all parameters. */ @@ -123,6 +168,7 @@ public static CarpoolStop createStopAt(int onboardCount, WgsCoordinate location) return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) .withOnboardCount(onboardCount) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .build(); } @@ -135,11 +181,26 @@ public static CarpoolStop createOriginStopWithTime( ZonedDateTime expectedDepartureTime, ZonedDateTime aimedDepartureTime ) { - return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-0")) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) .withOnboardCount(1) .withExpectedDepartureTime(expectedDepartureTime) .withAimedDepartureTime(aimedDepartureTime) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) + .build(); + } + + /** + * Creates an origin stop with specific deviation budget. + */ + public static CarpoolStop createOriginStopWithDeviationBudget( + WgsCoordinate location, + Duration deviationBudget + ) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) .build(); } @@ -157,12 +218,26 @@ public static CarpoolStop createDestinationStopWithTime( .withOnboardCount(1) .withExpectedArrivalTime(expectedArrivalTime) .withAimedArrivalTime(aimedArrivalTime) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) + .build(); + } + + /** + * Creates a destination stop with specific deviation budget. + */ + public static CarpoolStop createDestinationStopWithDeviationBudget( + WgsCoordinate location, + Duration deviationBudget + ) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) .build(); } private static CarpoolTrip buildTrip( int capacity, - Duration deviationBudget, ZonedDateTime startTime, List stops ) { @@ -170,8 +245,7 @@ private static CarpoolTrip buildTrip( var builder = new CarpoolTripBuilder(FeedScopedId.ofNullable("TEST", "trip-" + ++idCounter)) .withStops(stops) .withTotalCapacity(capacity) - .withStartTime(actualStartTime) - .withDeviationBudget(deviationBudget); + .withStartTime(actualStartTime); if (startTime != null) { builder.withEndTime(startTime.plusHours(1)); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java index 8364132d2e5..4cf3cb3b145 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java @@ -1,140 +1,164 @@ package org.opentripplanner.ext.carpooling.constraints; -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 static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateCumulativeDurations; import java.time.Duration; -import org.junit.jupiter.api.BeforeEach; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.model.GraphPath; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder; +import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; class PassengerDelayConstraintsTest { - private PassengerDelayConstraints constraints; + private static final AtomicInteger STOP_COUNTER = new AtomicInteger(0); + private static final Duration FIVE_MINUTES = Duration.ofMinutes(5); - @BeforeEach - void setup() { - constraints = new PassengerDelayConstraints(); + private static CarpoolStop stopWithBudget(Duration budget) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "stop-" + STOP_COUNTER.incrementAndGet())) + .withCoordinate(new org.opentripplanner.street.geometry.WgsCoordinate(59.9, 10.7)) + .withDeviationBudget(budget) + .build(); } @Test - void satisfiesConstraints_noExistingStops_alwaysAccepts() { - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10) }; + void satisfiesConstraints_delayWellUnderBudget_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(5), Duration.ofMinutes(15) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route with passenger inserted + // Stop1 delay: 7min - 5min = 2min (within 5min budget) + // Destination delay: 17min - 15min = 2min (within 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), }; - // Should accept - no existing passengers to protect assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 2 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayWellUnderThreshold_accepts() { - // Original timings: 0min -> 5min -> 15min - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(5), Duration.ofMinutes(15) }; + void satisfiesConstraints_delayExactlyAtBudget_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route: boarding -> pickup -> stop1 -> dropoff -> alighting - // Timings: 0min -> 3min -> 7min -> 12min -> 17min - // Stop1 delay: 7min - 5min = 2min (well under 5min threshold) + // Stop1 delay: 15min - 10min = 5min (exactly at 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayExactlyAtThreshold_accepts() { - // Original route with one stop + void satisfiesConstraints_delayOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is delayed by exactly 5 minutes - // Timings: 0min -> 5min -> 15min -> 20min -> 25min - // Stop1 delay: 15min - 10min = 5min (exactly at threshold) + // Stop1 delay: 16min - 10min = 6min (exceeds 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayOverThreshold_rejects() { - // Original route with one stop + void satisfiesConstraints_destinationOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(Duration.ofMinutes(20)), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is delayed by 6 minutes (over 5min threshold) - // Timings: 0min -> 5min -> 16min -> 21min -> 26min - // Stop1 delay: 16min - 10min = 6min (exceeds threshold) + // Stop1 delay: 12min - 10min = 2min (within 20min budget) + // Destination delay: 27min - 20min = 7min (exceeds 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), }; assertFalse( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_multipleStops_oneOverThreshold_rejects() { - // Original route: boarding -> stop1 -> stop2 -> alighting + void satisfiesConstraints_multipleStops_oneOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is ok (3min delay) but stop2 exceeds (7min delay) - // Timings: 0min -> 5min -> 13min -> 18min -> 27min -> 32min - // Stop1 delay: 13min - 10min = 3min ✓ - // Stop2 delay: 27min - 20min = 7min ✗ + // Stop1 delay: 13min - 10min = 3min ok + // Stop2 delay: 27min - 20min = 7min exceeds GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), @@ -144,29 +168,31 @@ void satisfiesConstraints_multipleStops_oneOverThreshold_rejects() { }; assertFalse( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_multipleStops_allUnderThreshold_accepts() { - // Original route: boarding -> stop1 -> stop2 -> alighting + void satisfiesConstraints_multipleStops_allUnderBudget_accepts() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where both stops have acceptable delays - // Timings: 0min -> 5min -> 12min -> 17min -> 24min -> 34min - // Stop1 delay: 12min - 10min = 2min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), @@ -176,294 +202,267 @@ void satisfiesConstraints_multipleStops_allUnderThreshold_accepts() { }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; + void satisfiesConstraints_differentBudgetsPerStop() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + // Stop 1 has 2min budget, destination has 10min budget + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(Duration.ofMinutes(2)), + stopWithBudget(Duration.ofMinutes(10)) + ); - // Passenger inserted at very beginning (pickup at 1, dropoff at 2) - // Modified: boarding -> pickup -> dropoff -> stop1 -> stop2 -> alighting - // Mapping: stop1 (orig 1) -> mod 3, stop2 (orig 2) -> mod 4 - // Timings: 0min -> 3min -> 5min -> 13min -> 24min -> 34min - // Stop1 delay: 13min - 10min = 3min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ + // Stop1 delay: 13min - 10min = 3min (exceeds 2min budget) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 2 + 3, + stops ) ); } @Test - void satisfiesConstraints_passengerAfterAllStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; + void satisfiesConstraints_noDelay_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Passenger inserted at very end (pickup at 3, dropoff at 4) - // Modified: boarding -> stop1 -> stop2 -> pickup -> dropoff -> alighting - // Mapping: stop1 (orig 1) -> mod 1, stop2 (orig 2) -> mod 2 - // Even though passenger comes after, routing to pickup might cause delays - // Timings: 0min -> 11min -> 22min -> 27min -> 30min -> 40min - // Stop1 delay: 11min - 10min = 1min ✓ - // Stop2 delay: 22min - 20min = 2min ✓ GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), + 1, 3, - 4 + stops ) ); } @Test - void satisfiesConstraints_passengerBetweenStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; - - // Passenger inserted between stops (pickup at 2, dropoff at 3) - // Modified: boarding -> stop1 -> pickup -> dropoff -> stop2 -> alighting - // Mapping: stop1 (orig 1) -> mod 1, stop2 (orig 2) -> mod 4 - // Timings: 0min -> 11min -> 14min -> 17min -> 24min -> 34min - // Stop1 delay: 11min - 10min = 1min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ - GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - }; - - assertTrue( - constraints.satisfiesConstraints( - originalTimes, - calculateCumulativeDurations(modifiedSegments), - 2, - 3 - ) - ); - } - - @Test - void customMaxDelay_acceptsWithinCustomThreshold() { - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(10)); - + void satisfiesConstraints_zeroBudget_rejectsAnyDelay() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO) + ); - // Stop1 delayed by 8 minutes (within 10min custom threshold) + // Stop1 delay: 10min + 1s - 10min = 1s (exceeds zero budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(13)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5).plusSeconds(1)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - customConstraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_rejectsOverCustomThreshold() { - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(2)); - - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + void satisfiesConstraints_zeroBudget_noDelay_accepts() { + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO) + ); - // Stop1 delayed by 3 minutes (over 2min custom threshold) + // Use the same GraphPaths to derive both original and modified times + // so there is truly zero delay (avoids rounding from GraphPath construction) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; + Duration[] cumulativeDurations = calculateCumulativeDurations(modifiedSegments); - assertFalse( - customConstraints.satisfiesConstraints( + // originalTimes = modified times at the original stop positions + // With pickup=1, dropoff=3: original indices [0,1,2] map to modified [0,2,4] + Duration[] originalTimes = { + cumulativeDurations[0], + cumulativeDurations[2], + cumulativeDurations[4], + }; + + assertTrue( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + cumulativeDurations, 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_zeroTolerance_rejectsAnyDelay() { - var strictConstraints = new PassengerDelayConstraints(Duration.ZERO); - + void satisfiesConstraints_largeBudget_acceptsLargeDelay() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ofHours(1)), + stopWithBudget(Duration.ofHours(1)) + ); - // Stop1 delayed by even 1 second + // Stop1 delay: 40min - 10min = 30min (within 60min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5).plusSeconds(1)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(35)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertFalse( - strictConstraints.satisfiesConstraints( + assertTrue( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_veryPermissive_acceptsLargeDelays() { - var permissiveConstraints = new PassengerDelayConstraints(Duration.ofHours(1)); - + void satisfiesConstraints_tightAndPermissiveStops_respectsEachBudget() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + // Stop 1 is strict (3min), destination is permissive (30min) + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ofMinutes(3)), + stopWithBudget(Duration.ofMinutes(30)) + ); - // Stop1 delayed by 30 minutes (well within 1 hour threshold) + // Stop1 delay: 12min - 10min = 2min (within 3min budget, ok) + // Destination delay: 47min - 20min = 27min (within 30min budget, ok) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(35)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(30)), }; assertTrue( - permissiveConstraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 3, + stops ) ); } @Test - void getMaxDelay_returnsConfiguredValue() { - assertEquals(Duration.ofMinutes(5), constraints.getMaxDelay()); - - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(10)); - assertEquals(Duration.ofMinutes(10), customConstraints.getMaxDelay()); - } - - @Test - void defaultMaxDelay_isFiveMinutes() { - assertEquals(Duration.ofMinutes(5), PassengerDelayConstraints.DEFAULT_MAX_DELAY); - } - - @Test - void constructor_negativeDelay_throwsException() { - assertThrows(IllegalArgumentException.class, () -> - new PassengerDelayConstraints(Duration.ofMinutes(-1)) + void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { + Duration[] originalTimes = { + Duration.ZERO, + Duration.ofMinutes(10), + Duration.ofMinutes(20), + Duration.ofMinutes(30), + }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) ); - } - @Test - void satisfiesConstraints_noDelay_accepts() { - // Route where insertion doesn't add any delay - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; - - // Modified route where stop1 arrives at exactly the same time - // (perfect routing somehow) + // Passenger inserted at very beginning (pickup at 1, dropoff at 2) + // Stop1 delay: 13min - 10min = 3min ok + // Stop2 delay: 24min - 20min = 4min ok + // Destination delay: 36min - 30min = 6min exceeds 5min budget GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(12)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), 1, - 3 + 2, + stops ) ); } @Test - void satisfiesConstraints_tripWithManyStops_checksAll() { - // Original route with 5 stops + void satisfiesConstraints_passengerBetweenStops_checksAllStops() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), - Duration.ofMinutes(40), - Duration.ofMinutes(50), - Duration.ofMinutes(60), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Insert passenger between stop2 and stop3 (positions 3, 4) - // All stops should have delays <= 5 minutes - // Modified indices: 0,1,2,pickup@3,dropoff@4,3,4,5,6 - // Note: With real State objects, durations will be slightly longer due to rounding - // (typically 1-3 seconds per path). We use slightly shorter durations to ensure - // the cumulative delays stay within the 5-minute threshold. + // Passenger inserted between stops (pickup at 2, dropoff at 3) + // Stop1 delay: 11min - 10min = 1min ok + // Stop2 delay: 24min - 20min = 4min ok + // Destination delay: 36min - 30min = 6min exceeds 5min budget GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(12)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, calculateCumulativeDurations(modifiedSegments), + 2, 3, - 4 + stops ) ); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java index dab358b4164..1ead1a0f674 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java @@ -6,9 +6,7 @@ import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStopAt; -import java.time.Duration; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; @@ -25,7 +23,6 @@ void buildFromValues_fromId_buildToCorrectValues() { var trip = builder .withTotalCapacity(2) .withProvider("UNIT") - .withDeviationBudget(Duration.of(8, ChronoUnit.MINUTES)) .withStartTime(startTime) .withEndTime(endTime) .withStops(List.of(stop)) @@ -33,7 +30,6 @@ void buildFromValues_fromId_buildToCorrectValues() { assertEquals(2, trip.totalCapacity()); assertEquals("UNIT", trip.provider()); - assertEquals(Duration.of(8, ChronoUnit.MINUTES), trip.deviationBudget()); assertEquals(startTime, trip.startTime()); assertEquals(endTime, trip.endTime()); assertEquals(stop, trip.stops().getFirst()); @@ -48,7 +44,6 @@ void buildFromValues_fromOriginal_buildToCorrectValues() { assertEquals(original.totalCapacity(), trip.totalCapacity()); assertEquals(original.provider(), trip.provider()); - assertEquals(original.deviationBudget(), trip.deviationBudget()); assertEquals(original.startTime(), trip.startTime()); assertEquals(original.endTime(), trip.endTime()); assertEquals(original.stops().getFirst(), trip.stops().getFirst()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java index e4770c3b231..b00c4b140c4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java @@ -1,13 +1,11 @@ package org.opentripplanner.ext.carpooling.routing; 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.ext.carpooling.CarpoolGraphPathBuilder.createGraphPaths; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithDeviationBudget; import java.time.Duration; import org.junit.jupiter.api.Test; @@ -17,7 +15,6 @@ class InsertionCandidateTest { @Test void additionalDuration_calculatesCorrectly() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - // 3 segments var segments = createGraphPaths(3); var candidate = new InsertionCandidate( @@ -44,7 +41,6 @@ void additionalDuration_zeroAdditional_returnsZero() { 2, segments, Duration.ofMinutes(10), - // Same as baseline Duration.ofMinutes(10), null ); @@ -52,65 +48,6 @@ void additionalDuration_zeroAdditional_returnsZero() { assertEquals(Duration.ZERO, candidate.additionalDuration()); } - @Test - void isWithinDeviationBudget_withinBudget_returnsTrue() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(10), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - // baseline - Duration.ofMinutes(10), - // total (8 min additional, within 10 min budget) - Duration.ofMinutes(18), - null - ); - - assertTrue(candidate.isWithinDeviationBudget()); - } - - @Test - void isWithinDeviationBudget_exceedsBudget_returnsFalse() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(5), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - // baseline - Duration.ofMinutes(10), - // total (10 min additional, exceeds 5 min budget) - Duration.ofMinutes(20), - null - ); - - assertFalse(candidate.isWithinDeviationBudget()); - } - - @Test - void isWithinDeviationBudget_exactlyAtBudget_returnsTrue() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(5), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - // Exactly 5 min additional - Duration.ofMinutes(15), - null - ); - - assertTrue(candidate.isWithinDeviationBudget()); - } - @Test void getPickupSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); @@ -127,7 +64,6 @@ void getPickupSegments_returnsCorrectRange() { ); var pickupSegments = candidate.getPickupSegments(); - // Segments 0-1 (before position 2) assertEquals(2, pickupSegments.size()); assertEquals(segments.subList(0, 2), pickupSegments); } @@ -167,7 +103,6 @@ void getSharedSegments_returnsCorrectRange() { ); var sharedSegments = candidate.getSharedSegments(); - // Segments 1-2 (positions 1 to 3) assertEquals(2, sharedSegments.size()); assertEquals(segments.subList(1, 3), sharedSegments); } @@ -207,7 +142,6 @@ void getDropoffSegments_returnsCorrectRange() { ); var dropoffSegments = candidate.getDropoffSegments(); - // Segments 3-4 (after position 3) assertEquals(2, dropoffSegments.size()); assertEquals(segments.subList(3, 5), dropoffSegments); } @@ -249,7 +183,6 @@ void toString_includesKeyInformation() { var str = candidate.toString(); assertTrue(str.contains("pickup@1")); assertTrue(str.contains("dropoff@2")); - // 5 min = 300s additional assertTrue(str.contains("300s")); assertTrue(str.contains("segments=3")); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java index 1c7ce6f1ba0..bdcf432eee1 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.model.GraphPath; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; import org.opentripplanner.ext.carpooling.util.StreetVertexUtils; @@ -42,7 +41,6 @@ class InsertionEvaluatorTest { - private PassengerDelayConstraints delayConstraints; private InsertionPositionFinder positionFinder; private LinkingContext linkingContext; private StreetVertexUtils streetVertexUtils; @@ -50,8 +48,7 @@ class InsertionEvaluatorTest { @BeforeEach void setup() { - delayConstraints = new PassengerDelayConstraints(); - positionFinder = new InsertionPositionFinder(delayConstraints, new BeelineEstimator()); + positionFinder = new InsertionPositionFinder(new BeelineEstimator()); vertexMap = new HashMap<>(); Map> locationVertices = new HashMap<>(); @@ -118,7 +115,6 @@ private InsertionCandidate findOptimalInsertion( } var evaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, carpoolRouter @@ -140,10 +136,15 @@ private WgsCoordinate getCoordinateBetween(WgsCoordinate coordinate1, WgsCoordin @Test void findOptimalInsertion_onDeviationBudgetExceeded_returnsNull() { + var deviationBudget = Duration.ofMinutes(5); var trip = createTripWithStops( OSLO_SOUTH, - List.of(createStopAt(OSLO_CENTER), createStopAt(OSLO_NORTHEAST)), - OSLO_NORTH + List.of( + createStopAt(OSLO_CENTER, deviationBudget), + createStopAt(OSLO_NORTHEAST, deviationBudget) + ), + OSLO_NORTH, + deviationBudget ); var mockPath = createGraphPath(Duration.ofMinutes(4)); @@ -161,7 +162,7 @@ void findOptimalInsertion_onDeviationBudgetExceeded_returnsNull() { @Test void findOptimalInsertion_noValidPositions_returnsNull() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); // Routing function returns null (simulating routing failure) // This causes evaluator to skip all positions CarpoolRouter routingFunction = (from, to) -> null; @@ -173,7 +174,7 @@ void findOptimalInsertion_noValidPositions_returnsNull() { @Test void findOptimalInsertion_oneValidPosition_returnsCandidate() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(); @@ -256,7 +257,7 @@ void findOptimalInsertion_tripWithStops_evaluatesAllPositions() { @Test void findOptimalInsertion_baselineDurationCalculationFails_returnsNull() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); CarpoolRouter routingFunction = (from, to) -> null; @@ -297,7 +298,7 @@ void findOptimalInsertion_selectsMinimumAdditionalDuration() { @Test void findOptimalInsertion_simpleTrip_hasExpectedStructure() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(); @@ -393,52 +394,6 @@ void findOptimalInsertion_insertBetweenTwoPoints_routesAllSegments() { assertTrue(callCount[0] >= 4, "Should have called routing at least 4 times"); } - @Test - void findOptimalInsertion_insertAtEnd_reusesMostSegments() { - // This test verifies that segment reuse optimization still works correctly - // Scenario: Trip A→B→C, insert passenger that allows some segment reuse - // Expected: Segments that have matching endpoints should be REUSED - - var stop1 = createStopAt(OSLO_EAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Baseline has 2 segments: CENTER→EAST, EAST→NORTH - var mockPath = createGraphPath(Duration.ofMinutes(3)); - - final int[] callCount = { 0 }; - CarpoolRouter carpoolRouter = (from, to) -> { - callCount[0]++; - return mockPath; - }; - - // Insert passenger - the algorithm will find the best position - var result = findOptimalInsertion(trip, OSLO_WEST, OSLO_SOUTH, carpoolRouter); - - assertNotNull(result, "Should find valid insertion"); - - // Duration between start and stop should be calculated correctly - assertTrue( - Duration.ofMinutes(3).minus(result.durationBetweenOriginAndDestination()).toSeconds() < 10, - "Baseline should be approximately 3 min (within 10s), got " + - result.durationBetweenOriginAndDestination() - ); - - // The modified route should have more segments than baseline - assertTrue( - result.routeSegments().size() >= 2, - "Modified route should have at least baseline segments" - ); - - // Additional duration should be positive (adding detour) - assertTrue( - result.additionalDuration().compareTo(Duration.ZERO) > 0, - "Adding passenger should increase duration" - ); - - // Routing was called for baseline and new segments - assertTrue(callCount[0] >= 2, "Should have called routing at least 2 times"); - } - @Test void findOptimalInsertion_pickupAtExistingPoint_handlesCorrectly() { // Scenario: Trip A→B→C, passenger pickup at B (existing point), dropoff at new point @@ -473,7 +428,7 @@ void findOptimalInsertion_singleSegmentTrip_routesAllNewSegments() { // Edge case: Simplest possible trip (2 points, 1 segment) // Any insertion will require routing all new segments - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(Duration.ofMinutes(5)); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java index b175950e9bc..56742cf5bea 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java @@ -19,8 +19,6 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; -import org.opentripplanner.ext.carpooling.util.BeelineEstimator; /** * Tests for {@link InsertionPositionFinder}. @@ -39,24 +37,20 @@ void setup() { void findViablePositions_simpleTrip_findsPositions() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST); + // Passenger picked up east of route, dropped off at destination — small compatible detour + var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_NORTH); assertFalse(viablePositions.isEmpty()); - // Simple trip (2 points) allows insertions at positions (1,2) and (1,3) - assertTrue(viablePositions.size() >= 1); } @Test void findViablePositions_incompatibleDirection_rejectsPosition() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - // Passenger going perpendicular (EAST to WEST when trip is CENTER to NORTH) - // This should result in some positions being rejected by directional checks + // Passenger going opposite direction (SOUTH→CENTER) when trip is CENTER→NORTH var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_CENTER); - // May not be completely empty, but should have fewer positions than compatible directions - // The directional check filters out positions that cause too much backtracking - assertNotNull(viablePositions); + assertTrue(viablePositions.isEmpty()); } @Test @@ -73,25 +67,19 @@ void findViablePositions_noCapacity_rejectsPosition() { @Test void findViablePositions_exceedsBeelineDelay_rejectsPosition() { - // Create finder with very restrictive delay constraints - var restrictiveConstraints = new PassengerDelayConstraints(Duration.ofSeconds(1)); - var restrictiveFinder = new InsertionPositionFinder( - restrictiveConstraints, - new BeelineEstimator() + // Create stops with very restrictive deviation budgets (1 second) + var restrictiveBudget = Duration.ofSeconds(1); + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStopAt(OSLO_EAST, restrictiveBudget)), + OSLO_NORTH, + restrictiveBudget ); - var trip = createTripWithStops(OSLO_CENTER, List.of(createStopAt(OSLO_EAST)), OSLO_NORTH); + // Passenger going opposite direction (WEST→SOUTH) with 1s budget — all positions should be rejected + var viablePositions = finder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH); - // Try to insert passenger that would cause significant detour - // Far from route - // Even farther - var viablePositions = restrictiveFinder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH); - - // With very restrictive constraints, positions causing significant detours should be rejected - // However, the beeline check only applies if there are existing stops (routePoints.size() > 2) - // With CENTER, EAST, NORTH we have 3 points, so the check should apply - // The result depends on the actual distances and heuristics - assertNotNull(viablePositions); + assertTrue(viablePositions.isEmpty()); } @Test diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java index c626208bbe9..74620fbdfa0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java @@ -1,75 +1,50 @@ package org.opentripplanner.ext.carpooling.constraints; import java.time.Duration; +import java.util.List; +import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.ext.carpooling.routing.InsertionPosition; -import org.opentripplanner.utils.time.DurationUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Validates that inserting a new passenger does not cause excessive delays - * for existing passengers in a carpool trip. + * for existing stops in a carpool trip. *

    - * Ensures that no existing passenger experiences: - * - More than {@code maxDelay} additional wait time at their pickup location - * - More than {@code maxDelay} later arrival at their dropoff location + * Ensures that no existing stop experiences more delay than its own + * {@link CarpoolStop#getDeviationBudget() deviationBudget} allows. *

    * This protects the rider experience by preventing situations where accepting * one more passenger significantly inconveniences existing bookings. */ -public class PassengerDelayConstraints { +public final class PassengerDelayConstraints { private static final Logger LOG = LoggerFactory.getLogger(PassengerDelayConstraints.class); - /** - * Default maximum delay: 5 minutes. - * No existing passenger should wait more than 5 minutes longer or arrive - * more than 5 minutes later due to a new passenger insertion. - */ - public static final Duration DEFAULT_MAX_DELAY = Duration.ofMinutes(5); - - private final Duration maxDelay; - - /** - * Creates constraints with default 5-minute maximum delay. - */ - public PassengerDelayConstraints() { - this(DEFAULT_MAX_DELAY); - } + private PassengerDelayConstraints() {} /** - * Creates constraints with custom maximum delay. - * - * @param maxDelay Maximum acceptable delay for existing passengers - */ - public PassengerDelayConstraints(Duration maxDelay) { - this.maxDelay = DurationUtils.requireNonNegative(maxDelay); - } - - /** - * Checks if a passenger insertion satisfies delay constraints. + * Checks if a passenger insertion satisfies delay constraints for all existing stops. + * Each stop is checked against its own deviation budget. * * @param originalCumulativeDurations Cumulative duration to each point in original route * @param modifiedCumulativeDurations Cumulative duration to each point in modified route * @param pickupPos 0-based index of the passenger's pickup in the modified route * @param dropoffPos 0-based index of the passenger's dropoff in the modified route - * @return true if all existing passengers experience acceptable delays + * @param stops The ordered list of stops in the original trip + * @return true if all existing stops experience acceptable delays */ - public boolean satisfiesConstraints( + public static boolean satisfiesConstraints( Duration[] originalCumulativeDurations, Duration[] modifiedCumulativeDurations, int pickupPos, - int dropoffPos + int dropoffPos, + List stops ) { - // If no existing stops (only boarding and alighting), no constraint to check - if (originalCumulativeDurations.length <= 2) { - return true; - } - - // Check delay at each existing stop (exclude boarding at 0 and alighting at end) + // Check delay at each existing stop (exclude origin at index 0) for ( int originalIndex = 1; - originalIndex < originalCumulativeDurations.length - 1; + originalIndex < originalCumulativeDurations.length; originalIndex++ ) { int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); @@ -78,33 +53,25 @@ public boolean satisfiesConstraints( Duration modifiedTime = modifiedCumulativeDurations[modifiedIndex]; Duration delay = modifiedTime.minus(originalTime); - if (delay.compareTo(maxDelay) > 0) { + Duration stopBudget = stops.get(originalIndex).getDeviationBudget(); + if (delay.compareTo(stopBudget) > 0) { LOG.debug( - "Insertion rejected: stop at position {} delayed by {}s (max: {}s)", + "Insertion rejected: stop at position {} delayed by {}s (budget: {}s)", originalIndex, delay.getSeconds(), - maxDelay.getSeconds() + stopBudget.getSeconds() ); return false; } LOG.trace( - "Stop at position {} delay: {}s (acceptable, max: {}s)", + "Stop at position {} delay: {}s (acceptable, budget: {}s)", originalIndex, delay.getSeconds(), - maxDelay.getSeconds() + stopBudget.getSeconds() ); } return true; } - - /** - * Gets the configured maximum delay. - * - * @return Maximum delay duration - */ - public Duration getMaxDelay() { - return maxDelay; - } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index 957ba780e45..f26fa89fe78 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -1,5 +1,6 @@ package org.opentripplanner.ext.carpooling.model; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.Objects; import javax.annotation.Nullable; @@ -16,12 +17,19 @@ public class CarpoolStop extends AbstractTransitEntitynot the original + * commitment from the SIRI feed: as the trip is updated with additional SIRI messages, + * the budget shrinks as prior detours eat into it. + * A value of {@link Duration#ZERO} means no further deviation is acceptable here. + */ + public Duration getDeviationBudget() { + return deviationBudget; + } + @Override public boolean sameAs(CarpoolStop other) { return false; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index 2ce812add77..dd92fdb08e4 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -1,6 +1,8 @@ package org.opentripplanner.ext.carpooling.model; +import java.time.Duration; import java.time.ZonedDateTime; +import java.util.Objects; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; @@ -18,6 +20,8 @@ public class CarpoolStopBuilder extends AbstractEntityBuilderOrigin/Destination Areas: Start and end zones for the driver's journey *

  • Stops: Ordered sequence of waypoints along the route where passengers * can be picked up or dropped off. Stops are dynamically updated as bookings occur.
  • - *
  • Deviation Budget: Maximum additional time the driver is willing to spend - * to pick up/drop off passengers (e.g., 5 minutes). This represents the driver's flexibility.
  • *
  • Total Capacity: Number of seats in the car, including the driver seat
  • * * @@ -63,10 +60,6 @@ public class CarpoolTrip private final ZonedDateTime startTime; private final ZonedDateTime endTime; private final String provider; - - // The amount of time the trip can deviate from the scheduled time in order to pick up or drop off - // a passenger. - private final Duration deviationBudget; private final int totalCapacity; // Ordered list of stops along the carpool route where passengers can be picked up or dropped off @@ -78,7 +71,6 @@ public CarpoolTrip(CarpoolTripBuilder builder) { this.endTime = builder.endTime(); this.provider = builder.provider(); this.totalCapacity = builder.totalCapacity(); - this.deviationBudget = builder.deviationBudget(); this.stops = Collections.unmodifiableList(builder.stops()); } @@ -120,10 +112,6 @@ public String provider() { return provider; } - public Duration deviationBudget() { - return deviationBudget; - } - /** * @return Total number of seats in the vehicle, including the driver seat */ diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java index c5c9f4c7d45..b9969b5b430 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java @@ -1,6 +1,5 @@ package org.opentripplanner.ext.carpooling.model; -import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; @@ -15,7 +14,6 @@ public class CarpoolTripBuilder extends AbstractEntityBuilder stops = new ArrayList<>(); @@ -28,7 +26,6 @@ public CarpoolTripBuilder(CarpoolTrip original) { this.startTime = original.startTime(); this.endTime = original.endTime(); this.provider = original.provider(); - this.deviationBudget = original.deviationBudget(); this.totalCapacity = original.totalCapacity(); this.stops = new ArrayList<>(original.stops()); } @@ -48,11 +45,6 @@ public CarpoolTripBuilder withProvider(String provider) { return this; } - public CarpoolTripBuilder withDeviationBudget(Duration deviationBudget) { - this.deviationBudget = deviationBudget; - return this; - } - public CarpoolTripBuilder withTotalCapacity(int totalCapacity) { this.totalCapacity = totalCapacity; return this; @@ -70,10 +62,6 @@ public String provider() { return provider; } - public Duration deviationBudget() { - return deviationBudget; - } - public int totalCapacity() { return totalCapacity; } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index c8b2e9b9c35..933252d2b03 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -38,13 +38,6 @@ public Duration additionalDuration() { return totalDuration.minus(durationBetweenOriginAndDestination); } - /** - * Checks if this insertion is within the trip's deviation budget. - */ - public boolean isWithinDeviationBudget() { - return additionalDuration().compareTo(trip.deviationBudget()) <= 0; - } - /** * Gets the pickup route segment(s) - from boarding to passenger pickup. * Returns all segments before the pickup position. diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java index f50b3a324da..fec089b1a1c 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java @@ -36,26 +36,20 @@ public class InsertionEvaluator { private static final Logger LOG = LoggerFactory.getLogger(InsertionEvaluator.class); - private static final Duration INITIAL_ADDITIONAL_DURATION = Duration.ofDays(1); - - private final PassengerDelayConstraints delayConstraints; private final LinkingContext linkingContext; private final StreetVertexUtils streetVertexUtils; private final CarpoolRouter carpoolRouter; /** - * Creates an evaluator with the specified routing function, delay constraints, and linking context. + * Creates an evaluator with the specified routing function and linking context. * - * @param delayConstraints Constraints for acceptable passenger delays * @param linkingContext Linking context with pre-linked vertices for routing */ public InsertionEvaluator( - PassengerDelayConstraints delayConstraints, LinkingContext linkingContext, StreetVertexUtils streetVertexUtils, CarpoolRouter carpoolRouter ) { - this.delayConstraints = delayConstraints; this.linkingContext = linkingContext; this.streetVertexUtils = streetVertexUtils; this.carpoolRouter = carpoolRouter; @@ -224,7 +218,7 @@ private InsertionCandidate findBestInsertion( NearbyStop transitStop ) { InsertionCandidate bestCandidate = null; - Duration minAdditionalDuration = INITIAL_ADDITIONAL_DURATION; + Duration minAdditionalDuration = Duration.ofDays(1); for (InsertionPosition position : viablePositions) { InsertionCandidate candidate = evaluateInsertion( @@ -245,11 +239,8 @@ private InsertionCandidate findBestInsertion( Duration additionalDuration = candidate.additionalDuration(); - // Check if this is the best so far and within deviation budget - if ( - additionalDuration.compareTo(minAdditionalDuration) < 0 && - additionalDuration.compareTo(tripWithVertices.trip().deviationBudget()) <= 0 - ) { + // Check if this is the best so far + if (additionalDuration.compareTo(minAdditionalDuration) < 0) { minAdditionalDuration = additionalDuration; bestCandidate = candidate; LOG.debug( @@ -301,14 +292,16 @@ private InsertionCandidate evaluateInsertion( } // Check passenger delay constraints + Duration[] modifiedCumulativeDurations = calculateCumulativeDurations( + modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]) + ); if ( - !delayConstraints.satisfiesConstraints( + !PassengerDelayConstraints.satisfiesConstraints( originalCumulativeDurations, - calculateCumulativeDurations( - modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]) - ), + modifiedCumulativeDurations, pickupPos, - dropoffPos + dropoffPos, + tripWithVertices.trip().stops() ) ) { LOG.trace( diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index 54cc5921d67..a71ec46e424 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; import org.opentripplanner.street.geometry.DirectionUtils; @@ -32,27 +31,21 @@ public class InsertionPositionFinder { /** Maximum bearing deviation allowed for forward progress (90° allows detours, prevents U-turns) */ private static final double FORWARD_PROGRESS_TOLERANCE_DEGREES = 90.0; - private final PassengerDelayConstraints delayConstraints; private final BeelineEstimator beelineEstimator; /** - * Creates a finder with default constraints and estimator. + * Creates a finder with default estimator. */ public InsertionPositionFinder() { - this(new PassengerDelayConstraints(), new BeelineEstimator()); + this(new BeelineEstimator()); } /** - * Creates a finder with specified constraints and estimator. + * Creates a finder with specified estimator. * - * @param delayConstraints Constraints for acceptable passenger delays * @param beelineEstimator Estimator for beeline travel times */ - public InsertionPositionFinder( - PassengerDelayConstraints delayConstraints, - BeelineEstimator beelineEstimator - ) { - this.delayConstraints = delayConstraints; + public InsertionPositionFinder(BeelineEstimator beelineEstimator) { this.beelineEstimator = beelineEstimator; } @@ -107,24 +100,23 @@ public List findViablePositions( continue; } - if (routePoints.size() > 2) { - if ( - !passesBeelineDelayCheck( - routePoints, - beelineTimes, - passengerPickup, - passengerDropoff, - pickupPos, - dropoffPos - ) - ) { - LOG.trace( - "Insertion at pickup={}, dropoff={} rejected by beeline delay heuristic", - pickupPos, - dropoffPos - ); - continue; - } + if ( + !passesBeelineDelayCheck( + routePoints, + beelineTimes, + passengerPickup, + passengerDropoff, + pickupPos, + dropoffPos, + trip + ) + ) { + LOG.trace( + "Insertion at pickup={}, dropoff={} rejected by beeline delay heuristic", + pickupPos, + dropoffPos + ); + continue; } viable.add(new InsertionPosition(pickupPos, dropoffPos)); @@ -235,6 +227,7 @@ private boolean maintainsForwardProgress( * @param passengerDropoff Passenger dropoff location * @param pickupPos 0-based index of the passenger's pickup in the modified route * @param dropoffPos 0-based index of the passenger's dropoff in the modified route + * @param trip The carpool trip being evaluated * @return true if insertion might satisfy delay constraints (proceed with A* routing) */ private boolean passesBeelineDelayCheck( @@ -243,7 +236,8 @@ private boolean passesBeelineDelayCheck( WgsCoordinate passengerPickup, WgsCoordinate passengerDropoff, int pickupPos, - int dropoffPos + int dropoffPos, + CarpoolTrip trip ) { // Build modified coordinate list with passenger stops inserted List modifiedCoords = new ArrayList<>(originalCoords); @@ -253,21 +247,22 @@ private boolean passesBeelineDelayCheck( // Calculate beeline times for modified route Duration[] modifiedBeelineTimes = beelineEstimator.calculateCumulativeTimes(modifiedCoords); - // Check delays at each existing stop (exclude boarding at 0 and alighting at end) - for (int originalIndex = 1; originalIndex < originalCoords.size() - 1; originalIndex++) { + // Check delays at each existing stop (exclude origin at index 0) + for (int originalIndex = 1; originalIndex < originalCoords.size(); originalIndex++) { int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); Duration originalTime = originalBeelineTimes[originalIndex]; Duration modifiedTime = modifiedBeelineTimes[modifiedIndex]; Duration beelineDelay = modifiedTime.minus(originalTime); + Duration stopBudget = trip.stops().get(originalIndex).getDeviationBudget(); // If even the optimistic beeline estimate exceeds threshold, actual routing will too - if (beelineDelay.compareTo(delayConstraints.getMaxDelay()) > 0) { + if (beelineDelay.compareTo(stopBudget) > 0) { LOG.trace( - "Stop at position {} has beeline delay {}s (exceeds {}s threshold)", + "Stop at position {} has beeline delay {}s (exceeds {}s budget)", originalIndex, beelineDelay.getSeconds(), - delayConstraints.getMaxDelay().getSeconds() + stopBudget.getSeconds() ); return false; } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 6cbe2eb276d..4157375e3a6 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -11,7 +11,6 @@ import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.ext.carpooling.CarpoolingRepository; import org.opentripplanner.ext.carpooling.CarpoolingService; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.filter.FilterChain; import org.opentripplanner.ext.carpooling.internal.CarpoolItineraryMapper; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; @@ -116,7 +115,6 @@ public class DefaultCarpoolingService implements CarpoolingService { private final StreetLimitationParametersService streetLimitationParametersService; private final FilterChain preFilters; private final CarpoolItineraryMapper itineraryMapper; - private final PassengerDelayConstraints delayConstraints; private final InsertionPositionFinder positionFinder; private final VertexLinker vertexLinker; @@ -143,8 +141,7 @@ public DefaultCarpoolingService( this.streetLimitationParametersService = streetLimitationParametersService; this.preFilters = FilterChain.standard(); this.itineraryMapper = new CarpoolItineraryMapper(transitService.getTimeZone()); - this.delayConstraints = new PassengerDelayConstraints(); - this.positionFinder = new InsertionPositionFinder(delayConstraints, new BeelineEstimator()); + this.positionFinder = new InsertionPositionFinder(new BeelineEstimator()); this.vertexLinker = vertexLinker; } @@ -227,7 +224,6 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC var streetVertexUtils = new StreetVertexUtils(this.vertexLinker, temporaryVerticesContainer); var insertionEvaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, router @@ -450,7 +446,6 @@ public List routeAccessEgress( }); var insertionEvaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, carpoolTreeVertexRouter diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 4325359c90a..367cd7c00fb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -78,8 +78,6 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { .withStartTime(startTime) .withEndTime(endTime) .withProvider(journey.getOperatorRef().getValue()) - // TODO: Find a better way to exchange deviation budget with providers. - .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .withTotalCapacity(totalCapacity) .withStops(stops) .build(); @@ -263,6 +261,8 @@ private CarpoolStop toCarpoolStop( .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) .withExpectedArrivalTime(isFirst ? null : call.getExpectedArrivalTime()) .withOnboardCount(extractOnboardCount(tripId, call)) + // TODO: Find a better way to exchange deviation budget with providers. + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .build(); } From 0c923013773ec642f8bf4f5b5d1dacb377e6df0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 13 Apr 2026 14:09:45 +0200 Subject: [PATCH 087/177] extracting deviation budget of carpooling trips from siri --- .../CarpoolEstimatedVehicleJourneyData.java | 93 +++++++++++++++++++ .../updater/CarpoolSiriMapperTest.java | 56 +++++++++++ .../ext/carpooling/model/CarpoolStop.java | 32 ++++--- .../carpooling/model/CarpoolStopBuilder.java | 11 +++ .../carpooling/updater/CarpoolSiriMapper.java | 54 ++++++++++- 5 files changed, 231 insertions(+), 15 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java index 4ffbc2dc2a4..6f3ac78704e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java @@ -210,6 +210,99 @@ private static void addTotalCapacity(EstimatedCall call, int totalCapacity) { call.getExpectedDepartureCapacities().add(capacity); } + public static EstimatedVehicleJourney journeyWithLatestExpectedArrivalTime( + int expectedArrivalMinutes, + int latestExpectedArrivalMinutes + ) { + var journey = minimalCompleteJourney(); + var lastStop = journey.getEstimatedCalls().getEstimatedCalls().getLast(); + var base = lastStop.getAimedArrivalTime(); + lastStop.setExpectedArrivalTime(base.plusMinutes(expectedArrivalMinutes)); + lastStop.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + return journey; + } + + public static EstimatedVehicleJourney journeyWithLatestExpectedArrivalTimeAimedOnly( + int latestExpectedArrivalMinutes + ) { + var journey = minimalCompleteJourney(); + var lastStop = journey.getEstimatedCalls().getEstimatedCalls().getLast(); + var base = lastStop.getAimedArrivalTime(); + lastStop.setExpectedArrivalTime(null); + lastStop.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + return journey; + } + + /** + * Builds a 3-stop journey (origin, intermediate, destination) where the intermediate and + * destination stops each get their own {@code expectedArrivalTime} and + * {@code latestExpectedArrivalTime}, enabling assertions on per-stop deviation budgets. + * Arrival times are offset from {@code now} in minutes. + */ + public static EstimatedVehicleJourney journeyWithPerStopLatestExpectedArrivalTimes( + int intermediateExpectedArrivalMinutes, + int intermediateLatestExpectedArrivalMinutes, + int lastExpectedArrivalMinutes, + int lastLatestExpectedArrivalMinutes + ) { + var base = ZonedDateTime.now(); + + var origin = forPoint(OSLO_EAST); + origin.setAimedDepartureTime(base); + addStopName(origin, "Origin"); + + var intermediate = createArrivalStop( + OSLO_NORTH, + "Intermediate", + base, + intermediateExpectedArrivalMinutes, + intermediateLatestExpectedArrivalMinutes + ); + intermediate.setAimedDepartureTime(base.plusMinutes(intermediateExpectedArrivalMinutes)); + + var last = createArrivalStop( + OSLO_NORTH, + "Last", + base, + lastExpectedArrivalMinutes, + lastLatestExpectedArrivalMinutes + ); + + var journey = new EstimatedVehicleJourney(); + var operator = new OperatorRefStructure(); + operator.setValue("TESTOPERATOR"); + journey.setEstimatedVehicleJourneyCode("unittest"); + journey.setOperatorRef(operator); + journey.setEstimatedCalls(new EstimatedVehicleJourney.EstimatedCalls()); + journey.getEstimatedCalls().getEstimatedCalls().add(origin); + journey.getEstimatedCalls().getEstimatedCalls().add(intermediate); + journey.getEstimatedCalls().getEstimatedCalls().add(last); + + return journey; + } + + private static EstimatedCall createArrivalStop( + WgsCoordinate coordinate, + String name, + ZonedDateTime base, + int expectedArrivalMinutes, + int latestExpectedArrivalMinutes + ) { + var arrivalTime = base.plusMinutes(expectedArrivalMinutes); + var call = forPoint(coordinate); + call.setAimedArrivalTime(arrivalTime); + call.setExpectedArrivalTime(arrivalTime); + call.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + addStopName(call, name); + return call; + } + + private static void addStopName(EstimatedCall call, String name) { + var nameStruct = new NaturalLanguageStringStructure(); + nameStruct.setValue(name); + call.getStopPointNames().add(nameStruct); + } + private static void addOnboardCount(EstimatedCall call, int onboardCount) { var occupancy = new VehicleOccupancyStructure(); occupancy.setOnboardCount(BigInteger.valueOf(onboardCount)); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java index 9c9d54f8a06..d685fc0747d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java @@ -5,7 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.arrivalIsAfterDepartureTime; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithDifferentCapacitiesPerCall; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithLatestExpectedArrivalTime; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithLatestExpectedArrivalTimeAimedOnly; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithOnboardCounts; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithPerStopLatestExpectedArrivalTimes; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithTotalCapacity; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.lessThanTwoStops; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.minimalCompleteJourney; @@ -13,9 +16,11 @@ import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.stopTimesAreOutOfOrder; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasAimedTimesOnly; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasExpectedTimesOnly; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_DEVIATION_BUDGET; import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_ONBOARD_COUNT; import static org.opentripplanner.ext.carpooling.model.CarpoolTrip.DEFAULT_TOTAL_CAPACITY; +import java.time.Duration; import org.junit.jupiter.api.Test; import uk.org.siri.siri21.EstimatedCall; @@ -184,4 +189,55 @@ void mapSiriToCarpoolTrip_negativeOnboardCount_returnsDefaultOnboardCount() { assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); } } + + // -- extractDeviationBudget tests -- + + @Test + void mapSiriToCarpoolTrip_noLatestExpectedArrivalTime_returnsDefaultDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + assertEquals(Duration.ZERO, mapped.stops().getFirst().getDeviationBudget()); + assertEquals(DEFAULT_DEVIATION_BUDGET, mapped.stops().getLast().getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_withLatestExpectedArrivalTime_computesDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(0, 10)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ofMinutes(10), lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_withLatestExpectedArrivalTimeNoExpected_usesAimedArrivalTime() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTimeAimedOnly(20)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ofMinutes(20), lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_originStop_hasZeroDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(0, 10)); + assertEquals(Duration.ZERO, mapped.stops().getFirst().getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_latestBeforeExpected_returnsZeroDeviationBudget() { + // latestExpectedArrival is before expectedArrival — schedule has slipped past commitment, + // no further deviation is acceptable + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(10, 5)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ZERO, lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_multiStopWithDifferingBudgets_eachStopHasOwnBudget() { + // 3-stop journey. Intermediate arrives at +20 with latest +23 (3 min slack), + // last arrives at +45 with latest +55 (10 min slack). + var mapped = mapper.mapSiriToCarpoolTrip( + journeyWithPerStopLatestExpectedArrivalTimes(20, 23, 45, 55) + ); + assertEquals(3, mapped.stops().size()); + assertEquals(Duration.ZERO, mapped.stops().get(0).getDeviationBudget()); + assertEquals(Duration.ofMinutes(3), mapped.stops().get(1).getDeviationBudget()); + assertEquals(Duration.ofMinutes(10), mapped.stops().get(2).getDeviationBudget()); + } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index f26fa89fe78..bc8147a8dae 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -24,10 +24,11 @@ public class CarpoolStop extends AbstractTransitEntity + * The result is the remaining slack at this stop, not an initial contract: + * {@code expectedArrivalTime} already reflects detours committed by prior passenger + * insertions, and {@code latestExpectedArrivalTime} is the unchanged commitment to the + * passenger at this stop. Each time this mapper runs against a fresh SIRI snapshot, the + * extracted value therefore shrinks in step with the consumed slack. + *

    + * Fallbacks: + *

      + *
    • Returns {@link CarpoolStop#DEFAULT_DEVIATION_BUDGET} if either timestamp is missing. + * This is intentionally permissive — the absence of a commitment should not block + * insertions.
    • + *
    • Returns {@link Duration#ZERO} (and logs a warning) if {@code latestExpectedArrivalTime} + * is before the arrival time — the schedule has slipped past the commitment, so no + * further deviation is acceptable.
    • + *
    + */ + private Duration extractDeviationBudget(EstimatedCall call) { + var latestExpected = call.getLatestExpectedArrivalTime(); + var arrivalTime = call.getExpectedArrivalTime() != null + ? call.getExpectedArrivalTime() + : call.getAimedArrivalTime(); + + if (latestExpected == null || arrivalTime == null) { + return CarpoolStop.DEFAULT_DEVIATION_BUDGET; + } + + Duration budget = Duration.between(arrivalTime, latestExpected); + if (budget.isNegative()) { + LOG.warn( + "latestExpectedArrivalTime ({}) is before arrivalTime ({}), using zero deviation budget", + latestExpected, + arrivalTime + ); + return Duration.ZERO; + } + return budget; + } + /** * Validates that the EstimatedCalls are properly ordered in time. * Ensures intermediate stops occur between the first (boarding) and last (alighting) calls. @@ -240,6 +281,11 @@ private void validateEstimatedCallOrder(List calls) { } } + /** + * Builds a {@link CarpoolStop} from a SIRI call. The origin (when {@code isFirst} is true) + * always gets {@link Duration#ZERO} as its deviation budget — the trip cannot start later + * than scheduled — regardless of any value extracted from the call. + */ private CarpoolStop toCarpoolStop( EstimatedCall call, String id, @@ -260,9 +306,9 @@ private CarpoolStop toCarpoolStop( .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) .withExpectedArrivalTime(isFirst ? null : call.getExpectedArrivalTime()) + .withLatestExpectedArrivalTime(isFirst ? null : call.getLatestExpectedArrivalTime()) .withOnboardCount(extractOnboardCount(tripId, call)) - // TODO: Find a better way to exchange deviation budget with providers. - .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) + .withDeviationBudget(isFirst ? Duration.ZERO : extractDeviationBudget(call)) .build(); } From a393571410d9b2540330ae61c6db97d12a42f7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Tue, 14 Apr 2026 15:12:26 +0200 Subject: [PATCH 088/177] using pickupTime from car preferences to calculate intermediate stop times of carpooling trips. Refactored calculation of duration before and with passenger into InsertionCandidate --- .../PassengerDelayConstraintsTest.java | 86 ++++++++-- .../routing/InsertionCandidateTest.java | 152 ++++++++++++++++++ .../routing/InsertionEvaluatorTest.java | 6 +- .../routing/InsertionPositionFinderTest.java | 10 +- ...aultCarpoolingServiceAccessEgressTest.java | 15 +- .../DefaultCarpoolingServiceDirectTest.java | 15 +- .../carpooling/util/BeelineEstimatorTest.java | 10 +- .../carpooling/util/GraphPathUtilsTest.java | 102 ++++++++++++ .../internal/CarpoolItineraryMapper.java | 30 +--- .../routing/InsertionCandidate.java | 73 ++++++++- .../routing/InsertionEvaluator.java | 14 +- .../routing/InsertionPositionFinder.java | 19 ++- .../service/DefaultCarpoolingService.java | 70 +++----- .../ext/carpooling/util/BeelineEstimator.java | 19 +-- .../ext/carpooling/util/GraphPathUtils.java | 55 ++++++- 15 files changed, 538 insertions(+), 138 deletions(-) create mode 100644 application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java index 4cf3cb3b145..a29b8b62686 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java @@ -49,7 +49,7 @@ void satisfiesConstraints_delayWellUnderBudget_accepts() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -77,7 +77,7 @@ void satisfiesConstraints_delayExactlyAtBudget_accepts() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -105,7 +105,7 @@ void satisfiesConstraints_delayOverBudget_rejects() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -134,7 +134,7 @@ void satisfiesConstraints_destinationOverBudget_rejects() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -170,7 +170,7 @@ void satisfiesConstraints_multipleStops_oneOverBudget_rejects() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -204,7 +204,7 @@ void satisfiesConstraints_multipleStops_allUnderBudget_accepts() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -233,7 +233,7 @@ void satisfiesConstraints_differentBudgetsPerStop() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -260,7 +260,7 @@ void satisfiesConstraints_noDelay_accepts() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -288,7 +288,7 @@ void satisfiesConstraints_zeroBudget_rejectsAnyDelay() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -312,7 +312,7 @@ void satisfiesConstraints_zeroBudget_noDelay_accepts() { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - Duration[] cumulativeDurations = calculateCumulativeDurations(modifiedSegments); + Duration[] cumulativeDurations = calculateCumulativeDurations(modifiedSegments, Duration.ZERO); // originalTimes = modified times at the original stop positions // With pickup=1, dropoff=3: original indices [0,1,2] map to modified [0,2,4] @@ -353,7 +353,7 @@ void satisfiesConstraints_largeBudget_acceptsLargeDelay() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -383,7 +383,7 @@ void satisfiesConstraints_tightAndPermissiveStops_respectsEachBudget() { assertTrue( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 3, stops @@ -421,7 +421,7 @@ void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, 2, stops @@ -429,6 +429,64 @@ void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { ); } + @Test + void satisfiesConstraints_nonZeroStopDuration_countsDwellAtIntermediateStops() { + // Uses a non-zero stopDuration to verify dwell at intermediate stops is included in the + // budget check. The modified route has 2 extra dwells vs. the baseline (4 segments vs. 2), + // which alone accounts for 2 of the 6-minute destination delay that pushes it over budget. + Duration stopDuration = Duration.ofMinutes(1); + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); + + // Baseline: 2 segments of 10min. With 1-min dwell: cumulative = [0, 10, 21] + GraphPath[] baselineSegments = new GraphPath[] { + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + }; + Duration[] originalTimes = calculateCumulativeDurations(baselineSegments, stopDuration); + + // Modified (pickup=1, dropoff=3): 4 segments of 6min. With 1-min dwell: cumulative = [0, 6, 13, 20, 27] + // Destination delay: 27 - 21 = 6min, exceeds 5min budget. + GraphPath[] overBudgetSegments = new GraphPath[] { + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + }; + + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( + originalTimes, + calculateCumulativeDurations(overBudgetSegments, stopDuration), + 1, + 3, + stops + ) + ); + + // Shortening one segment to 5min: cumulative = [0, 6, 12, 19, 26] + // Destination delay: 26 - 21 = 5min, exactly at budget → accepts. + GraphPath[] atBudgetSegments = new GraphPath[] { + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + }; + + assertTrue( + PassengerDelayConstraints.satisfiesConstraints( + originalTimes, + calculateCumulativeDurations(atBudgetSegments, stopDuration), + 1, + 3, + stops + ) + ); + } + @Test void satisfiesConstraints_passengerBetweenStops_checksAllStops() { Duration[] originalTimes = { @@ -459,7 +517,7 @@ void satisfiesConstraints_passengerBetweenStops_checksAllStops() { assertFalse( PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 2, 3, stops diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java index b00c4b140c4..6c03d797397 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java @@ -2,16 +2,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder.createGraphPath; import static org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder.createGraphPaths; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.carpooling.util.GraphPathUtils; class InsertionCandidateTest { + private static final Duration STOP_DURATION = Duration.ofMinutes(2); + @Test void additionalDuration_calculatesCorrectly() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); @@ -24,6 +29,7 @@ void additionalDuration_calculatesCorrectly() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -42,6 +48,7 @@ void additionalDuration_zeroAdditional_returnsZero() { segments, Duration.ofMinutes(10), Duration.ofMinutes(10), + STOP_DURATION, null ); @@ -60,6 +67,7 @@ void getPickupSegments_returnsCorrectRange() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -80,6 +88,7 @@ void getPickupSegments_positionZero_returnsEmpty() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -99,6 +108,7 @@ void getSharedSegments_returnsCorrectRange() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -119,6 +129,7 @@ void getSharedSegments_adjacentPositions_returnsSingleSegment() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -138,6 +149,7 @@ void getDropoffSegments_returnsCorrectRange() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -158,6 +170,7 @@ void getDropoffSegments_atEnd_returnsEmpty() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -177,6 +190,7 @@ void toString_includesKeyInformation() { segments, Duration.ofMinutes(10), Duration.ofMinutes(15), + STOP_DURATION, null ); @@ -186,4 +200,142 @@ void toString_includesKeyInformation() { assertTrue(str.contains("300s")); assertTrue(str.contains("segments=3")); } + + /** + * No pickup segments → durationUntilPickup is zero. + * Single shared segment → passengerRideDuration is just the segment duration (no stop delays). + */ + @Test + void durations_noPickupSegments_singleSharedSegment() { + var stopDuration = Duration.ofMinutes(2); + var sharedPath = createGraphPath(Duration.ofMinutes(10)); + var sharedDuration = GraphPathUtils.calculateDuration(sharedPath); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var candidate = new InsertionCandidate( + trip, + 0, + 1, + List.of(sharedPath), + Duration.ofMinutes(10), + Duration.ofMinutes(10), + stopDuration, + null + ); + + assertEquals(Duration.ofMinutes(10), sharedDuration); + assertEquals(Duration.ZERO, candidate.getDurationUntilDepartureWithPassenger()); + assertEquals(sharedDuration, candidate.getPassengerRideDuration()); + } + + /** + * Single pickup segment → durationUntilPickup = segment duration + boarding time. + * Single shared segment → passengerRideDuration = segment duration (no stop delays). + */ + @Test + void durations_onePickupSegment_singleSharedSegment() { + var stopDuration = Duration.ofMinutes(3); + var pickupPath = createGraphPath(Duration.ofMinutes(8)); + var sharedPath = createGraphPath(Duration.ofMinutes(15)); + + var pickupDuration = GraphPathUtils.calculateDuration(pickupPath); + var sharedDuration = GraphPathUtils.calculateDuration(sharedPath); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var candidate = new InsertionCandidate( + trip, + 1, + 2, + List.of(pickupPath, sharedPath), + Duration.ofMinutes(20), + Duration.ofMinutes(26), + stopDuration, + null + ); + + assertEquals( + pickupDuration.plus(stopDuration), + candidate.getDurationUntilDepartureWithPassenger() + ); + assertEquals(sharedDuration, candidate.getPassengerRideDuration()); + } + + /** + * Two pickup segments → intermediate stop delay between them + boarding time. + * Two shared segments → intermediate stop delay + pickup point delay. + */ + @Test + void durations_multiplePickupAndSharedSegments() { + var stopDuration = Duration.ofMinutes(2); + var pickup0 = createGraphPath(Duration.ofMinutes(5)); + var pickup1 = createGraphPath(Duration.ofMinutes(7)); + var shared0 = createGraphPath(Duration.ofMinutes(10)); + var shared1 = createGraphPath(Duration.ofMinutes(12)); + + var pickup0Duration = GraphPathUtils.calculateDuration(pickup0); + var pickup1Duration = GraphPathUtils.calculateDuration(pickup1); + var shared0Duration = GraphPathUtils.calculateDuration(shared0); + var shared1Duration = GraphPathUtils.calculateDuration(shared1); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var candidate = new InsertionCandidate( + trip, + 2, + 4, + List.of(pickup0, pickup1, shared0, shared1), + Duration.ofMinutes(30), + Duration.ofMinutes(40), + stopDuration, + null + ); + + // 2 pickup segments: travel + 1 intermediate stop + boarding + var expectedPickup = pickup0Duration + .plus(stopDuration) + .plus(pickup1Duration) + .plus(stopDuration); + assertEquals(expectedPickup, candidate.getDurationUntilDepartureWithPassenger()); + + // 2 shared segments: travel + 1 intermediate stop delay + var expectedRide = shared0Duration.plus(stopDuration).plus(shared1Duration); + assertEquals(expectedRide, candidate.getPassengerRideDuration()); + } + + /** + * Larger stop duration scales the durations proportionally. + */ + @Test + void durations_scaleWithStopDuration() { + var shared0 = createGraphPath(Duration.ofMinutes(10)); + var shared1 = createGraphPath(Duration.ofMinutes(10)); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + + var candidateSmall = new InsertionCandidate( + trip, + 0, + 2, + List.of(shared0, shared1), + Duration.ofMinutes(20), + Duration.ofMinutes(22), + Duration.ofMinutes(1), + null + ); + var candidateLarge = new InsertionCandidate( + trip, + 0, + 2, + List.of(shared0, shared1), + Duration.ofMinutes(20), + Duration.ofMinutes(30), + Duration.ofMinutes(5), + null + ); + + // 2 shared segments → 1x stopDuration difference (1 intermediate stop) + var difference = candidateLarge + .getPassengerRideDuration() + .minus(candidateSmall.getPassengerRideDuration()); + assertEquals(Duration.ofMinutes(4), difference); + } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java index bdcf432eee1..40ce9d36403 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java @@ -107,7 +107,8 @@ private InsertionCandidate findOptimalInsertion( List viablePositions = positionFinder.findViablePositions( trip, passengerPickup, - passengerDropoff + passengerDropoff, + Duration.ZERO ); if (viablePositions.isEmpty()) { @@ -117,7 +118,8 @@ private InsertionCandidate findOptimalInsertion( var evaluator = new InsertionEvaluator( linkingContext, streetVertexUtils, - carpoolRouter + carpoolRouter, + Duration.ZERO ); return evaluator.findBestInsertion( tripWithVertices, diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java index 56742cf5bea..a3c72d0f6b7 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java @@ -38,7 +38,7 @@ void findViablePositions_simpleTrip_findsPositions() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); // Passenger picked up east of route, dropped off at destination — small compatible detour - var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_NORTH); + var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_NORTH, Duration.ZERO); assertFalse(viablePositions.isEmpty()); } @@ -48,7 +48,7 @@ void findViablePositions_incompatibleDirection_rejectsPosition() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); // Passenger going opposite direction (SOUTH→CENTER) when trip is CENTER→NORTH - var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_CENTER); + var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_CENTER, Duration.ZERO); assertTrue(viablePositions.isEmpty()); } @@ -59,7 +59,7 @@ void findViablePositions_noCapacity_rejectsPosition() { var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH)); var trip = createTripWithCapacity(0, stops); - var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST); + var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST, Duration.ZERO); // Should reject all positions due to capacity assertTrue(viablePositions.isEmpty()); @@ -77,7 +77,7 @@ void findViablePositions_exceedsBeelineDelay_rejectsPosition() { ); // Passenger going opposite direction (WEST→SOUTH) with 1s budget — all positions should be rejected - var viablePositions = finder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH); + var viablePositions = finder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH, Duration.ZERO); assertTrue(viablePositions.isEmpty()); } @@ -88,7 +88,7 @@ void findViablePositions_multipleStops_checksAllCombinations() { var stop2 = createStopAt(OSLO_WEST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH); + var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH, Duration.ZERO); // Should evaluate multiple pickup/dropoff combinations // Exact count depends on directional and beeline filtering diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java index c29eae69219..1338fce9fe1 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java @@ -638,9 +638,12 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { assertFalse(results.isEmpty(), "Should find access results"); + // Departure time of the passenger is when the car departs from the pickup with the + // passenger onboard, i.e. trip start + driving to P2 + boarding dwell at P2. + var pickupTime = RouteRequest.defaultValue().preferences().car().pickupTime(); var expectedDeparture = (int) Duration.between( transitSearchTimeZero.toInstant(), - departureTime.plus(drivingDurationAToP2).toInstant() + departureTime.plus(drivingDurationAToP2).plus(pickupTime).toInstant() ).getSeconds(); int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); @@ -680,15 +683,13 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { ); var drivingP2ToStop = expectedDrivingP2ToStop.get(accessEgress.stop()); - // The service adds CARPOOL_STOP_DURATION (1 min) for the passenger pickup at P2 - var expectedArrival = - expectedDeparture + - (int) drivingP2ToStop.getSeconds() + - (int) DefaultCarpoolingService.CARPOOL_STOP_DURATION.getSeconds(); + // Boarding dwell is already part of the passenger's departure time, so arrival is + // simply departure plus driving time from P2 to the transit stop. + var expectedArrival = expectedDeparture + (int) drivingP2ToStop.getSeconds(); assertEquals( expectedArrival, accessEgress.getArrivalTimeOfPassenger(), - "Arrival time should equal passenger departure plus driving time from P2 to stop plus pickup duration" + "Arrival time should equal passenger departure plus driving time from P2 to stop" ); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index ffbb51720ef..420a87f79a7 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -423,10 +423,15 @@ void resultItinerariesHaveValidStartAndEndTimes() { pathToDropoff.states.getLast().getTime() ); - var expectedStartTime = departureTime.plus(drivingToPickup); - var expectedEndTime = departureTime.plus(drivingToDropoff); - var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); + var stopDuration = request.preferences().car().pickupTime(); + // Start time is when the car departs from the pickup with the passenger onboard, + // i.e. after the boarding dwell at the pickup. + var expectedStartTime = departureTime.plus(drivingToPickup).plus(stopDuration); + // End time is start time plus the passenger ride duration. Since the direct route + // from trip start to dropoff goes via pickup, drivingToDropoff == drivingToPickup + // + drivingPickupToDropoff, so end time = departureTime + drivingToDropoff + stopDuration. + var expectedEndTime = departureTime.plus(drivingToDropoff).plus(stopDuration); var results = service.routeDirect(request, linkingContext); @@ -439,12 +444,12 @@ void resultItinerariesHaveValidStartAndEndTimes() { assertEquals( expectedStartTime.toInstant(), itinerary.startTime().toInstant(), - "Start time should equal trip departure plus driving time to pickup" + "Start time should equal trip departure plus driving time to pickup plus boarding time" ); assertEquals( expectedEndTime.toInstant(), itinerary.endTime().toInstant(), - "End time should equal trip departure plus driving time to dropoff" + "End time should equal trip departure plus driving time to dropoff plus stop duration at pickup" ); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java index d3c2fb48d54..61023d92683 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java @@ -73,7 +73,7 @@ void calculateCumulativeTimes_simpleRoute_calculatesCorrectly() { // Route: Oslo Center → Oslo East → Oslo North List points = List.of(OSLO_CENTER, OSLO_EAST, OSLO_NORTH); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(3, times.length); // Start at 0 @@ -94,7 +94,7 @@ void calculateCumulativeTimes_simpleRoute_calculatesCorrectly() { void calculateCumulativeTimes_singlePoint_returnsZero() { List points = List.of(OSLO_CENTER); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(1, times.length); assertEquals(Duration.ZERO, times[0]); @@ -104,7 +104,7 @@ void calculateCumulativeTimes_singlePoint_returnsZero() { void calculateCumulativeTimes_emptyList_returnsEmptyArray() { List points = List.of(); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(0, times.length); } @@ -120,7 +120,7 @@ void calculateCumulativeTimes_multipleStops_timesAreMonotonic() { OSLO_NORTHWEST ); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); // Times should be strictly increasing for (int i = 1; i < times.length; i++) { @@ -257,7 +257,7 @@ void estimateDuration_longDistance_scalesCorrectly() { void calculateCumulativeTimes_twoPoints_calculatesCorrectly() { List points = List.of(OSLO_CENTER, OSLO_NORTH); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(2, times.length); assertEquals(Duration.ZERO, times[0]); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java new file mode 100644 index 00000000000..8984f7366e4 --- /dev/null +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java @@ -0,0 +1,102 @@ +package org.opentripplanner.ext.carpooling.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class GraphPathUtilsTest { + + private static final Duration TEN_MINUTES = Duration.ofMinutes(10); + private static final Duration ONE_MINUTE = Duration.ofMinutes(1); + + @Test + void calculateCumulativeDurations_sixSegmentsWithStopDelay() { + Duration[] segments = { + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals( + new Duration[] { + Duration.ofMinutes(0), + Duration.ofMinutes(10), + Duration.ofMinutes(21), + Duration.ofMinutes(32), + Duration.ofMinutes(43), + Duration.ofMinutes(54), + Duration.ofMinutes(65), + }, + result + ); + } + + @Test + void calculateCumulativeDurations_noStopDelay() { + Duration[] segments = { TEN_MINUTES, TEN_MINUTES, TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, Duration.ZERO); + + assertArrayEquals( + new Duration[] { + Duration.ofMinutes(0), + Duration.ofMinutes(10), + Duration.ofMinutes(20), + Duration.ofMinutes(30), + }, + result + ); + } + + @Test + void calculateCumulativeDurations_singleSegment_noStopDelayApplied() { + Duration[] segments = { TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals(new Duration[] { Duration.ZERO, TEN_MINUTES }, result); + } + + @Test + void calculateCumulativeDurations_twoSegments_stopDelayOnlyAtSecondPoint() { + Duration[] segments = { TEN_MINUTES, TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals( + new Duration[] { Duration.ofMinutes(0), Duration.ofMinutes(10), Duration.ofMinutes(21) }, + result + ); + } + + @Test + void calculateCumulativeDurations_noSegments() { + Duration[] segments = {}; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals(new Duration[] { Duration.ZERO }, result); + } + + @Test + void calculateCumulativeDurations_varyingSegmentDurations() { + Duration[] segments = { Duration.ofMinutes(5), Duration.ofMinutes(15), Duration.ofMinutes(10) }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations( + segments, + Duration.ofMinutes(2) + ); + + assertEquals(Duration.ofMinutes(0), result[0]); + assertEquals(Duration.ofMinutes(5), result[1]); + assertEquals(Duration.ofMinutes(22), result[2]); + assertEquals(Duration.ofMinutes(34), result[3]); + } +} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java index a08cc8c9fa3..975a5bb5b72 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java @@ -1,6 +1,5 @@ package org.opentripplanner.ext.carpooling.internal; -import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; @@ -45,7 +44,7 @@ * The passenger's start time is the later of: *
      *
    1. The passenger's requested departure time
    2. - *
    3. When the driver arrives at the pickup location
    4. + *
    5. When the car departs with the passenger (driver arrival + boarding time)
    6. *
    *

    * This ensures the itinerary reflects realistic timing: passengers can't board before the @@ -103,8 +102,8 @@ public CarpoolItineraryMapper(ZoneId timeZone) { *

    * The method calculates three key times: *

      - *
    1. Driver pickup arrival: Driver's start time + pickup segment durations
    2. - *
    3. Passenger start: max(requested time, driver arrival time)
    4. + *
    5. Departure with passenger: Driver's start time + pickup travel + boarding time
    6. + *
    7. Passenger start: max(requested time, departure with passenger time)
    8. *
    9. Passenger end: start time + shared segment durations
    10. *
    * @@ -125,29 +124,16 @@ public Itinerary toItinerary(RouteRequest request, InsertionCandidate candidate) return null; } - var pickupSegments = candidate.getPickupSegments(); - Duration pickupDuration = Duration.ZERO; - for (var segment : pickupSegments) { - pickupDuration = pickupDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - - var driverPickupTime = candidate.trip().startTime().plus(pickupDuration); + var driverPickupTime = candidate + .trip() + .startTime() + .plus(candidate.getDurationUntilDepartureWithPassenger()); var startTime = request.dateTime().isAfter(driverPickupTime.toInstant()) ? request.dateTime().atZone(timeZone) : driverPickupTime; - // Calculate shared journey duration - Duration carpoolDuration = Duration.ZERO; - for (var segment : sharedSegments) { - carpoolDuration = carpoolDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - - var endTime = startTime.plus(carpoolDuration); + var endTime = startTime.plus(candidate.getPassengerRideDuration()); var firstSegment = sharedSegments.getFirst(); var lastSegment = sharedSegments.getLast(); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index 933252d2b03..5a19bfd43be 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -4,6 +4,7 @@ import java.util.List; import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; +import org.opentripplanner.ext.carpooling.util.GraphPathUtils; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; @@ -29,8 +30,44 @@ public record InsertionCandidate( List> routeSegments, Duration durationBetweenOriginAndDestination, Duration totalDuration, - NearbyStop transitStop + Duration stopDuration, + NearbyStop transitStop, + Duration totalTripDuration ) { + public InsertionCandidate( + CarpoolTrip trip, + int pickupPosition, + int dropoffPosition, + List> routeSegments, + Duration durationBetweenOriginAndDestination, + Duration totalDuration, + Duration stopDuration, + NearbyStop transitStop + ) { + this( + trip, + pickupPosition, + dropoffPosition, + routeSegments, + durationBetweenOriginAndDestination, + totalDuration, + stopDuration, + transitStop, + computeTotalTripDuration(routeSegments, stopDuration) + ); + } + + private static Duration computeTotalTripDuration( + List> routeSegments, + Duration stopDuration + ) { + Duration[] cumulativeDurations = GraphPathUtils.calculateCumulativeDurations( + routeSegments.toArray(new GraphPath[0]), + stopDuration + ); + return cumulativeDurations[cumulativeDurations.length - 1]; + } + /** * Calculates the additional duration caused by inserting this passenger. */ @@ -68,6 +105,40 @@ public List> getDropoffSegments() { return routeSegments.subList(dropoffPosition, routeSegments.size()); } + /** + * Calculates the duration from trip start until the car departs with the passenger onboard. + * Includes travel time through pickup segments, intermediate stop delays, and boarding time + * at the pickup point. + * Returns {@link Duration#ZERO} when the passenger boards at the trip origin (no pickup segments). + */ + public Duration getDurationUntilDepartureWithPassenger() { + var pickupSegments = getPickupSegments(); + if (pickupSegments.isEmpty()) { + return Duration.ZERO; + } + return totalSegmentDuration(pickupSegments, stopDuration).plus(stopDuration); + } + + /** + * Calculates the duration of the passenger's ride from pickup to dropoff. + * Includes travel time through shared segments and stop delays at intermediate stops. + * For a single shared segment (direct ride), no stop delays are added. + */ + public Duration getPassengerRideDuration() { + return totalSegmentDuration(getSharedSegments(), stopDuration); + } + + private static Duration totalSegmentDuration( + List> segments, + Duration stopDuration + ) { + return segments + .stream() + .map(GraphPathUtils::calculateDuration) + .reduce(Duration.ZERO, Duration::plus) + .plus(stopDuration.multipliedBy(Math.max(0, segments.size() - 1))); + } + @Override public String toString() { return String.format( diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java index fec089b1a1c..2a339a7e815 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java @@ -39,20 +39,24 @@ public class InsertionEvaluator { private final LinkingContext linkingContext; private final StreetVertexUtils streetVertexUtils; private final CarpoolRouter carpoolRouter; + private final Duration stopDuration; /** * Creates an evaluator with the specified routing function and linking context. * * @param linkingContext Linking context with pre-linked vertices for routing + * @param stopDuration Duration added at each intermediate stop (from car pickupTime preference) */ public InsertionEvaluator( LinkingContext linkingContext, StreetVertexUtils streetVertexUtils, - CarpoolRouter carpoolRouter + CarpoolRouter carpoolRouter, + Duration stopDuration ) { this.linkingContext = linkingContext; this.streetVertexUtils = streetVertexUtils; this.carpoolRouter = carpoolRouter; + this.stopDuration = stopDuration; } /** @@ -96,7 +100,7 @@ public List findBestInsertions( return List.of(); } - Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments); + Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( tripWithVertices.vertices().getFirst(), @@ -176,7 +180,7 @@ public InsertionCandidate findBestInsertion( linkingContext ); - Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments); + Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( tripWithVertices.vertices().getFirst(), @@ -293,7 +297,8 @@ private InsertionCandidate evaluateInsertion( // Check passenger delay constraints Duration[] modifiedCumulativeDurations = calculateCumulativeDurations( - modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]) + modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]), + stopDuration ); if ( !PassengerDelayConstraints.satisfiesConstraints( @@ -319,6 +324,7 @@ private InsertionCandidate evaluateInsertion( modifiedSegments, durationBetweenOriginAndDestination, totalDuration, + stopDuration, transitStop ); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index a71ec46e424..43e8a2f6d17 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -56,16 +56,20 @@ public InsertionPositionFinder(BeelineEstimator beelineEstimator) { * @param trip The carpool trip being evaluated * @param passengerPickup Passenger's pickup location * @param passengerDropoff Passenger's dropoff location + * @param stopDuration Dwell time added at each intermediate stop; used by the beeline delay + * heuristic so its cumulative-time estimates match the per-stop budget + * check used downstream * @return List of viable insertion positions (may be empty) */ public List findViablePositions( CarpoolTrip trip, WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff + WgsCoordinate passengerDropoff, + Duration stopDuration ) { List routePoints = trip.routePoints(); - Duration[] beelineTimes = beelineEstimator.calculateCumulativeTimes(routePoints); + Duration[] beelineTimes = beelineEstimator.calculateCumulativeTimes(routePoints, stopDuration); List viable = new ArrayList<>(); @@ -108,7 +112,8 @@ public List findViablePositions( passengerDropoff, pickupPos, dropoffPos, - trip + trip, + stopDuration ) ) { LOG.trace( @@ -237,7 +242,8 @@ private boolean passesBeelineDelayCheck( WgsCoordinate passengerDropoff, int pickupPos, int dropoffPos, - CarpoolTrip trip + CarpoolTrip trip, + Duration stopDuration ) { // Build modified coordinate list with passenger stops inserted List modifiedCoords = new ArrayList<>(originalCoords); @@ -245,7 +251,10 @@ private boolean passesBeelineDelayCheck( modifiedCoords.add(dropoffPos, passengerDropoff); // Calculate beeline times for modified route - Duration[] modifiedBeelineTimes = beelineEstimator.calculateCumulativeTimes(modifiedCoords); + Duration[] modifiedBeelineTimes = beelineEstimator.calculateCumulativeTimes( + modifiedCoords, + stopDuration + ); // Check delays at each existing stop (exclude origin at index 0) for (int originalIndex = 1; originalIndex < originalCoords.size(); originalIndex++) { diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 4157375e3a6..d548872073f 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.ext.carpooling.CarpoolingRepository; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.carpooling.filter.FilterChain; @@ -43,9 +42,7 @@ import org.opentripplanner.street.linking.TemporaryVerticesContainer; import org.opentripplanner.street.linking.VertexLinker; import org.opentripplanner.street.model.StreetMode; -import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.service.TransitService; @@ -98,13 +95,6 @@ public class DefaultCarpoolingService implements CarpoolingService { private static final Duration DEFAULT_SEARCH_WINDOW = Duration.ofMinutes(30); // How far away in time a carpooling trip can be from the requested departure time to be considered private static final Duration ACCESS_EGRESS_SEARCH_WINDOW = Duration.ofHours(12); - /* - The time it takes to pick up or drop off a passenger and start driving again. - It is only used for access/egress, and is a temporary solution. - The implementation will be changed for both direct and access/egress when implementing the field - latestExpectedArrivalTime from siri. - */ - static final Duration CARPOOL_STOP_DURATION = Duration.ofMinutes(1); /* This is needed for managing computational complexity unless we find a smarter way of searching for nearby stops. @@ -223,10 +213,13 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC var streetVertexUtils = new StreetVertexUtils(this.vertexLinker, temporaryVerticesContainer); + var stopDuration = request.preferences().car().pickupTime(); + var insertionEvaluator = new InsertionEvaluator( linkingContext, streetVertexUtils, - router + router, + stopDuration ); // Find optimal insertions for remaining trips @@ -236,7 +229,8 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC List viablePositions = positionFinder.findViablePositions( trip, passengerPickup, - passengerDropoff + passengerDropoff, + stopDuration ); if (viablePositions.isEmpty()) { @@ -445,10 +439,13 @@ public List routeAccessEgress( }); }); + var stopDuration = request.preferences().car().pickupTime(); + var insertionEvaluator = new InsertionEvaluator( linkingContext, streetVertexUtils, - carpoolTreeVertexRouter + carpoolTreeVertexRouter, + stopDuration ); var candidateTripsWithViableStopsAndPositions = candidateTripsWithVertices @@ -468,7 +465,8 @@ public List routeAccessEgress( var viablePositions = positionFinder.findViablePositions( tripWithVertices.trip(), pickUpCoord, - dropOffCoord + dropOffCoord, + stopDuration ); return new ViableAccessEgress( nearbyStop, @@ -521,43 +519,17 @@ private void validateRequest(RouteRequest request) throws RoutingValidationExcep } } - private Duration getTotalDurationOfSegments( - List> segments, - Duration extraTimeForStop - ) { - return segments - .stream() - .map(it -> Duration.between(it.states.getFirst().getTime(), it.states.getLast().getTime())) - .reduce(Duration.ZERO, Duration::plus) - .plus(extraTimeForStop.multipliedBy(segments.size() - 1)); - } - private CarpoolAccessEgress createCarpoolAccessEgress( InsertionCandidate insertionCandidate, ZonedDateTime transitSearchTimeZero, Double carpoolReluctance ) { - var pickUpIndex = insertionCandidate.pickupPosition(); - var dropOffIndex = insertionCandidate.dropoffPosition() - 1; + var sharedSegments = insertionCandidate.getSharedSegments(); + var durationUntilPickup = insertionCandidate.getDurationUntilDepartureWithPassenger(); + var passengerRideDuration = insertionCandidate.getPassengerRideDuration(); - var segmentsBeforeInsertion = insertionCandidate.routeSegments().subList(0, pickUpIndex); - var segmentsWithPassenger = insertionCandidate - .routeSegments() - .subList(pickUpIndex, dropOffIndex + 1); - - var durationBeforeInsertion = getTotalDurationOfSegments( - segmentsBeforeInsertion, - CARPOOL_STOP_DURATION - ); - - // Adding an extra CARPOOL_STOP_DURATION for the time it takes to pick up the passenger - var durationWithPassenger = getTotalDurationOfSegments( - segmentsWithPassenger, - CARPOOL_STOP_DURATION - ).plus(CARPOOL_STOP_DURATION); - - var startTimeOfSegment = insertionCandidate.trip().startTime().plus(durationBeforeInsertion); - var endTimeOfSegment = startTimeOfSegment.plus(durationWithPassenger); + var startTimeOfSegment = insertionCandidate.trip().startTime().plus(durationUntilPickup); + var endTimeOfSegment = startTimeOfSegment.plus(passengerRideDuration); var relativeStartTime = TimeUtils.toTransitTimeSeconds( transitSearchTimeZero, @@ -568,16 +540,14 @@ private CarpoolAccessEgress createCarpoolAccessEgress( endTimeOfSegment.toInstant() ); - var accessEgress = new CarpoolAccessEgress( + return new CarpoolAccessEgress( insertionCandidate.transitStop().stop.getIndex(), - durationWithPassenger, + passengerRideDuration, relativeStartTime, relativeEndTime, - segmentsWithPassenger, + sharedSegments, TimeAndCost.ZERO, carpoolReluctance ); - - return accessEgress; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java index 8b00c19f681..96fd7f1da72 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java @@ -85,25 +85,22 @@ public Duration estimateDuration(WgsCoordinate from, WgsCoordinate to) { } /** - * Calculates cumulative travel times to each point in a route. - * Returns an array where index i contains the cumulative duration from the start to point i. + * Calculates cumulative travel times to each point in a route, including stop duration + * at each intermediate point. * * @param points Route points in order + * @param stopDuration Duration added at each intermediate stop (not at the first point) * @return Array of cumulative durations (first element is always Duration.ZERO) */ - public Duration[] calculateCumulativeTimes(List points) { + public Duration[] calculateCumulativeTimes(List points, Duration stopDuration) { if (points.isEmpty()) { return new Duration[0]; } - Duration[] cumulativeTimes = new Duration[points.size()]; - cumulativeTimes[0] = Duration.ZERO; - - for (int i = 0; i < points.size() - 1; i++) { - Duration segmentDuration = estimateDuration(points.get(i), points.get(i + 1)); - cumulativeTimes[i + 1] = cumulativeTimes[i].plus(segmentDuration); + Duration[] segmentDurations = new Duration[points.size() - 1]; + for (int i = 0; i < segmentDurations.length; i++) { + segmentDurations[i] = estimateDuration(points.get(i), points.get(i + 1)); } - - return cumulativeTimes; + return GraphPathUtils.calculateCumulativeDurations(segmentDurations, stopDuration); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java index 12bb9449238..169408af479 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java @@ -6,18 +6,59 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; -public class GraphPathUtils { +public final class GraphPathUtils { + + private GraphPathUtils() {} + + /** + * Calculates cumulative durations from pre-routed segments, including stop duration + * at each intermediate stop. + * + * @param segments Pre-routed segments + * @param stopDuration Duration added at each intermediate stop + */ + public static Duration[] calculateCumulativeDurations( + GraphPath[] segments, + Duration stopDuration + ) { + Duration[] segmentDurations = new Duration[segments.length]; + for (int i = 0; i < segments.length; i++) { + segmentDurations[i] = calculateDuration(segments[i]); + } + return calculateCumulativeDurations(segmentDurations, stopDuration); + } /** - * Calculates cumulative durations from pre-routed segments. + * Calculates cumulative arrival times from segment durations, including a stop delay + * at each intermediate point. The stop delay is added before each segment + * except the first, modelling time spent at an intermediate stop before departing + * to the next point. No delay is added at the origin (before segment 0) or after the + * final segment. + *

    + * Given N segments, the result has N+1 entries: + *

    +   *   result[0] = 0                                           (origin)
    +   *   result[1] = segment[0]                                  (no preceding stop delay)
    +   *   result[2] = segment[0] + stopDuration + segment[1]
    +   *   result[k] = result[k-1] + stopDuration + segment[k-1]  (for k >= 2)
    +   * 
    + * Example: 6 segments of 10 min each, 1 min stop duration: + * {@code [0, 10, 21, 32, 43, 54, 65]} + * + * @param segmentDurations Duration of each segment + * @param stopDuration Duration added before each segment except the first + * @return Array of cumulative durations with length segmentDurations.length + 1 */ - public static Duration[] calculateCumulativeDurations(GraphPath[] segments) { - Duration[] cumulativeDurations = new Duration[segments.length + 1]; + public static Duration[] calculateCumulativeDurations( + Duration[] segmentDurations, + Duration stopDuration + ) { + Duration[] cumulativeDurations = new Duration[segmentDurations.length + 1]; cumulativeDurations[0] = Duration.ZERO; - for (int i = 0; i < segments.length; i++) { - Duration segmentDuration = calculateDuration(segments[i]); - cumulativeDurations[i + 1] = cumulativeDurations[i].plus(segmentDuration); + for (int i = 0; i < segmentDurations.length; i++) { + Duration stopDelay = i > 0 ? stopDuration : Duration.ZERO; + cumulativeDurations[i + 1] = cumulativeDurations[i].plus(stopDelay).plus(segmentDurations[i]); } return cumulativeDurations; From a330e590acaae6921f0324ea0d0586d229aca29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Wed, 15 Apr 2026 20:45:40 +0200 Subject: [PATCH 089/177] changed CarpoolGraphPathBuilder in tests to avoid rounding errors --- .../carpooling/CarpoolGraphPathBuilder.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java index c5a9d22dd79..27b7647b30c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java @@ -18,9 +18,6 @@ public class CarpoolGraphPathBuilder { // Walking speed in m/s (OTP default from WalkPreferences) private static final double WALKING_SPEED_MPS = 1.33; - // Default number of edges to distribute duration across - private static final int DEFAULT_NUM_EDGES = 3; - /** * Creates a GraphPath with default 5-minute duration. */ @@ -30,6 +27,10 @@ public static GraphPath createGraphPath() { /** * Creates a GraphPath with specified duration using State chain. + * Uses a single edge with floor distance to avoid rounding errors: the edge traversal + * applies ceiling when converting to milliseconds, and State.getTime() applies ceiling + * when converting to seconds, so floor distance ensures the final second-precision + * duration matches the requested value. * * @param duration Total duration for the path * @return GraphPath with real State objects and accurate timing @@ -37,17 +38,9 @@ public static GraphPath createGraphPath() { public static GraphPath createGraphPath(Duration duration) { var builder = TestStateBuilder.ofWalking(); - // Calculate distance needed for target duration - double totalDistanceMeters = duration.toSeconds() * WALKING_SPEED_MPS; - - // Distribute across multiple edges for realistic path - int numEdges = DEFAULT_NUM_EDGES; - int distancePerEdge = (int) Math.ceil(totalDistanceMeters / numEdges); + int distanceMeters = (int) (duration.toSeconds() * WALKING_SPEED_MPS); - // Build state chain with calculated distances - for (int i = 0; i < numEdges; i++) { - builder.streetEdge("segment-" + i, distancePerEdge); - } + builder.streetEdge("segment-0", distanceMeters); return new GraphPath<>(builder.build()); } From 25e2e63ba4d6b2fd2beeffcb28785c917ce8fea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Wed, 15 Apr 2026 20:46:30 +0200 Subject: [PATCH 090/177] removed parameters durationBetweenOriginAndDestination and totalDuration from InsertionCandidate, and replaced function additionalDuration with totalTripDuration --- .../routing/InsertionCandidateTest.java | 132 +++--------------- .../routing/InsertionEvaluatorTest.java | 85 ++++++----- .../opentripplanner/ext/carpooling/README.md | 28 ++-- .../routing/InsertionCandidate.java | 19 +-- .../routing/InsertionEvaluator.java | 62 +------- .../service/DefaultCarpoolingService.java | 2 +- 6 files changed, 96 insertions(+), 232 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java index 6c03d797397..d10e1d1aabf 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java @@ -18,41 +18,25 @@ class InsertionCandidateTest { private static final Duration STOP_DURATION = Duration.ofMinutes(2); @Test - void additionalDuration_calculatesCorrectly() { + void totalTripDuration_calculatesFromSegments() { + // Simple trip origin → destination, with passenger pickup and dropoff inserted: + // origin → pickup (5 min) → dropoff (10 min) → destination (8 min) var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(3); + var originToPickup = createGraphPath(Duration.ofMinutes(5)); + var pickupToDropoff = createGraphPath(Duration.ofMinutes(10)); + var dropoffToDestination = createGraphPath(Duration.ofMinutes(8)); var candidate = new InsertionCandidate( trip, 1, 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), + List.of(originToPickup, pickupToDropoff, dropoffToDestination), STOP_DURATION, null ); - assertEquals(Duration.ofMinutes(5), candidate.additionalDuration()); - } - - @Test - void additionalDuration_zeroAdditional_returnsZero() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(10), - STOP_DURATION, - null - ); - - assertEquals(Duration.ZERO, candidate.additionalDuration()); + // 5 + 2 (stop) + 10 + 2 (stop) + 8 = 27 minutes + assertEquals(Duration.ofMinutes(27), candidate.totalTripDuration()); } @Test @@ -60,16 +44,7 @@ void getPickupSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 2, - 4, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 2, 4, segments, STOP_DURATION, null); var pickupSegments = candidate.getPickupSegments(); assertEquals(2, pickupSegments.size()); @@ -81,16 +56,7 @@ void getPickupSegments_positionZero_returnsEmpty() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 0, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 0, 2, segments, STOP_DURATION, null); var pickupSegments = candidate.getPickupSegments(); assertTrue(pickupSegments.isEmpty()); @@ -101,16 +67,7 @@ void getSharedSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var sharedSegments = candidate.getSharedSegments(); assertEquals(2, sharedSegments.size()); @@ -122,16 +79,7 @@ void getSharedSegments_adjacentPositions_returnsSingleSegment() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 1, 2, segments, STOP_DURATION, null); var sharedSegments = candidate.getSharedSegments(); assertEquals(1, sharedSegments.size()); @@ -142,16 +90,7 @@ void getDropoffSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var dropoffSegments = candidate.getDropoffSegments(); assertEquals(2, dropoffSegments.size()); @@ -163,16 +102,7 @@ void getDropoffSegments_atEnd_returnsEmpty() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var dropoffSegments = candidate.getDropoffSegments(); assertTrue(dropoffSegments.isEmpty()); @@ -183,21 +113,12 @@ void toString_includesKeyInformation() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - STOP_DURATION, - null - ); + var candidate = new InsertionCandidate(trip, 1, 2, segments, STOP_DURATION, null); var str = candidate.toString(); assertTrue(str.contains("pickup@1")); assertTrue(str.contains("dropoff@2")); - assertTrue(str.contains("300s")); + assertTrue(str.contains("duration=")); assertTrue(str.contains("segments=3")); } @@ -212,16 +133,7 @@ void durations_noPickupSegments_singleSharedSegment() { var sharedDuration = GraphPathUtils.calculateDuration(sharedPath); var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var candidate = new InsertionCandidate( - trip, - 0, - 1, - List.of(sharedPath), - Duration.ofMinutes(10), - Duration.ofMinutes(10), - stopDuration, - null - ); + var candidate = new InsertionCandidate(trip, 0, 1, List.of(sharedPath), stopDuration, null); assertEquals(Duration.ofMinutes(10), sharedDuration); assertEquals(Duration.ZERO, candidate.getDurationUntilDepartureWithPassenger()); @@ -247,8 +159,6 @@ void durations_onePickupSegment_singleSharedSegment() { 1, 2, List.of(pickupPath, sharedPath), - Duration.ofMinutes(20), - Duration.ofMinutes(26), stopDuration, null ); @@ -283,8 +193,6 @@ void durations_multiplePickupAndSharedSegments() { 2, 4, List.of(pickup0, pickup1, shared0, shared1), - Duration.ofMinutes(30), - Duration.ofMinutes(40), stopDuration, null ); @@ -316,8 +224,6 @@ void durations_scaleWithStopDuration() { 0, 2, List.of(shared0, shared1), - Duration.ofMinutes(20), - Duration.ofMinutes(22), Duration.ofMinutes(1), null ); @@ -326,8 +232,6 @@ void durations_scaleWithStopDuration() { 0, 2, List.of(shared0, shared1), - Duration.ofMinutes(20), - Duration.ofMinutes(30), Duration.ofMinutes(5), null ); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java index 40ce9d36403..b4e593bb316 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java @@ -268,20 +268,44 @@ void findOptimalInsertion_baselineDurationCalculationFails_returnsNull() { assertNull(result); } + /** + * Given two viable insertion positions with different total trip durations, + * the evaluator should select the one with the shorter total. + * + * Trip: SOUTH → CENTER → NORTH (baseline: 10 + 10 = 20 min) + * Passenger: pickup at EAST, dropoff at WEST + * + * Position (1,2) modified route: SOUTH → EAST → WEST → CENTER → NORTH + * segments: 8 + 4 + 9 + 10(reused) = 31 min + * + * Position (2,3) modified route: SOUTH → CENTER → EAST → WEST → NORTH + * segments: 10(reused) + 3 + 4 + 5 = 22 min ← shorter, should be selected + */ @Test - void findOptimalInsertion_selectsMinimumAdditionalDuration() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); + void findBestInsertion_selectsShorterTotalTripDuration() { + var stop = createStopAt(OSLO_CENTER, Duration.ofMinutes(30)); + var trip = createTripWithStops(OSLO_SOUTH, List.of(stop), OSLO_NORTH, Duration.ofMinutes(30)); + var tripWithVertices = createTripWithVertices(trip); final Map, GraphPath> pathsMap = new HashMap<>( Map.of( + // Baseline segments + new Pair<>(OSLO_SOUTH, OSLO_CENTER), + createGraphPath(Duration.ofMinutes(10)), new Pair<>(OSLO_CENTER, OSLO_NORTH), createGraphPath(Duration.ofMinutes(10)), - new Pair<>(OSLO_CENTER, OSLO_EAST), - createGraphPath(Duration.ofMinutes(4)), + // Position (1,2) new segments + new Pair<>(OSLO_SOUTH, OSLO_EAST), + createGraphPath(Duration.ofMinutes(8)), new Pair<>(OSLO_EAST, OSLO_WEST), - createGraphPath(Duration.ofMinutes(5)), + createGraphPath(Duration.ofMinutes(4)), + new Pair<>(OSLO_WEST, OSLO_CENTER), + createGraphPath(Duration.ofMinutes(9)), + // Position (2,3) new segments + new Pair<>(OSLO_CENTER, OSLO_EAST), + createGraphPath(Duration.ofMinutes(3)), new Pair<>(OSLO_WEST, OSLO_NORTH), - createGraphPath(Duration.ofMinutes(6)) + createGraphPath(Duration.ofMinutes(5)) ) ); @@ -289,13 +313,25 @@ void findOptimalInsertion_selectsMinimumAdditionalDuration() { CarpoolRouter routingFunction = (from, to) -> pathsMap.get(new Pair<>(getCoordinate(from), getCoordinate(to))); - var result = findOptimalInsertion(trip, OSLO_EAST, OSLO_WEST, routingFunction); + var viablePositions = List.of(new InsertionPosition(1, 2), new InsertionPosition(2, 3)); + + var evaluator = new InsertionEvaluator( + linkingContext, + streetVertexUtils, + routingFunction, + Duration.ZERO + ); + var result = evaluator.findBestInsertion( + tripWithVertices, + viablePositions, + OSLO_EAST, + OSLO_WEST + ); assertNotNull(result); - // Should have selected one of the evaluated insertions - // The exact additional duration depends on which position was evaluated first - assertTrue(result.additionalDuration().compareTo(Duration.ofMinutes(20)) <= 0); - assertTrue(result.additionalDuration().compareTo(Duration.ZERO) > 0); + assertEquals(2, result.pickupPosition()); + assertEquals(3, result.dropoffPosition()); + assertEquals(Duration.ofMinutes(22), result.totalTripDuration()); } @Test @@ -369,27 +405,13 @@ void findOptimalInsertion_insertBetweenTwoPoints_routesAllSegments() { // Note: With real State objects, exact durations will have minor rounding differences // (typically 1-2 seconds per edge due to millisecond rounding in StreetEdge.doTraverse()) - // The baseline should be approximately 10 minutes (within 10 seconds tolerance) - assertTrue( - Math.abs(result.durationBetweenOriginAndDestination().toSeconds() - 600) < 10, - "Baseline should be approximately 10 min (within 10s), got " + - result.durationBetweenOriginAndDestination() - ); - - // CRITICAL: Total duration should be sum of NEW segments, NOT baseline duration + // CRITICAL: Total driving duration should be sum of NEW segments, NOT baseline duration // Total = 3 + 2 + 4 = 9 minutes (approximately, with rounding) // If bug exists, segment A→C would incorrectly use baseline (10 min) → total would be wrong assertTrue( - Math.abs(result.totalDuration().toSeconds() - 540) < 10, - "Total duration should be approximately 9 min (within 10s), got " + result.totalDuration() - ); - - // Additional duration should be negative (this insertion is actually faster!) - // This is realistic for insertions that "shortcut" part of the baseline route - assertTrue( - result.additionalDuration().isNegative(), - "Additional duration should be negative (insertion is faster), got " + - result.additionalDuration() + Math.abs(result.totalTripDuration().toSeconds() - 540) < 10, + "Total driving duration should be approximately 9 min (within 10s), got " + + result.totalTripDuration() ); // Routing was called at least 4 times (1 baseline + 3 new segments minimum) @@ -448,8 +470,7 @@ void findOptimalInsertion_singleSegmentTrip_routesAllNewSegments() { // Routing was called for baseline and new segments assertTrue(callCount[0] >= 4, "Should have called routing at least 4 times"); - // Total duration should be positive - assertTrue(result.totalDuration().compareTo(Duration.ZERO) > 0); - assertTrue(result.durationBetweenOriginAndDestination().compareTo(Duration.ZERO) > 0); + // Total driving duration should be positive + assertTrue(result.totalTripDuration().compareTo(Duration.ZERO) > 0); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index 5994d55c17b..0f48b40177c 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -290,13 +290,10 @@ Configure the SIRI-ET updater to receive trip updates: Represents a driver's journey offering carpool seats: - **id**: Unique trip identifier -- **boardingArea**: Start zone for driver journey -- **alightingArea**: End zone for driver journey -- **startTime**: When driver departs -- **endTime**: When driver arrives (includes deviation budget) -- **deviationBudget**: Extra time driver is willing to spend for passengers +- **startTime**: When the driver departs +- **endTime**: When the driver arrives - **totalCapacity**: Number of seats in the car, including the driver seat -- **stops**: Ordered list of waypoints (includes booked passenger stops) +- **stops**: Ordered list of waypoints; the first stop is the origin, the last is the destination, and booked passenger stops are inserted in between - **provider**: Source system identifier ### CarpoolStop @@ -304,7 +301,12 @@ Represents a driver's journey offering carpool seats: Waypoint along a carpool route: - **coordinate**: Geographic location -- **estimatedArrivalTime**: When driver expects to arrive +- **aimedArrivalTime**: Planned arrival time (null for the origin stop) +- **expectedArrivalTime**: Currently expected arrival time, updated via real-time (null for the origin stop) +- **latestExpectedArrivalTime**: Latest arrival time the driver commits to (null if not provided); used to derive `deviationBudget` +- **aimedDepartureTime**: Planned departure time (null for the destination stop) +- **expectedDepartureTime**: Currently expected departure time (null for the destination stop) +- **deviationBudget**: Extra time the driver is willing to spend on deviations before reaching this stop - **onboardCount**: Number of passengers onboard (including the driver) when departing this stop ### InsertionPosition @@ -319,12 +321,12 @@ Represents a viable pickup/dropoff position pair: Result of finding optimal passenger insertion: - **trip**: The original carpool trip -- **pickupPosition**: Where to insert passenger pickup (index) -- **dropoffPosition**: Where to insert passenger dropoff (index) -- **segments**: Routed path segments for modified route -- **baselineDuration**: Original trip duration -- **totalDuration**: Modified trip duration (with passenger) -- **additionalDuration**: Extra time added (= totalDuration - baselineDuration) +- **pickupPosition**: 0-based index of the passenger's pickup in the modified route +- **dropoffPosition**: 0-based index of the passenger's dropoff in the modified route +- **routeSegments**: Routed path segments forming the complete modified route +- **stopDuration**: Dwell time added at each intermediate stop (from the car routing preferences' `pickupTime`) +- **transitStop**: Passenger's access/egress stop, if any +- **totalTripDuration**: Total trip duration including driving and stop delays, computed from `routeSegments` and `stopDuration` ## Performance Characteristics diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index 5a19bfd43be..2e40f75ff0b 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -17,7 +17,7 @@ * - The original trip * - Insertion positions (where pickup and dropoff occur in the modified route) * - Route segments (all GraphPaths forming the complete modified route) - * - Timing information (baseline and total duration, deviation) + * - Timing information *

    * {@code pickupPosition} and {@code dropoffPosition} are 0-based indices of the passenger's * pickup and dropoff stops in the modified route (the route after the passenger's stops have @@ -28,8 +28,6 @@ public record InsertionCandidate( int pickupPosition, int dropoffPosition, List> routeSegments, - Duration durationBetweenOriginAndDestination, - Duration totalDuration, Duration stopDuration, NearbyStop transitStop, Duration totalTripDuration @@ -39,8 +37,6 @@ public InsertionCandidate( int pickupPosition, int dropoffPosition, List> routeSegments, - Duration durationBetweenOriginAndDestination, - Duration totalDuration, Duration stopDuration, NearbyStop transitStop ) { @@ -49,8 +45,6 @@ public InsertionCandidate( pickupPosition, dropoffPosition, routeSegments, - durationBetweenOriginAndDestination, - totalDuration, stopDuration, transitStop, computeTotalTripDuration(routeSegments, stopDuration) @@ -68,13 +62,6 @@ private static Duration computeTotalTripDuration( return cumulativeDurations[cumulativeDurations.length - 1]; } - /** - * Calculates the additional duration caused by inserting this passenger. - */ - public Duration additionalDuration() { - return totalDuration.minus(durationBetweenOriginAndDestination); - } - /** * Gets the pickup route segment(s) - from boarding to passenger pickup. * Returns all segments before the pickup position. @@ -142,11 +129,11 @@ private static Duration totalSegmentDuration( @Override public String toString() { return String.format( - "InsertionCandidate{trip=%s, pickup@%d, dropoff@%d, additional=%ds, segments=%d}", + "InsertionCandidate{trip=%s, pickup@%d, dropoff@%d, duration=%ds, segments=%d}", trip.getId(), pickupPosition, dropoffPosition, - additionalDuration().getSeconds(), + totalTripDuration.getSeconds(), routeSegments.size() ); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java index 2a339a7e815..195ca0ae8eb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java @@ -1,7 +1,6 @@ package org.opentripplanner.ext.carpooling.routing; import static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateCumulativeDurations; -import static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateDuration; import java.time.Duration; import java.util.ArrayList; @@ -102,22 +101,6 @@ public List findBestInsertions( Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); - GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( - tripWithVertices.vertices().getFirst(), - tripWithVertices.vertices().getLast() - ); - if (pathBetweenOriginAndDestination == null) { - LOG.error( - "Could not route between origin and destination for trip {}", - tripWithVertices.trip().getId() - ); - return List.of(); - } - - Duration durationBetweenOriginAndDestination = calculateDuration( - pathBetweenOriginAndDestination - ); - return tripWithViableAccessEgress .viableAccessEgress() .stream() @@ -136,7 +119,6 @@ public List findBestInsertions( dropOffVertex, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, viableAccessEgress.transitStop() ); }) @@ -182,22 +164,6 @@ public InsertionCandidate findBestInsertion( Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); - GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( - tripWithVertices.vertices().getFirst(), - tripWithVertices.vertices().getLast() - ); - if (pathBetweenOriginAndDestination == null) { - LOG.error( - "Could not route between origin and destination for trip {}", - tripWithVertices.trip().getId() - ); - return null; - } - - Duration durationBetweenOriginAndDestination = calculateDuration( - pathBetweenOriginAndDestination - ); - return findBestInsertion( tripWithVertices, viablePositions, @@ -205,7 +171,6 @@ public InsertionCandidate findBestInsertion( passengerDropoffVertex, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, null ); } @@ -218,11 +183,9 @@ private InsertionCandidate findBestInsertion( Vertex passengerDropoff, GraphPath[] baselineSegments, Duration[] cumulativeDurations, - Duration durationBetweenOriginAndDestination, NearbyStop transitStop ) { InsertionCandidate bestCandidate = null; - Duration minAdditionalDuration = Duration.ofDays(1); for (InsertionPosition position : viablePositions) { InsertionCandidate candidate = evaluateInsertion( @@ -233,7 +196,6 @@ private InsertionCandidate findBestInsertion( passengerDropoff, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, transitStop ); @@ -241,17 +203,16 @@ private InsertionCandidate findBestInsertion( continue; } - Duration additionalDuration = candidate.additionalDuration(); - - // Check if this is the best so far - if (additionalDuration.compareTo(minAdditionalDuration) < 0) { - minAdditionalDuration = additionalDuration; + if ( + bestCandidate == null || + candidate.totalTripDuration().compareTo(bestCandidate.totalTripDuration()) < 0 + ) { bestCandidate = candidate; LOG.debug( - "New best insertion: pickup@{}, dropoff@{}, additional={}s", + "New best insertion: pickup@{}, dropoff@{}, duration={}s", position.pickupPos(), position.dropoffPos(), - additionalDuration.getSeconds() + candidate.totalTripDuration().getSeconds() ); } } @@ -271,7 +232,6 @@ private InsertionCandidate evaluateInsertion( Vertex passengerDropoff, GraphPath[] baselineSegments, Duration[] originalCumulativeDurations, - Duration durationBetweenOriginAndDestination, NearbyStop transitStop ) { List> modifiedSegments = buildModifiedSegments( @@ -287,14 +247,6 @@ private InsertionCandidate evaluateInsertion( return null; } - // Calculate total duration - Duration totalDuration = Duration.ZERO; - for (GraphPath segment : modifiedSegments) { - totalDuration = totalDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - // Check passenger delay constraints Duration[] modifiedCumulativeDurations = calculateCumulativeDurations( modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]), @@ -322,8 +274,6 @@ private InsertionCandidate evaluateInsertion( pickupPos, dropoffPos, modifiedSegments, - durationBetweenOriginAndDestination, - totalDuration, stopDuration, transitStop ); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index d548872073f..d5a3d60e5c5 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -264,7 +264,7 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC ); }) .filter(Objects::nonNull) - .sorted(Comparator.comparing(InsertionCandidate::additionalDuration)) + .sorted(Comparator.comparing(InsertionCandidate::totalTripDuration)) .limit(DEFAULT_MAX_CARPOOL_DIRECT_RESULTS) .toList(); From a042f8febfe7f2c0da7077c68dd710a5e2a3dbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Thu, 16 Apr 2026 20:45:30 +0200 Subject: [PATCH 091/177] reusing satisfiesConstraints in PassengerDelayConstraints in passesBeelineDelayCheck, since the logic for checking deviationBudget was duplicated --- .../PassengerDelayConstraints.java | 17 +++------- .../routing/InsertionPositionFinder.java | 31 ++++++------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java index 74620fbdfa0..f7564ac4df7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java @@ -49,27 +49,20 @@ public static boolean satisfiesConstraints( ) { int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); - Duration originalTime = originalCumulativeDurations[originalIndex]; - Duration modifiedTime = modifiedCumulativeDurations[modifiedIndex]; - Duration delay = modifiedTime.minus(originalTime); - + Duration delay = modifiedCumulativeDurations[modifiedIndex].minus( + originalCumulativeDurations[originalIndex] + ); Duration stopBudget = stops.get(originalIndex).getDeviationBudget(); + if (delay.compareTo(stopBudget) > 0) { LOG.debug( - "Insertion rejected: stop at position {} delayed by {}s (budget: {}s)", + "Stop at position {} delayed by {}s exceeds budget of {}s", originalIndex, delay.getSeconds(), stopBudget.getSeconds() ); return false; } - - LOG.trace( - "Stop at position {} delay: {}s (acceptable, budget: {}s)", - originalIndex, - delay.getSeconds(), - stopBudget.getSeconds() - ); } return true; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index 43e8a2f6d17..ff1417dc9c8 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -3,6 +3,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; import org.opentripplanner.street.geometry.DirectionUtils; @@ -256,27 +257,13 @@ private boolean passesBeelineDelayCheck( stopDuration ); - // Check delays at each existing stop (exclude origin at index 0) - for (int originalIndex = 1; originalIndex < originalCoords.size(); originalIndex++) { - int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); - - Duration originalTime = originalBeelineTimes[originalIndex]; - Duration modifiedTime = modifiedBeelineTimes[modifiedIndex]; - Duration beelineDelay = modifiedTime.minus(originalTime); - - Duration stopBudget = trip.stops().get(originalIndex).getDeviationBudget(); - // If even the optimistic beeline estimate exceeds threshold, actual routing will too - if (beelineDelay.compareTo(stopBudget) > 0) { - LOG.trace( - "Stop at position {} has beeline delay {}s (exceeds {}s budget)", - originalIndex, - beelineDelay.getSeconds(), - stopBudget.getSeconds() - ); - return false; - } - } - - return true; + // If even the optimistic beeline estimate exceeds a stop's budget, actual routing will too + return PassengerDelayConstraints.satisfiesConstraints( + originalBeelineTimes, + modifiedBeelineTimes, + pickupPos, + dropoffPos, + trip.stops() + ); } } From ee9d8f437429201dff60b3aac8a5241027aa733b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Fri, 17 Apr 2026 12:54:55 +0200 Subject: [PATCH 092/177] DefaultCarpoolingServiceDirectTest: fix of problem with resultItinerariesHaveValidStartAndEndTimes --- .../DefaultCarpoolingServiceDirectTest.java | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 420a87f79a7..5896dd7347d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -42,17 +42,27 @@ * These tests use a real street graph to verify the full direct routing pipeline * including filtering, position finding, insertion evaluation, and itinerary mapping. *

    - * Graph layout (going east, ~2km total): + * Graph layout (main road going east, P and Q sit south of the road): *

    - *   A ---- B ----------- C ---- D
    - *   0m   500m          1500m  2000m
    - *    \                         /
    - *     P ------(shortcut)------Q
    + *           500m         1000m         500m
    + *      A ---------- B ----------- C ---------- D
    + *      |\          /               \          /
    + *      | \        / 255         255 \        / 255
    + *      |  \      /                   \      /
    + *      |   P ---------- 1400 ---------- Q
    + *      |            (P-Q shortcut)    /
    + *      +-------------- 1500 ----------+
    + *               (direct A-Q bypass)
      *
    - *   A = tripStart, D = tripEnd (graph intersections)
    - *   P-Q direct shortcut edge exists (bypasses B and C)
    - *   P = passenger pickup (south, between A and B, linked via LinkingContext)
    - *   Q = passenger dropoff (south, between C and D, linked via LinkingContext)
    + *   A = tripStart, D = tripEnd
    + *   P = passenger pickup, connected to both A and B (255m each)
    + *   Q = passenger dropoff, connected to both C and D (255m each)
    + *   P-Q direct shortcut (1400m) beats the main-road P-B-C-Q path (1510m) by 110m,
    + *   so the carpool's shared segment (pickup -> dropoff) routes over it.
    + *   A-Q direct bypass (1500m) is shorter than A-P-Q (255 + 1400 = 1655m), so the
    + *   shortest A->Q path skips the pickup. The carpool itself is still forced to drive
    + *   A->P->Q to pick up the passenger; this edge exists so the test cannot rely on
    + *   "route tripStart to dropoff directly" as a proxy for what the carpool drives.
      * 
    */ class DefaultCarpoolingServiceDirectTest extends GraphRoutingTest { @@ -123,7 +133,8 @@ public void build() { biStreet(B, P, 255); biStreet(C, Q, 255); biStreet(D, Q, 255); - biStreet(P, Q, 1500); + biStreet(P, Q, 1400); + biStreet(A, Q, 1500); biStreet(Q, F, (int) DistanceBasedFilter.DEFAULT_MAX_DISTANCE_METERS + 10000); } } @@ -405,9 +416,14 @@ void resultItinerariesHaveValidStartAndEndTimes() { var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); repository.upsertCarpoolTrip(trip); - // Independently compute driving durations to derive expected start/end times + // The carpool is forced to route via the pickup, so we sum the two segments it actually + // drives (tripStart -> pickup, then pickup -> dropoff) rather than routing tripStart -> dropoff + // directly. The graph includes an A-Q bypass edge whose shortest path skips the pickup, so a + // test that used router.route(tripStart, dropoff) here would not match what the carpool + // drives; this guards against regressing to that shortcut-in-the-test. var router = new CarpoolTreeStreetRouter(); router.addVertex(vertexTripStart, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + router.addVertex(vertexPickup, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); var pathToPickup = router.route(vertexTripStart, vertexPickup); assertNotNull(pathToPickup, "Should route from trip start to pickup"); @@ -416,11 +432,11 @@ void resultItinerariesHaveValidStartAndEndTimes() { pathToPickup.states.getLast().getTime() ); - var pathToDropoff = router.route(vertexTripStart, vertexDropoff); - assertNotNull(pathToDropoff, "Should route from trip start to dropoff"); - var drivingToDropoff = Duration.between( - pathToDropoff.states.getFirst().getTime(), - pathToDropoff.states.getLast().getTime() + var pathPickupToDropoff = router.route(vertexPickup, vertexDropoff); + assertNotNull(pathPickupToDropoff, "Should route from pickup to dropoff"); + var drivingPickupToDropoff = Duration.between( + pathPickupToDropoff.states.getFirst().getTime(), + pathPickupToDropoff.states.getLast().getTime() ); var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); @@ -428,10 +444,7 @@ void resultItinerariesHaveValidStartAndEndTimes() { // Start time is when the car departs from the pickup with the passenger onboard, // i.e. after the boarding dwell at the pickup. var expectedStartTime = departureTime.plus(drivingToPickup).plus(stopDuration); - // End time is start time plus the passenger ride duration. Since the direct route - // from trip start to dropoff goes via pickup, drivingToDropoff == drivingToPickup - // + drivingPickupToDropoff, so end time = departureTime + drivingToDropoff + stopDuration. - var expectedEndTime = departureTime.plus(drivingToDropoff).plus(stopDuration); + var expectedEndTime = expectedStartTime.plus(drivingPickupToDropoff); var results = service.routeDirect(request, linkingContext); @@ -449,7 +462,7 @@ void resultItinerariesHaveValidStartAndEndTimes() { assertEquals( expectedEndTime.toInstant(), itinerary.endTime().toInstant(), - "End time should equal trip departure plus driving time to dropoff plus stop duration at pickup" + "End time should equal start time plus driving time from pickup to dropoff" ); } } From d334ed275718e4fe4ee0819be906f92f0fbc0741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 20 Apr 2026 13:20:04 +0200 Subject: [PATCH 093/177] fixed duplicate call to GeometryUtils in CarpoolItineraryMapper --- .../ext/carpooling/internal/CarpoolItineraryMapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java index 975a5bb5b72..bce22067f7d 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java @@ -179,7 +179,6 @@ public Itinerary toItinerary(CarpoolAccessEgress accessEgress) { .withEndTime(endTime) .withFrom(Place.normal(fromVertex, new NonLocalizedString("Carpool boarding"))) .withTo(Place.normal(toVertex, new NonLocalizedString("Carpool alighting"))) - .withGeometry(GeometryUtils.concatenateLineStrings(allEdges, Edge::getGeometry)) .withDistanceMeters(allEdges.stream().mapToDouble(Edge::getDistanceMeters).sum()) .withGeneralizedCost((int) cost) .withGeometry(geometry) From 613eff8e868dbf83230efffeeb71c6635018995a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 20 Apr 2026 18:06:03 +0200 Subject: [PATCH 094/177] CarpoolItineraryMapper uses driver's real pickup time, dropping max() with request time --- .../DefaultCarpoolingServiceDirectTest.java | 62 +++++++++++++++++++ .../internal/CarpoolItineraryMapper.java | 48 +++++--------- .../service/DefaultCarpoolingService.java | 4 +- .../mapping/RaptorPathToItineraryMapper.java | 5 +- 4 files changed, 79 insertions(+), 40 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 5896dd7347d..8de297b055d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -410,6 +410,68 @@ void routeFollowsIntermediateStopsInsteadOfDirectPath() { ); } + @Test + void itineraryReflectsDriverScheduleWhenTripDepartsBeforeRequestTime() { + // Trip starts 10 min before the passenger's requested time but within the 30-min + // TimeBasedFilter window, so the trip is accepted. The driver arrives at the pickup + // well before the requested time — the question is what the returned itinerary says. + var departureTime = SEARCH_TIME.minusMinutes(10); + var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); + repository.upsertCarpoolTrip(trip); + + var router = new CarpoolTreeStreetRouter(); + router.addVertex(vertexTripStart, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + router.addVertex(vertexPickup, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + + var pathToPickup = router.route(vertexTripStart, vertexPickup); + assertNotNull(pathToPickup); + var drivingToPickup = Duration.between( + pathToPickup.states.getFirst().getTime(), + pathToPickup.states.getLast().getTime() + ); + + var pathPickupToDropoff = router.route(vertexPickup, vertexDropoff); + assertNotNull(pathPickupToDropoff); + var drivingPickupToDropoff = Duration.between( + pathPickupToDropoff.states.getFirst().getTime(), + pathPickupToDropoff.states.getLast().getTime() + ); + + var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); + var stopDuration = request.preferences().car().pickupTime(); + + // The driver's real pickup time is fixed by the trip's schedule. It does NOT shift + // forward just because the passenger requested a later departure — the driver cannot + // wait (committed schedule / other passengers). + var actualPickupTime = departureTime.plus(drivingToPickup).plus(stopDuration); + + // Guard the premise of this test: the requested time is after the real pickup time. + assertTrue( + request.dateTime().isAfter(actualPickupTime.toInstant()), + "Test premise: request time must be after the driver's real pickup time" + ); + + var expectedStartTime = actualPickupTime; + var expectedEndTime = expectedStartTime.plus(drivingPickupToDropoff); + + var results = service.routeDirect(request, linkingContext); + + assertFalse(results.isEmpty(), "Trip within search window should produce a result"); + + var itinerary = results.getFirst(); + assertEquals( + expectedStartTime.toInstant(), + itinerary.startTime().toInstant(), + "Itinerary start time must match the driver's real pickup time, not the passenger's " + + "requested time — the driver cannot wait for the passenger" + ); + assertEquals( + expectedEndTime.toInstant(), + itinerary.endTime().toInstant(), + "Itinerary end time must match the driver's real dropoff time" + ); + } + @Test void resultItinerariesHaveValidStartAndEndTimes() { var departureTime = SEARCH_TIME.plusMinutes(10); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java index bce22067f7d..9666f0813d5 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java @@ -1,6 +1,5 @@ package org.opentripplanner.ext.carpooling.internal; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import javax.annotation.Nullable; @@ -10,10 +9,8 @@ import org.opentripplanner.ext.carpooling.model.CarpoolLeg; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; import org.opentripplanner.ext.carpooling.routing.InsertionCandidate; -import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; -import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; @@ -41,14 +38,11 @@ * *

    Time Calculation

    *

    - * The passenger's start time is the later of: - *

      - *
    1. The passenger's requested departure time
    2. - *
    3. When the car departs with the passenger (driver arrival + boarding time)
    4. - *
    - *

    - * This ensures the itinerary reflects realistic timing: passengers can't board before the - * driver arrives, but they also won't board earlier than they wanted to depart. + * The passenger's start time is always the driver's real pickup time (trip start + pickup + * travel + boarding dwell). It is not shifted to match the passenger's requested + * departure time: the driver is on a committed schedule and cannot wait. Whether the + * passenger should show up early, or whether a trip starting before the requested time + * should be matched at all, is a filtering concern and lives upstream of this mapper. * *

    Geometry and Cost

    *

    @@ -70,25 +64,17 @@ */ public class CarpoolItineraryMapper { - private final ZoneId timeZone; private final ZonedDateTime transitSearchTimeZero; /** - * Creates a new carpool itinerary mapper with the specified timezone. - *

    - * The timezone is used to convert passenger requested departure times from Instant to - * ZonedDateTime for comparison with driver pickup times. - *

    - * @param timeZone the timezone for time conversions, typically from TransitService.getTimeZone() - * @param transitSearchTimeZero the base time for access egress requests. It is not used for access / egress + * @param transitSearchTimeZero the base time for access egress requests; not used for direct */ - public CarpoolItineraryMapper(ZoneId timeZone, ZonedDateTime transitSearchTimeZero) { - this.timeZone = ZoneIdFallback.zoneId(timeZone); + public CarpoolItineraryMapper(ZonedDateTime transitSearchTimeZero) { this.transitSearchTimeZero = transitSearchTimeZero; } - public CarpoolItineraryMapper(ZoneId timeZone) { - this(timeZone, null); + public CarpoolItineraryMapper() { + this(null); } /** @@ -100,11 +86,10 @@ public CarpoolItineraryMapper(ZoneId timeZone) { * *

    Time Calculation Details

    *

    - * The method calculates three key times: + * Start and end times come entirely from the driver's schedule: *

      - *
    1. Departure with passenger: Driver's start time + pickup travel + boarding time
    2. - *
    3. Passenger start: max(requested time, departure with passenger time)
    4. - *
    5. Passenger end: start time + shared segment durations
    6. + *
    7. Start: Driver's start time + pickup travel + boarding time
    8. + *
    9. End: Start time + shared segment durations
    10. *
    * *

    Null Return Cases

    @@ -112,27 +97,22 @@ public CarpoolItineraryMapper(ZoneId timeZone) { * Returns {@code null} if the candidate has no shared segments, which should never happen * for valid insertion candidates but serves as a safety check. * - * @param request the original routing request containing passenger preferences and timing * @param candidate the insertion candidate containing route segments and trip details * @return an itinerary with a single carpool leg, or null if shared segments are empty * (should not occur for valid candidates) */ @Nullable - public Itinerary toItinerary(RouteRequest request, InsertionCandidate candidate) { + public Itinerary toItinerary(InsertionCandidate candidate) { var sharedSegments = candidate.getSharedSegments(); if (sharedSegments.isEmpty()) { return null; } - var driverPickupTime = candidate + var startTime = candidate .trip() .startTime() .plus(candidate.getDurationUntilDepartureWithPassenger()); - var startTime = request.dateTime().isAfter(driverPickupTime.toInstant()) - ? request.dateTime().atZone(timeZone) - : driverPickupTime; - var endTime = startTime.plus(candidate.getPassengerRideDuration()); var firstSegment = sharedSegments.getFirst(); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index d5a3d60e5c5..14737e9c605 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -130,7 +130,7 @@ public DefaultCarpoolingService( this.repository = repository; this.streetLimitationParametersService = streetLimitationParametersService; this.preFilters = FilterChain.standard(); - this.itineraryMapper = new CarpoolItineraryMapper(transitService.getTimeZone()); + this.itineraryMapper = new CarpoolItineraryMapper(); this.positionFinder = new InsertionPositionFinder(new BeelineEstimator()); this.vertexLinker = vertexLinker; } @@ -272,7 +272,7 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC itineraries = insertionCandidates .stream() - .map(candidate -> itineraryMapper.toItinerary(request, candidate)) + .map(itineraryMapper::toItinerary) .filter(Objects::nonNull) .toList(); } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index dd2e9fe8e50..be7afe6ee0c 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -102,10 +102,7 @@ public RaptorPathToItineraryMapper( graph.ellipsoidToGeoidDifference ); this.transitService = transitService; - this.carpoolItineraryMapper = new CarpoolItineraryMapper( - transitService.getTimeZone(), - transitSearchTimeZero - ); + this.carpoolItineraryMapper = new CarpoolItineraryMapper(transitSearchTimeZero); } public Itinerary createItinerary(RaptorPath path) { From 0506f8fa51d4d8c1413b0901bf3dfb1ffef4916a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 07:17:59 +0200 Subject: [PATCH 095/177] Remove route names propagation --- .../graph_builder/module/osm/OsmDatabase.java | 32 ------------------- .../opentripplanner/osm/model/OsmEntity.java | 6 ---- .../osm/tagmapping/OsmTagMapper.java | 1 - .../main/resources/WayProperties.properties | 1 - .../resources/WayProperties_de.properties | 1 - .../resources/WayProperties_fi.properties | 1 - .../resources/WayProperties_fr.properties | 1 - .../resources/WayProperties_hu.properties | 1 - .../resources/WayProperties_nl.properties | 1 - .../resources/WayProperties_no.properties | 1 - .../resources/WayProperties_sv.properties | 1 - 11 files changed, 47 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 067919a3c60..9ff1b023bab 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 @@ -35,7 +35,6 @@ import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.osm.model.OsmRelation; import org.opentripplanner.osm.model.OsmRelationMember; -import org.opentripplanner.osm.model.OsmTag; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.HashGridSpatialIndex; @@ -936,37 +935,6 @@ private void processRestriction(OsmRelation relation) { * Handle route=road and route=bicycle relations. */ private void processRoute(OsmRelation relation) { - for (OsmRelationMember member : relation.getMembers()) { - if (!(member.hasTypeWay() && waysById.containsKey(member.getRef()))) { - continue; - } - - OsmWay way = waysById.get(member.getRef()); - if (way == null) { - continue; - } - - if (relation.hasTag("name")) { - if (way.hasTag("otp:route_name")) { - way.addTag( - "otp:route_name", - addUniqueName(way.getTag("otp:route_name"), relation.getTag("name")) - ); - } else { - way.addTag(new OsmTag("otp:route_name", relation.getTag("name"))); - } - } - if (relation.hasTag("ref")) { - if (way.hasTag("otp:route_ref")) { - way.addTag( - "otp:route_ref", - addUniqueName(way.getTag("otp:route_ref"), relation.getTag("ref")) - ); - } else { - way.addTag(new OsmTag("otp:route_ref", relation.getTag("ref"))); - } - } - } processBicycleRoute(relation); } diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index a5af1711504..671ad884213 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -534,15 +534,9 @@ public I18NString getAssumedName() { false ); } - if (tags.containsKey("otp:route_name")) { - return new NonLocalizedString(tags.get("otp:route_name")); - } if (this.creativeName != null) { return this.creativeName; } - if (tags.containsKey("otp:route_ref")) { - return new NonLocalizedString(tags.get("otp:route_ref")); - } if (tags.containsKey("ref")) { return new NonLocalizedString(tags.get("ref")); } 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 793c345b501..dedc01f2b4a 100644 --- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java +++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java @@ -404,7 +404,6 @@ static void populateNotesAndNames(WayPropertySetBuilder props) { props.createNames("indoor=area", "name.indoor_area"); // 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"); diff --git a/domain-core/src/main/resources/WayProperties.properties b/domain-core/src/main/resources/WayProperties.properties index 64f1b11312f..b3febc025a5 100644 --- a/domain-core/src/main/resources/WayProperties.properties +++ b/domain-core/src/main/resources/WayProperties.properties @@ -11,7 +11,6 @@ name.bridleway=bridleway name.corridor=corridor name.indoor_area=indoor area -name.otp_route_ref=Route {otp:route_ref} name.platform_ref=Platform {ref} name.platform=platform diff --git a/domain-core/src/main/resources/WayProperties_de.properties b/domain-core/src/main/resources/WayProperties_de.properties index 1ea1ada4c15..71e040d661c 100644 --- a/domain-core/src/main/resources/WayProperties_de.properties +++ b/domain-core/src/main/resources/WayProperties_de.properties @@ -12,7 +12,6 @@ name.bridleway=Reitweg name.corridor=Korridor name.indoor_area=Innenbereich -name.otp_route_ref=Route {otp:route_ref} name.platform_ref=Plattform {ref} name.platform=Plattform diff --git a/domain-core/src/main/resources/WayProperties_fi.properties b/domain-core/src/main/resources/WayProperties_fi.properties index 8ab02ec91e2..3cd50db8960 100644 --- a/domain-core/src/main/resources/WayProperties_fi.properties +++ b/domain-core/src/main/resources/WayProperties_fi.properties @@ -11,7 +11,6 @@ name.bridleway=ratsupolku name.corridor=käytävä name.indoor_area=sisätila -name.otp_route_ref=linja {otp:route_ref} name.platform_ref=laituri {ref} name.platform=laituri diff --git a/domain-core/src/main/resources/WayProperties_fr.properties b/domain-core/src/main/resources/WayProperties_fr.properties index 57b7a970e5c..9aea533b20b 100644 --- a/domain-core/src/main/resources/WayProperties_fr.properties +++ b/domain-core/src/main/resources/WayProperties_fr.properties @@ -12,7 +12,6 @@ name.bridleway=sentier \u00e9questre name.corridor=couloir name.indoor_area=zone int\u00e9rieure -name.otp_route_ref=Ligne {otp:route_ref} name.platform_ref=Plate-forme {ref} name.platform=plate-forme diff --git a/domain-core/src/main/resources/WayProperties_hu.properties b/domain-core/src/main/resources/WayProperties_hu.properties index 1163d9f96be..1cc2980322e 100644 --- a/domain-core/src/main/resources/WayProperties_hu.properties +++ b/domain-core/src/main/resources/WayProperties_hu.properties @@ -11,7 +11,6 @@ name.bridleway=nyomvonal name.corridor=folyoso name.indoor_area=belteri terulet -name.otp_route_ref={otp:route_ref} name.platform_ref={ref} name.platform=kocsi\u00E1ll\u00E1s / v\u00E1g\u00E1ny diff --git a/domain-core/src/main/resources/WayProperties_nl.properties b/domain-core/src/main/resources/WayProperties_nl.properties index 29991f07a4e..381956f56d2 100644 --- a/domain-core/src/main/resources/WayProperties_nl.properties +++ b/domain-core/src/main/resources/WayProperties_nl.properties @@ -12,7 +12,6 @@ name.bridleway=ruiterpad name.corridor=gang name.indoor_area=binnenruimte -name.otp_route_ref=Lijn {otp:route_ref} name.platform_ref=Spoor {ref} name.platform=spoor diff --git a/domain-core/src/main/resources/WayProperties_no.properties b/domain-core/src/main/resources/WayProperties_no.properties index 6a3c406ea83..ae211f90ead 100644 --- a/domain-core/src/main/resources/WayProperties_no.properties +++ b/domain-core/src/main/resources/WayProperties_no.properties @@ -11,7 +11,6 @@ name.bridleway=bridleway name.corridor=korridor name.indoor_area=innendors omrade -name.otp_route_ref=Rute {otp:route_ref} name.platform_ref=Plattform {ref} name.platform=plattform diff --git a/domain-core/src/main/resources/WayProperties_sv.properties b/domain-core/src/main/resources/WayProperties_sv.properties index 810251b57af..159dc43dc4e 100644 --- a/domain-core/src/main/resources/WayProperties_sv.properties +++ b/domain-core/src/main/resources/WayProperties_sv.properties @@ -11,7 +11,6 @@ name.bridleway=ridvägem name.corridor=korridor name.indoor_area=inomhusomrade -name.otp_route_ref=linje {otp:route_ref} name.platform_ref=plattform {ref} name.platform=plattformen From f858f7f126dfc5e30fde9ac8bfcc6274c9925f22 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 08:58:38 +0200 Subject: [PATCH 096/177] Simplify code --- .../graph_builder/module/osm/OsmDatabase.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 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 9ff1b023bab..b1f78f70f91 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 @@ -805,19 +805,6 @@ private void processRelations() { } } - /** - * Handle route=bicycle relations. Copies their network type to all way members. - * - * @see "https://wiki.openstreetmap.org/wiki/Tag:route%3Dbicycle" - */ - private void processBicycleRoute(OsmRelation relation) { - if (relation.isBicycleRoute()) { - // we treat networks without known network type like local networks - var network = relation.getTagOpt("network").orElse("lcn"); - setNetworkForAllMembers(relation, network); - } - } - private void setNetworkForAllMembers(OsmRelation relation, String key) { relation .getMembers() @@ -935,7 +922,11 @@ private void processRestriction(OsmRelation relation) { * Handle route=road and route=bicycle relations. */ private void processRoute(OsmRelation relation) { - processBicycleRoute(relation); + if (relation.isBicycleRoute()) { + // we treat networks without known network type like local networks + var network = relation.getTagOpt("network").orElse("lcn"); + setNetworkForAllMembers(relation, network); + } } /** From 7a7b10825ac85e36d61a9b8b33b154160e7bae78 Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Tue, 21 Apr 2026 10:14:20 +0200 Subject: [PATCH 097/177] docs: Apply PR review rewording AccessEgressFetcher javadoc --- .../algorithm/raptoradapter/router/AccessEgressFetcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java index 4674236d4ad..c9d33afb7e9 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java @@ -46,7 +46,7 @@ class AccessEgressFetcher { * @param transitSearchTimeZero the point in time all times in seconds are counted from * @param additionalSearchDays extra search days beyond the departure day, required for flex * routing - * @param linkingContext context for linking origin/destination to the street network + * @param linkingContext contains temporary vertices for request locations. */ public AccessEgressFetcher( RouteRequest request, From bfe71154de892ed6066d9436751fc3699cf8ae5b Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 21 Apr 2026 10:22:32 +0200 Subject: [PATCH 098/177] Apply review feedback: package layout, strategy naming, and log formatting - Move WarmupParameters and WarmupApi to org.opentripplanner.warmup.api so the warmup module's public types live in an api subpackage. - Rename interface WarmupQueryExecutor to WarmupQueryStrategy; the name matches the Strategy pattern the worker uses. - Introduce ModeCombinations, a small helper shared by both executors that holds the access/egress mode lists and maps a query counter to a mode pair via modulo. WarmupQueryStrategy.execute() now takes the running queryCount, so the worker no longer computes a modeIndex. - Use DurationUtils.durationToStr() for log timings instead of raw milliseconds, so durations render as "1s200ms" rather than e.g. "1200 ms" (and never leak nanoseconds). - Rewrite the package.md design section to reflect the new layout and fix the inaccurate SPI wording (WarmupParameters is a record, not an interface). --- .../standalone/config/RouterConfig.java | 2 +- .../config/routerconfig/WarmupConfig.java | 4 +- .../warmup/GtfsWarmupQueryExecutor.java | 29 ++++-------- .../warmup/ModeCombinations.java | 44 +++++++++++++++++++ .../warmup/TransmodelWarmupQueryExecutor.java | 25 ++++------- .../warmup/WarmupLauncher.java | 1 + .../warmup/WarmupQueryExecutor.java | 12 ----- .../warmup/WarmupQueryStrategy.java | 14 ++++++ .../opentripplanner/warmup/WarmupWorker.java | 33 ++++++++------ .../warmup/{ => api}/WarmupApi.java | 2 +- .../warmup/{ => api}/WarmupParameters.java | 2 +- .../warmup/configure/WarmupModule.java | 2 +- .../org/opentripplanner/warmup/package.md | 13 +++--- 13 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java delete mode 100644 application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java create mode 100644 application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java rename application/src/main/java/org/opentripplanner/warmup/{ => api}/WarmupApi.java (93%) rename application/src/main/java/org/opentripplanner/warmup/{ => api}/WarmupParameters.java (93%) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java index ccd3e079ee4..6866d371ba9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java @@ -28,7 +28,7 @@ import org.opentripplanner.standalone.config.sandbox.GtfsApiConfig; import org.opentripplanner.standalone.config.sandbox.TransmodelAPIConfig; import org.opentripplanner.updater.UpdatersParameters; -import org.opentripplanner.warmup.WarmupParameters; +import org.opentripplanner.warmup.api.WarmupParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java index 41d8eaadd9f..e63f8477afb 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -8,8 +8,8 @@ import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.model.StreetMode; -import org.opentripplanner.warmup.WarmupApi; -import org.opentripplanner.warmup.WarmupParameters; +import org.opentripplanner.warmup.api.WarmupApi; +import org.opentripplanner.warmup.api.WarmupParameters; /** * Maps the {@code warmup} section of {@code router-config.json} into a {@link WarmupParameters} diff --git a/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java index 8b57077f794..33a8ee0bd7c 100644 --- a/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java +++ b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java @@ -18,7 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class GtfsWarmupQueryExecutor implements WarmupQueryExecutor { +class GtfsWarmupQueryExecutor implements WarmupQueryStrategy { private static final Logger LOG = LoggerFactory.getLogger(GtfsWarmupQueryExecutor.class); @@ -55,8 +55,7 @@ class GtfsWarmupQueryExecutor implements WarmupQueryExecutor { private final GraphQL graphQL; private final GraphQLRequestContext requestContext; - private final List accessModes; - private final List egressModes; + private final ModeCombinations modeCombinations; GtfsWarmupQueryExecutor( OtpServerRequestContext context, @@ -67,24 +66,12 @@ class GtfsWarmupQueryExecutor implements WarmupQueryExecutor { this.graphQL = GraphQL.newGraphQL(context.gtfsSchema()) .defaultDataFetcherExceptionHandler(new OtpDataFetcherExceptionHandler()) .build(); - this.accessModes = accessModes - .stream() - .map(m -> AccessModeMapper.map(m).name()) - .toList(); - this.egressModes = egressModes - .stream() - .map(m -> EgressModeMapper.map(m).name()) - .toList(); + this.modeCombinations = new ModeCombinations(accessModes, egressModes); } @Override - public int modeCombinationCount() { - return accessModes.size(); - } - - @Override - public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex) { - var variables = buildVariables(from, to, arriveBy, modeIndex); + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount) { + var variables = buildVariables(from, to, arriveBy, queryCount); var input = ExecutionInput.newExecutionInput() .query(QUERY) @@ -105,7 +92,7 @@ Map buildVariables( WgsCoordinate from, WgsCoordinate to, boolean arriveBy, - int modeIndex + int queryCount ) { var now = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); var dateTime = arriveBy ? Map.of("latestArrival", now) : Map.of("earliestDeparture", now); @@ -116,8 +103,8 @@ Map buildVariables( Map.entry("toLat", to.latitude()), Map.entry("toLon", to.longitude()), Map.entry("dateTime", dateTime), - Map.entry("accessMode", accessModes.get(modeIndex % accessModes.size())), - Map.entry("egressMode", egressModes.get(modeIndex % egressModes.size())) + Map.entry("accessMode", AccessModeMapper.map(modeCombinations.access(queryCount)).name()), + Map.entry("egressMode", EgressModeMapper.map(modeCombinations.egress(queryCount)).name()) ); } } diff --git a/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java b/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java new file mode 100644 index 00000000000..6b83fa49733 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java @@ -0,0 +1,44 @@ +package org.opentripplanner.warmup; + +import java.util.List; +import org.opentripplanner.street.model.StreetMode; + +/** + * Access/egress mode pairs to cycle through during warmup. Entries {@code i} of the access and + * egress lists form one pair. The cycle is driven by a caller-supplied counter; modulo arithmetic + * maps the counter to a pair so warmup queries iterate through every combination before repeating. + */ +class ModeCombinations { + + private final List accessModes; + private final List egressModes; + + ModeCombinations(List accessModes, List egressModes) { + if (accessModes.size() != egressModes.size()) { + throw new IllegalArgumentException( + "accessModes and egressModes must have the same size, got %d and %d.".formatted( + accessModes.size(), + egressModes.size() + ) + ); + } + this.accessModes = List.copyOf(accessModes); + this.egressModes = List.copyOf(egressModes); + } + + int size() { + return accessModes.size(); + } + + StreetMode access(int counter) { + return accessModes.get(indexFor(counter)); + } + + StreetMode egress(int counter) { + return egressModes.get(indexFor(counter)); + } + + private int indexFor(int counter) { + return Math.floorMod(counter - 1, size()); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java index d51bf71195b..4cfb6f85d85 100644 --- a/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java +++ b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class TransmodelWarmupQueryExecutor implements WarmupQueryExecutor { +class TransmodelWarmupQueryExecutor implements WarmupQueryStrategy { private static final Logger LOG = LoggerFactory.getLogger(TransmodelWarmupQueryExecutor.class); @@ -49,8 +49,7 @@ class TransmodelWarmupQueryExecutor implements WarmupQueryExecutor { private final OtpServerRequestContext serverContext; private final TransmodelRequestContext requestContext; private final GraphQLSchema schema; - private final List accessModes; - private final List egressModes; + private final ModeCombinations modeCombinations; TransmodelWarmupQueryExecutor( OtpServerRequestContext context, @@ -65,18 +64,12 @@ class TransmodelWarmupQueryExecutor implements WarmupQueryExecutor { context.transitService(), context.empiricalDelayService() ); - this.accessModes = accessModes.stream().map(this::toGraphQLName).toList(); - this.egressModes = egressModes.stream().map(this::toGraphQLName).toList(); + this.modeCombinations = new ModeCombinations(accessModes, egressModes); } @Override - public int modeCombinationCount() { - return accessModes.size(); - } - - @Override - public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex) { - var variables = buildVariables(from, to, arriveBy, modeIndex); + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount) { + var variables = buildVariables(from, to, arriveBy, queryCount); var input = ExecutionInput.newExecutionInput() .query(QUERY) @@ -98,7 +91,7 @@ public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, i } } - private String toGraphQLName(StreetMode mode) { + private static String toGraphQLName(StreetMode mode) { return EnumTypes.STREET_MODE.getValues() .stream() .filter(v -> v.getValue() == mode) @@ -111,7 +104,7 @@ Map buildVariables( WgsCoordinate from, WgsCoordinate to, boolean arriveBy, - int modeIndex + int queryCount ) { return Map.of( "fromLat", @@ -125,9 +118,9 @@ Map buildVariables( "arriveBy", arriveBy, "accessMode", - accessModes.get(modeIndex % accessModes.size()), + toGraphQLName(modeCombinations.access(queryCount)), "egressMode", - egressModes.get(modeIndex % egressModes.size()) + toGraphQLName(modeCombinations.egress(queryCount)) ); } } diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java index 5189955a382..9c8f7e69bd2 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java @@ -4,6 +4,7 @@ import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.service.TimetableRepository; import org.opentripplanner.updater.GraphUpdaterManager; +import org.opentripplanner.warmup.api.WarmupParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java deleted file mode 100644 index 59ba2d7ba4b..00000000000 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupQueryExecutor.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opentripplanner.warmup; - -import org.opentripplanner.street.geometry.WgsCoordinate; - -/** Strategy for executing warmup queries against a specific GraphQL API. */ -interface WarmupQueryExecutor { - /** The number of distinct access/egress mode combinations to cycle through. */ - int modeCombinationCount(); - - /** @return true if the query executed without GraphQL errors. */ - boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int modeIndex); -} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java new file mode 100644 index 00000000000..3a3fe2e4652 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java @@ -0,0 +1,14 @@ +package org.opentripplanner.warmup; + +import org.opentripplanner.street.geometry.WgsCoordinate; + +/** Strategy for executing warmup queries against a specific GraphQL API. */ +interface WarmupQueryStrategy { + /** + * Execute one warmup query. The strategy picks access/egress modes and any other per-query + * parameters from the given {@code queryCount}, so the caller only needs a running counter. + * + * @return true if the query executed without GraphQL errors. + */ + boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount); +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java index 08a0a04f326..da90f05dca7 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java @@ -5,6 +5,8 @@ import java.util.function.Supplier; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.updater.GraphUpdaterStatus; +import org.opentripplanner.utils.time.DurationUtils; +import org.opentripplanner.warmup.api.WarmupParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +29,7 @@ class WarmupWorker implements Runnable { private static final int MAX_QUERIES = 20; private final WarmupParameters parameters; - private final WarmupQueryExecutor queryExecutor; + private final WarmupQueryStrategy queryStrategy; private final Supplier updaterStatusProvider; WarmupWorker( @@ -37,7 +39,7 @@ class WarmupWorker implements Runnable { ) { this.parameters = parameters; this.updaterStatusProvider = updaterStatusProvider; - this.queryExecutor = switch (parameters.api()) { + this.queryStrategy = switch (parameters.api()) { case TRANSMODEL -> new TransmodelWarmupQueryExecutor( serverContext, parameters.accessModes(), @@ -68,28 +70,27 @@ public void run() { while (queryCount < MAX_QUERIES) { if (isHealthy()) { LOG.info( - "Application warmup complete: {} queries ({} failures) in {} ms. All updaters primed.", + "Application warmup complete: {} queries ({} failures) in {}. All updaters primed.", queryCount, failureCount, - Duration.between(startTime, Instant.now()).toMillis() + DurationUtils.durationToStr(Duration.between(startTime, Instant.now())) ); return; } queryCount++; boolean arriveBy = queryCount % 2 == 0; - int modeIndex = (queryCount - 1) % queryExecutor.modeCombinationCount(); - if (!executeQuery(queryCount, arriveBy, modeIndex)) { + if (!executeQuery(queryCount, arriveBy)) { failureCount++; } } LOG.info( - "Application warmup reached maximum of {} queries ({} failures) in {} ms" + + "Application warmup reached maximum of {} queries ({} failures) in {}" + " before all updaters were primed.", MAX_QUERIES, failureCount, - Duration.between(startTime, Instant.now()).toMillis() + DurationUtils.durationToStr(Duration.between(startTime, Instant.now())) ); } catch (Throwable e) { LOG.error("Application warmup terminated by error after {} queries.", queryCount, e); @@ -97,24 +98,28 @@ public void run() { } /** @return true if the query succeeded without errors, false otherwise. */ - private boolean executeQuery(int queryCount, boolean arriveBy, int modeIndex) { + private boolean executeQuery(int queryCount, boolean arriveBy) { var queryStart = Instant.now(); try { - boolean success = queryExecutor.execute( + boolean success = queryStrategy.execute( parameters.from(), parameters.to(), arriveBy, - modeIndex + queryCount ); var elapsed = Duration.between(queryStart, Instant.now()); - LOG.info("Warmup query #{} completed in {} ms.", queryCount, elapsed.toMillis()); + LOG.info( + "Warmup query #{} completed in {}.", + queryCount, + DurationUtils.durationToStr(elapsed) + ); return success; } catch (Exception e) { var elapsed = Duration.between(queryStart, Instant.now()); LOG.info( - "Warmup query #{} failed in {} ms: {}", + "Warmup query #{} failed in {}: {}", queryCount, - elapsed.toMillis(), + DurationUtils.durationToStr(elapsed), e.getMessage() ); LOG.debug("Warmup query #{} exception detail", queryCount, e); diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupApi.java b/application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java similarity index 93% rename from application/src/main/java/org/opentripplanner/warmup/WarmupApi.java rename to application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java index 91eb1e70749..96cf590c2d9 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupApi.java +++ b/application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java @@ -1,4 +1,4 @@ -package org.opentripplanner.warmup; +package org.opentripplanner.warmup.api; import org.opentripplanner.core.model.doc.DocumentedEnum; diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java b/application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java similarity index 93% rename from application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java rename to application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java index 63a550f53f9..e0aec217f97 100644 --- a/application/src/main/java/org/opentripplanner/warmup/WarmupParameters.java +++ b/application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java @@ -1,4 +1,4 @@ -package org.opentripplanner.warmup; +package org.opentripplanner.warmup.api; import java.util.List; import org.opentripplanner.street.geometry.WgsCoordinate; diff --git a/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java index d31803b7381..ac553f9d2aa 100644 --- a/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java +++ b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java @@ -8,7 +8,7 @@ import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.transit.service.TimetableRepository; import org.opentripplanner.warmup.WarmupLauncher; -import org.opentripplanner.warmup.WarmupParameters; +import org.opentripplanner.warmup.api.WarmupParameters; /** * Dagger wiring for the application warmup feature. diff --git a/application/src/main/java/org/opentripplanner/warmup/package.md b/application/src/main/java/org/opentripplanner/warmup/package.md index 73756f72136..a750913b469 100644 --- a/application/src/main/java/org/opentripplanner/warmup/package.md +++ b/application/src/main/java/org/opentripplanner/warmup/package.md @@ -17,16 +17,17 @@ arrives. ## Design -`WarmupParameters` is the SPI consumed by the module. `WarmupConfig` -(in `standalone.config.routerconfig`) implements this interface and maps the JSON config section -into parameter values. +The public API of the module lives in the `api` subpackage and exposes only value objects: +`WarmupParameters` (the configured parameter values) and `WarmupApi` (which GraphQL API to exercise). +`WarmupConfig` (in `standalone.config.routerconfig`) reads the JSON config section and produces a +`WarmupParameters` instance. -`WarmupQueryExecutor` is the strategy interface with two implementations: +`WarmupQueryStrategy` is the strategy interface with two implementations: - `TransmodelWarmupQueryExecutor` -- builds and executes TransModel `trip` queries. - `GtfsWarmupQueryExecutor` -- builds and executes GTFS `planConnection` queries. -Each executor receives configurable access/egress mode lists (`StreetMode` values) and maps them -to the API-specific GraphQL enum names. +Each executor owns a `ModeCombinations` helper that holds the configured access/egress mode lists +and maps a running query counter to the next access/egress pair via modulo arithmetic. ## Configuration From ae0b58e5343bc87a386a4f221d2ac79ca2b2d0c6 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Tue, 21 Apr 2026 10:34:53 +0000 Subject: [PATCH 099/177] Upgrade debug client to version 2026/04/2026-04-21T10:34 --- 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 ec630252590..12e8ecb4b28 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug - - + +
    From 63156bbeff05519cd569dfa69ae13cbf44f24832 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 21 Apr 2026 10:35:04 +0000 Subject: [PATCH 100/177] Add changelog entry for #7494 [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 863b391d7e1..06b5bb11730 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -24,6 +24,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Improve SpeedTest warm-up [#7534](https://github.com/opentripplanner/OpenTripPlanner/pull/7534) - Remove `GraphVisualizer` and inefficient methods in `ShortestPathTree` [#7526](https://github.com/opentripplanner/OpenTripPlanner/pull/7526) - Fix cost mismatch by anchoring pattern-ride pareto cost to last stop [#7504](https://github.com/opentripplanner/OpenTripPlanner/pull/7504) +- Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 58219945ccd90937845bc62ad717a824c636a1ad Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 21 Apr 2026 12:18:50 +0000 Subject: [PATCH 101/177] Add changelog entry for #7508 [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 06b5bb11730..1afa6f61d2c 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -25,6 +25,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Remove `GraphVisualizer` and inefficient methods in `ShortestPathTree` [#7526](https://github.com/opentripplanner/OpenTripPlanner/pull/7526) - Fix cost mismatch by anchoring pattern-ride pareto cost to last stop [#7504](https://github.com/opentripplanner/OpenTripPlanner/pull/7504) - Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) +- De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 5d5ca84060375266a012a52ef96ff1a6cc6b6f50 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 21 Apr 2026 14:31:05 +0100 Subject: [PATCH 102/177] simplify tests --- .../osm/model/NodeBuilder.java | 23 ++++- .../osm/model/RelationBuilder.java | 17 ++-- .../osm/moduletests/StationEntrancesTest.java | 85 ++++++++----------- 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java index cd1284fded6..81efd5fb52a 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java @@ -4,9 +4,28 @@ public class NodeBuilder { + private final OsmNode node; + + private NodeBuilder(OsmNode node) { + this.node = node; + } + public static OsmNode node(long id, WgsCoordinate wgsCoordinate) { - var node = new OsmNode(wgsCoordinate.latitude(), wgsCoordinate.longitude()); - node.setId(id); + return of(id, wgsCoordinate).build(); + } + + public static NodeBuilder of(long id, WgsCoordinate wgsCoordinate) { + var builder = new NodeBuilder(new OsmNode(wgsCoordinate.latitude(), wgsCoordinate.longitude())); + builder.node.setId(id); + return builder; + } + + public NodeBuilder withTag(String key, String value) { + node.addTag(key, value); + return this; + } + + public OsmNode build() { return node; } } diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java index 784de04d1d0..f7ec670535b 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java @@ -5,19 +5,24 @@ public class RelationBuilder { private final OsmRelation relation = new OsmRelation(); public static RelationBuilder ofMultiPolygon() { - var builder = new RelationBuilder(); - builder.relation.addTag("type", "multipolygon"); - builder.relation.addTag("highway", "pedestrian"); - return builder; + return ofType("multipolygon").withTag("highway", "pedestrian"); } public static RelationBuilder ofTurnRestriction(String restrictionType) { + return ofType("restriction").withTag("restriction", restrictionType); + } + + public static RelationBuilder ofType(String type) { var builder = new RelationBuilder(); - builder.relation.addTag("type", "restriction"); - builder.relation.addTag("restriction", restrictionType); + builder.relation.addTag("type", type); return builder; } + public RelationBuilder withTag(String key, String value) { + relation.addTag(key, value); + return this; + } + public RelationBuilder withWayMember(long id, String role) { return withMember(id, role, OsmMemberType.WAY); } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java index 22de5df9e05..2adc8b6bea1 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java @@ -3,17 +3,16 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import java.util.List; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.OsmModuleTestFactory; import org.opentripplanner.osm.TestOsmProvider; -import org.opentripplanner.osm.model.OsmMemberType; +import org.opentripplanner.osm.model.NodeBuilder; import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.osm.model.OsmRelation; -import org.opentripplanner.osm.model.OsmRelationMember; -import org.opentripplanner.osm.model.OsmWay; +import org.opentripplanner.osm.model.RelationBuilder; +import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.street.model.vertex.OsmVertex; import org.opentripplanner.street.model.vertex.StationEntranceVertex; @@ -21,56 +20,40 @@ public class StationEntrancesTest { private static final Graph GRAPH = new Graph(); - private static final OsmNode ENTRANCE_IN_STOP_AREA = new OsmNode(0, 0); - - static { - ENTRANCE_IN_STOP_AREA.setId(1); - ENTRANCE_IN_STOP_AREA.addTag("entrance", "yes"); - } - - private static final OsmNode ENTRANCE_OUTSIDE_STOP_AREA = new OsmNode(1, 1); - - static { - ENTRANCE_OUTSIDE_STOP_AREA.setId(2); - ENTRANCE_OUTSIDE_STOP_AREA.addTag("entrance", "yes"); - } - - private static final OsmNode SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA = new OsmNode(2, 2); - - static { - SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.setId(3); - SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.addTag("railway", "subway_entrance"); - SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA.addTag("entrance", "yes"); - } - - private static final OsmWay FOOTWAY = new OsmWay(); - - static { - FOOTWAY.addNodeRef(1); - FOOTWAY.addNodeRef(2); - FOOTWAY.addNodeRef(3); - FOOTWAY.addTag("highway", "footway"); - } - - private static final OsmRelation STOP_AREA = new OsmRelation(); - - static { - STOP_AREA.addTag("type", "public_transport"); - STOP_AREA.addTag("public_transport", "stop_area"); - - var member = new OsmRelationMember(); - member.setType(OsmMemberType.NODE); - member.setRef(1); - STOP_AREA.addMember(member); - } + private static final OsmNode ENTRANCE_IN_STOP_AREA = NodeBuilder.of(1, new WgsCoordinate(0, 0)) + .withTag("entrance", "yes") + .build(); + private static final OsmNode ENTRANCE_OUTSIDE_STOP_AREA = NodeBuilder.of( + 2, + new WgsCoordinate(1, 1) + ) + .withTag("entrance", "yes") + .build(); + + private static final OsmNode SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA = NodeBuilder.of( + 3, + new WgsCoordinate(2, 2) + ) + .withTag("railway", "subway_entrance") + .withTag("entrance", "yes") + .build(); + + private static final OsmRelation STOP_AREA = RelationBuilder.ofType("public_transport") + .withTag("public_transport", "stop_area") + .withNodeMember(ENTRANCE_IN_STOP_AREA.getId(), "") + .build(); @BeforeAll public static void setUp() { - var osmProvider = new TestOsmProvider( - List.of(STOP_AREA), - List.of(FOOTWAY), - List.of(ENTRANCE_IN_STOP_AREA, ENTRANCE_OUTSIDE_STOP_AREA, SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) - ); + var osmProvider = TestOsmProvider.of() + .addWayFromNodes( + way -> way.addTag("highway", "footway"), + ENTRANCE_IN_STOP_AREA, + ENTRANCE_OUTSIDE_STOP_AREA, + SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA + ) + .addRelation(STOP_AREA) + .build(); OsmModuleTestFactory.of(osmProvider) .withGraph(GRAPH) From ba683055756ba3ad5e5041fbce309124cd2f3209 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 21 Apr 2026 15:03:59 +0100 Subject: [PATCH 103/177] add GraphFetcher in application test fixture --- .../street/graph/GraphFetcher.java | 25 +++++++++++++++++ .../osm/moduletests/StationEntrancesTest.java | 27 +++++++++---------- 2 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java new file mode 100644 index 00000000000..f7296b10704 --- /dev/null +++ b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java @@ -0,0 +1,25 @@ +package org.opentripplanner.street.graph; + +import java.util.Optional; +import org.opentripplanner.osm.model.OsmNode; +import org.opentripplanner.street.model.vertex.OsmVertex; + +public class GraphFetcher { + + private final Graph graph; + + public GraphFetcher(Graph graph) { + this.graph = graph; + } + + public Optional getVertexForOsmNode(OsmNode node) { + return graph.getVerticesOfType(OsmVertex.class) + .stream() + .filter(v -> v.nodeId() == node.getId()) + .findFirst(); + } + + public Graph graph() { + return graph; + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java index 2adc8b6bea1..449781ee739 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java @@ -14,12 +14,12 @@ import org.opentripplanner.osm.model.RelationBuilder; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.street.model.vertex.OsmVertex; +import org.opentripplanner.street.graph.GraphFetcher; import org.opentripplanner.street.model.vertex.StationEntranceVertex; public class StationEntrancesTest { - private static final Graph GRAPH = new Graph(); + private static final GraphFetcher GRAPH = new GraphFetcher(new Graph()); private static final OsmNode ENTRANCE_IN_STOP_AREA = NodeBuilder.of(1, new WgsCoordinate(0, 0)) .withTag("entrance", "yes") .build(); @@ -56,7 +56,7 @@ public static void setUp() { .build(); OsmModuleTestFactory.of(osmProvider) - .withGraph(GRAPH) + .withGraph(GRAPH.graph()) .builder() .withIncludeOsmStationEntrances(true) .withIssueStore(DataImportIssueStore.NOOP) @@ -66,28 +66,25 @@ public static void setUp() { @Test void entranceInStopArea() { - assertInstanceOf(StationEntranceVertex.class, getVertexForOsmNode(ENTRANCE_IN_STOP_AREA)); + assertInstanceOf( + StationEntranceVertex.class, + GRAPH.getVertexForOsmNode(ENTRANCE_IN_STOP_AREA).orElseThrow() + ); } @Test void entranceOutsideStopArea() { - assertFalse(getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA) instanceof StationEntranceVertex); + assertFalse( + GRAPH.getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA).orElseThrow() instanceof + StationEntranceVertex + ); } @Test void stationEntranceOutsideStopArea() { assertInstanceOf( StationEntranceVertex.class, - getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) + GRAPH.getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA).orElseThrow() ); } - - private OsmVertex getVertexForOsmNode(OsmNode node) { - var vertices = GRAPH.getVerticesOfType(OsmVertex.class); - return vertices - .stream() - .filter(v -> v.nodeId() == node.getId()) - .findFirst() - .orElseThrow(); - } } From 37e410508b1251b4902ffea9f3955fe7c8dce12f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 16:21:43 +0200 Subject: [PATCH 104/177] Apply review feedback --- .../opentripplanner/osm/model/OsmEntity.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index 59f4bfd4870..78d986daa76 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -225,15 +225,13 @@ public Map getTags() { * Is the tag defined? */ public boolean hasTag(String tag) { - tag = tag.toLowerCase(); - return tags != null && tags.containsKey(tag); + return getTag(tag) != null; } /** * Determines if a tag contains a false value. 'no', 'false', and '0' are considered false. */ public boolean isTagFalse(String tag) { - tag = tag.toLowerCase(); if (tags == null) { return false; } @@ -258,7 +256,6 @@ public Accessibility explicitWheelchairAccessibility() { * Determines if a tag contains a true value. 'yes', 'true', and '1' are considered true. */ public boolean isTagTrue(String tag) { - tag = tag.toLowerCase(); if (tags == null) { return false; } @@ -325,7 +322,6 @@ protected boolean isExplicitlyAllowed(String key) { if (isTagTrue(key)) { return true; } - key = key.toLowerCase(); String value = getTag(key); return ( "designated".equals(value) || @@ -341,9 +337,8 @@ protected boolean isExplicitlyAllowed(String key) { */ @Nullable public String getTag(String tag) { - tag = tag.toLowerCase(); if (tags != null) { - return tags.get(tag); + return tags.get(tag.toLowerCase()); } return null; } @@ -505,12 +500,11 @@ public OptionalInt parseIntOrBoolean(String tag, Consumer errorHandler) * Checks if a tag contains the specified value. */ public boolean isTag(String tag, String value) { - tag = tag.toLowerCase(); - if (tags != null && tags.containsKey(tag) && value != null) { - return value.equals(tags.get(tag)); + if (tags == null || value == null) { + return false; + } else { + return value.equals(tags.get(tag.toLowerCase())); } - - return false; } /** From a918793842e8fe258e7eb7203f501a76badf13c2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 17:10:04 +0200 Subject: [PATCH 105/177] Update application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java Co-authored-by: Ville Pihlava --- .../main/java/org/opentripplanner/osm/model/OsmEntity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index 78d986daa76..0bfdee8f410 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -504,7 +504,9 @@ public boolean isTag(String tag, String value) { return false; } else { return value.equals(tags.get(tag.toLowerCase())); - } +public boolean isTag(String tag, String value) { + return tags != null && value != null && value.equals(tags.get(tag.toLowerCase())); +} } /** From decc648b554d98820f2b8d327fe3a1755626dc07 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Tue, 21 Apr 2026 16:18:37 +0100 Subject: [PATCH 106/177] apply review suggestions --- .../osm/model/NodeBuilder.java | 4 +++ .../osm/model/RelationBuilder.java | 8 +++++ .../street/graph/GraphFetcher.java | 9 ++--- .../osm/moduletests/StationEntrancesTest.java | 15 +++------ .../osm/model/OsmNodeTest.java | 33 +++++++------------ 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java index 81efd5fb52a..90b26726657 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java @@ -14,6 +14,10 @@ public static OsmNode node(long id, WgsCoordinate wgsCoordinate) { return of(id, wgsCoordinate).build(); } + public static NodeBuilder of() { + return new NodeBuilder(new OsmNode()); + } + public static NodeBuilder of(long id, WgsCoordinate wgsCoordinate) { var builder = new NodeBuilder(new OsmNode(wgsCoordinate.latitude(), wgsCoordinate.longitude())); builder.node.setId(id); diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java index f7ec670535b..7505db0a436 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java @@ -12,6 +12,10 @@ public static RelationBuilder ofTurnRestriction(String restrictionType) { return ofType("restriction").withTag("restriction", restrictionType); } + public static RelationBuilder ofStopArea() { + return ofType("public_transport").withTag("public_transport", "stop_area"); + } + public static RelationBuilder ofType(String type) { var builder = new RelationBuilder(); builder.relation.addTag("type", type); @@ -27,6 +31,10 @@ public RelationBuilder withWayMember(long id, String role) { return withMember(id, role, OsmMemberType.WAY); } + public RelationBuilder withNodeMember(long id) { + return withMember(id, "", OsmMemberType.NODE); + } + public RelationBuilder withNodeMember(long id, String role) { return withMember(id, role, OsmMemberType.NODE); } diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java index f7296b10704..92a7088eb15 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java +++ b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java @@ -1,6 +1,5 @@ package org.opentripplanner.street.graph; -import java.util.Optional; import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.street.model.vertex.OsmVertex; @@ -12,11 +11,13 @@ public GraphFetcher(Graph graph) { this.graph = graph; } - public Optional getVertexForOsmNode(OsmNode node) { - return graph.getVerticesOfType(OsmVertex.class) + public OsmVertex getVertexForOsmNode(OsmNode node) { + return graph + .getVerticesOfType(OsmVertex.class) .stream() .filter(v -> v.nodeId() == node.getId()) - .findFirst(); + .findFirst() + .orElseThrow(); } public Graph graph() { diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java index 449781ee739..807c7b347bf 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java @@ -38,9 +38,8 @@ public class StationEntrancesTest { .withTag("entrance", "yes") .build(); - private static final OsmRelation STOP_AREA = RelationBuilder.ofType("public_transport") - .withTag("public_transport", "stop_area") - .withNodeMember(ENTRANCE_IN_STOP_AREA.getId(), "") + private static final OsmRelation STOP_AREA = RelationBuilder.ofStopArea() + .withNodeMember(ENTRANCE_IN_STOP_AREA.getId()) .build(); @BeforeAll @@ -66,17 +65,13 @@ public static void setUp() { @Test void entranceInStopArea() { - assertInstanceOf( - StationEntranceVertex.class, - GRAPH.getVertexForOsmNode(ENTRANCE_IN_STOP_AREA).orElseThrow() - ); + assertInstanceOf(StationEntranceVertex.class, GRAPH.getVertexForOsmNode(ENTRANCE_IN_STOP_AREA)); } @Test void entranceOutsideStopArea() { assertFalse( - GRAPH.getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA).orElseThrow() instanceof - StationEntranceVertex + GRAPH.getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA) instanceof StationEntranceVertex ); } @@ -84,7 +79,7 @@ void entranceOutsideStopArea() { void stationEntranceOutsideStopArea() { assertInstanceOf( StationEntranceVertex.class, - GRAPH.getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA).orElseThrow() + GRAPH.getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) ); } } diff --git a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java index 0b65d08d049..3484c3dac1e 100644 --- a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java +++ b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java @@ -13,27 +13,18 @@ public class OsmNodeTest { public static Stream entranceTestCases() { - var mainEntrance = new OsmNode(); - mainEntrance.addTag("entrance", "main"); - - var trainStationEntrance = new OsmNode(); - trainStationEntrance.addTag("railway", "train_station_entrance"); - - var subwayEntrance = new OsmNode(); - subwayEntrance.addTag("railway", "subway_entrance"); - - var publicTransportEntrance = new OsmNode(); - publicTransportEntrance.addTag("public_transport", "entrance"); - - var trainStationEntranceWithEntranceTag = new OsmNode(); - trainStationEntranceWithEntranceTag.addTag("railway", "train_station_entrance"); - trainStationEntranceWithEntranceTag.addTag("entrance", "main"); - - var notAnEntrance = new OsmNode(); - notAnEntrance.addTag("entrance", "no"); - - var emergencyEntrance = new OsmNode(); - notAnEntrance.addTag("entrance", "emergency"); + var mainEntrance = NodeBuilder.of().withTag("entrance", "main").build(); + var trainStationEntrance = NodeBuilder.of() + .withTag("railway", "train_station_entrance") + .build(); + var subwayEntrance = NodeBuilder.of().withTag("railway", "subway_entrance").build(); + var publicTransportEntrance = NodeBuilder.of().withTag("public_transport", "entrance").build(); + var trainStationEntranceWithEntranceTag = NodeBuilder.of() + .withTag("railway", "train_station_entrance") + .withTag("entrance", "main") + .build(); + var notAnEntrance = NodeBuilder.of().withTag("entrance", "no").build(); + var emergencyEntrance = NodeBuilder.of().withTag("entrance", "emergency").build(); return Stream.of( Arguments.argumentSet("main entrance", mainEntrance, true, false), From 80aeb2dbb1a1edc00b5bec2888be4757974401f0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 17:20:32 +0200 Subject: [PATCH 107/177] Fix format --- .../main/java/org/opentripplanner/osm/model/OsmEntity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index 0bfdee8f410..1d56d3ac0aa 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -500,13 +500,7 @@ public OptionalInt parseIntOrBoolean(String tag, Consumer errorHandler) * Checks if a tag contains the specified value. */ public boolean isTag(String tag, String value) { - if (tags == null || value == null) { - return false; - } else { - return value.equals(tags.get(tag.toLowerCase())); -public boolean isTag(String tag, String value) { return tags != null && value != null && value.equals(tags.get(tag.toLowerCase())); -} } /** From e48680106b3285edcf2993e84c08f626c66dc256 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 20 Apr 2026 22:03:41 +0200 Subject: [PATCH 108/177] Add flex routes to FlexIndex --- .../org/opentripplanner/ext/flex/FlexIndex.java | 16 +++++++++++++++- .../stops/DigitransitStopPropertyMapper.java | 8 +++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index d230cf28e3b..f70fd951ab4 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.flex; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import java.time.LocalDate; import java.util.ArrayList; @@ -25,17 +26,24 @@ public class FlexIndex { private final Map> flexTripsRunningOnDate = new HashMap<>(); + private final Multimap routeByStop; + public FlexIndex(TimetableRepository timetableRepository) { + var routeByStopBuilder = ImmutableSetMultimap.builder(); + for (FlexTrip flexTrip : timetableRepository.getAllFlexTrips()) { - routeById.put(flexTrip.getTrip().getRoute().getId(), flexTrip.getTrip().getRoute()); + var route = flexTrip.getTrip().getRoute(); + routeById.put(route.getId(), route); tripById.put(flexTrip.getTrip().getId(), flexTrip); for (StopLocation stop : flexTrip.getStops()) { if (stop instanceof GroupStop groupStop) { for (StopLocation stopElement : groupStop.getChildLocations()) { flexTripsByStop.put(stopElement, flexTrip); + routeByStopBuilder.put(stopElement, route); } } else { flexTripsByStop.put(stop, flexTrip); + routeByStopBuilder.put(stop, route); } } @@ -55,6 +63,8 @@ public FlexIndex(TimetableRepository timetableRepository) { }); }); } + + routeByStop = routeByStopBuilder.build(); } public Collection> getFlexTripsByStop(StopLocation stopLocation) { @@ -77,6 +87,10 @@ public Collection getAllFlexRoutes() { return tripById.values(); } + public Collection findRoutes(StopLocation stop){ + return routeByStop.get(stop); + } + /** * Returns flex trips for the given running date. Running date is not necessarily the same as the * service date. A Trip "runs through" a date if any of its arrivals or departures is happening on diff --git a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index fa1bb3a0a26..3606f3824f3 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.core.model.i18n.I18NStringMapper; import org.opentripplanner.framework.json.ObjectMappers; @@ -60,9 +61,10 @@ protected static Collection getBaseKeyValues( protected static String getRoutes(TransitService transitService, RegularStop stop) { try { - var objects = transitService - .findRoutes(stop) - .stream() + var flexRoutes = transitService.getFlexIndex().findRoutes(stop); + var fixedRoutes= transitService.findRoutes(stop); + var objects = Stream.concat(flexRoutes.stream(), fixedRoutes.stream()) + .distinct() .map(route -> { var routeObject = OBJECT_MAPPER.createObjectNode(); routeObject.put("gtfsType", route.getGtfsType()); From cdc57f4cb620f71bdadf5f4eee220d88edef06d3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 21 Apr 2026 11:23:57 +0200 Subject: [PATCH 109/177] Add tests for flex index for routes --- .../ext/flex/FlexIndexTest.java | 59 ++++++++++++++++++- .../ScheduledFlexPathCalculatorTest.java | 4 +- .../ext/flex/template/ClosestTripTest.java | 2 +- .../ext/flex/trip/FlexTripsMapperTest.java | 2 +- .../flex/trip/ScheduledDeviatedTripTest.java | 8 +-- .../trip/UnscheduledDrivingDurationTest.java | 4 +- .../ext/flex/trip/UnscheduledTripTest.java | 26 ++++---- .../opentripplanner/ext/flex/FlexIndex.java | 2 +- .../stops/DigitransitStopPropertyMapper.java | 14 ++++- .../model/FlexStopTimesFactory.java} | 27 +++++++-- 10 files changed, 113 insertions(+), 35 deletions(-) rename application/src/{ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java => test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java} (78%) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java index 8783ba89b35..d8c923cd437 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java @@ -1,9 +1,12 @@ package org.opentripplanner.ext.flex; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.groupStop; +import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.trip; import java.time.LocalDate; import java.util.Collection; @@ -13,17 +16,21 @@ import org.opentripplanner.ext.flex.trip.UnscheduledTrip; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.site.GroupStop; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.TimetableRepository; class FlexIndexTest { + public static final Route ROUTE_2 = TimetableRepositoryForTest.route("r2").build(); + @Test void testFlexTripSpanningMidnight() { TimetableRepository repo = new TimetableRepository(); FeedScopedId serviceId = id("S1"); - Trip trip = TimetableRepositoryForTest.trip("T1").withServiceId(serviceId).build(); + Trip trip = trip("T1").withServiceId(serviceId).build(); UnscheduledTrip flexTrip = UnscheduledTrip.of(id("FT1")) .withTrip(trip) @@ -65,7 +72,7 @@ void testFlexTripSpanningMidnight() { void testFlexTripStartingAfterMidnight() { TimetableRepository repo = new TimetableRepository(); FeedScopedId serviceId = id("S2"); - Trip trip = TimetableRepositoryForTest.trip("T2").withServiceId(serviceId).build(); + Trip trip = trip("T2").withServiceId(serviceId).build(); UnscheduledTrip flexTrip = UnscheduledTrip.of(id("FT2")) .withTrip(trip) @@ -90,4 +97,50 @@ void testFlexTripStartingAfterMidnight() { assertEquals(1, tripsOnNextDay.size(), "Should have 1 trip on next day"); assertEquals(serviceDate, tripsOnNextDay.iterator().next().serviceDate()); } + + @Test + void routesAtArea() { + var repo = new TimetableRepository(); + + var st1 = area("10:00", "12:00"); + var st2 = area("14:00", "16:00"); + + var flexTrip = UnscheduledTrip.of(id("T2")) + .withTrip(trip("T2").withRoute(ROUTE_2).build()) + .withStopTimes(List.of(st1, st2)) + .build(); + + repo.addFlexTrip(flexTrip.getId(), flexTrip); + + var index = new FlexIndex(repo); + + assertThat(index.findRoutes(st1.getStop())).containsExactly(ROUTE_2); + assertThat(index.findRoutes(st2.getStop())).containsExactly(ROUTE_2); + } + + @Test + void routesAtGroup() { + var repo = new TimetableRepository(); + + var st1 = groupStop("10:00", "12:00"); + var st2 = groupStop("14:00", "16:00"); + + var flexTrip = UnscheduledTrip.of(id("T2")) + .withTrip(trip("T2").withRoute(ROUTE_2).build()) + .withStopTimes(List.of(st1, st2)) + .build(); + + repo.addFlexTrip(flexTrip.getId(), flexTrip); + + var index = new FlexIndex(repo); + + var groupStop = (GroupStop) st1.getStop(); + assertThat(groupStop.getChildLocations()).isNotEmpty(); + groupStop + .getChildLocations() + .forEach(child -> { + assertThat(index.findRoutes(child)).containsExactly(ROUTE_2); + assertThat(index.findRoutes(child)).containsExactly(ROUTE_2); + }); + } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java index 5b67266ea70..391da6b3a94 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStop; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java index 4aaf46fc3aa..3641c7991f9 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; +import static org.opentripplanner.model.FlexStopTimesFactory.area; import java.time.Instant; import java.time.LocalDate; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java index a236b87e967..a23cb83a679 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP; +import static org.opentripplanner.model.FlexStopTimesFactory.area; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index e5d6c0b9c54..f7abcccf82d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.areaWithContinuousStopping; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopWithContinuousStopping; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.areaWithContinuousStopping; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStop; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStopWithContinuousStopping; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java index bceaf934a80..4f78826f347 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java @@ -9,9 +9,9 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner._support.geometry.LineStrings; -import org.opentripplanner.ext.flex.FlexStopTimesForTest; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; +import org.opentripplanner.model.FlexStopTimesFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.routing.api.request.framework.TimePenalty; @@ -23,7 +23,7 @@ class UnscheduledDrivingDurationTest { boardStopPosition, alightStopPosition ) -> new FlexPath(10_000, (int) Duration.ofMinutes(10).toSeconds(), () -> LineStrings.SIMPLE); - private static final StopTime STOP_TIME = FlexStopTimesForTest.area("10:00", "18:00"); + private static final StopTime STOP_TIME = FlexStopTimesFactory.area("10:00", "18:00"); @Test void noPenalty() { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 33f891afa41..da0e7f6bb45 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.ext.flex.FlexStopTimesForTest; +import org.opentripplanner.model.FlexStopTimesFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -46,18 +46,18 @@ class UnscheduledTripTest { @Nested class IsUnscheduledTrip { - private static final StopTime SCHEDULED_STOP = FlexStopTimesForTest.regularStop("10:00"); - private static final StopTime UNSCHEDULED_STOP = FlexStopTimesForTest.area("10:10", "10:20"); + private static final StopTime SCHEDULED_STOP = FlexStopTimesFactory.regularStop("10:00"); + private static final StopTime UNSCHEDULED_STOP = FlexStopTimesFactory.area("10:10", "10:20"); private static final StopTime CONTINUOUS_PICKUP_STOP = - FlexStopTimesForTest.regularStopWithContinuousPickup("10:30"); + FlexStopTimesFactory.regularStopWithContinuousPickup("10:30"); private static final StopTime CONTINUOUS_DROP_OFF_STOP = - FlexStopTimesForTest.regularStopWithContinuousDropOff("10:40"); + FlexStopTimesFactory.regularStopWithContinuousDropOff("10:40"); // disallowed by the GTFS spec private static final StopTime FLEX_AND_CONTINUOUS_PICKUP_STOP = - FlexStopTimesForTest.areaWithContinuousPickup("10:50"); + FlexStopTimesFactory.areaWithContinuousPickup("10:50"); private static final StopTime FLEX_AND_CONTINUOUS_DROP_OFF_STOP = - FlexStopTimesForTest.areaWithContinuousDropOff("11:00"); + FlexStopTimesFactory.areaWithContinuousDropOff("11:00"); static List> notUnscheduled() { return List.of( @@ -103,8 +103,8 @@ void isUnscheduled(List stopTimes) { @Test void testMaxSpanDays() { var stopTimes = List.of( - FlexStopTimesForTest.area("10:10", "14:10"), - FlexStopTimesForTest.area("11:10", "15:10") + FlexStopTimesFactory.area("10:10", "14:10"), + FlexStopTimesFactory.area("11:10", "15:10") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); @@ -114,8 +114,8 @@ void testMaxSpanDays() { @Test void testMaxSpanDaysOvernight() { var stopTimes = List.of( - FlexStopTimesForTest.area("10:10", "14:10"), - FlexStopTimesForTest.area("21:10", "26:10") + FlexStopTimesFactory.area("10:10", "14:10"), + FlexStopTimesFactory.area("21:10", "26:10") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); assertEquals(1, trip.maxSpanDays()); @@ -124,8 +124,8 @@ void testMaxSpanDaysOvernight() { @Test void testMaxSpanDaysNextDay() { var stopTimes = List.of( - FlexStopTimesForTest.area("24:00", "26:00"), - FlexStopTimesForTest.area("24:00", "26:00") + FlexStopTimesFactory.area("24:00", "26:00"), + FlexStopTimesFactory.area("24:00", "26:00") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); assertEquals(1, trip.maxSpanDays()); diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index f70fd951ab4..29335c0c4b7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -87,7 +87,7 @@ public Collection getAllFlexRoutes() { return tripById.values(); } - public Collection findRoutes(StopLocation stop){ + public Collection findRoutes(StopLocation stop) { return routeByStop.get(stop); } diff --git a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index 3606f3824f3..059faac6cba 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -15,6 +15,7 @@ import org.opentripplanner.core.model.i18n.I18NStringMapper; import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.service.TransitService; @@ -61,8 +62,8 @@ protected static Collection getBaseKeyValues( protected static String getRoutes(TransitService transitService, RegularStop stop) { try { - var flexRoutes = transitService.getFlexIndex().findRoutes(stop); - var fixedRoutes= transitService.findRoutes(stop); + var flexRoutes = findFlexRoutes(transitService, stop); + var fixedRoutes = transitService.findRoutes(stop); var objects = Stream.concat(flexRoutes.stream(), fixedRoutes.stream()) .distinct() .map(route -> { @@ -77,6 +78,15 @@ protected static String getRoutes(TransitService transitService, RegularStop sto } } + private static Collection findFlexRoutes(TransitService transitService, RegularStop stop) { + var flexIndex = transitService.getFlexIndex(); + if (flexIndex == null) { + return List.of(); + } else { + return flexIndex.findRoutes(stop); + } + } + protected static String getType(TransitService transitService, RegularStop stop) { Collection patternsForStop = transitService.findPatterns(stop); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java b/application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java similarity index 78% rename from application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java rename to application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java index 89e38edda54..1d17817bc53 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java @@ -1,21 +1,27 @@ -package org.opentripplanner.ext.flex; +package org.opentripplanner.model; import org.opentripplanner._support.geometry.Polygons; -import org.opentripplanner.model.PickDrop; -import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.site.GroupStop; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.utils.time.TimeUtils; -public class FlexStopTimesForTest { +public class FlexStopTimesFactory { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final StopLocation AREA_STOP = TEST_MODEL.areaStop("area") .withGeometry(Polygons.BERLIN) .build(); - private static final RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build(); + private static final RegularStop REGULAR_STOP_1 = TEST_MODEL.stop("stop-1").build(); + private static final RegularStop REGULAR_STOP_2 = TEST_MODEL.stop("stop-2").build(); + private static final RegularStop REGULAR_STOP_3 = TEST_MODEL.stop("stop-3").build(); + private static final GroupStop GROUP_STOP = TEST_MODEL.groupStop( + "group-stop1", + REGULAR_STOP_2, + REGULAR_STOP_3 + ); private static final Trip TRIP = TimetableRepositoryForTest.trip("flex").build(); @@ -89,10 +95,19 @@ public static StopTime regularStopWithContinuousDropOff(String time) { public static StopTime regularStop(int arrivalTime, int departureTime) { var stopTime = new StopTime(); - stopTime.setStop(REGULAR_STOP); + stopTime.setStop(REGULAR_STOP_1); stopTime.setArrivalTime(arrivalTime); stopTime.setDepartureTime(departureTime); stopTime.setTrip(TRIP); return stopTime; } + + public static StopTime groupStop(String windowStart, String windowEnd) { + var stopTime = new StopTime(); + stopTime.setStop(GROUP_STOP); + stopTime.setFlexWindowStart(TimeUtils.time(windowStart)); + stopTime.setFlexWindowEnd(TimeUtils.time(windowEnd)); + stopTime.setTrip(TRIP); + return stopTime; + } } From 4955fce150fa8cb5fcff3c14cdcdfd4d95cd8396 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 09:54:48 +0200 Subject: [PATCH 110/177] Move code into transit service --- .../stops/DigitransitStopPropertyMapper.java | 18 +++--------------- .../transit/service/DefaultTransitService.java | 10 +++++++++- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index 059faac6cba..fa1bb3a0a26 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -10,12 +10,10 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.core.model.i18n.I18NStringMapper; import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.inspector.vector.KeyValue; -import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.service.TransitService; @@ -62,10 +60,9 @@ protected static Collection getBaseKeyValues( protected static String getRoutes(TransitService transitService, RegularStop stop) { try { - var flexRoutes = findFlexRoutes(transitService, stop); - var fixedRoutes = transitService.findRoutes(stop); - var objects = Stream.concat(flexRoutes.stream(), fixedRoutes.stream()) - .distinct() + var objects = transitService + .findRoutes(stop) + .stream() .map(route -> { var routeObject = OBJECT_MAPPER.createObjectNode(); routeObject.put("gtfsType", route.getGtfsType()); @@ -78,15 +75,6 @@ protected static String getRoutes(TransitService transitService, RegularStop sto } } - private static Collection findFlexRoutes(TransitService transitService, RegularStop stop) { - var flexIndex = transitService.getFlexIndex(); - if (flexIndex == null) { - return List.of(); - } else { - return flexIndex.findRoutes(stop); - } - } - protected static String getType(TransitService transitService, RegularStop stop) { Collection patternsForStop = transitService.findPatterns(stop); diff --git a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index c7ba45c9882..a8323e993e3 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -67,6 +67,7 @@ import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GraphUpdaterStatus; import org.opentripplanner.utils.collection.CollectionsView; +import org.opentripplanner.utils.collection.SetUtils; import org.opentripplanner.utils.time.ServiceDateUtils; /** @@ -254,7 +255,14 @@ public Collection findRoutes(FindRoutesRequest request) { @Override public Set findRoutes(StopLocation stop) { OTPRequestTimeoutException.checkForTimeout(); - return this.timetableRepositoryIndex.getRoutesForStop(stop); + Collection flexRoutes = List.of(); + var flexIndex = timetableRepositoryIndex.getFlexIndex(); + if (flexIndex != null) { + flexRoutes = flexIndex.findRoutes(stop); + } + var fixedRoutes = timetableRepositoryIndex.getRoutesForStop(stop); + + return SetUtils.combine(flexRoutes, fixedRoutes); } @Override From e1b1b143eb1b23b409f21404af7dccfdf9c353bf Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Wed, 22 Apr 2026 09:56:38 +0200 Subject: [PATCH 111/177] config: Change default fare type to 'off' --- .../java/org/opentripplanner/ext/fares/FaresConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java index c520685ff5f..fdf1df541fb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java @@ -58,7 +58,7 @@ public static FareServiceFactory fromConfig(JsonNode config) { } if (type == null) { - type = "default"; + type = "off"; } FareServiceFactory factory = createFactory(type); From 14627cb38a9e5791e0e2659425860acaddb6ece2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 10:28:44 +0200 Subject: [PATCH 112/177] Add Javadoc --- .../src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index 29335c0c4b7..d0d254783cb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -87,6 +87,11 @@ public Collection getAllFlexRoutes() { return tripById.values(); } + /** + * For a given stop location returns the flex routes that visit it, taking care to resolve + * members of group stops (which are visited "transitively", ie. not directly but by + * them being members of the group stop). + */ public Collection findRoutes(StopLocation stop) { return routeByStop.get(stop); } From 6ebe87babf7039e9d6ccbdee1ba34b490fe8c763 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Wed, 22 Apr 2026 12:41:51 +0300 Subject: [PATCH 113/177] make LinearCostFunctionInput fields required --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 89cd5af070d..35799a94a02 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4508,13 +4508,13 @@ input InputUnpreferred { """ A generalized linear cost function that can be applied to a cost value. The coefficient is used to scale the input Cost linearly and the constant is used -to add a fixed offset. If we assume a cost t, then f(t) = coefficient * t + constant. +to add a fixed offset. If we assume a cost c, then f(c) = coefficient * c + constant. """ input LinearCostFunctionInput { "The coefficient that scales the Cost input linearly." - coefficient: Float - "The 0th degree constant of the function as a Cost, must be a positive integer" - constant: Cost + coefficient: Float! + "The 0th degree constant of the function as a Cost, must be a non-negative integer" + constant: Cost! } "Filters an entity by a date range." From b7bd62e628ad7c690812f83489bc147892b18747 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 11:43:30 +0200 Subject: [PATCH 114/177] Improve javadoc --- .../org/opentripplanner/transit/service/TransitService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java index f1ccb36728d..fdbabf8aa3b 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -122,6 +122,8 @@ public interface TransitService { /** * Return the routes using the given stop, not including real-time updates. + * This includes area stops and resolution of members of group stops - if a trip visits a group + * stop, all member stops are considered visited, too. */ Set findRoutes(StopLocation stop); From 5ee0710014bd2f97d364a3306c91991e037d6e77 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 12:08:56 +0200 Subject: [PATCH 115/177] Fix computation of time delta --- .../org/opentripplanner/street/search/state/StateEditor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index ff02b50d9b2..c9ce7aeef37 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -115,10 +115,11 @@ public State makeState() { if (backState != null) { // check that time changes are coherent with edge traversal // direction + int timeDelta = (int)(time_ms - backState.getTimeMilliseconds()); if ( traversingBackward - ? (backState.getTimeDeltaMilliseconds() > 0) - : (backState.getTimeDeltaMilliseconds() < 0) + ? (timeDelta > 0) + : (timeDelta < 0) ) { LOG.trace("Time was incremented the wrong direction during state editing. {}", backEdge); return null; From 5d78efd18a8cc23f68e150202247e331759e1a43 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 12:30:13 +0200 Subject: [PATCH 116/177] Use edge instead of state --- .../org/opentripplanner/street/search/state/StateEditor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index c9ce7aeef37..a02f307dced 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -177,7 +177,7 @@ public void incrementTimeInMilliseconds(long milliseconds) { if (milliseconds < 0) { LOG.warn( "A state's time is being incremented by a negative amount while traversing edge " + - backState + backEdge ); defectiveTraversal = true; return; From 613a1e965e41190edab4b1aa2b1f0c94bc9f8d3f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 12:41:56 +0200 Subject: [PATCH 117/177] Fix formatting --- .../street/model/path/StreetPath.java | 4 ++-- .../street/search/state/StateEditor.java | 11 +++-------- .../opentripplanner/street/model/edge/TestEdge.java | 2 ++ .../street/search/state/TestStateBuilder.java | 12 +++++++----- .../street/search/StreetPathTest.java | 6 +++--- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/street/src/main/java/org/opentripplanner/street/model/path/StreetPath.java b/street/src/main/java/org/opentripplanner/street/model/path/StreetPath.java index 0fcb34a5851..a095778cfdf 100644 --- a/street/src/main/java/org/opentripplanner/street/model/path/StreetPath.java +++ b/street/src/main/java/org/opentripplanner/street/model/path/StreetPath.java @@ -72,7 +72,8 @@ public Duration duration() { } public LineString geometry() { - var geometries = edges.stream() + var geometries = edges + .stream() .filter(Edge::includeGeometryInPath) .map(Edge::getGeometry) .filter(Objects::nonNull) @@ -86,7 +87,6 @@ public List states() { return states; } - /// Get the last state in the patn public State lastState() { return states.getLast(); diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index a02f307dced..04a2fc8407c 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -115,12 +115,8 @@ public State makeState() { if (backState != null) { // check that time changes are coherent with edge traversal // direction - int timeDelta = (int)(time_ms - backState.getTimeMilliseconds()); - if ( - traversingBackward - ? (timeDelta > 0) - : (timeDelta < 0) - ) { + int timeDelta = (int) (time_ms - backState.getTimeMilliseconds()); + if (traversingBackward ? (timeDelta > 0) : (timeDelta < 0)) { LOG.trace("Time was incremented the wrong direction during state editing. {}", backEdge); return null; } @@ -176,8 +172,7 @@ public void incrementWeight(double weight) { public void incrementTimeInMilliseconds(long milliseconds) { if (milliseconds < 0) { LOG.warn( - "A state's time is being incremented by a negative amount while traversing edge " + - backEdge + "A state's time is being incremented by a negative amount while traversing edge " + backEdge ); defectiveTraversal = true; return; diff --git a/street/src/test-fixtures/java/org/opentripplanner/street/model/edge/TestEdge.java b/street/src/test-fixtures/java/org/opentripplanner/street/model/edge/TestEdge.java index 64484d3a9a3..c8b31e004e8 100644 --- a/street/src/test-fixtures/java/org/opentripplanner/street/model/edge/TestEdge.java +++ b/street/src/test-fixtures/java/org/opentripplanner/street/model/edge/TestEdge.java @@ -8,6 +8,7 @@ import org.opentripplanner.street.search.state.State; public class TestEdge extends Edge { + private final double weight; private final double distanceMeters; private final int durationSeconds; @@ -63,6 +64,7 @@ public boolean includeGeometryInPath() { } public static class TestEdgeBuilder { + Vertex from; Vertex to; double weight = 100; diff --git a/street/src/test-fixtures/java/org/opentripplanner/street/search/state/TestStateBuilder.java b/street/src/test-fixtures/java/org/opentripplanner/street/search/state/TestStateBuilder.java index 93f1953a96a..ab3235af408 100644 --- a/street/src/test-fixtures/java/org/opentripplanner/street/search/state/TestStateBuilder.java +++ b/street/src/test-fixtures/java/org/opentripplanner/street/search/state/TestStateBuilder.java @@ -50,10 +50,7 @@ private TestStateBuilder(StreetMode mode) { } private TestStateBuilder(StreetSearchRequest request) { - currentState = new State( - StreetModelFactory.intersectionVertex(count, count), - request - ); + currentState = new State(StreetModelFactory.intersectionVertex(count, count), request); } public static TestStateBuilder of(StreetSearchRequest request) { @@ -110,7 +107,12 @@ public TestStateBuilder streetEdge(Consumer> customizer) { var from = (StreetVertex) currentState.vertex; var to = StreetModelFactory.intersectionVertex(count, count); - var edgeBuilder = StreetModelFactory.streetEdgeBuilder(from, to, 100, StreetTraversalPermission.ALL); + var edgeBuilder = StreetModelFactory.streetEdgeBuilder( + from, + to, + 100, + StreetTraversalPermission.ALL + ); customizer.accept(edgeBuilder); var edge = edgeBuilder.buildAndConnect(); var states = edge.traverse(currentState); diff --git a/street/src/test/java/org/opentripplanner/street/search/StreetPathTest.java b/street/src/test/java/org/opentripplanner/street/search/StreetPathTest.java index 6d6b2e94025..3c7b4858cb7 100644 --- a/street/src/test/java/org/opentripplanner/street/search/StreetPathTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/StreetPathTest.java @@ -87,7 +87,7 @@ void subPath() { .build(); var path = new StreetPath(state); - var subPath = path.subPath(1,3); + var subPath = path.subPath(1, 3); assertEquals(START_TIME.plus(Duration.ofSeconds(10)), subPath.startTime()); assertEquals(START_TIME.plus(Duration.ofSeconds(15)), subPath.endTime()); assertEquals(5.0, subPath.weight()); @@ -99,6 +99,6 @@ private TestStateBuilder startState() { .withStartTime(START_TIME) .withMode(StreetMode.WALK) .build(); - return TestStateBuilder.of(request); + return TestStateBuilder.of(request); } -} \ No newline at end of file +} From 4b58e11b39d2fc7800906f56d2f29a3b14467972 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 22 Apr 2026 13:02:37 +0200 Subject: [PATCH 118/177] Remove Hamburg speed test config [ci skip] --- .github/workflows/performance-test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index f4e2d021d21..ddc730fbeae 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -39,12 +39,6 @@ jobs: # extended locations that are run only after merging to dev-2.x - # Hamburg is disabled because of https://github.com/opentripplanner/OpenTripPlanner/issues/6430 - # - location: hamburg # German city - # iterations: 1 - # jfr-delay: "50s" - # profile: extended - - location: baden-wuerttemberg # German state of Baden-Württemberg: https://en.wikipedia.org/wiki/Baden-W%C3%BCrttemberg iterations: 1 jfr-delay: "50s" From 1f43aab92e32ec2f5e2ee0d30f1cb1efc5ff2d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Wed, 22 Apr 2026 13:08:01 +0200 Subject: [PATCH 119/177] no longer sorting or limiting the number of itineraries in routeDirect in DefaultCarpoolingService --- .../DefaultCarpoolingServiceDirectTest.java | 20 ------------------- .../ext/carpooling/CarpoolingService.java | 3 +-- .../service/DefaultCarpoolingService.java | 11 ++-------- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 8de297b055d..7ebe61ae7d8 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -281,26 +281,6 @@ void returnsEmptyWhenDropoffExceedsMaxDistance() { ); } - @Test - void returnsAtMostMaxDirectResults() { - int maxResults = DefaultCarpoolingService.DEFAULT_MAX_CARPOOL_DIRECT_RESULTS; - for (int i = 0; i < maxResults + 2; i++) { - var departureTime = SEARCH_TIME.plusMinutes(5 + i * 5); - var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); - repository.upsertCarpoolTrip(trip); - } - - var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); - - var results = service.routeDirect(request, linkingContext); - - assertEquals( - maxResults, - results.size(), - "Should return exactly DEFAULT_MAX_CARPOOL_DIRECT_RESULTS results" - ); - } - @Test void twoTripsReturnTwoResults() { var departureTime1 = SEARCH_TIME.plusMinutes(10); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java index 995ae8bb286..2d110d2b138 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java @@ -24,8 +24,7 @@ public interface CarpoolingService { * * @param request the routing request containing passenger origin, destination, and preferences * @param linkingContext linking context with pre-linked vertices for the request - * @return list of carpool itineraries, sorted by quality (additional travel time), may be empty - * if no compatible trips found. Results are limited to avoid overwhelming users. + * @return list of carpool itineraries, may be empty if no compatible trips found * @throws IllegalArgumentException if request is null */ List routeDirect(RouteRequest request, LinkingContext linkingContext); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 14737e9c605..c7aef9eb971 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -91,7 +90,6 @@ public class DefaultCarpoolingService implements CarpoolingService { private static final Logger LOG = LoggerFactory.getLogger(DefaultCarpoolingService.class); - static final int DEFAULT_MAX_CARPOOL_DIRECT_RESULTS = 3; private static final Duration DEFAULT_SEARCH_WINDOW = Duration.ofMinutes(30); // How far away in time a carpooling trip can be from the requested departure time to be considered private static final Duration ACCESS_EGRESS_SEARCH_WINDOW = Duration.ofHours(12); @@ -148,14 +146,11 @@ public DefaultCarpoolingService( * routing to find the insertion that minimizes additional driver travel time while * respecting delay constraints. * - *

    - * Results are sorted by additional travel time and limited to - * {@value #DEFAULT_MAX_CARPOOL_DIRECT_RESULTS} itineraries. * * @param request the routing request. Must have {@link StreetMode#CARPOOL} as the direct mode. * @param linkingContext pre-linked vertices for the passenger's origin and destination - * @return a list of carpool itineraries sorted by additional travel time, or an empty list - * if no viable matches are found or the direct mode is not CARPOOL + * @return a list of carpool itineraries, or an empty list if no viable matches are found + * or the direct mode is not CARPOOL * @throws RoutingValidationException if origin or destination coordinates are missing */ @Override @@ -264,8 +259,6 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC ); }) .filter(Objects::nonNull) - .sorted(Comparator.comparing(InsertionCandidate::totalTripDuration)) - .limit(DEFAULT_MAX_CARPOOL_DIRECT_RESULTS) .toList(); LOG.debug("Found {} viable insertion candidates", insertionCandidates.size()); From a5da3e1914d8086f8127523a052ecf7d280a15f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Wed, 22 Apr 2026 13:08:17 +0200 Subject: [PATCH 120/177] boarding time at pickup stop of carpooling trips is now part of getPassengerRideDuration instead of getDurationUntilDepartureWithPassenger --- .../routing/InsertionCandidateTest.java | 37 ++++++++----------- ...aultCarpoolingServiceAccessEgressTest.java | 17 +++++---- .../DefaultCarpoolingServiceDirectTest.java | 31 +++++++++------- .../internal/CarpoolItineraryMapper.java | 19 ++++++---- .../routing/CarpoolAccessEgress.java | 11 +++++- .../routing/InsertionCandidate.java | 27 +++++++------- .../service/DefaultCarpoolingService.java | 2 +- 7 files changed, 78 insertions(+), 66 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java index d10e1d1aabf..d5d3ada4d17 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java @@ -123,8 +123,8 @@ void toString_includesKeyInformation() { } /** - * No pickup segments → durationUntilPickup is zero. - * Single shared segment → passengerRideDuration is just the segment duration (no stop delays). + * No pickup segments → durationUntilPickup is zero and no boarding dwell is added to the ride. + * Single shared segment → passengerRideDuration is just the segment duration. */ @Test void durations_noPickupSegments_singleSharedSegment() { @@ -136,13 +136,13 @@ void durations_noPickupSegments_singleSharedSegment() { var candidate = new InsertionCandidate(trip, 0, 1, List.of(sharedPath), stopDuration, null); assertEquals(Duration.ofMinutes(10), sharedDuration); - assertEquals(Duration.ZERO, candidate.getDurationUntilDepartureWithPassenger()); + assertEquals(Duration.ZERO, candidate.getDurationUntilPickupArrival()); assertEquals(sharedDuration, candidate.getPassengerRideDuration()); } /** - * Single pickup segment → durationUntilPickup = segment duration + boarding time. - * Single shared segment → passengerRideDuration = segment duration (no stop delays). + * Single pickup segment → durationUntilPickup = segment duration (boarding excluded). + * Single shared segment → passengerRideDuration = boarding time + segment duration. */ @Test void durations_onePickupSegment_singleSharedSegment() { @@ -163,16 +163,13 @@ void durations_onePickupSegment_singleSharedSegment() { null ); - assertEquals( - pickupDuration.plus(stopDuration), - candidate.getDurationUntilDepartureWithPassenger() - ); - assertEquals(sharedDuration, candidate.getPassengerRideDuration()); + assertEquals(pickupDuration, candidate.getDurationUntilPickupArrival()); + assertEquals(stopDuration.plus(sharedDuration), candidate.getPassengerRideDuration()); } /** - * Two pickup segments → intermediate stop delay between them + boarding time. - * Two shared segments → intermediate stop delay + pickup point delay. + * Two pickup segments → travel + 1 intermediate stop, no boarding dwell. + * Two shared segments → boarding dwell + travel + 1 intermediate stop. */ @Test void durations_multiplePickupAndSharedSegments() { @@ -197,15 +194,12 @@ void durations_multiplePickupAndSharedSegments() { null ); - // 2 pickup segments: travel + 1 intermediate stop + boarding - var expectedPickup = pickup0Duration - .plus(stopDuration) - .plus(pickup1Duration) - .plus(stopDuration); - assertEquals(expectedPickup, candidate.getDurationUntilDepartureWithPassenger()); + // 2 pickup segments: travel + 1 intermediate stop (boarding now belongs to the ride) + var expectedPickup = pickup0Duration.plus(stopDuration).plus(pickup1Duration); + assertEquals(expectedPickup, candidate.getDurationUntilPickupArrival()); - // 2 shared segments: travel + 1 intermediate stop delay - var expectedRide = shared0Duration.plus(stopDuration).plus(shared1Duration); + // 2 shared segments: boarding dwell + travel + 1 intermediate stop delay + var expectedRide = stopDuration.plus(shared0Duration).plus(stopDuration).plus(shared1Duration); assertEquals(expectedRide, candidate.getPassengerRideDuration()); } @@ -236,7 +230,8 @@ void durations_scaleWithStopDuration() { null ); - // 2 shared segments → 1x stopDuration difference (1 intermediate stop) + // Pickup at origin (no pickup segments) → no boarding dwell, so only the 1 intermediate + // stop between the 2 shared segments scales: 1x stopDuration difference. var difference = candidateLarge .getPassengerRideDuration() .minus(candidateSmall.getPassengerRideDuration()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java index 1338fce9fe1..050d28faae1 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java @@ -638,12 +638,13 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { assertFalse(results.isEmpty(), "Should find access results"); - // Departure time of the passenger is when the car departs from the pickup with the - // passenger onboard, i.e. trip start + driving to P2 + boarding dwell at P2. + // Departure time of the passenger is when the car arrives at the pickup (P2). The + // boarding dwell at P2 is part of the CarpoolAccessEgress duration, not added before + // the departure time. var pickupTime = RouteRequest.defaultValue().preferences().car().pickupTime(); var expectedDeparture = (int) Duration.between( transitSearchTimeZero.toInstant(), - departureTime.plus(drivingDurationAToP2).plus(pickupTime).toInstant() + departureTime.plus(drivingDurationAToP2).toInstant() ).getSeconds(); int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); @@ -683,13 +684,15 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { ); var drivingP2ToStop = expectedDrivingP2ToStop.get(accessEgress.stop()); - // Boarding dwell is already part of the passenger's departure time, so arrival is - // simply departure plus driving time from P2 to the transit stop. - var expectedArrival = expectedDeparture + (int) drivingP2ToStop.getSeconds(); + // Boarding dwell at P2 is now part of the passenger's ride duration, so the arrival + // time is the departure (arrival at P2) plus boarding plus driving from P2 to the stop. + var expectedArrival = + expectedDeparture + (int) pickupTime.getSeconds() + (int) drivingP2ToStop.getSeconds(); assertEquals( expectedArrival, accessEgress.getArrivalTimeOfPassenger(), - "Arrival time should equal passenger departure plus driving time from P2 to stop" + "Arrival time should equal passenger departure plus boarding dwell plus driving " + + "time from P2 to stop" ); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 7ebe61ae7d8..1dfd2482fbb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -420,19 +420,21 @@ void itineraryReflectsDriverScheduleWhenTripDepartsBeforeRequestTime() { var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); var stopDuration = request.preferences().car().pickupTime(); - // The driver's real pickup time is fixed by the trip's schedule. It does NOT shift + // The driver's pickup arrival time is fixed by the trip's schedule. It does NOT shift // forward just because the passenger requested a later departure — the driver cannot // wait (committed schedule / other passengers). - var actualPickupTime = departureTime.plus(drivingToPickup).plus(stopDuration); + var actualPickupArrivalTime = departureTime.plus(drivingToPickup); - // Guard the premise of this test: the requested time is after the real pickup time. + // Guard the premise of this test: the requested time is after the real pickup arrival. assertTrue( - request.dateTime().isAfter(actualPickupTime.toInstant()), - "Test premise: request time must be after the driver's real pickup time" + request.dateTime().isAfter(actualPickupArrivalTime.toInstant()), + "Test premise: request time must be after the driver's real pickup arrival time" ); - var expectedStartTime = actualPickupTime; - var expectedEndTime = expectedStartTime.plus(drivingPickupToDropoff); + // Itinerary start time is when the car arrives at the pickup; the boarding dwell is part + // of the leg's duration, so it shows up in the end time. + var expectedStartTime = actualPickupArrivalTime; + var expectedEndTime = expectedStartTime.plus(stopDuration).plus(drivingPickupToDropoff); var results = service.routeDirect(request, linkingContext); @@ -442,7 +444,7 @@ void itineraryReflectsDriverScheduleWhenTripDepartsBeforeRequestTime() { assertEquals( expectedStartTime.toInstant(), itinerary.startTime().toInstant(), - "Itinerary start time must match the driver's real pickup time, not the passenger's " + + "Itinerary start time must match the driver's pickup arrival time, not the passenger's " + "requested time — the driver cannot wait for the passenger" ); assertEquals( @@ -483,10 +485,10 @@ void resultItinerariesHaveValidStartAndEndTimes() { var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); var stopDuration = request.preferences().car().pickupTime(); - // Start time is when the car departs from the pickup with the passenger onboard, - // i.e. after the boarding dwell at the pickup. - var expectedStartTime = departureTime.plus(drivingToPickup).plus(stopDuration); - var expectedEndTime = expectedStartTime.plus(drivingPickupToDropoff); + // Start time is when the car arrives at the pickup. The boarding dwell is part of the + // leg's duration, so it is included in the end time rather than before the start. + var expectedStartTime = departureTime.plus(drivingToPickup); + var expectedEndTime = expectedStartTime.plus(stopDuration).plus(drivingPickupToDropoff); var results = service.routeDirect(request, linkingContext); @@ -499,12 +501,13 @@ void resultItinerariesHaveValidStartAndEndTimes() { assertEquals( expectedStartTime.toInstant(), itinerary.startTime().toInstant(), - "Start time should equal trip departure plus driving time to pickup plus boarding time" + "Start time should equal trip departure plus driving time to pickup (arrival at pickup)" ); assertEquals( expectedEndTime.toInstant(), itinerary.endTime().toInstant(), - "End time should equal start time plus driving time from pickup to dropoff" + "End time should equal start time plus boarding dwell plus driving time from pickup " + + "to dropoff" ); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java index 9666f0813d5..2347a58d1e7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java @@ -38,8 +38,9 @@ * *

    Time Calculation

    *

    - * The passenger's start time is always the driver's real pickup time (trip start + pickup - * travel + boarding dwell). It is not shifted to match the passenger's requested + * The passenger's start time is the moment the driver arrives at the pickup location + * (trip start + pickup travel); the boarding dwell is included in the leg's duration, not + * added before it. The start time is not shifted to match the passenger's requested * departure time: the driver is on a committed schedule and cannot wait. Whether the * passenger should show up early, or whether a trip starting before the requested time * should be matched at all, is a filtering concern and lives upstream of this mapper. @@ -88,8 +89,13 @@ public CarpoolItineraryMapper() { *

    * Start and end times come entirely from the driver's schedule: *

      - *
    1. Start: Driver's start time + pickup travel + boarding time
    2. - *
    3. End: Start time + shared segment durations
    4. + *
    5. Start: {@code trip.startTime() +} + * {@link InsertionCandidate#getDurationUntilPickupArrival()} — the moment the driver + * arrives at the pickup point.
    6. + *
    7. End: {@code start +} + * {@link InsertionCandidate#getPassengerRideDuration()}, which already includes the + * boarding dwell at the pickup and any intermediate stop delays along the shared + * segments.
    8. *
    * *

    Null Return Cases

    @@ -108,10 +114,7 @@ public Itinerary toItinerary(InsertionCandidate candidate) { return null; } - var startTime = candidate - .trip() - .startTime() - .plus(candidate.getDurationUntilDepartureWithPassenger()); + var startTime = candidate.trip().startTime().plus(candidate.getDurationUntilPickupArrival()); var endTime = startTime.plus(candidate.getPassengerRideDuration()); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java index 7f6fc597580..2e629ffd5e1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java @@ -14,12 +14,19 @@ public class CarpoolAccessEgress implements RoutingAccessEgress { /** - * The departure time of the passenger in seconds since transitSearchTimeZero. + * The Raptor departure time of this access/egress leg, in seconds since + * {@code transitSearchTimeZero}. For a carpool leg this is the moment the car arrives at the + * pickup: the passenger must be ready by this instant, since the driver is on a committed + * schedule and cannot wait. The boarding dwell at the pickup is part of + * {@link #durationInSeconds}, not of the time before departure. */ private final int departureTimeOfPassenger; /** - * The arrival time of the passenger in seconds since transitSearchTimeZero. + * The Raptor arrival time of this access/egress leg, in seconds since + * {@code transitSearchTimeZero}. For a carpool leg this is the moment the car reaches the + * dropoff (the transit stop for access, the passenger's destination for egress), after + * boarding dwell and shared travel. */ private final int arrivalTimeOfPassenger; private final int stop; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index 2e40f75ff0b..1ffb28491c8 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -93,26 +93,27 @@ public List> getDropoffSegments() { } /** - * Calculates the duration from trip start until the car departs with the passenger onboard. - * Includes travel time through pickup segments, intermediate stop delays, and boarding time - * at the pickup point. + * Calculates the duration from trip start until the car arrives at the passenger's pickup. + * Includes travel time through pickup segments and intermediate stop delays between them, but + * excludes the boarding dwell at the pickup itself — that is accounted for in + * {@link #getPassengerRideDuration()}. * Returns {@link Duration#ZERO} when the passenger boards at the trip origin (no pickup segments). */ - public Duration getDurationUntilDepartureWithPassenger() { - var pickupSegments = getPickupSegments(); - if (pickupSegments.isEmpty()) { - return Duration.ZERO; - } - return totalSegmentDuration(pickupSegments, stopDuration).plus(stopDuration); + public Duration getDurationUntilPickupArrival() { + return totalSegmentDuration(getPickupSegments(), stopDuration); } /** - * Calculates the duration of the passenger's ride from pickup to dropoff. - * Includes travel time through shared segments and stop delays at intermediate stops. - * For a single shared segment (direct ride), no stop delays are added. + * Calculates the duration of the passenger's ride from pickup arrival to dropoff. + * Includes the boarding dwell at the pickup (when there are pickup segments preceding it), + * travel time through shared segments, and stop delays at intermediate stops between shared + * segments. The no-pickup-segments case (passenger boarding at the trip origin) cannot occur + * today — the search never places {@code pickupPosition == 0} — but the branch guards against + * it by omitting the boarding dwell. */ public Duration getPassengerRideDuration() { - return totalSegmentDuration(getSharedSegments(), stopDuration); + Duration boardingDwell = pickupPosition == 0 ? Duration.ZERO : stopDuration; + return totalSegmentDuration(getSharedSegments(), stopDuration).plus(boardingDwell); } private static Duration totalSegmentDuration( diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index c7aef9eb971..1fccd53892e 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -518,7 +518,7 @@ private CarpoolAccessEgress createCarpoolAccessEgress( Double carpoolReluctance ) { var sharedSegments = insertionCandidate.getSharedSegments(); - var durationUntilPickup = insertionCandidate.getDurationUntilDepartureWithPassenger(); + var durationUntilPickup = insertionCandidate.getDurationUntilPickupArrival(); var passengerRideDuration = insertionCandidate.getPassengerRideDuration(); var startTimeOfSegment = insertionCandidate.trip().startTime().plus(durationUntilPickup); From 87dc018ba52b00b06b9639bf8f8c90c9fadc39e2 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Wed, 22 Apr 2026 10:54:24 +0200 Subject: [PATCH 121/177] Refactor: Better encapsulation in GenericLocation --- .../ojp/mapping/RouteRequestMapperTest.java | 22 +++--- .../service/DefaultCarpoolingService.java | 9 +-- .../SorlandsbanenNorwayService.java | 9 +-- .../api/common/LocationStringParser.java | 6 +- .../LegacyRouteRequestMapper.java | 2 +- .../routerequest/RouteRequestMapper.java | 9 ++- .../mapping/GenericLocationMapper.java | 10 ++- .../model/GenericLocation.java | 67 +++++++++++++++---- .../org/opentripplanner/model/plan/Place.java | 21 ++++-- .../mapping/RaptorPathToItineraryMapper.java | 2 +- .../api/request/via/VisitViaLocation.java | 2 +- .../linking/LinkingContextFactory.java | 64 ++++++++---------- .../api/common/LocationStringParserTest.java | 67 +++++++------------ .../routerequest/RouteRequestMapperTest.java | 14 ++-- .../_RouteRequestTestContext.java | 4 +- .../model/GenericLocationTest.java | 13 ++-- .../algorithm/StreetModeLinkingTest.java | 12 ++-- .../mapping/BikeRentalSnapshotTest.java | 21 +++--- .../algorithm/mapping/CarSnapshotTest.java | 28 ++++---- .../mapping/ElevationSnapshotTest.java | 27 ++++---- .../algorithm/mapping/SnapshotTestBase.java | 9 ++- .../mapping/TransitSnapshotTest.java | 35 +++++----- .../router/street/AccessEgressRouterTest.java | 2 +- .../algorithm/via/ViaRoutingWorkerTest.java | 2 +- .../linking/LinkingContextFactoryTest.java | 21 +++--- .../LinkingContextRequestMapperTest.java | 4 +- .../StreetSearchRequestMapperTest.java | 6 +- .../model/testcase/TestCaseDefinition.java | 12 +++- .../io/TestCaseDefinitionCsvFile.java | 16 +++-- 29 files changed, 279 insertions(+), 237 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java index 1c1980b1bb6..73dd4b4828e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java @@ -45,10 +45,10 @@ void mapWithCoordinates() { var routeRequest = mapper.map(tripRequest); - assertEquals(47.3769, routeRequest.from().lat); - assertEquals(8.5417, routeRequest.from().lng); - assertEquals(46.9480, routeRequest.to().lat); - assertEquals(7.4474, routeRequest.to().lng); + assertEquals(47.3769, routeRequest.from().wgsCoordinate().latitude()); + assertEquals(8.5417, routeRequest.from().wgsCoordinate().longitude()); + assertEquals(46.9480, routeRequest.to().wgsCoordinate().latitude()); + assertEquals(7.4474, routeRequest.to().wgsCoordinate().longitude()); assertTransitFilters("[ALLOW_ALL]", routeRequest); assertEquals(StreetMode.WALK, routeRequest.journey().access().mode()); @@ -62,8 +62,8 @@ void mapWithStopPlaceRef() { var routeRequest = mapper.map(tripRequest); - assertEquals(id("stop1"), routeRequest.from().stopId); - assertEquals(id("stop2"), routeRequest.to().stopId); + assertEquals(id("stop1"), routeRequest.from().stopId()); + assertEquals(id("stop2"), routeRequest.to().stopId()); } @Test @@ -75,8 +75,8 @@ void mapWithStopPointRef() { var routeRequest = mapper.map(tripRequest); assertNotNull(routeRequest.to()); - assertEquals(id("stopPoint1"), routeRequest.from().stopId); - assertEquals(id("stopPoint2"), routeRequest.to().stopId); + assertEquals(id("stopPoint1"), routeRequest.from().stopId()); + assertEquals(id("stopPoint2"), routeRequest.to().stopId()); } @Test @@ -87,9 +87,9 @@ void mapWithMixedLocationTypes() { var routeRequest = mapper.map(tripRequest); - assertEquals(47.3769, routeRequest.from().lat); - assertEquals(8.5417, routeRequest.from().lng); - assertEquals(id("stop1"), routeRequest.to().stopId); + assertEquals(47.3769, routeRequest.from().wgsCoordinate().latitude()); + assertEquals(8.5417, routeRequest.from().wgsCoordinate().longitude()); + assertEquals(id("stop1"), routeRequest.to().stopId()); } @Test diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 6cbe2eb276d..d854a8ef6b0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -353,10 +353,7 @@ public List routeAccessEgress( or the passenger's destination if the request is for egress */ GenericLocation passengerLocation = accessOrEgress.isAccess() ? request.from() : request.to(); - WgsCoordinate passengerCoordinates = new WgsCoordinate( - passengerLocation.lat, - passengerLocation.lng - ); + WgsCoordinate passengerCoordinates = passengerLocation.wgsCoordinate(); var passengerDepartureTime = request.dateTime(); @@ -514,12 +511,12 @@ public List routeAccessEgress( private void validateRequest(RouteRequest request) throws RoutingValidationException { Objects.requireNonNull(request.from()); Objects.requireNonNull(request.to()); - if (request.from().lat == null || request.from().lng == null) { + if (request.from().wgsCoordinate() == null) { throw new RoutingValidationException( List.of(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE)) ); } - if (request.to().lat == null || request.to().lng == null) { + if (request.to().wgsCoordinate() == null) { throw new RoutingValidationException( List.of(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)) ); diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java index 4f5bf3a9e63..a9abf3f017a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java @@ -93,17 +93,18 @@ private static WgsCoordinate findStopCoordinate( Collection accessEgress, RaptorTransitData raptorTransitData ) { - if (location.lat != null) { - return new WgsCoordinate(location.lat, location.lng); + var coord = location.wgsCoordinate(); + if (coord != null) { + return coord; } StopLocation firstStop = null; for (RoutingAccessEgress it : accessEgress) { StopLocation stop = raptorTransitData.getStopByIndex(it.stop()); - if (stop.getId().equals(location.stopId)) { + if (stop.getId().equals(location.stopId())) { return stop.getCoordinate(); } - if (idIsParentStation(stop, location.stopId)) { + if (idIsParentStation(stop, location.stopId())) { return stop.getParentStation().getCoordinate(); } if (firstStop == null) { diff --git a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java index c70148a2b9e..82f5dbfeaae 100644 --- a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java +++ b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java @@ -62,9 +62,11 @@ public static GenericLocation getGenericLocation(String label, String place) { if (matcher.find()) { var lat = Double.parseDouble(matcher.group(1)); var lon = Double.parseDouble(matcher.group(4)); - return new GenericLocation(label, null, lat, lon); + return GenericLocation.fromCoordinate(lat, lon, label); } else { - return new GenericLocation(label, FeedScopedId.parseOptional(place).orElse(null), null, null); + return FeedScopedId.parseOptional(place) + .map(id -> GenericLocation.fromStopId(id, label)) + .orElse(GenericLocation.fromUnspecified(label)); } } } 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 23ae84bfb3c..e14d5c7ab7b 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 @@ -262,7 +262,7 @@ private static GenericLocation toGenericLocation(Map m) { String address = (String) m.get("address"); if (address != null) { - return new GenericLocation(address, null, lat, lng); + return GenericLocation.fromCoordinate(lat, lng, address); } return GenericLocation.fromCoordinate(lat, lng); 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 690dc71b548..41777b61fea 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 @@ -184,7 +184,7 @@ private static GenericLocation parseGenericLocation( var stopId = stopLocation.getGraphQLStopLocationId(); return FeedScopedId.parseOptional(stopId) .map(feedScopedId -> - new GenericLocation(locationInput.getGraphQLLabel(), feedScopedId, null, null) + GenericLocation.fromStopId(feedScopedId, locationInput.getGraphQLLabel()) ) .orElseThrow(() -> new IllegalArgumentException("Stop id %s is not of valid format.".formatted(stopId)) @@ -192,11 +192,10 @@ private static GenericLocation parseGenericLocation( } var coordinate = locationInput.getGraphQLLocation().getGraphQLCoordinate(); - return new GenericLocation( - locationInput.getGraphQLLabel(), - null, + return GenericLocation.fromCoordinate( coordinate.getGraphQLLatitude(), - coordinate.getGraphQLLongitude() + coordinate.getGraphQLLongitude(), + locationInput.getGraphQLLabel() ); } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java index 9da4de2180b..2ff3f610d38 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java @@ -30,6 +30,14 @@ GenericLocation toGenericLocation(Map m) { String name = (String) m.get("name"); name = name == null ? "" : name; - return new GenericLocation(name, stopId, lat, lon); + if (stopId != null && lat != null && lon != null) { + return GenericLocation.fromStopIdWithFallback(stopId, lat, lon, name); + } else if (stopId != null) { + return GenericLocation.fromStopId(stopId, name); + } else if (lat != null && lon != null) { + return GenericLocation.fromCoordinate(lat, lon, name); + } else { + return GenericLocation.fromUnspecified(name); + } } } diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 4d79bbc5f15..14502d141be 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -4,6 +4,7 @@ import javax.annotation.Nullable; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.utils.lang.StringUtils; import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder; @@ -16,31 +17,23 @@ public class GenericLocation { public static final GenericLocation UNKNOWN = new GenericLocation(null, null, null, null); - /** - * A label for the place, if provided. This is pass-through information and does not affect - * routing in any way. - */ @Nullable - public final String label; + private final String label; - /** - * Refers to a specific element in the OTP model. This can currently be a regular stop, area stop, - * group stop, station, multi-modal station or group of stations. - */ @Nullable - public final FeedScopedId stopId; + private final FeedScopedId stopId; /** * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not * found. */ @Nullable - public final Double lat; + private final Double lat; @Nullable - public final Double lng; + private final Double lng; - public GenericLocation( + private GenericLocation( @Nullable String label, @Nullable FeedScopedId stopId, @Nullable Double lat, @@ -56,10 +49,24 @@ public static GenericLocation fromStopId(FeedScopedId id) { return new GenericLocation(null, id, null, null); } + public static GenericLocation fromStopId(FeedScopedId id, @Nullable String label) { + return new GenericLocation(label, id, null, null); + } + public static GenericLocation fromStopId(String name, String feedId, String stopId) { return new GenericLocation(name, new FeedScopedId(feedId, stopId), null, null); } + /// Create a GenericLocation of a stop id with fallback coordinates if the id is not found. + public static GenericLocation fromStopIdWithFallback( + FeedScopedId id, + double lat, + double lng, + @Nullable String label + ) { + return new GenericLocation(label, id, lat, lng); + } + /** * Create a new location based on a coordinate - the input is primitive doubles to prevent * inserting {@code null} values. @@ -68,6 +75,14 @@ public static GenericLocation fromCoordinate(double lat, double lng) { return new GenericLocation(null, null, lat, lng); } + public static GenericLocation fromCoordinate(double lat, double lng, @Nullable String label) { + return new GenericLocation(label, null, lat, lng); + } + + public static GenericLocation fromUnspecified(@Nullable String label) { + return new GenericLocation(label, null, null, null); + } + /** * Returns this as a Coordinate object. */ @@ -79,6 +94,32 @@ public Coordinate getCoordinate() { return new Coordinate(this.lng, this.lat); } + @Nullable + public WgsCoordinate wgsCoordinate() { + if (lat == null || lng == null) { + return null; + } + return new WgsCoordinate(lat, lng); + } + + /** + * Refers to a specific element in the OTP model. This can currently be a regular stop, area stop, + * group stop, station, multi-modal station or group of stations. + */ + @Nullable + public FeedScopedId stopId() { + return stopId; + } + + /** + * A label for the place, if provided. This is pass-through information and does not affect + * routing in any way. + */ + @Nullable + public String label() { + return label; + } + public boolean isSpecified() { return stopId != null || (lat != null && lng != null); } diff --git a/application/src/main/java/org/opentripplanner/model/plan/Place.java b/application/src/main/java/org/opentripplanner/model/plan/Place.java index d11cd2a6d22..9fc4a1c3d96 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Place.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Place.java @@ -82,6 +82,10 @@ private Place( this.viaLocationType = viaLocationType; } + public static Place noCoords(I18NString name) { + return new Place(name, null, VertexType.NORMAL, null, null, null, null); + } + public static Place normal(Double lat, Double lon, I18NString name) { return new Place( name, @@ -94,6 +98,10 @@ public static Place normal(Double lat, Double lon, I18NString name) { ); } + public static Place normal(WgsCoordinate coord, I18NString name) { + return new Place(name, coord, VertexType.NORMAL, null, null, null, null); + } + public static Place normal( Vertex vertex, I18NString name, @@ -174,14 +182,13 @@ public static Place forGenericLocation( I18NString defaultName ) { if (location == null) { - return Place.normal(null, null, defaultName); + return Place.noCoords(defaultName); } - - return Place.normal( - location.lat, - location.lng, - NonLocalizedString.ofNullableOrElse(location.label, defaultName) - ); + var coord = location.wgsCoordinate(); + if (coord == null) { + return Place.noCoords(NonLocalizedString.ofNullableOrElse(location.label(), defaultName)); + } + return Place.normal(coord, NonLocalizedString.ofNullableOrElse(location.label(), defaultName)); } public static Place forVehicleRentalPlace(VehicleRentalPlaceVertex vertex) { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index dd2e9fe8e50..c24bfada4ed 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -474,7 +474,7 @@ private Itinerary mapUnknownRaptorPath(RaptorPath path) { } private Place mapPlace(GenericLocation location) { - return Place.normal(location.lat, location.lng, new NonLocalizedString(location.label)); + return Place.normal(location.wgsCoordinate(), new NonLocalizedString(location.label())); } private ZonedDateTime createZonedDateTime(int timeInSeconds) { diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java index 4437d2f94f3..f82bc183d36 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java @@ -58,7 +58,7 @@ public GenericLocation coordinateLocation() { if (coordinate == null) { return null; } - return new GenericLocation(label(), null, coordinate.latitude(), coordinate.longitude()); + return GenericLocation.fromCoordinate(coordinate.latitude(), coordinate.longitude(), label()); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java b/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java index 29010f7fac6..2168dfe1178 100644 --- a/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java @@ -193,8 +193,8 @@ private Map> getVerticesForViaLocationsWithCoordina } private Set getStopVertices(GenericLocation location) { - if (location != null && location.stopId != null) { - return findStopOrChildStopVertices(location.stopId); + if (location != null && location.stopId() != null) { + return findStopOrChildStopVertices(location.stopId()); } return Set.of(); } @@ -281,7 +281,7 @@ private Set getStreetVerticesForLocation( .toList(); var results = new HashSet(); - if (location.stopId != null) { + if (location.stopId() != null) { if (!modes.stream().allMatch(TraverseMode::isInCar)) { results.addAll(getStreetVerticesForStop(location)); } @@ -299,7 +299,7 @@ private Set getStreetVerticesForLocation( vertexCreationService.createVertexFromCoordinate( container, location.getCoordinate(), - location.label, + location.label(), modes, type ) @@ -311,19 +311,19 @@ private Set getStreetVerticesForLocation( private Set getStreetVerticesForStop(GenericLocation location) { // check if there is a stop by the given id - var stopVertex = graph.findStopVertex(location.stopId); + var stopVertex = graph.findStopVertex(location.stopId()); if (stopVertex.isPresent()) { return Set.of(stopVertex.get()); } // station centroids may be used instead of child stop vertices for stations - var centroidVertex = graph.findStationCentroidVertex(location.stopId); + var centroidVertex = graph.findStationCentroidVertex(location.stopId()); if (centroidVertex.isPresent()) { return Set.of(centroidVertex.get()); } // in the regular case you want to resolve a (multi-modal) station into its child stops - var childVertices = findStopOrChildStopVertices(location.stopId); + var childVertices = findStopOrChildStopVertices(location.stopId()); if (!childVertices.isEmpty()) { return childVertices.stream().map(Vertex.class::cast).collect(Collectors.toUnmodifiableSet()); } @@ -339,43 +339,33 @@ private Optional getCarRoutableStreetVertex( GenericLocation location, LocationType type ) { + Coordinate coordinate = location.getCoordinate(); // Fetch coordinate from stop, if not given in request - if (location.getCoordinate() == null) { - var stopVertex = graph.getStopVertex(location.stopId); + if (coordinate == null) { + var stopVertex = graph.getStopVertex(location.stopId()); if (stopVertex != null) { - var c = stopVertex.toWgsCoordinate(); - location = new GenericLocation( - location.label, - location.stopId, - c.latitude(), - c.longitude() - ); + coordinate = stopVertex.getCoordinate(); } else { // For car routing, we use station's coordinate instead of child stops' if stop location is // a station. - var coordinate = findStopLocationsGroupCentroid.apply(location.stopId); - if (coordinate.isPresent()) { - var c = coordinate.get(); - location = new GenericLocation( - location.label, - location.stopId, - c.latitude(), - c.longitude() - ); - } + coordinate = findStopLocationsGroupCentroid + .apply(location.stopId()) + .map(WgsCoordinate::asJtsCoordinate) + .orElse(null); } } - return location.getCoordinate() != null - ? Optional.of( - vertexCreationService.createVertexFromCoordinate( - container, - location.getCoordinate(), - location.label, - List.of(TraverseMode.CAR), - type - ) - ) - : Optional.empty(); + if (coordinate == null) { + return Optional.empty(); + } + return Optional.of( + vertexCreationService.createVertexFromCoordinate( + container, + coordinate, + location.label(), + List.of(TraverseMode.CAR), + type + ) + ); } private void checkIfVerticesFound( diff --git a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java index cab62f54b77..ad1e403f89f 100644 --- a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java +++ b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java @@ -13,75 +13,58 @@ public class LocationStringParserTest { @Test public void testFromOldStyleString() { GenericLocation loc = LocationStringParser.fromOldStyleString("name::12345"); - assertEquals("name", loc.label); - assertNull(loc.stopId); - assertNull(loc.lat); - assertNull(loc.lng); + assertEquals("name", loc.label()); + assertNull(loc.stopId()); + assertNull(loc.wgsCoordinate()); assertNull(loc.getCoordinate()); } @Test public void testWithLabelAndCoord() { GenericLocation loc = LocationStringParser.fromOldStyleString("name::1.0,2.5"); - assertEquals("name", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(1.0), loc.lat); - assertEquals(Double.valueOf(2.5), loc.lng); + assertEquals("name", loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 200"); - assertEquals("Label Label", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(-15.0), loc.lat); - assertEquals(Double.valueOf(200), loc.lng); + assertEquals("Label Label", loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(200, -15), loc.getCoordinate()); loc = LocationStringParser.fromOldStyleString("A Label::122,-22.3"); - assertEquals("A Label", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(122), loc.lat); - assertEquals(Double.valueOf(-22.3), loc.lng); + assertEquals("A Label", loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); } @Test public void testWithId() { GenericLocation loc = LocationStringParser.fromOldStyleString("name::aFeed:A1B2C3"); - assertEquals("name", loc.label); - assertEquals(loc.stopId, new FeedScopedId("aFeed", "A1B2C3")); - assertNull(loc.lat); - assertNull(loc.lng); + assertEquals("name", loc.label()); + assertEquals(loc.stopId(), new FeedScopedId("aFeed", "A1B2C3")); assertNull(loc.getCoordinate()); loc = LocationStringParser.fromOldStyleString("feed:4321"); - assertNull(loc.label); - assertEquals(loc.stopId, new FeedScopedId("feed", "4321")); - assertNull(loc.lat); - assertNull(loc.lng); + assertNull(loc.label()); + assertEquals(loc.stopId(), new FeedScopedId("feed", "4321")); assertNull(loc.getCoordinate()); } @Test public void testWithCoordOnly() { GenericLocation loc = LocationStringParser.fromOldStyleString("1.0,2.5"); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(1.0), loc.lat); - assertEquals(Double.valueOf(2.5), loc.lng); + assertNull(loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); loc = LocationStringParser.fromOldStyleString(" -15.0, 200"); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(-15.0), loc.lat); - assertEquals(Double.valueOf(200), loc.lng); + assertNull(loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(200, -15), loc.getCoordinate()); loc = LocationStringParser.fromOldStyleString("122,-22.3 "); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(122), loc.lat); - assertEquals(Double.valueOf(-22.3), loc.lng); + assertNull(loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); } @@ -89,17 +72,17 @@ public void testWithCoordOnly() { public void testFromOldStyleStringIncomplete() { String input = "0::"; GenericLocation loc = LocationStringParser.fromOldStyleString(input); - assertEquals("0", loc.label); - assertNull(loc.stopId); + assertEquals("0", loc.label()); + assertNull(loc.stopId()); input = "::1"; loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label); - assertNull(loc.stopId); + assertEquals("", loc.label()); + assertNull(loc.stopId()); input = "::"; loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label); - assertNull(loc.stopId); + assertEquals("", loc.label()); + assertNull(loc.stopId()); } } 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 18ab6017d71..407ea13be95 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 @@ -32,10 +32,8 @@ void testMinimalArgs() { var defaultRequest = RouteRequest.defaultValue(); var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); - assertEquals(_RouteRequestTestContext.ORIGIN.x, routeRequest.from().lat); - assertEquals(_RouteRequestTestContext.ORIGIN.y, routeRequest.from().lng); - assertEquals(_RouteRequestTestContext.DESTINATION.x, routeRequest.to().lat); - assertEquals(_RouteRequestTestContext.DESTINATION.y, routeRequest.to().lng); + assertEquals(_RouteRequestTestContext.ORIGIN, routeRequest.from().getCoordinate()); + assertEquals(_RouteRequestTestContext.DESTINATION, routeRequest.to().getCoordinate()); assertEquals(testCtx.locale(), routeRequest.preferences().locale()); assertEquals(defaultRequest.journey().wheelchair(), routeRequest.journey().wheelchair()); assertEquals(defaultRequest.arriveBy(), routeRequest.arriveBy()); @@ -140,10 +138,10 @@ void testStopLocationAndLabel() { ); var env = testCtx.executionContext(stopLocationArgs); var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); - assertEquals(FeedScopedId.parseStrict(stopA), routeRequest.from().stopId); - assertEquals(originLabel, routeRequest.from().label); - assertEquals(FeedScopedId.parseStrict(stopB), routeRequest.to().stopId); - assertEquals(destinationLabel, routeRequest.to().label); + assertEquals(FeedScopedId.parseStrict(stopA), routeRequest.from().stopId()); + assertEquals(originLabel, routeRequest.from().label()); + assertEquals(FeedScopedId.parseStrict(stopB), routeRequest.to().stopId()); + assertEquals(destinationLabel, routeRequest.to().label()); } @Test diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java index 7ed6f883f01..549ed5f7f8a 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java @@ -39,12 +39,12 @@ class _RouteRequestTestContext { static final Map ARGS = Map.ofEntries( entry( "origin", - Map.ofEntries(entry("location", Map.of("coordinate", mapCoordinate(ORIGIN.x, ORIGIN.y)))) + Map.ofEntries(entry("location", Map.of("coordinate", mapCoordinate(ORIGIN.y, ORIGIN.x)))) ), entry( "destination", Map.ofEntries( - entry("location", Map.of("coordinate", mapCoordinate(DESTINATION.x, DESTINATION.y))) + entry("location", Map.of("coordinate", mapCoordinate(DESTINATION.y, DESTINATION.x))) ) ) ); diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 950e6e32642..3e25024aea8 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -15,16 +15,21 @@ class GenericLocationTest { private static final FeedScopedId STOP_ID = new FeedScopedId("F", "Stop:1"); private static final double LATITUDE = 20.0; private static final double LONGITUDE = 30.0; - private final GenericLocation subject = new GenericLocation(LABEL, STOP_ID, LATITUDE, LONGITUDE); + private final GenericLocation subject = GenericLocation.fromStopIdWithFallback( + STOP_ID, + LATITUDE, + LONGITUDE, + LABEL + ); @Test void fromStopId() { - assertEquals(STOP_ID, subject.stopId); + assertEquals(STOP_ID, subject.stopId()); } @Test void getCoordinate() { - assertEquals(STOP_ID, subject.stopId); + assertEquals(STOP_ID, subject.stopId()); } @Test @@ -35,7 +40,7 @@ void isSpecified() { @Test void testEquals() { - var copy = new GenericLocation(LABEL, STOP_ID, LATITUDE, LONGITUDE); + var copy = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); AssertEqualsAndHashCode.verify(subject).sameAs(copy).differentFrom(UNKNOWN); } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java index 616c4f30d18..a2e32c291f8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java @@ -84,11 +84,10 @@ public class StreetModeLinkingTest extends GraphRoutingTest { * A place used as dummy to/from, when testing from/to. It can be anywhere, except * the same location as the place under test. */ - private static final GenericLocation ANY_PLACE = new GenericLocation( - "Any place - not used", - null, + private static final GenericLocation ANY_PLACE = GenericLocation.fromCoordinate( LATITUDE_START, - LONGITUDE_0 + LONGITUDE_0, + "Any place - not used" ); private Graph graph; @@ -138,7 +137,8 @@ public void build() { graph.hasStreets = true; TestStreetLinkerModule.link(graph, otpModel.timetableRepository()); - this.stopLocation = new GenericLocation(stop.getLabelString(), stop.getId(), null, null); + String label = stop.getLabelString(); + this.stopLocation = GenericLocation.fromStopId(stop.getId(), label); } private static List testPedestrianLinkingTestCases() { @@ -327,7 +327,7 @@ static LinkingTestCase of(StreetTraversalPermission permission) { * {@code N, N+1, N-1, N+2, N-2 ... } */ GenericLocation placeCloseToStreet() { - return new GenericLocation("On " + name, null, LATITUDE_MIDDLE, longitude + OFFSET); + return GenericLocation.fromCoordinate(LATITUDE_MIDDLE, longitude + OFFSET, "On " + name); } StreetEdgeBuilder createStreetEdgeBuilder(GraphRoutingTest.Builder factory) { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java index 90447ea9425..f50a47b6052 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java @@ -23,25 +23,22 @@ public class BikeRentalSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "SW Johnson St. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.52832, - -122.70059 + -122.70059, + "SW Johnson St. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Hoyt St. & NW 20th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.52704, - -122.69240 + -122.69240, + "NW Hoyt St. & NW 20th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Everett St. & NW 5th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.52523, - -122.67525 + -122.67525, + "NW Everett St. & NW 5th Ave. (P3)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java index 966a0a32b83..cac78ed91f8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java @@ -20,32 +20,28 @@ public class CarSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "NW Pettygrove Ave. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.53261, - -122.70075 + -122.70075, + "NW Pettygrove Ave. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Marshall St. & NW 24th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.53046, - -122.70067 + -122.70067, + "NW Marshall St. & NW 24th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "Chapman Elementary School (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.53335, - -122.70517 + -122.70517, + "Chapman Elementary School (P3)" ); - static GenericLocation p4 = new GenericLocation( - "Legacy Good Samaritan Medical Center (P4)", - null, + static GenericLocation p4 = GenericLocation.fromCoordinate( 45.53060, - -122.69771 + -122.69771, + "Legacy Good Samaritan Medical Center (P4)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java index 4a1b05fabcd..b809f8e6f3c 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java @@ -26,28 +26,29 @@ public class ElevationSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "SW Johnson St. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.52832, - -122.70059 + -122.70059, + "SW Johnson St. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Hoyt St. & NW 20th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.52704, - -122.69240 + -122.69240, + "NW Hoyt St. & NW 20th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Everett St. & NW 5th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.52523, - -122.67525 + -122.67525, + "NW Everett St. & NW 5th Ave. (P3)" ); - static GenericLocation p4 = new GenericLocation("Sulzer Pump (P4)", null, 45.54549, -122.69659); + static GenericLocation p4 = GenericLocation.fromCoordinate( + 45.54549, + -122.69659, + "Sulzer Pump (P4)" + ); @BeforeAll public static void beforeClass() { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java index e6dd69432f5..5b5945ac8ae 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java @@ -295,10 +295,13 @@ private String createDebugUrlForRequest(RouteRequest request) { private String formatPlace(GenericLocation location) { String formatted; - if (location.stopId != null) { - formatted = String.format("%s::%s", location.label, location.stopId); + if (location.stopId() != null) { + formatted = String.format("%s::%s", location.label(), location.stopId()); + } else if (location.wgsCoordinate() != null) { + var coord = location.wgsCoordinate(); + formatted = String.format("%s::%s,%s", location.label(), coord.latitude(), coord.longitude()); } else { - formatted = String.format("%s::%s,%s", location.label, location.lat, location.lng); + formatted = String.format("%s::null", location.label()); } return URLEncoder.encode(formatted, StandardCharsets.UTF_8); } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java index 26df46ac506..208ca689594 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java @@ -20,39 +20,34 @@ public class TransitSnapshotTest extends SnapshotTestBase { static GenericLocation ps = GenericLocation.fromStopId("NE 12th & Couch", "prt", "6577"); - static GenericLocation p0 = new GenericLocation( - "SE Stark St. & SE 17th Ave. (P0)", - null, + static GenericLocation p0 = GenericLocation.fromCoordinate( 45.519320, - -122.648567 + -122.648567, + "SE Stark St. & SE 17th Ave. (P0)" ); - static GenericLocation p1 = new GenericLocation( - "SE Morrison St. & SE 17th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.51726, - -122.64847 + -122.64847, + "SE Morrison St. & SE 17th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Northrup St. & NW 22nd Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.53122, - -122.69659 + -122.69659, + "NW Northrup St. & NW 22nd Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Northrup St. & NW 24th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.53100, - -122.70029 + -122.70029, + "NW Northrup St. & NW 24th Ave. (P3)" ); - static GenericLocation p4 = new GenericLocation( - "NE Thompson St. & NE 18th Ave. (P4)", - null, + static GenericLocation p4 = GenericLocation.fromCoordinate( 45.53896, - -122.64699 + -122.64699, + "NE Thompson St. & NE 18th Ave. (P4)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java index 9467e929d8a..fadc688b950 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java @@ -214,7 +214,7 @@ private GenericLocation location(WgsCoordinate coordinate) { } private GenericLocation location(FeedScopedId id) { - return new GenericLocation(null, id, null, null); + return GenericLocation.fromStopId(id); } private GenericLocation location(String id) { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java index 29571098e03..d3dc06d99e8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java @@ -119,7 +119,7 @@ public void testViaRoutingWorker() { private RoutingResponse createRoutingResponse(RouteRequest req) { // request from A or C? var c = fromA.coordinate; - var firstOrSecondSearch = req.from().lng == c.longitude() && req.from().lat == c.latitude(); + var firstOrSecondSearch = req.from().wgsCoordinate().equals(c); var searchItineraries = firstOrSecondSearch ? firstSearch : secondSearch; diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java index 5853b7f8f08..a18758bb3f1 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java @@ -302,9 +302,9 @@ void locationsShouldBeRoutableWithTheGivenModes() { @Test void verticesShouldInheritNamesFromLocations() { try (var container = new TemporaryVerticesContainer()) { - var from = new GenericLocation("First", null, 0.5, 0.5); - var via = new GenericLocation("Second", null, 0.4, 0.6); - var to = new GenericLocation("Third", null, 0.6, 0.4); + var from = GenericLocation.fromCoordinate(0.5, 0.5, "First"); + var via = GenericLocation.fromCoordinate(0.4, 0.6, "Second"); + var to = GenericLocation.fromCoordinate(0.6, 0.4, "Third"); var request = LinkingContextRequest.of() .withFrom(from) .withTo(to) @@ -378,7 +378,7 @@ void locationOutsideBoundsException() { var request = LinkingContextRequest.of() .withFrom(GenericLocation.fromCoordinate(80, 80)) .withTo(GenericLocation.fromCoordinate(85, 85)) - .withViaLocationsWithCoordinates(List.of(new GenericLocation("Via1", null, 87.0, 87.0))) + .withViaLocationsWithCoordinates(List.of(GenericLocation.fromCoordinate(87.0, 87.0, "Via1"))) .withDirectMode(StreetMode.WALK) .build(); var exception = assertThrows(RoutingValidationException.class, () -> @@ -424,12 +424,17 @@ void nonExistingPlaceIdWithCoordinatesShouldFallbackToCoordinates() { var nonExistingStopId = new FeedScopedId("F", "NonExistingStop"); // Create locations with both a non-existing stop ID and valid coordinates - var from = new GenericLocation("From", nonExistingStopId, stopA.getLat(), stopA.getLon()); - var to = new GenericLocation( - "To", + var from = GenericLocation.fromStopIdWithFallback( + nonExistingStopId, + stopA.getLat(), + stopA.getLon(), + "From" + ); + var to = GenericLocation.fromStopIdWithFallback( new FeedScopedId("F", "AnotherNonExisting"), stopD.getLat(), - stopD.getLon() + stopD.getLon(), + "To" ); var request = LinkingContextRequest.of() diff --git a/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java index fc78b6cd3f3..7b8e84713fb 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java @@ -15,8 +15,8 @@ class LinkingContextRequestMapperTest { - private static final GenericLocation FROM = new GenericLocation("from", null, 1.0, 0.0); - private static final GenericLocation TO = new GenericLocation("to", null, 0.0, 1.0); + private static final GenericLocation FROM = GenericLocation.fromCoordinate(1.0, 0.0, "from"); + private static final GenericLocation TO = GenericLocation.fromCoordinate(0.0, 1.0, "to"); private static final List VIA = List.of( new VisitViaLocation("via", null, List.of(), WgsCoordinate.GREENWICH) ); diff --git a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java index fe838307944..ce39b0e77b1 100644 --- a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java @@ -79,7 +79,7 @@ void mapVehicleWalking() { Instant dateTime = INSTANT; builder.withDateTime(dateTime); - var from = new GenericLocation(null, id("STOP"), null, null); + var from = GenericLocation.fromStopId(id("STOP")); builder.withFrom(from); var to = GenericLocation.fromCoordinate(60.0, 20.0); builder.withTo(to); @@ -101,7 +101,7 @@ void mapVehicleWalking() { @ParameterizedTest @ValueSource(booleans = { true, false }) void mapTransferRequest(boolean arriveBy) { - var from = new GenericLocation(null, id("STOP"), null, null); + var from = GenericLocation.fromStopId(id("STOP")); var to = GenericLocation.fromCoordinate(60.0, 20.0); var builder = builder() .withArriveBy(arriveBy) @@ -332,7 +332,7 @@ void mapCarRentalArrivalRequest() { var dateTime = Instant.parse("2022-11-10T10:00:00Z"); var rentalDuration = Duration.ofHours(2); builder.withDateTime(dateTime); - var from = new GenericLocation(null, FeedScopedIdForTestFactory.id("STOP"), null, null); + var from = GenericLocation.fromStopId(FeedScopedIdForTestFactory.id("STOP")); builder.withFrom(from); var to = GenericLocation.fromCoordinate(60.0, 20.0); builder.withTo(to); diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java index 906158f0991..afe7c9ef1c2 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java @@ -30,9 +30,9 @@ public String toString() { return String.format( "#%s %s - via:%s - %s, %s - via:%s - %s, %s-%s(%s)", id, - fromPlace.label, + fromPlace.label(), viaLocation != null ? viaLocation.label() : null, - toPlace.label, + toPlace.label(), coordinateString(fromPlace), viaLocation != null ? coordinateString(viaLocation.coordinateLocation()) : null, coordinateString(toPlace), @@ -59,6 +59,12 @@ public boolean arrivalTimeSet() { } private String coordinateString(GenericLocation location) { - return ValueObjectToStringBuilder.of().addCoordinate(location.lat, location.lng).toString(); + var coord = location.wgsCoordinate(); + if (coord == null) { + return ValueObjectToStringBuilder.of().addCoordinate(null, null).toString(); + } + return ValueObjectToStringBuilder.of() + .addCoordinate(coord.latitude(), coord.longitude()) + .toString(); } } diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java index 8c593638199..19935ee5645 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java @@ -33,15 +33,15 @@ TestCaseDefinition parseRow() throws IOException { parseTime("departure"), parseTime("arrival"), parseDuration("window"), - new GenericLocation( + genericLocation( parseString("origin"), - FeedScopedId.ofNullable(feedId, parseString("fromPlace")), + parseString("fromPlace"), parseDouble("fromLat"), parseDouble("fromLon") ), - new GenericLocation( + genericLocation( parseString("destination"), - FeedScopedId.ofNullable(feedId, parseString("toPlace")), + parseString("toPlace"), parseDouble("toLat"), parseDouble("toLon") ), @@ -51,6 +51,14 @@ TestCaseDefinition parseRow() throws IOException { ); } + private GenericLocation genericLocation(String label, String id, double lat, double lon) { + var feedScopedId = FeedScopedId.ofNullable(feedId, id); + if (feedScopedId == null) { + return GenericLocation.fromCoordinate(lat, lon, label); + } + return GenericLocation.fromStopIdWithFallback(feedScopedId, lat, lon, label); + } + @Nullable private VisitViaLocation parseViaLocation() { try { From 0da83fa593b20ce43979cac7052314a9c4e58f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bliks=C3=A5s=20Carlsen?= Date: Mon, 20 Apr 2026 18:51:16 +0200 Subject: [PATCH 122/177] removed directional filtering from carpooling --- .../DirectionalCompatibilityFilterTest.java | 185 ------------------ .../carpooling/filter/FilterChainTest.java | 11 -- .../routing/InsertionPositionFinderTest.java | 14 +- .../ext/carpooling/Architecture.md | 8 +- .../opentripplanner/ext/carpooling/README.md | 23 +-- .../DirectionalCompatibilityFilter.java | 140 ------------- .../ext/carpooling/filter/FilterChain.java | 9 +- .../ext/carpooling/filter/TripFilter.java | 2 +- .../routing/InsertionPositionFinder.java | 111 ----------- .../service/DefaultCarpoolingService.java | 6 +- doc/user/sandbox/Carpooling.md | 2 - 11 files changed, 14 insertions(+), 497 deletions(-) delete mode 100644 application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java delete mode 100644 application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java deleted file mode 100644 index 0216a4de21d..00000000000 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -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.ext.carpooling.CarpoolTestCoordinates.LAKE_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_SOUTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTHEAST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStopAt; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithStops; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opentripplanner.street.geometry.WgsCoordinate; - -class DirectionalCompatibilityFilterTest { - - private DirectionalCompatibilityFilter filter; - - @BeforeEach - void setup() { - filter = new DirectionalCompatibilityFilter(); - } - - @Test - void accepts_passengerAlignedWithTrip_returnsTrue() { - // Trip goes north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger also going north - var passengerPickup = OSLO_EAST; - // Northeast - var passengerDropoff = new WgsCoordinate(59.9549, 10.7922); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerOppositeDirection_returnsFalse() { - // Trip goes north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going south - var passengerPickup = OSLO_EAST; - var passengerDropoff = OSLO_CENTER; - - assertFalse(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_tripAroundLake_passengerOnSegment_returnsTrue() { - // Trip goes around a lake: North → East → South → West - var stop1 = createStopAt(LAKE_EAST); - var stop2 = createStopAt(LAKE_SOUTH); - var trip = createTripWithStops(LAKE_NORTH, List.of(stop1, stop2), LAKE_WEST); - - // Passenger aligned with the southward segment (East → South) - // East side - var passengerPickup = new WgsCoordinate(59.9339, 10.7922); - // South of east - var passengerDropoff = new WgsCoordinate(59.9139, 10.7922); - - // Should accept because passenger aligns with East→South segment - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerFarFromRoute_butDirectionallyAligned_returnsTrue() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger far to the east but directionally aligned (both going north) - // Way east - var passengerPickup = new WgsCoordinate(59.9139, 11.0000); - var passengerDropoff = new WgsCoordinate(59.9439, 11.0000); - - // Should accept - only checks direction, not distance (that's DistanceBasedFilter's job) - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerPartiallyAligned_withinTolerance_returnsTrue() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going northeast (~45° off) - // Should accept within default tolerance (60°) - assertTrue(filter.accepts(trip, OSLO_CENTER, OSLO_NORTHEAST)); - } - - @Test - void accepts_passengerPerpendicularToTrip_returnsFalse() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going east (90° perpendicular) - // Should reject (exceeds 60° tolerance) - assertFalse(filter.accepts(trip, OSLO_CENTER, OSLO_EAST)); - } - - @Test - void accepts_complexRoute_multipleSegments_findsCompatibleSegment() { - // Trip with multiple segments going different directions - // Go east first - var stop1 = createStopAt(OSLO_EAST); - // Then northeast - var stop2 = createStopAt(OSLO_NORTHEAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Passenger going northeast (aligns with second segment) - var passengerPickup = new WgsCoordinate(59.9289, 10.7722); - var passengerDropoff = new WgsCoordinate(59.9389, 10.7822); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_tripWithSingleStop_checksAllSegments() { - var stop1 = createStopAt(OSLO_EAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Passenger aligned with first segment (Center → East) - var passengerPickup = new WgsCoordinate(59.9139, 10.7622); - var passengerDropoff = new WgsCoordinate(59.9139, 10.7822); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerWithinCorridorButWrongDirection_returnsFalse() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger nearby but going opposite direction - // North - var passengerPickup = new WgsCoordinate(59.9239, 10.7522); - // South (backtracking) - var passengerDropoff = new WgsCoordinate(59.9139, 10.7522); - - assertFalse(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void customBearingTolerance_acceptsWithinCustomTolerance() { - // Custom filter with 90° tolerance (very permissive) - var customFilter = new DirectionalCompatibilityFilter(90.0); - - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going east (90° perpendicular) - // Should accept with 90° tolerance (default 60° would reject) - assertTrue(customFilter.accepts(trip, OSLO_CENTER, OSLO_EAST)); - } - - @Test - void customBearingTolerance_rejectsOutsideCustomTolerance() { - // Custom filter with 30° tolerance (strict) - var customFilter = new DirectionalCompatibilityFilter(30.0); - - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going northeast (~45° off) - // Should reject with 30° tolerance (default 60° would accept) - assertFalse(customFilter.accepts(trip, OSLO_CENTER, OSLO_NORTHEAST)); - } - - @Test - void getBearingToleranceDegrees_returnsConfiguredValue() { - var customFilter = new DirectionalCompatibilityFilter(45.0); - assertEquals(45.0, customFilter.getBearingToleranceDegrees()); - } - - @Test - void defaultBearingTolerance_is60Degrees() { - assertEquals(60.0, filter.getBearingToleranceDegrees()); - } -} diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java index c37532160a4..bed4e6d9cc9 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java @@ -70,17 +70,6 @@ void accepts_firstFilterRejects_doesNotCallOthers() { assertFalse(filter2Called[0], "Filter2 should not have been called due to short-circuit"); } - @Test - void standard_checksDirectionalCompatibility() { - var chain = FilterChain.standard(); - - // Trip going north, passenger going south - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Should reject due to directional filter - assertFalse(chain.accepts(trip, OSLO_EAST, OSLO_CENTER)); - } - @Test void emptyChain_acceptsAll() { var chain = new FilterChain(List.of()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java index a3c72d0f6b7..ca1a46a9761 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java @@ -22,7 +22,7 @@ /** * Tests for {@link InsertionPositionFinder}. - * Focuses on heuristic validation: capacity, directional compatibility, and beeline delays. + * Focuses on heuristic validation: capacity and beeline delays. */ class InsertionPositionFinderTest { @@ -43,16 +43,6 @@ void findViablePositions_simpleTrip_findsPositions() { assertFalse(viablePositions.isEmpty()); } - @Test - void findViablePositions_incompatibleDirection_rejectsPosition() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going opposite direction (SOUTH→CENTER) when trip is CENTER→NORTH - var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_CENTER, Duration.ZERO); - - assertTrue(viablePositions.isEmpty()); - } - @Test void findViablePositions_noCapacity_rejectsPosition() { // Create a trip with 0 available seats @@ -91,7 +81,7 @@ void findViablePositions_multipleStops_checksAllCombinations() { var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH, Duration.ZERO); // Should evaluate multiple pickup/dropoff combinations - // Exact count depends on directional and beeline filtering + // Exact count depends on beeline filtering assertNotNull(viablePositions); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md index 447fb65ee76..3baadb83d06 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md @@ -19,13 +19,11 @@ org.opentripplanner.ext.carpooling/ ├── filter/ # Trip pre-filtering │ ├── TripFilter # Filter interface │ ├── TimeBasedFilter # Time window filtering -│ ├── DistanceBasedFilter # Geographic distance checks -│ └── DirectionalCompatibilityFilter # Directional alignment +│ └── DistanceBasedFilter # Geographic distance checks ├── constraints/ # Post-routing constraints │ └── PassengerDelayConstraints # Protects existing passengers ├── util/ # Utilities -│ ├── BeelineEstimator # Fast travel time estimates -│ └── DirectionalCalculator # Geographic bearing calculations +│ └── BeelineEstimator # Fast travel time estimates ├── updater/ # Real-time updates │ ├── SiriETCarpoolingUpdater # SIRI-ET integration │ └── CarpoolSiriMapper # Maps SIRI to domain model @@ -43,7 +41,6 @@ Fast pre-screening to eliminate incompatible trips: - **Capacity Filter**: Checks if any seats are available - **Time-Based Filter**: Ensures departure time compatibility - **Distance-Based Filter**: Validates pickup/dropoff are within 50km of driver's route -- **Directional Compatibility Filter**: Verifies passenger direction aligns with trip route ### 2. Routing Phase Optimal insertion point calculation: @@ -54,7 +51,6 @@ Optimal insertion point calculation: ### 3. Constraint Validation - **Capacity constraints**: Ensures vehicle capacity is not exceeded -- **Directional constraints**: Prevents backtracking (90° tolerance) - **Passenger delay constraints**: Protects existing passengers (max 5 minutes additional delay) - **Deviation budget**: Respects driver's maximum acceptable detour time diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index 0f48b40177c..6689cc46afc 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -35,14 +35,12 @@ The carpooling extension enables OpenTripPlanner to find carpool trip options by │ 1. Filter Phase (FilterChain) │ │ - Capacity check │ │ - Time window check │ -│ - Direction check │ │ - Distance check │ │ │ │ 2. Insertion Phase │ │ 2a. Position Pre-screening │ │ (InsertionPositionFinder) │ │ - Capacity check │ -│ - Directional check │ │ - Beeline delay heuristic │ │ │ │ 2b. Routing & Selection │ @@ -54,7 +52,6 @@ The carpooling extension enables OpenTripPlanner to find carpool trip options by │ │ │ 3. Validation Phase (CompositeValidator) │ │ - Capacity timeline check │ -│ - Directional consistency check │ │ - Deviation budget check │ │ │ └────────┬───────────────────────────────────┘ @@ -85,7 +82,6 @@ org.opentripplanner.ext.carpooling/ ├── filter/ # Pre-screening filters │ ├── FilterChain.java # Composite filter │ ├── TimeBasedFilter.java # Time window check -│ ├── DirectionalCompatibilityFilter.java # Direction check │ └── DistanceBasedFilter.java # Distance check │ ├── routing/ # Insertion optimization @@ -96,8 +92,7 @@ org.opentripplanner.ext.carpooling/ │ ├── validation/ # Constraint validation │ ├── CompositeValidator.java # Composite validator -│ ├── CapacityValidator.java # Capacity timeline check -│ └── DirectionalValidator.java # Backtracking check +│ └── CapacityValidator.java # Capacity timeline check │ ├── internal/ # Implementation details │ ├── DefaultCarpoolingRepository.java # In-memory repository @@ -107,8 +102,7 @@ org.opentripplanner.ext.carpooling/ │ └── SiriETCarpoolingUpdater.java # SIRI-ET message processing │ ├── util/ # Utilities -│ ├── BeelineEstimator.java # Straight-line distance estimation -│ └── DirectionalCalculator.java # Bearing and direction calculations +│ └── BeelineEstimator.java # Straight-line distance estimation │ ├── constraints/ # Constraint definitions │ └── PassengerDelayConstraints.java # Delay limits for passengers @@ -124,8 +118,7 @@ org.opentripplanner.ext.carpooling/ Filters eliminate obviously incompatible trips **without any street routing**: 1. **TimeBasedFilter**: Is the trip timing compatible with passenger request? -2. **DirectionalCompatibilityFilter**: Are driver and passenger heading the same direction? -3. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? +2. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? **Performance**: O(n) where n = number of active trips. @@ -145,15 +138,13 @@ For each remaining trip: 2. For each position pair, check: a. Capacity: Does insertion exceed vehicle capacity at any point? - b. Direction: Does insertion cause backtracking or U-turns? - c. Beeline delay: Do straight-line estimates exceed delay threshold? + b. Beeline delay: Do straight-line estimates exceed delay threshold? 3. Return only "viable" positions that pass all checks ``` **Key optimizations**: - **Capacity validation**: Uses `CarpoolTrip.hasCapacityForInsertion()` to check entire journey range -- **Directional filtering**: Prevents insertions that deviate >90° from route bearing - **Beeline heuristic**: Optimistic straight-line estimates eliminate positions early - **No routing yet**: All checks use geometric calculations only @@ -191,11 +182,7 @@ Ensures the proposed insertion satisfies all constraints: - Tracks passenger count at each stop - Ensures capacity never exceeds vehicle limit -2. **DirectionalValidator**: Ensures no backtracking - - Computes bearings between consecutive stops - - Rejects if bearing changes > threshold (indicates backtracking) - -3. **Deviation Budget Check**: Ensures additional time ≤ driver's stated willingness +2. **Deviation Budget Check**: Ensures additional time ≤ driver's stated willingness **All validators must pass** for an insertion to be considered valid. diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java deleted file mode 100644 index 9cd14a1c2a1..00000000000 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import org.opentripplanner.ext.carpooling.model.CarpoolTrip; -import org.opentripplanner.street.geometry.DirectionUtils; -import org.opentripplanner.street.geometry.WgsCoordinate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filters trips based on directional compatibility with the passenger journey. - *

    - * This prevents carpooling from becoming a taxi service by ensuring trips and - * passengers are going in generally the same direction. Uses optimized segment-based - * analysis to handle routes that take detours (e.g., driving around a lake). - *

    - */ -public class DirectionalCompatibilityFilter implements TripFilter { - - private static final Logger LOG = LoggerFactory.getLogger(DirectionalCompatibilityFilter.class); - - /** - * Default maximum bearing difference for compatibility. - * 60° allows for reasonable detours while preventing perpendicular or opposite directions. - */ - public static final double DEFAULT_BEARING_TOLERANCE_DEGREES = 60.0; - - private final double bearingToleranceDegrees; - - public DirectionalCompatibilityFilter() { - this(DEFAULT_BEARING_TOLERANCE_DEGREES); - } - - public DirectionalCompatibilityFilter(double bearingToleranceDegrees) { - this.bearingToleranceDegrees = bearingToleranceDegrees; - } - - @Override - public boolean accepts( - CarpoolTrip trip, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - List routePoints = trip.routePoints(); - - if (routePoints.size() < 2) { - LOG.warn("Trip {} has fewer than 2 route points, rejecting", trip.getId()); - return false; - } - - double passengerBearing = DirectionUtils.getAzimuth( - passengerPickup.asJtsCoordinate(), - passengerDropoff.asJtsCoordinate() - ); - - for (int i = 0; i < routePoints.size() - 1; i++) { - if (isSegmentCompatible(routePoints.get(i), routePoints.get(i + 1), passengerBearing)) { - LOG.debug( - "Trip {} accepted: passenger journey aligns with segment {} ({} to {})", - trip.getId(), - i, - routePoints.get(i), - routePoints.get(i + 1) - ); - return true; - } - } - - // Check full route as fallback - if (isSegmentCompatible(routePoints.getFirst(), routePoints.getLast(), passengerBearing)) { - LOG.debug( - "Trip {} accepted: passenger journey aligns with full route ({} to {})", - trip.getId(), - routePoints.getFirst(), - routePoints.getLast() - ); - return true; - } - - LOG.debug( - "Trip {} rejected by directional filter: passenger journey (bearing {}°) not aligned with any route segments", - trip.getId(), - Math.round(passengerBearing) - ); - return false; - } - - @Override - public boolean acceptsAccessEgress( - CarpoolTrip trip, - WgsCoordinate coordinateOfPassenger, - Instant passengerDepartureTime, - Duration searchWindow - ) { - var tripStartCoordinate = trip.routePoints().getFirst().asJtsCoordinate(); - var tripEndCoordinate = trip.routePoints().getLast().asJtsCoordinate(); - var passengerCoordJts = coordinateOfPassenger.asJtsCoordinate(); - - var tripBearing = DirectionUtils.getAzimuth(tripStartCoordinate, tripEndCoordinate); - var startToPassengerBearing = DirectionUtils.getAzimuth(tripStartCoordinate, passengerCoordJts); - var endToPassengerBearing = DirectionUtils.getAzimuth(passengerCoordJts, tripEndCoordinate); - - return ( - bearingsAreWithinTolerance(tripBearing, startToPassengerBearing) && - bearingsAreWithinTolerance(tripBearing, endToPassengerBearing) - ); - } - - double getBearingToleranceDegrees() { - return bearingToleranceDegrees; - } - - /** - * Checks if a segment is directionally compatible with the passenger journey. - * - * @param segmentStart Start coordinate of the segment - * @param segmentEnd End coordinate of the segment - * @param passengerBearing Bearing of passenger journey - * @return true if segment bearing is within tolerance of passenger bearing - */ - private boolean isSegmentCompatible( - WgsCoordinate segmentStart, - WgsCoordinate segmentEnd, - double passengerBearing - ) { - double segmentBearing = DirectionUtils.getAzimuth( - segmentStart.asJtsCoordinate(), - segmentEnd.asJtsCoordinate() - ); - - return bearingsAreWithinTolerance(segmentBearing, passengerBearing); - } - - private boolean bearingsAreWithinTolerance(double bearing1, double bearing2) { - double bearingDiff = DirectionUtils.bearingDifference(bearing1, bearing2); - return bearingDiff <= bearingToleranceDegrees; - } -} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java index 663f9263cb9..f3caa61d9f9 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java @@ -15,7 +15,6 @@ * The standard filter chain includes (in order of performance impact): * 1. TimeBasedFilter - Very fast (O(1)) * 2. DistanceBasedFilter - Fast (O(1) with 4 distance calculations) - * 3. DirectionalCompatibilityFilter - Medium (O(n) with n = number of stops) */ public class FilterChain implements TripFilter { @@ -32,13 +31,7 @@ public FilterChain(List filters) { * the benefit of short-circuit evaluation. */ public static FilterChain standard() { - return new FilterChain( - List.of( - new TimeBasedFilter(), - new DistanceBasedFilter(), - new DirectionalCompatibilityFilter() - ) - ); + return new FilterChain(List.of(new TimeBasedFilter(), new DistanceBasedFilter())); } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java index 92086bf3ff5..a8a011c3c6a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java @@ -9,7 +9,7 @@ * Interface for filtering carpool trips before expensive routing calculations. *

    * Filters are applied as a pre-screening mechanism to quickly eliminate - * incompatible trips based on various criteria (direction, capacity, time, distance, etc.). + * incompatible trips based on various criteria (capacity, time, distance, etc.). *

    * Supports both direct routing (pickup + dropoff) and access/egress routing * (single passenger coordinate near a transit stop). diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index ff1417dc9c8..25e094a63cd 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -6,7 +6,6 @@ import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; -import org.opentripplanner.street.geometry.DirectionUtils; import org.opentripplanner.street.geometry.WgsCoordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +17,6 @@ * are worth evaluating with expensive A* routing. It validates positions using: *

      *
    • Capacity constraints - ensures available seats throughout the journey
    • - *
    • Directional compatibility - prevents backtracking and U-turns
    • *
    • Beeline delay heuristic - optimistic straight-line time estimates
    • *
    *

    @@ -29,9 +27,6 @@ public class InsertionPositionFinder { private static final Logger LOG = LoggerFactory.getLogger(InsertionPositionFinder.class); - /** Maximum bearing deviation allowed for forward progress (90° allows detours, prevents U-turns) */ - private static final double FORWARD_PROGRESS_TOLERANCE_DEGREES = 90.0; - private final BeelineEstimator beelineEstimator; /** @@ -88,23 +83,6 @@ public List findViablePositions( continue; } - if ( - !insertionMaintainsForwardProgress( - routePoints, - pickupPos, - dropoffPos, - passengerPickup, - passengerDropoff - ) - ) { - LOG.trace( - "Insertion at pickup={}, dropoff={} rejected by directional check", - pickupPos, - dropoffPos - ); - continue; - } - if ( !passesBeelineDelayCheck( routePoints, @@ -132,95 +110,6 @@ public List findViablePositions( return viable; } - /** - * Checks if inserting pickup/dropoff points maintains forward progress. - * Prevents backtracking by ensuring insertions don't cause the route - * to deviate too far from its intended direction. - * - * @param routePoints Current route points - * @param pickupPos 0-based index of the passenger's pickup in the modified route - * @param dropoffPos 0-based index of the passenger's dropoff in the modified route - * @param passengerPickup Passenger pickup coordinate - * @param passengerDropoff Passenger dropoff coordinate - * @return true if insertion maintains forward progress - */ - private boolean insertionMaintainsForwardProgress( - List routePoints, - int pickupPos, - int dropoffPos, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - if (pickupPos > 0 && pickupPos < routePoints.size()) { - WgsCoordinate prevPoint = routePoints.get(pickupPos - 1); - WgsCoordinate nextPoint = routePoints.get(pickupPos); - - if (!maintainsForwardProgress(prevPoint, passengerPickup, nextPoint)) { - return false; - } - } - - if (dropoffPos > 0 && dropoffPos <= routePoints.size()) { - WgsCoordinate prevPoint; - if (dropoffPos == pickupPos) { - prevPoint = passengerPickup; - } else if (dropoffPos - 1 < routePoints.size()) { - prevPoint = routePoints.get(dropoffPos - 1); - } else { - return true; - } - - if (dropoffPos < routePoints.size()) { - WgsCoordinate nextPoint = routePoints.get(dropoffPos); - - return maintainsForwardProgress(prevPoint, passengerDropoff, nextPoint); - } - } - - return true; - } - - /** - * Checks if inserting a new point maintains forward progress. - */ - private boolean maintainsForwardProgress( - WgsCoordinate previous, - WgsCoordinate newPoint, - WgsCoordinate next - ) { - // Skip check if inserting at an existing point (newPoint equals next or previous) - // This avoids undefined bearing calculations from a point to itself - if (newPoint.equals(next) || newPoint.equals(previous)) { - return true; - } - - // Calculate intended direction (previous → next) - double intendedBearing = DirectionUtils.getAzimuth( - previous.asJtsCoordinate(), - next.asJtsCoordinate() - ); - - // Calculate detour directions - double bearingToNew = DirectionUtils.getAzimuth( - previous.asJtsCoordinate(), - newPoint.asJtsCoordinate() - ); - double bearingFromNew = DirectionUtils.getAzimuth( - newPoint.asJtsCoordinate(), - next.asJtsCoordinate() - ); - - // Check deviations - double deviationToNew = DirectionUtils.bearingDifference(intendedBearing, bearingToNew); - double deviationFromNew = DirectionUtils.bearingDifference(intendedBearing, bearingFromNew); - - // Allow some deviation but not complete reversal - return ( - deviationToNew <= FORWARD_PROGRESS_TOLERANCE_DEGREES && - deviationFromNew <= FORWARD_PROGRESS_TOLERANCE_DEGREES - ); - } - /** * Checks if an insertion position passes the beeline delay heuristic. * This is a fast, optimistic check using straight-line distance estimates. diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 1fccd53892e..213dbc5cf13 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -62,10 +62,10 @@ * The service executes routing requests in three distinct phases: *

      *
    1. Pre-filtering ({@link FilterChain}): Quickly eliminates incompatible - * trips based on capacity, time windows, direction, and distance.
    2. + * trips based on capacity, time windows, and distance. *
    3. Position Finding ({@link InsertionPositionFinder}): For trips that * pass filtering, identifies viable pickup/dropoff position pairs using fast heuristics - * (capacity, direction, beeline delay estimates). No routing is performed in this phase.
    4. + * (capacity, beeline delay estimates). No routing is performed in this phase. *
    5. Insertion Evaluation ({@link InsertionEvaluator}): For viable positions, * computes actual routes using A* street routing. Evaluates all feasible insertion positions * and selects the one minimizing additional travel time while satisfying delay constraints.
    6. @@ -139,7 +139,7 @@ public DefaultCarpoolingService( * This method executes the full three-phase carpooling algorithm: *
        *
      1. Pre-filtering: All trips from the repository are filtered by capacity, - * time window, direction, and distance to quickly eliminate incompatible matches.
      2. + * time window, and distance to quickly eliminate incompatible matches. *
      3. Position finding: For each surviving trip, viable pickup/dropoff * insertion positions are identified using beeline heuristics (no routing).
      4. *
      5. Insertion evaluation: Viable positions are evaluated with A* street diff --git a/doc/user/sandbox/Carpooling.md b/doc/user/sandbox/Carpooling.md index c2475efcd41..a11a5503b55 100644 --- a/doc/user/sandbox/Carpooling.md +++ b/doc/user/sandbox/Carpooling.md @@ -72,7 +72,6 @@ The carpooling service matches passengers with compatible carpool trips based on - **Availability**: Checks if seats are available in the vehicle - **Time Compatibility**: Ensures the trip timing works for the passenger - **Route Alignment**: Validates that pickup and dropoff locations are reasonably close to the driver's route -- **Direction**: Verifies the passenger's travel direction aligns with the trip route The system automatically calculates the optimal pickup and dropoff points along the driver's route that minimize additional travel time while respecting all constraints. @@ -81,7 +80,6 @@ The system automatically calculates the optimal pickup and dropoff points along To ensure a good experience for all users, the system enforces several constraints: - **Vehicle Capacity**: Never exceeds the maximum number of seats -- **Route Logic**: Prevents backtracking or illogical detours - **Existing Passenger Protection**: Limits additional delay to existing passengers (maximum 5 minutes) - **Driver Deviation Budget**: Respects the driver's maximum acceptable detour time (currently 15 minutes) From b781b37e57cd0bb78423dd088738b06f66fbedbe Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Wed, 22 Apr 2026 15:18:12 +0200 Subject: [PATCH 123/177] Use coordinate internally in GenericLocation --- .../model/GenericLocation.java | 47 ++++++++----------- .../api/common/LocationStringParserTest.java | 16 +++---- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 14502d141be..861c55fa16b 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -15,7 +15,7 @@ */ public class GenericLocation { - public static final GenericLocation UNKNOWN = new GenericLocation(null, null, null, null); + public static final GenericLocation UNKNOWN = new GenericLocation(null, null, null); @Nullable private final String label; @@ -28,33 +28,28 @@ public class GenericLocation { * found. */ @Nullable - private final Double lat; - - @Nullable - private final Double lng; + WgsCoordinate coordinate; private GenericLocation( @Nullable String label, @Nullable FeedScopedId stopId, - @Nullable Double lat, - @Nullable Double lng + @Nullable WgsCoordinate coordinate ) { this.label = label; this.stopId = stopId; - this.lat = lat; - this.lng = lng; + this.coordinate = coordinate; } public static GenericLocation fromStopId(FeedScopedId id) { - return new GenericLocation(null, id, null, null); + return new GenericLocation(null, id, null); } public static GenericLocation fromStopId(FeedScopedId id, @Nullable String label) { - return new GenericLocation(label, id, null, null); + return new GenericLocation(label, id, null); } public static GenericLocation fromStopId(String name, String feedId, String stopId) { - return new GenericLocation(name, new FeedScopedId(feedId, stopId), null, null); + return new GenericLocation(name, new FeedScopedId(feedId, stopId), null); } /// Create a GenericLocation of a stop id with fallback coordinates if the id is not found. @@ -64,7 +59,7 @@ public static GenericLocation fromStopIdWithFallback( double lng, @Nullable String label ) { - return new GenericLocation(label, id, lat, lng); + return new GenericLocation(label, id, new WgsCoordinate(lat, lng)); } /** @@ -72,15 +67,15 @@ public static GenericLocation fromStopIdWithFallback( * inserting {@code null} values. */ public static GenericLocation fromCoordinate(double lat, double lng) { - return new GenericLocation(null, null, lat, lng); + return new GenericLocation(null, null, new WgsCoordinate(lat, lng)); } public static GenericLocation fromCoordinate(double lat, double lng, @Nullable String label) { - return new GenericLocation(label, null, lat, lng); + return new GenericLocation(label, null, new WgsCoordinate(lat, lng)); } public static GenericLocation fromUnspecified(@Nullable String label) { - return new GenericLocation(label, null, null, null); + return new GenericLocation(label, null, null); } /** @@ -88,18 +83,15 @@ public static GenericLocation fromUnspecified(@Nullable String label) { */ @Nullable public Coordinate getCoordinate() { - if (this.lat == null || this.lng == null) { + if (this.coordinate == null) { return null; } - return new Coordinate(this.lng, this.lat); + return coordinate.asJtsCoordinate(); } @Nullable public WgsCoordinate wgsCoordinate() { - if (lat == null || lng == null) { - return null; - } - return new WgsCoordinate(lat, lng); + return coordinate; } /** @@ -121,7 +113,7 @@ public String label() { } public boolean isSpecified() { - return stopId != null || (lat != null && lng != null); + return stopId != null || coordinate != null; } @Override @@ -133,14 +125,13 @@ public boolean equals(Object o) { return ( Objects.equals(label, that.label) && Objects.equals(stopId, that.stopId) && - Objects.equals(lat, that.lat) && - Objects.equals(lng, that.lng) + Objects.equals(coordinate, that.coordinate) ); } @Override public int hashCode() { - return Objects.hash(label, stopId, lat, lng); + return Objects.hash(label, stopId, coordinate); } @Override @@ -154,7 +145,9 @@ public String toString() { buf.addText(label).addText(" "); } buf.addObj(stopId); - buf.addCoordinate(lat, lng); + if (coordinate != null) { + buf.addCoordinate(coordinate.latitude(), coordinate.longitude()); + } return buf.toString(); } } diff --git a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java index ad1e403f89f..a9f75a62f95 100644 --- a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java +++ b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java @@ -26,15 +26,15 @@ public void testWithLabelAndCoord() { assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 200"); + loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 170"); assertEquals("Label Label", loc.label()); assertNull(loc.stopId()); - assertEquals(new Coordinate(200, -15), loc.getCoordinate()); + assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("A Label::122,-22.3"); + loc = LocationStringParser.fromOldStyleString("A Label::89,-22.3"); assertEquals("A Label", loc.label()); assertNull(loc.stopId()); - assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); + assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); } @Test @@ -57,15 +57,15 @@ public void testWithCoordOnly() { assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString(" -15.0, 200"); + loc = LocationStringParser.fromOldStyleString(" -15.0, 170"); assertNull(loc.label()); assertNull(loc.stopId()); - assertEquals(new Coordinate(200, -15), loc.getCoordinate()); + assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("122,-22.3 "); + loc = LocationStringParser.fromOldStyleString("89,-22.3 "); assertNull(loc.label()); assertNull(loc.stopId()); - assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); + assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); } @Test From 113e5c460d5901a18ad88c409f84c98b6779ecf6 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 22 Apr 2026 14:31:30 +0000 Subject: [PATCH 124/177] Add changelog entry for #7536 [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 1afa6f61d2c..b2b2175ad91 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -26,6 +26,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Fix cost mismatch by anchoring pattern-ride pareto cost to last stop [#7504](https://github.com/opentripplanner/OpenTripPlanner/pull/7504) - Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) - De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) +- Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 4a3010b9e1d160ea9651de8eaa627c135726869b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:42:08 +0000 Subject: [PATCH 125/177] Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.6.1 --- application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/pom.xml b/application/pom.xml index d55f7c9c911..a9af197ee94 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -321,7 +321,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.6 + 5.6.1 commons-cli From aa6b6444482a540fbd0f77e940b3daaa007930bf Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Wed, 22 Apr 2026 22:02:22 +0100 Subject: [PATCH 126/177] update version of includeOsmStationEntrances to V2_10 --- .../standalone/config/buildconfig/OsmConfig.java | 4 ++-- doc/user/BuildConfiguration.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java index 7d71518e2ef..fa5f1f72ce2 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java @@ -1,7 +1,7 @@ package org.opentripplanner.standalone.config.buildconfig; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParameters; import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParametersBuilder; @@ -89,7 +89,7 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters( .withIncludeOsmStationEntrances( node .of("includeOsmStationEntrances") - .since(V2_7) + .since(V2_10) .summary( "Whether to include station entrances from the OSM data." + documentationAddition ) diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index d30b6d64a65..e79a218d8f6 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -85,12 +85,12 @@ Sections follow that describe particular settings in more depth. |    [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | |    [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | | [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 | -|       includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.7 | +|       includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.10 | |       [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 | |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | |       timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 | | osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | -|    includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. | *Optional* | `false` | 2.7 | +|    includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. | *Optional* | `false` | 2.10 | |    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 | |    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 | | [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | From a3a104f80cd3c2b079667cd71bbc41cb10eadb18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:04:47 +0000 Subject: [PATCH 127/177] Update Maven plugins --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index e0523905703..79552958b0f 100644 --- a/pom.xml +++ b/pom.xml @@ -307,7 +307,7 @@ but we need the Maven project version as well, so we perform substitution. --> io.github.git-commit-id git-commit-id-maven-plugin - 9.0.2 + 9.2.0 @@ -332,7 +332,7 @@ org.openrewrite.maven rewrite-maven-plugin - 6.34.0 + 6.37.0 true @@ -351,7 +351,7 @@ org.openrewrite.recipe rewrite-static-analysis - 2.30.1 + 2.33.0 @@ -375,7 +375,7 @@ com.puppycrawl.tools checkstyle - 13.3.0 + 13.4.0 From 5d5dd758b8613d37d955e7131dd3ae9f368cf9fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:04:55 +0000 Subject: [PATCH 128/177] Update dependency com.graphql-java:graphql-java to v26 --- application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/pom.xml b/application/pom.xml index d55f7c9c911..4ea2ceedd5c 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -311,7 +311,7 @@ com.graphql-java graphql-java - 25.0 + 26.0 com.graphql-java From 6f9c242de1f26a3bfd34ce5f8a8df1feed32d84f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 23 Apr 2026 07:12:51 +0200 Subject: [PATCH 129/177] Fix Javadoc --- .../src/main/java/org/opentripplanner/osm/model/OsmEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index 1d56d3ac0aa..f8f7632d42d 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -936,7 +936,7 @@ public boolean hasNoName() { * set on the entity in OSM. * * @see OsmEntity#isNamed() - * @see https://wiki.openstreetmap.org/wiki/Tag:noname%3Dyes + * @link https://wiki.openstreetmap.org/wiki/Tag:noname%3Dyes */ public boolean isExplicitlyUnnamed() { return isTagTrue("noname"); From 38b3558d7a802b3bc4a2b228886f9eca5d89bdae Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 23 Apr 2026 07:35:44 +0200 Subject: [PATCH 130/177] Fix Javadoc --- .../org/opentripplanner/raptor/spi/RaptorSlackProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java index c233155f6e9..3ca17ad83a9 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java @@ -37,7 +37,7 @@ public interface RaptorSlackProvider { int alightSlack(int slackIndex); /** - * Return the {@link #boardSlack(int) plus {@link #alightSlack(int) + * Return the {@link #boardSlack(int)} plus {@link #alightSlack(int)} * slack. *

        * Unit: seconds. From 6c5c74d4df83084ddfdb029820eac228f13343a8 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 23 Apr 2026 06:23:14 +0000 Subject: [PATCH 131/177] Add changelog entry for #7170 [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 b2b2175ad91..fa496b95214 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -27,6 +27,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) - De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) - Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) +- Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrance` to `includeOsmStationEntrance` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 3680b4c48722c822610320b5f64e44dddde28a8d Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 23 Apr 2026 07:49:47 +0200 Subject: [PATCH 132/177] Disallow empty GenericLocation --- .../api/common/LocationStringParser.java | 13 +++--- .../LegacyRouteRequestMapper.java | 4 +- .../mapping/GenericLocationMapper.java | 16 ++++---- .../transmodel/mapping/TripRequestMapper.java | 4 +- .../mapping/ViaLocationDeprecatedMapper.java | 5 ++- .../transmodel/mapping/ViaRequestMapper.java | 12 +++++- .../model/GenericLocation.java | 18 ++++---- .../java/org/opentripplanner/GtfsTest.java | 6 +-- .../api/common/LocationStringParserTest.java | 41 +++++++------------ .../model/GenericLocationTest.java | 8 ++-- .../routing/core/RouteRequestTest.java | 4 +- .../linking/LinkingContextRequestTest.java | 2 - 12 files changed, 62 insertions(+), 71 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java index 82f5dbfeaae..54d6b5c5a86 100644 --- a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java +++ b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java @@ -1,5 +1,6 @@ package org.opentripplanner.api.common; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.opentripplanner.core.model.id.FeedScopedId; @@ -37,7 +38,7 @@ public class LocationStringParser { * Creates the GenericLocation by parsing a "name::place" string, where "place" is a geographic * coordinate string (latitude,longitude) or a feed scoped ID (feedId:stopId). */ - public static GenericLocation fromOldStyleString(String input) { + public static Optional fromOldStyleString(String input) { String name = null; String place = input; if (input.contains("::")) { @@ -53,20 +54,18 @@ public static GenericLocation fromOldStyleString(String input) { * for the location that can pass through to the routing response unchanged. The place contains * latitude and longitude or a stop ID. */ - public static GenericLocation getGenericLocation(String label, String place) { + public static Optional getGenericLocation(String label, String place) { if (place == null) { - return null; + return Optional.empty(); } Matcher matcher = LAT_LON_PATTERN.matcher(place); if (matcher.find()) { var lat = Double.parseDouble(matcher.group(1)); var lon = Double.parseDouble(matcher.group(4)); - return GenericLocation.fromCoordinate(lat, lon, label); + return Optional.of(GenericLocation.fromCoordinate(lat, lon, label)); } else { - return FeedScopedId.parseOptional(place) - .map(id -> GenericLocation.fromStopId(id, label)) - .orElse(GenericLocation.fromUnspecified(label)); + return FeedScopedId.parseOptional(place).map(id -> GenericLocation.fromStopId(id, label)); } } } 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 e14d5c7ab7b..7c40ec26484 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 @@ -45,10 +45,10 @@ public static RouteRequest toRouteRequest( CallerWithEnvironment callWith = new CallerWithEnvironment(environment); callWith.argument("fromPlace", (String from) -> - request.withFrom(LocationStringParser.fromOldStyleString(from)) + LocationStringParser.fromOldStyleString(from).ifPresent(request::withFrom) ); callWith.argument("toPlace", (String to) -> - request.withTo(LocationStringParser.fromOldStyleString(to)) + LocationStringParser.fromOldStyleString(to).ifPresent(request::withTo) ); callWith.argument("from", (Map v) -> request.withFrom(toGenericLocation(v))); diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java index 2ff3f610d38..efe1ab9053a 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.transmodel.mapping; import java.util.Map; +import java.util.Optional; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.GenericLocation; @@ -13,10 +14,9 @@ class GenericLocationMapper { this.idMapper = idMapper; } - /** - * Maps a GraphQL Location input type to a GenericLocation - */ - GenericLocation toGenericLocation(Map m) { + /// Maps a GraphQL Location input type to a GenericLocation. + /// Returns an empty result If the input does not contain a coordinate or an id. + Optional toGenericLocation(Map m) { Map coordinates = (Map) m.get("coordinates"); Double lat = null; Double lon = null; @@ -31,13 +31,13 @@ GenericLocation toGenericLocation(Map m) { name = name == null ? "" : name; if (stopId != null && lat != null && lon != null) { - return GenericLocation.fromStopIdWithFallback(stopId, lat, lon, name); + return Optional.of(GenericLocation.fromStopIdWithFallback(stopId, lat, lon, name)); } else if (stopId != null) { - return GenericLocation.fromStopId(stopId, name); + return Optional.of(GenericLocation.fromStopId(stopId, name)); } else if (lat != null && lon != null) { - return GenericLocation.fromCoordinate(lat, lon, name); + return Optional.of(GenericLocation.fromCoordinate(lat, lon, name)); } else { - return GenericLocation.fromUnspecified(name); + return Optional.empty(); } } } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index df8f399bab9..0f82395c74e 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -48,10 +48,10 @@ public RouteRequestBuilder createRequestBuilder(DataFetchingEnvironment environm DataFetcherDecorator callWith = new DataFetcherDecorator(environment); callWith.argument("from", (Map v) -> - requestBuilder.withFrom(genericLocationMapper.toGenericLocation(v)) + genericLocationMapper.toGenericLocation(v).ifPresent(requestBuilder::withFrom) ); callWith.argument("to", (Map v) -> - requestBuilder.withTo(genericLocationMapper.toGenericLocation(v)) + genericLocationMapper.toGenericLocation(v).ifPresent(requestBuilder::withTo) ); callWith.argument("passThroughPoints", (List> v) -> { requestBuilder.withViaLocations(tripViaLocationMapper.toLegacyPassThroughLocations(v)); diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java index d9b45db54de..929fdf42d1f 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; +import org.opentripplanner.apis.support.InvalidInputException; import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -23,7 +24,9 @@ class ViaLocationDeprecatedMapper { ViaLocationDeprecated mapViaLocation(Map viaLocation) { try { return new ViaLocationDeprecated( - genericLocationMapper.toGenericLocation(viaLocation), + genericLocationMapper + .toGenericLocation(viaLocation) + .orElseThrow(() -> new InvalidInputException("Invalid via location")), false, (Duration) viaLocation.get("minSlack"), (Duration) viaLocation.get("maxSlack") diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index 254ef916438..632bf17818c 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -61,8 +61,16 @@ public RouteViaRequest createRouteViaRequest(DataFetchingEnvironment environment ) ) .withSearchWindow(environment.getArgumentOrDefault("searchWindow", request.searchWindow())) - .withFrom(genericLocationMapper.toGenericLocation(environment.getArgument("from"))) - .withTo(genericLocationMapper.toGenericLocation(environment.getArgument("to"))) + .withFrom( + genericLocationMapper + .toGenericLocation(environment.getArgument("from")) + .orElse(null) + ) + .withTo( + genericLocationMapper + .toGenericLocation(environment.getArgument("to")) + .orElse(null) + ) .withNumItineraries( environment.getArgumentOrDefault("numTripPatterns", request.numItineraries()) ) diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 861c55fa16b..14c7c95a140 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -15,8 +15,6 @@ */ public class GenericLocation { - public static final GenericLocation UNKNOWN = new GenericLocation(null, null, null); - @Nullable private final String label; @@ -35,16 +33,23 @@ private GenericLocation( @Nullable FeedScopedId stopId, @Nullable WgsCoordinate coordinate ) { + if (stopId == null && coordinate == null) { + throw new IllegalArgumentException( + "GenericLocation requires either a stop id or a coordinate" + ); + } this.label = label; this.stopId = stopId; this.coordinate = coordinate; } public static GenericLocation fromStopId(FeedScopedId id) { + Objects.requireNonNull(id); return new GenericLocation(null, id, null); } public static GenericLocation fromStopId(FeedScopedId id, @Nullable String label) { + Objects.requireNonNull(id); return new GenericLocation(label, id, null); } @@ -59,6 +64,7 @@ public static GenericLocation fromStopIdWithFallback( double lng, @Nullable String label ) { + Objects.requireNonNull(id); return new GenericLocation(label, id, new WgsCoordinate(lat, lng)); } @@ -74,10 +80,6 @@ public static GenericLocation fromCoordinate(double lat, double lng, @Nullable S return new GenericLocation(label, null, new WgsCoordinate(lat, lng)); } - public static GenericLocation fromUnspecified(@Nullable String label) { - return new GenericLocation(label, null, null); - } - /** * Returns this as a Coordinate object. */ @@ -136,10 +138,6 @@ public int hashCode() { @Override public String toString() { - if (UNKNOWN.equals(this)) { - return "Unknown location"; - } - ValueObjectToStringBuilder buf = ValueObjectToStringBuilder.of().skipNull(); if (StringUtils.hasValue(label)) { buf.addText(label).addText(" "); diff --git a/application/src/test/java/org/opentripplanner/GtfsTest.java b/application/src/test/java/org/opentripplanner/GtfsTest.java index 5b4874afb63..0fcdd35830d 100644 --- a/application/src/test/java/org/opentripplanner/GtfsTest.java +++ b/application/src/test/java/org/opentripplanner/GtfsTest.java @@ -21,13 +21,13 @@ import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; -import org.opentripplanner.api.common.LocationStringParser; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; import org.opentripplanner.gtfs.graphbuilder.GtfsBundle; import org.opentripplanner.gtfs.graphbuilder.GtfsBundleTestFactory; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; +import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RealTimeRaptorTransitDataUpdater; @@ -97,10 +97,10 @@ public Itinerary plan( .withDateTime(Instant.ofEpochSecond(Math.abs(dateTime))); if (fromVertex != null && !fromVertex.isEmpty()) { - builder.withFrom(LocationStringParser.getGenericLocation(null, FEED_ID + ":" + fromVertex)); + builder.withFrom(GenericLocation.fromStopId(FeedScopedId.of(FEED_ID, fromVertex))); } if (toVertex != null && !toVertex.isEmpty()) { - builder.withTo(LocationStringParser.getGenericLocation(null, FEED_ID + ":" + toVertex)); + builder.withTo(GenericLocation.fromStopId(FeedScopedId.of(FEED_ID, toVertex))); } if (onTripId != null && !onTripId.isEmpty()) { // TODO VIA - set different on-board request diff --git a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java index a9f75a62f95..abf4461538e 100644 --- a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java +++ b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.id.FeedScopedId; @@ -12,26 +13,23 @@ public class LocationStringParserTest { @Test public void testFromOldStyleString() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::12345"); - assertEquals("name", loc.label()); - assertNull(loc.stopId()); - assertNull(loc.wgsCoordinate()); - assertNull(loc.getCoordinate()); + var loc = LocationStringParser.fromOldStyleString("name::12345"); + assertEquals(Optional.empty(), loc); } @Test public void testWithLabelAndCoord() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::1.0,2.5"); + GenericLocation loc = LocationStringParser.fromOldStyleString("name::1.0,2.5").get(); assertEquals("name", loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 170"); + loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 170").get(); assertEquals("Label Label", loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("A Label::89,-22.3"); + loc = LocationStringParser.fromOldStyleString("A Label::89,-22.3").get(); assertEquals("A Label", loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); @@ -39,12 +37,12 @@ public void testWithLabelAndCoord() { @Test public void testWithId() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::aFeed:A1B2C3"); + GenericLocation loc = LocationStringParser.fromOldStyleString("name::aFeed:A1B2C3").get(); assertEquals("name", loc.label()); assertEquals(loc.stopId(), new FeedScopedId("aFeed", "A1B2C3")); assertNull(loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("feed:4321"); + loc = LocationStringParser.fromOldStyleString("feed:4321").get(); assertNull(loc.label()); assertEquals(loc.stopId(), new FeedScopedId("feed", "4321")); assertNull(loc.getCoordinate()); @@ -52,17 +50,17 @@ public void testWithId() { @Test public void testWithCoordOnly() { - GenericLocation loc = LocationStringParser.fromOldStyleString("1.0,2.5"); + GenericLocation loc = LocationStringParser.fromOldStyleString("1.0,2.5").get(); assertNull(loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString(" -15.0, 170"); + loc = LocationStringParser.fromOldStyleString(" -15.0, 170").get(); assertNull(loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("89,-22.3 "); + loc = LocationStringParser.fromOldStyleString("89,-22.3 ").get(); assertNull(loc.label()); assertNull(loc.stopId()); assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); @@ -70,19 +68,8 @@ public void testWithCoordOnly() { @Test public void testFromOldStyleStringIncomplete() { - String input = "0::"; - GenericLocation loc = LocationStringParser.fromOldStyleString(input); - assertEquals("0", loc.label()); - assertNull(loc.stopId()); - - input = "::1"; - loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label()); - assertNull(loc.stopId()); - - input = "::"; - loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label()); - assertNull(loc.stopId()); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("0::")); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("::1")); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("::")); } } diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 3e25024aea8..285f224ba2f 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -1,9 +1,7 @@ package org.opentripplanner.model; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.model.GenericLocation.UNKNOWN; import org.junit.jupiter.api.Test; import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; @@ -22,6 +20,8 @@ class GenericLocationTest { LABEL ); + private final GenericLocation other = GenericLocation.fromCoordinate(LATITUDE, LONGITUDE, LABEL); + @Test void fromStopId() { assertEquals(STOP_ID, subject.stopId()); @@ -35,18 +35,16 @@ void getCoordinate() { @Test void isSpecified() { assertTrue(subject.isSpecified()); - assertFalse(UNKNOWN.isSpecified()); } @Test void testEquals() { var copy = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); - AssertEqualsAndHashCode.verify(subject).sameAs(copy).differentFrom(UNKNOWN); + AssertEqualsAndHashCode.verify(subject).sameAs(copy).differentFrom(other); } @Test void testToString() { - assertEquals("Unknown location", UNKNOWN.toString()); assertEquals("A place F:Stop:1 (20.0, 30.0)", subject.toString()); } } diff --git a/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java b/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java index 3a3a4fb0f05..c9817e74bc9 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java @@ -257,7 +257,7 @@ void testToString() { @Test void testValidateMissingFrom() { expectOneRoutingValidationException( - () -> minimal.copyOf().withFrom(GenericLocation.UNKNOWN).buildRequest(), + () -> minimal.copyOf().withFrom(null).buildRequest(), RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE ); @@ -266,7 +266,7 @@ void testValidateMissingFrom() { @Test void testValidateMissingTo() { expectOneRoutingValidationException( - () -> minimal.copyOf().withTo(GenericLocation.UNKNOWN).buildRequest(), + () -> minimal.copyOf().withTo(null).buildRequest(), RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE ); diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java index 38f422761e4..d643ad89f9c 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java @@ -85,8 +85,6 @@ void testEqualsAndHashCode() { @Test void testToString() { - var defaultRequest = LinkingContextRequest.of().withFrom(GenericLocation.UNKNOWN).build(); - assertEquals("LinkingContextRequest{from: Unknown location}", defaultRequest.toString()); assertEquals( "LinkingContextRequest{" + "from: (1.0, 2.0), " + From 71aeca10ae300d4029dde5a23105aa3faca49ff7 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 23 Apr 2026 09:01:24 +0200 Subject: [PATCH 133/177] refactor: Remove a factory method --- .../ext/flex/FlexIntegrationTest.java | 3 +- .../ScheduledDeviatedTripIntegrationTest.java | 10 ++++--- .../model/GenericLocation.java | 4 --- .../opentripplanner/model/plan/PlaceTest.java | 5 +++- .../mapping/TransitSnapshotTest.java | 11 +++++--- .../linking/LinkingContextFactoryTest.java | 28 +++++++++++-------- .../StreetSearchRequestMapperTest.java | 5 ++-- 7 files changed, 39 insertions(+), 27 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java index 3f7fa497cf2..939286c451c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.TestOtpModel; import org.opentripplanner.TestServerContext; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -123,7 +124,7 @@ void shouldReturnARouteTransferringFromBusToFlex() { @Test void shouldReturnARouteWithTwoTransfers() { - var from = GenericLocation.fromStopId("ALEX DR@ALEX WAY", "MARTA", "97266"); + var from = GenericLocation.fromStopId(new FeedScopedId("MARTA", "97266"), "ALEX DR@ALEX WAY"); var to = GenericLocation.fromCoordinate(33.86701256815635, -84.61787939071655); var itin = getItinerary(from, to, 3); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java index 2a6b6783fee..f35d159c0bb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java @@ -101,11 +101,13 @@ void flexTripInTransitMode() { ); // from zone 3 to zone 2 - var from = GenericLocation.fromStopId("Transfer Point for Route 30", feedId, "cujv"); + var from = GenericLocation.fromStopId( + new FeedScopedId(feedId, "cujv"), + "Transfer Point for Route 30" + ); var to = GenericLocation.fromStopId( - "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point", - feedId, - "yz85" + new FeedScopedId(feedId, "yz85"), + "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point" ); var itineraries = getItineraries(from, to, serverContext); diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 14c7c95a140..3285927bbb4 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -53,10 +53,6 @@ public static GenericLocation fromStopId(FeedScopedId id, @Nullable String label return new GenericLocation(label, id, null); } - public static GenericLocation fromStopId(String name, String feedId, String stopId) { - return new GenericLocation(name, new FeedScopedId(feedId, stopId), null); - } - /// Create a GenericLocation of a stop id with fallback coordinates if the id is not found. public static GenericLocation fromStopIdWithFallback( FeedScopedId id, diff --git a/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java b/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java index 4b08b4ffa94..a98ffc10570 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java @@ -173,7 +173,10 @@ public void forStopWithViaLocationType() { @Test void forGenericLocation() { - GenericLocation location = GenericLocation.fromStopId(PLACE_NAME, "id", "stopId"); + GenericLocation location = GenericLocation.fromStopId( + new FeedScopedId("id", "stopId"), + PLACE_NAME + ); Place place = Place.forGenericLocation(location, DEFAULT_PLACE_NAME); assertNotNull(place); assertEquals(PLACE_NAME, place.name.toString()); diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java index 208ca689594..c390a03eee5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.model.StreetMode; @@ -13,12 +14,14 @@ public class TransitSnapshotTest extends SnapshotTestBase { static GenericLocation ptc = GenericLocation.fromStopId( - "Rose Quarter Transit Center", - "prt", - "79-tc" + new FeedScopedId("prt", "79-tc"), + "Rose Quarter Transit Center" ); - static GenericLocation ps = GenericLocation.fromStopId("NE 12th & Couch", "prt", "6577"); + static GenericLocation ps = GenericLocation.fromStopId( + new FeedScopedId("prt", "6577"), + "NE 12th & Couch" + ); static GenericLocation p0 = GenericLocation.fromCoordinate( 45.519320, diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java index a18758bb3f1..1268f5f18bb 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java @@ -161,7 +161,10 @@ void stationId() { id -> Optional.empty() ); var container = new TemporaryVerticesContainer(); - var from = GenericLocation.fromStopId("station", OMEGA_ID.getFeedId(), OMEGA_ID.getId()); + var from = GenericLocation.fromStopId( + new FeedScopedId(OMEGA_ID.getFeedId(), OMEGA_ID.getId()), + "station" + ); var request = LinkingContextRequest.of() .withFrom(from) .withTo(stopToLocation(stopB)) @@ -192,15 +195,15 @@ void stationCentroidForCar() { } ); var container = new TemporaryVerticesContainer(); + String label1 = stationAlpha.getName().toString(); var from = GenericLocation.fromStopId( - stationAlpha.getName().toString(), - stationAlpha.getId().getFeedId(), - stationAlpha.getId().getId() + new FeedScopedId(stationAlpha.getId().getFeedId(), stationAlpha.getId().getId()), + label1 ); + String label = multiModalStation.getName().toString(); var to = GenericLocation.fromStopId( - multiModalStation.getName().toString(), - multiModalStation.getId().getFeedId(), - multiModalStation.getId().getId() + new FeedScopedId(multiModalStation.getId().getFeedId(), multiModalStation.getId().getId()), + label ); var request = LinkingContextRequest.of() .withFrom(from) @@ -224,7 +227,10 @@ void stationCentroidForCar() { @Test void centroid() { var container = new TemporaryVerticesContainer(); - var from = GenericLocation.fromStopId("station", ALPHA_ID.getFeedId(), ALPHA_ID.getId()); + var from = GenericLocation.fromStopId( + new FeedScopedId(ALPHA_ID.getFeedId(), ALPHA_ID.getId()), + "station" + ); var request = LinkingContextRequest.of() .withFrom(from) .withTo(stopToLocation(stopB)) @@ -514,10 +520,10 @@ private Set toStops(Set fromVertices) { } private GenericLocation stopToLocation(RegularStop s) { + String label = s.getName().toString(); return GenericLocation.fromStopId( - s.getName().toString(), - s.getId().getFeedId(), - s.getId().getId() + new FeedScopedId(s.getId().getFeedId(), s.getId().getId()), + label ); } diff --git a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java index ce39b0e77b1..9b5ca0d743a 100644 --- a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RequestModes; @@ -62,8 +63,8 @@ void mapFromToCoordinates() { void mapFromToStopIds() { var builder = builder(); - var from = GenericLocation.fromStopId("S1", "A", "STOP1"); - var to = GenericLocation.fromStopId("S2", "A", "STOP2"); + var from = GenericLocation.fromStopId(new FeedScopedId("A", "STOP1"), "S1"); + var to = GenericLocation.fromStopId(new FeedScopedId("A", "STOP2"), "S2"); var req = builder.withDateTime(INSTANT).withFrom(from).withTo(to).buildRequest(); From 0ba5a008f4abbccebd36ff1a7eabe00171368e14 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 23 Apr 2026 09:13:58 +0200 Subject: [PATCH 134/177] Remove isSpecified method --- .../opentripplanner/model/GenericLocation.java | 4 ---- .../routing/api/request/RouteRequest.java | 8 ++++---- .../api/request/ViaLocationDeprecated.java | 3 --- .../routing/linking/LinkingContextFactory.java | 15 +++------------ .../model/GenericLocationTest.java | 6 ------ 5 files changed, 7 insertions(+), 29 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 3285927bbb4..6ab3976580c 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -110,10 +110,6 @@ public String label() { return label; } - public boolean isSpecified() { - return stopId != null || coordinate != null; - } - @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index b2401d3f191..5b898604451 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -234,13 +234,13 @@ public ListSection cropItinerariesAt() { public void validateOriginAndDestination() { List routingErrors = new ArrayList<>(2); - if (from == null || !from.isSpecified()) { + if (from == null) { routingErrors.add( new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE) ); } - if (to == null || !to.isSpecified()) { + if (to == null) { routingErrors.add(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)); } @@ -521,13 +521,13 @@ public List validateFromAndToLocation() { List routingErrors = new ArrayList<>(2); - if (from == null || !from.isSpecified()) { + if (from == null) { routingErrors.add( new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE) ); } - if (to == null || !to.isSpecified()) { + if (to == null) { routingErrors.add(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)); } return routingErrors; diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java b/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java index 7d864dddb16..3145462d0fc 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java @@ -29,8 +29,5 @@ public record ViaLocationDeprecated( Objects.requireNonNull(minSlack); Objects.requireNonNull(maxSlack); Objects.requireNonNull(point); - if (!point.isSpecified()) { - throw new IllegalArgumentException("The Via location is not specified"); - } } } diff --git a/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java b/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java index 2168dfe1178..b32511aacbe 100644 --- a/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/linking/LinkingContextFactory.java @@ -126,7 +126,7 @@ private Set getFromVertices( LinkingContextRequest request ) { var from = request.from(); - if (from == null || !from.isSpecified()) { + if (from == null) { return Set.of(); } var modes = request.accessMode() != StreetMode.NOT_SET @@ -143,7 +143,7 @@ private Set getToVertices( LinkingContextRequest request ) { var to = request.to(); - if (to == null || !to.isSpecified()) { + if (to == null) { return Set.of(); } var modes = request.egressMode() != StreetMode.NOT_SET @@ -269,10 +269,6 @@ private Set getStreetVerticesForLocation( EnumSet streetModes, LocationType type ) { - if (!location.isSpecified()) { - return Set.of(); - } - // Differentiate between driving and non-driving, as driving is not available from transit stops List modes = streetModes .stream() @@ -388,12 +384,7 @@ private void checkIfVerticesFound( } // check that vertices where found if to-location was specified - if ( - to != null && - to.isSpecified() && - toStopVertices.isEmpty() && - isDisconnected(toVertices, LocationType.TO) - ) { + if (to != null && toStopVertices.isEmpty() && isDisconnected(toVertices, LocationType.TO)) { routingErrors.add( new RoutingError(getRoutingErrorCodeForDisconnected(to), InputField.TO_PLACE) ); diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 285f224ba2f..2276596575e 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -1,7 +1,6 @@ package org.opentripplanner.model; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; @@ -32,11 +31,6 @@ void getCoordinate() { assertEquals(STOP_ID, subject.stopId()); } - @Test - void isSpecified() { - assertTrue(subject.isSpecified()); - } - @Test void testEquals() { var copy = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); From 02e347aaa3ca08851217243980b95d9a2fb15cb0 Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 23 Apr 2026 09:19:25 +0200 Subject: [PATCH 135/177] Move comment --- .../org/opentripplanner/model/GenericLocation.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 6ab3976580c..f5998190be1 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -21,10 +21,6 @@ public class GenericLocation { @Nullable private final FeedScopedId stopId; - /** - * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not - * found. - */ @Nullable WgsCoordinate coordinate; @@ -77,7 +73,8 @@ public static GenericLocation fromCoordinate(double lat, double lng, @Nullable S } /** - * Returns this as a Coordinate object. + * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not + * found. */ @Nullable public Coordinate getCoordinate() { @@ -87,6 +84,10 @@ public Coordinate getCoordinate() { return coordinate.asJtsCoordinate(); } + /** + * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not + * found. + */ @Nullable public WgsCoordinate wgsCoordinate() { return coordinate; From dde1fbd205d79051e048635ad7e8c33e67cce975 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 23 Apr 2026 07:54:58 +0200 Subject: [PATCH 136/177] Fix Javadoc --- .../api/model/transit/FeedScopedIdMapper.java | 2 +- .../opentripplanner/api/resource/DebugOutput.java | 2 +- .../graph_builder/module/StreetLinkerModule.java | 2 +- .../module/ned/MissingElevationHandler.java | 2 +- .../transit/request/TripTimesForDaysIndex.java | 4 ++-- .../request/preference/TransferPreferences.java | 2 +- .../config/configure/LoadConfigModule.java | 2 +- .../transit/api/model/FilterValues.java | 14 +++++++------- .../transit/api/request/FindRoutesRequest.java | 2 +- .../api/request/FindStopLocationsRequest.java | 2 +- .../api/request/TripOnServiceDateRequest.java | 2 +- .../transit/api/request/TripRequest.java | 2 +- .../transit/model/basic/NarrowedTransitMode.java | 2 +- .../apis/gtfs/service/ApiTransitServiceTest.java | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java b/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java index 9a9aa8151a4..b39a01b603b 100644 --- a/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java +++ b/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java @@ -25,7 +25,7 @@ default FeedScopedId parseStrict(String id) throws InvalidInputException { * @param id a string representation of the id that should be parsed. May be null or * blank * @return Optional.empty() if the input id is null or blank, otherwise a - * FeedScopedId wrapped in an FeedScopedId wrapped in an Optional */ default Optional parseNullSafe(@Nullable String id) { if (id == null) { diff --git a/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java b/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java index 818b72fc6b3..18886979785 100644 --- a/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java +++ b/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java @@ -19,7 +19,7 @@ public class DebugOutput { * Time taken in the transit router (including access/egress street router) in nanoseconds. * Detailed timing information within the transit router is sored in transitRouterTimes. * - * @see DebugOutput#transitRouterTimes; + * @see DebugOutput#transitRouterTimes */ public final long transitRouterTime; diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java index 03acbcb606d..f25082742b3 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java @@ -181,7 +181,7 @@ private void linkStopToStreetNetwork(TransitStopVertex tStop, StopLinkType linkT *

        * This does not apply to zones as street vertices store which zones they are part of. * - * @see https://github.com/opentripplanner/OpenTripPlanner/issues/5498 + * @link https://github.com/opentripplanner/OpenTripPlanner/issues/5498 */ private void linkToDriveableEdge(TransitStopVertex tStop) { vertexLinker.linkVertexPermanently( diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java b/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java index c846ea9a94f..a08ce522259 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java @@ -41,7 +41,7 @@ *

      6. * If a vertex only had a single path, then the last known elevation is used *
      7. - * + *
      8. * Once elevations for vertices are interpolated they are used to set the elevation profile * for the incoming / outgoing StreetEdges *
      9. diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java index d16d17bc572..902ae7296d3 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java @@ -11,13 +11,13 @@ * at 04:05+1d, while the first bus on Sundays may leave at 03:00. Be aware, these two trips may * not have the same service calendar, normally they are different. For example "Sundays" might be * all Sundays and public holidays. - *

        + *

        * The trip index is a sorted list of trips based on the first stop departure time. The index * contains two pointers, the first is the day and the second is the trip index on that day: * ``` * tripIndexForTripPatternPerDates -> (day, tripIndexForTripPatternPerDate) * ``` - *

        + *

        * This class might at first look a bit complicated. A much easier approach would be to just sort * on departure times using a Java built in sort, but sorting is expensive. This code will merge * the timetables instead - which is faster. In 99% of the cases we will just do one extra diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java index 92ef21972bf..08ed0ec99f2 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java @@ -115,7 +115,7 @@ public double waitReluctance() { /** * Ideally maxTransfers should be set in the router config, not from the client. Instead, the - * client should use {@link #maxAdditionalTransfers)} instead to pass in the max number of + * client should use {@link #maxAdditionalTransfers} instead to pass in the max number of * additional/extra transfers relative to the best trip (with the fewest possible transfers) * within constraint of the other search parameters. This might be too complicated to explain to * the customer, so you might stick to the old limit, but that has side-effects where you might diff --git a/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java b/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java index ad62c4c7126..ff81e863339 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java @@ -21,7 +21,7 @@ * types. The {@link ConfigModule} is a separate module to be able to use it without this module; * If the {@link ConfigModel} is already instantiated. *

        - * The binding to {@link OtpDataStoreConfig} and {@link @TransitServicePeriod} is done + * The binding to {@link OtpDataStoreConfig} and {@link TransitServicePeriod} is done * here, not in the {@link ConfigModel}, because they are only needed at load time - if this change, * then move the binding to the {@link ConfigModule}. */ diff --git a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java index 7d2ef3f09d9..a2fba3b95ab 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java +++ b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java @@ -10,11 +10,11 @@ /** * {@link FilterValues} is meant to be used when filtering results from {@link TransitService}. - *

        + *

        * This abstraction over the Collection type lets us keep filter specific functionality separate * from interpretation of various states of a collection. For instance in which case the filter values * should match all entities they are meant to filter. - *

        + *

        * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. */ public abstract class FilterValues { @@ -31,7 +31,7 @@ public abstract class FilterValues { /** * Returns a {@link FilterValues} that matches everything if there are no filter values. - *

        + *

        * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -46,7 +46,7 @@ public static FilterValues ofEmptyIsEverything( /** * Returns a {@link FilterValues} that matches everything if the filter values are null. - *

        + *

        * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -62,7 +62,7 @@ public static FilterValues ofNullIsEverything( /** * Returns a {@link RequiredFilterValues} that throws an exception at creation time if the filter * values is null or empty. - *

        + *

        * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -79,7 +79,7 @@ public static RequiredFilterValues ofRequired( * Returns True if the collection of filter values matches everything that it could filter. If this * is the case, then the filter values should not be used to filter anything and filtering logic can * safely ignore it. - *

        + *

        * @return boolean */ public abstract boolean includeEverything(); @@ -87,7 +87,7 @@ public static RequiredFilterValues ofRequired( /** * Returns the collection of filter values. If the filter values effectively don't filter anything, * an exception is thrown. - *

        + *

        * @return Collection - The values of the filter. */ public Collection get() { diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java index d0f2153921d..f2efc2b2273 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java @@ -6,7 +6,7 @@ /** * A request for finding {@link Route}s. - *

        + *

        * This request is used to retrieve Routes that match the provided filter values. * At least one filter value must be provided. */ diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java index 55c36bfce40..20a65e7183b 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java @@ -4,7 +4,7 @@ /** * A request for finding {@link StopLocation}. - *

        + *

        * This request is used to retrieve StopLocations that match the provided filter values. */ public class FindStopLocationsRequest { diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java index 231366d8433..9dcd312080f 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java @@ -9,7 +9,7 @@ /** * A request for trips on certain service dates. - *

        + *

        * This request is used to retrieve {@link TripOnServiceDate}s that match the provided filter * values. */ diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java index f63af90fdd9..5cc3ec2ed8a 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java @@ -9,7 +9,7 @@ /** * A request for {@link Trip}s. - *

        + *

        * This request is used to retrieve {@link Trip}s that match the provided filter values. */ public class TripRequest { diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java b/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java index 362c1e2c16d..f32576b5b13 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java @@ -85,7 +85,7 @@ public String toString() { * Make sure the String serialization is deterministic by sorting the elements in * alphabetic order. * - * @see MainAndSubMode::toString(Collection) + * @see MainAndSubMode#toString(Collection) */ public static String toString(Collection modes) { return modes != null diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java index 4a5516859ff..a85aea7cb91 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java @@ -88,7 +88,7 @@ void justScheduledTrips() { * Tests that you get a single {@link TripTimeOnDate} for a stop in a pattern even if several * trips in the pattern have the same stop skipped. * - * @see https://github.com/opentripplanner/OpenTripPlanner/issues/6654 + * @link https://github.com/opentripplanner/OpenTripPlanner/issues/6654 */ @Test void skipStopInMultipleTripsInPattern() { From 2bb42230fc24ff4ed618c3790ae07e89b5b88b0c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:30:07 +0000 Subject: [PATCH 137/177] Update dependency io.github.git-commit-id:git-commit-id-maven-plugin to v10 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 79552958b0f..3cf5d16e2e3 100644 --- a/pom.xml +++ b/pom.xml @@ -307,7 +307,7 @@ but we need the Maven project version as well, so we perform substitution. --> io.github.git-commit-id git-commit-id-maven-plugin - 9.2.0 + 10.0.0 From f9b7776feb04f7d4af26b6a79bbd54a63ab22d97 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 23 Apr 2026 09:32:56 +0200 Subject: [PATCH 138/177] Update to latest changes --- .../apis/transmodel/TransmodelGraphQLSchemaFactory.java | 2 +- .../support/AbortOnUnprocessableRequestExecutionStrategy.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java index 6cad89af21d..6fe59457b61 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java @@ -368,7 +368,7 @@ private GraphQLSchema createDefault() { replacementForRelationType ); - GraphQLOutputType timetabledPassingTime = TimetabledPassingTimeType.create( + var timetabledPassingTime = TimetabledPassingTimeType.create( bookingArrangementType, noticeType, quayType, diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java b/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java index 3fa3a985cb2..009d592b5d7 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.transmodel.support; import graphql.execution.AsyncExecutionStrategy; +import graphql.execution.DataFetcherResult; import graphql.execution.ExecutionStrategyParameters; import graphql.schema.DataFetchingEnvironment; import java.io.Closeable; @@ -37,7 +38,7 @@ public AbortOnUnprocessableRequestExecutionStrategy() { } @Override - protected CompletableFuture handleFetchingException( + protected CompletableFuture> handleFetchingException( DataFetchingEnvironment environment, ExecutionStrategyParameters params, Throwable e From c2588a3de53f41dacd22ec65ef9b7b6287142e62 Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Thu, 23 Apr 2026 13:03:07 +0200 Subject: [PATCH 139/177] refactor: Extract RequestPreProcessor from RoutingWorker Co-authored-by: Thomas Gran --- .../algorithm/RequestPreProcessor.java | 54 +++++++++++++++++++ .../routing/algorithm/RoutingWorker.java | 45 ++-------------- .../algorithm/RoutingWorkerRequest.java | 52 ++++++++++++++++++ .../service/DefaultRoutingService.java | 12 ++++- 4 files changed, 120 insertions(+), 43 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java create mode 100644 application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java new file mode 100644 index 00000000000..10d782a4d00 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java @@ -0,0 +1,54 @@ +package org.opentripplanner.routing.algorithm; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Objects; +import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.utils.time.ServiceDateUtils; + +/** + * Pre-processes a {@link RouteRequest} into a {@link RoutingWorkerRequest} for usage in + * {@link RoutingWorker}. This includes extending the request with a page cursor and amending + * its data with additional resolved data, such as additional search days. + */ +public final class RequestPreProcessor { + + private final RaptorTuningParameters tuningParameters; + private final ZoneId zoneId; + + public RequestPreProcessor(RaptorTuningParameters tuningParameters, ZoneId zoneId) { + this.tuningParameters = tuningParameters; + this.zoneId = zoneId; + } + + /** + * Pre-process {@code originalRequest} and return a {@link RoutingWorkerRequest} with all values + * needed to initialize a {@link RoutingWorker}. + */ + public RoutingWorkerRequest computeRequest(RouteRequest originalRequest) { + var request = originalRequest.withPageCursor(); + var transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); + var additionalSearchDays = createAdditionalSearchDays(tuningParameters, zoneId, request); + return new RoutingWorkerRequest(request, transitSearchTimeZero, additionalSearchDays); + } + + private static AdditionalSearchDays createAdditionalSearchDays( + RaptorTuningParameters raptorTuningParameters, + ZoneId zoneId, + RouteRequest request + ) { + var dateTime = Objects.requireNonNull(request.dateTime()); + var searchDateTime = ZonedDateTime.ofInstant(dateTime, zoneId); + var maxWindow = raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(); + + return new AdditionalSearchDays( + request.arriveBy(), + searchDateTime, + request.searchWindow(), + maxWindow, + request.preferences().system().maxJourneyDuration() + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index 0c57860d5e4..37b801749cd 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -2,7 +2,6 @@ import java.time.Duration; import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; @@ -15,7 +14,6 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.grouppriority.TransitGroupPriorityItineraryDecorator; import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; -import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.mapping.PagingServiceFactory; @@ -41,7 +39,6 @@ import org.opentripplanner.street.linking.TemporaryVerticesContainer; import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; -import org.opentripplanner.utils.time.ServiceDateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,15 +56,6 @@ public class RoutingWorker { private final RouteRequest request; private final OtpServerRequestContext serverContext; - /** - * The transit service time-zero normalized for the current search. All transit times are relative - * to a "time-zero". This enables us to use an integer(small memory footprint). The times are - * number for seconds past the {@code transitSearchTimeZero}. In the internal model all times are - * stored relative to the {@link java.time.LocalDate}, but to be able - * to compare trip times for different service days we normalize all times by calculating an - * offset. Now all times for the selected trip patterns become relative to the {@code - * transitSearchTimeZero}. - */ private final ZonedDateTime transitSearchTimeZero; private final AdditionalSearchDays additionalSearchDays; private final TransitGroupPriorityService transitGroupPriorityService; @@ -78,23 +66,15 @@ public class RoutingWorker { @Nullable private LinkingContext currentLinkingContext = null; - public RoutingWorker( - OtpServerRequestContext serverContext, - RouteRequest orginalRequest, - ZoneId zoneId - ) { - this.request = orginalRequest.withPageCursor(); + public RoutingWorker(OtpServerRequestContext serverContext, RoutingWorkerRequest workerRequest) { + this.request = workerRequest.request(); + this.transitSearchTimeZero = workerRequest.transitSearchTimeZero(); + this.additionalSearchDays = workerRequest.additionalSearchDays(); this.serverContext = serverContext; this.debugTimingAggregator = new DebugTimingAggregator( serverContext.meterRegistry(), request.preferences().system().tags() ); - this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); - this.additionalSearchDays = createAdditionalSearchDays( - serverContext.raptorTuningParameters(), - zoneId, - request - ); this.transitGroupPriorityService = TransitGroupPriorityService.of( request.preferences().transit().relaxTransitGroupPriority(), request.journey().transit().priorityGroupsByAgency(), @@ -191,23 +171,6 @@ public RoutingResponse route() { ); } - private static AdditionalSearchDays createAdditionalSearchDays( - RaptorTuningParameters raptorTuningParameters, - ZoneId zoneId, - RouteRequest request - ) { - var searchDateTime = ZonedDateTime.ofInstant(request.dateTime(), zoneId); - var maxWindow = raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(); - - return new AdditionalSearchDays( - request.arriveBy(), - searchDateTime, - request.searchWindow(), - maxWindow, - request.preferences().system().maxJourneyDuration() - ); - } - /** * Calculate the earliest-departure-time used in the transit search. * This method returns {@code null} if no transit search is performed. diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java new file mode 100644 index 00000000000..12e96789f03 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java @@ -0,0 +1,52 @@ +package org.opentripplanner.routing.algorithm; + +import java.time.ZonedDateTime; +import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; +import org.opentripplanner.routing.api.request.RouteRequest; + +/** + * A trip planning request for handling in {@link RoutingWorker} + */ +public final class RoutingWorkerRequest { + + private final RouteRequest request; + private final ZonedDateTime transitSearchTimeZero; + private final AdditionalSearchDays additionalSearchDays; + + /** + * @param request the route request ready for routing. It should already be pre-processed to have + * e.g. page cursor applied + */ + public RoutingWorkerRequest( + RouteRequest request, + ZonedDateTime transitSearchTimeZero, + AdditionalSearchDays additionalSearchDays + ) { + this.request = request; + this.transitSearchTimeZero = transitSearchTimeZero; + this.additionalSearchDays = additionalSearchDays; + } + + /** The route request with the page cursor applied. */ + public RouteRequest request() { + return request; + } + + /** + * The transit service time-zero normalized for the current search. All transit times are relative + * to a "time-zero". This enables us to use an integer(small memory footprint). The times are + * number for seconds past the {@code transitSearchTimeZero}. In the internal model all times are + * stored relative to the {@link java.time.LocalDate}, but to be able + * to compare trip times for different service days we normalize all times by calculating an + * offset. Now all times for the selected trip patterns become relative to the {@code + * transitSearchTimeZero}. + */ + public ZonedDateTime transitSearchTimeZero() { + return transitSearchTimeZero; + } + + /** The number of extra calendar days to search before and after the requested date. */ + public AdditionalSearchDays additionalSearchDays() { + return additionalSearchDays; + } +} diff --git a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java index 7972e3acf03..23d06bb04e3 100644 --- a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java +++ b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java @@ -4,7 +4,9 @@ import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.RequestPreProcessor; import org.opentripplanner.routing.algorithm.RoutingWorker; +import org.opentripplanner.routing.algorithm.RoutingWorkerRequest; import org.opentripplanner.routing.algorithm.via.ViaRoutingWorker; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.routing.api.request.RouteRequest; @@ -39,7 +41,7 @@ public RoutingResponse route(RouteRequest request) { LOG.debug("Request: {}", request); OTPRequestTimeoutException.checkForTimeout(); request.validateOriginAndDestination(); - var worker = new RoutingWorker(serverContext, request, timeZone); + var worker = new RoutingWorker(serverContext, mapRequest(request)); var response = worker.route(); logResponse(response); return response; @@ -50,12 +52,18 @@ public ViaRoutingResponse route(RouteViaRequest request) { LOG.debug("Request: {}", request); OTPRequestTimeoutException.checkForTimeout(); var viaRoutingWorker = new ViaRoutingWorker(request, req -> - new RoutingWorker(serverContext, req, serverContext.transitService().getTimeZone()).route() + new RoutingWorker(serverContext, mapRequest(req)).route() ); // TODO: Add output logging here, see route(..) method return viaRoutingWorker.route(); } + private RoutingWorkerRequest mapRequest(RouteRequest request) { + return new RequestPreProcessor(serverContext.raptorTuningParameters(), timeZone).computeRequest( + request + ); + } + private void logResponse(RoutingResponse response) { if (response.getTripPlan().itineraries.isEmpty() && response.getRoutingErrors().isEmpty()) { // We should provide an error if there is no results, this is important for the client so From 98093b0b0f8684a2c7831ac10a47ff963fdc3c6f Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Thu, 23 Apr 2026 14:20:39 +0300 Subject: [PATCH 140/177] fix: map defaults into relaxTransitGroupPriority instead of the input type --- .../apis/gtfs/DefaultValueInjector.java | 23 +++++++++++----- .../TransitPreferencesMapper.java | 2 +- .../apis/gtfs/SchemaFactoryTest.java | 26 ++++++++++++++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index 572474affc9..975ac23303a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -316,13 +316,22 @@ private static void setTransitDefaults( "TimetablePreferencesInput.includeRealTimeCancellations", transit.includeRealtimeCancellations() ) - .intReq( - "LinearCostFunctionInput.constant", - transit.relaxTransitGroupPriority().constant().toSeconds() - ) - .floatReq( - "LinearCostFunctionInput.coefficient", - transit.relaxTransitGroupPriority().coefficient() + .objectReq( + "TransitPreferencesInput.relaxTransitGroupPriority", + ObjectValue.newObjectValue() + .objectField( + ObjectField.newObjectField() + .name("constant") + .value(IntValue.of(transit.relaxTransitGroupPriority().constant().toSeconds())) + .build() + ) + .objectField( + ObjectField.newObjectField() + .name("coefficient") + .value(FloatValue.of(transit.relaxTransitGroupPriority().coefficient())) + .build() + ) + .build() ); } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index d26295626b2..2bb878cbb32 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -7,9 +7,9 @@ import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; +import org.opentripplanner.apis.support.InvalidInputException; import org.opentripplanner.core.model.basic.Cost; import org.opentripplanner.routing.api.request.framework.CostLinearFunction; -import org.opentripplanner.apis.support.InvalidInputException; import org.opentripplanner.routing.api.request.preference.TransferPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.utils.collection.CollectionUtils; diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index 3f4acadd3eb..7d88bc07ac7 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -7,6 +7,7 @@ import graphql.language.FloatValue; import graphql.language.IntValue; +import graphql.language.ObjectValue; import graphql.language.Value; import graphql.schema.AsyncDataFetcher; import graphql.schema.DataFetcher; @@ -34,6 +35,8 @@ void testDefaultValueInjection() { double walkSpeed = 15; var maxTransfers = 2; var numItineraries = 63; + var relaxCoefficient = 1; + var relaxConstant = 0; var routeRequest = RouteRequest.of() .withPreferences(preferences -> { @@ -60,12 +63,27 @@ void testDefaultValueInjection() { ); assertEquals(maxTransfers, defaultMaxTransfers.getValue().intValue()); - var defaultRelaxCoefficient = (FloatValue) getDefaultValueForField( + var defaultRelax = (ObjectValue) getDefaultValueForField( schema, - "LinearCostFunctionInput", - "coefficient" + "TransitPreferencesInput", + "relaxTransitGroupPriority" ); - assertEquals(1.0, defaultRelaxCoefficient.getValue().floatValue()); + var defaultRelaxCoefficient = (FloatValue) defaultRelax + .getObjectFields() + .stream() + .filter(f -> f.getName().equals("coefficient")) + .findFirst() + .orElseThrow() + .getValue(); + assertEquals(relaxCoefficient, defaultRelaxCoefficient.getValue().doubleValue()); + var defaultRelaxConstant = (IntValue) defaultRelax + .getObjectFields() + .stream() + .filter(f -> f.getName().equals("constant")) + .findFirst() + .orElseThrow() + .getValue(); + assertEquals(relaxConstant, defaultRelaxConstant.getValue().intValue()); var defaultNumberOfItineraries = (IntValue) getDefaultValueForArgument( schema, From f19bf15cff99abe3e170251fd39776f90e709d97 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Thu, 23 Apr 2026 15:11:29 +0300 Subject: [PATCH 141/177] chore: add one paragraph of documentation --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 +++++ 1 file changed, 5 insertions(+) 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 54a23ba42c9..7e2b30f5361 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5108,6 +5108,11 @@ input TransitPreferencesInput { The relaxation is done using a linear function applied to the generalized cost. If not defined, a function with a constant 0 and a coefficient of 1.0 is used, meaning the cost comparison is not relaxed between transit groups. + + A relax-cost is used to increase the limit when comparing one cost to another cost. + This is used to include more results into the result. A `coefficient=2.0` means a path (itinerary) + with twice as high cost as another one, is accepted. A `constant=300` means a "fixed" + constant is added to the limit. """ relaxTransitGroupPriority: LinearCostFunctionInput "Preferences related to cancellations and real-time." From a4269db054bada2ee1a017ccfc753aea96674e7e Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Thu, 23 Apr 2026 14:17:59 +0200 Subject: [PATCH 142/177] Fix formatting --- .../apis/transmodel/mapping/ViaRequestMapper.java | 10 ++-------- .../org/opentripplanner/model/GenericLocationTest.java | 9 +++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index 632bf17818c..8d9b52b8703 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -62,15 +62,9 @@ public RouteViaRequest createRouteViaRequest(DataFetchingEnvironment environment ) .withSearchWindow(environment.getArgumentOrDefault("searchWindow", request.searchWindow())) .withFrom( - genericLocationMapper - .toGenericLocation(environment.getArgument("from")) - .orElse(null) - ) - .withTo( - genericLocationMapper - .toGenericLocation(environment.getArgument("to")) - .orElse(null) + genericLocationMapper.toGenericLocation(environment.getArgument("from")).orElse(null) ) + .withTo(genericLocationMapper.toGenericLocation(environment.getArgument("to")).orElse(null)) .withNumItineraries( environment.getArgumentOrDefault("numTripPatterns", request.numItineraries()) ) diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 2276596575e..4313d36c848 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; @@ -31,6 +32,14 @@ void getCoordinate() { assertEquals(STOP_ID, subject.stopId()); } + @Test + void testInvalid() { + assertThrows(NullPointerException.class, () -> GenericLocation.fromStopId(null)); + assertThrows(NullPointerException.class, () -> + GenericLocation.fromStopIdWithFallback(null, 0.0, 0.0, "label") + ); + } + @Test void testEquals() { var copy = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); From 7960c8eac0db3f9475151423f69294254dd06f5a Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Wed, 22 Apr 2026 16:48:12 +0200 Subject: [PATCH 143/177] Skip transit search when no access or egress mode is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten the early-exit guard in TransitRouter.route() so that it also bails when both the access and egress street modes are NOT_SET. In that case raptor has no way to reach transit, would fail in verifyAccessEgress, and the exception is silently caught in RoutingWorker — all after an unnecessary RaptorRoutingRequestTransitData build plus the access/egress street searches. This hardens the router against API paths that specify only a direct mode without explicitly disabling transit, which the Transmodel TransitFilterOldWayMapper does not pick up as "transit disabled" when the request omits the transportModes key. --- .../raptoradapter/router/TransitRouter.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 93fafd0603f..ac40c88376d 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -36,6 +36,7 @@ import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.framework.EntityNotFoundException; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.site.StopLocation; @@ -108,7 +109,7 @@ public static TransitRouterResult route( } private TransitRouterResult route() { - if (!request.journey().transit().enabled()) { + if (!request.journey().transit().enabled() || cannotReachTransit(request)) { return new TransitRouterResult(List.of(), null); } @@ -343,4 +344,23 @@ private ExtraMcRouterSearch createExtraMcRouterSearch( ? null : service.createExtraMcRouterSearch(request, accessEgresses, raptorTransitData); } + + /** + * Returns {@code true} when the request has no way to reach transit on either the access or the + * egress side. A side is unreachable when its street mode is {@link StreetMode#NOT_SET} and the + * corresponding endpoint is not a stop (a stop endpoint provides a zero-distance access/egress, + * so it does not need a street mode). In that case the raptor search would fail in + * {@link #verifyAccessEgress} anyway, so we skip the expensive per-request transit-data build. + * This guards against API callers that supply a direct street mode without explicitly disabling + * transit (e.g. Transmodel queries like {@code modes: { directMode: foot }} that omit + * {@code transportModes}, which is not picked up by the request mappers). + */ + private static boolean cannotReachTransit(RouteRequest request) { + var journey = request.journey(); + boolean accessUnreachable = + journey.access().mode() == StreetMode.NOT_SET && request.from().stopId == null; + boolean egressUnreachable = + journey.egress().mode() == StreetMode.NOT_SET && request.to().stopId == null; + return accessUnreachable || egressUnreachable; + } } From b4235e33a94a6a12e92ea11e9ffa78e15cb173f2 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 23 Apr 2026 15:44:30 +0200 Subject: [PATCH 144/177] Move cannotReachTransit predicate to RouteRequest Pull the "can this request reach transit?" check off TransitRouter and onto RouteRequest, where it belongs alongside the other request-shape queries such as isViaSearch and allowTransferOptimization. Callers can now ask request.cannotReachTransit() directly without duplicating the predicate, and TransitRouter no longer needs the StreetMode import. --- .../raptoradapter/router/TransitRouter.java | 22 +------------------ .../routing/api/request/RouteRequest.java | 19 ++++++++++++++++ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index ac40c88376d..92e8fb989a8 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -36,7 +36,6 @@ import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.framework.EntityNotFoundException; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.site.StopLocation; @@ -109,7 +108,7 @@ public static TransitRouterResult route( } private TransitRouterResult route() { - if (!request.journey().transit().enabled() || cannotReachTransit(request)) { + if (!request.journey().transit().enabled() || request.cannotReachTransit()) { return new TransitRouterResult(List.of(), null); } @@ -344,23 +343,4 @@ private ExtraMcRouterSearch createExtraMcRouterSearch( ? null : service.createExtraMcRouterSearch(request, accessEgresses, raptorTransitData); } - - /** - * Returns {@code true} when the request has no way to reach transit on either the access or the - * egress side. A side is unreachable when its street mode is {@link StreetMode#NOT_SET} and the - * corresponding endpoint is not a stop (a stop endpoint provides a zero-distance access/egress, - * so it does not need a street mode). In that case the raptor search would fail in - * {@link #verifyAccessEgress} anyway, so we skip the expensive per-request transit-data build. - * This guards against API callers that supply a direct street mode without explicitly disabling - * transit (e.g. Transmodel queries like {@code modes: { directMode: foot }} that omit - * {@code transportModes}, which is not picked up by the request mappers). - */ - private static boolean cannotReachTransit(RouteRequest request) { - var journey = request.journey(); - boolean accessUnreachable = - journey.access().mode() == StreetMode.NOT_SET && request.from().stopId == null; - boolean egressUnreachable = - journey.egress().mode() == StreetMode.NOT_SET && request.to().stopId == null; - return accessUnreachable || egressUnreachable; - } } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index b2401d3f191..12ed64d4686 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -22,6 +22,7 @@ import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; +import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.utils.collection.ListSection; import org.opentripplanner.utils.time.TimeUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; @@ -277,6 +278,24 @@ public boolean allowTransferOptimization() { return !isViaSearch() || via.stream().allMatch(ViaLocation::isPassThroughLocation); } + /** + * Returns {@code true} when the request has no way to reach transit on either the access or the + * egress side. A side is unreachable when its street mode is {@link StreetMode#NOT_SET} and the + * corresponding endpoint is not a stop (a stop endpoint provides a zero-distance access/egress, + * so it does not need a street mode). Transit routers can use this to skip their per-request + * data-provider build when neither the access nor the egress side can feed raptor. This guards + * against API callers that supply a direct street mode without explicitly disabling transit + * (e.g. Transmodel queries like {@code modes: { directMode: foot }} that omit + * {@code transportModes}, which is not picked up by the request mappers). + */ + public boolean cannotReachTransit() { + boolean accessUnreachable = + journey.access().mode() == StreetMode.NOT_SET && (from == null || from.stopId == null); + boolean egressUnreachable = + journey.egress().mode() == StreetMode.NOT_SET && (to == null || to.stopId == null); + return accessUnreachable || egressUnreachable; + } + /** * Return {@code true} if at least one via location is set! */ From 7ecc0e68df6a8cfb8fe055b4a9bb2518bd834388 Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 23 Apr 2026 15:52:57 +0200 Subject: [PATCH 145/177] Fold transit().enabled() check into RouteRequest.cannotReachTransit Callers always combine the two "should we skip the transit search?" conditions the same way. Moving the OR into cannotReachTransit lets TransitRouter.route() ask a single question and keeps both reasons together in RouteRequest, where they belong. --- .../raptoradapter/router/TransitRouter.java | 2 +- .../routing/api/request/RouteRequest.java | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 92e8fb989a8..d8aab382bdf 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -108,7 +108,7 @@ public static TransitRouterResult route( } private TransitRouterResult route() { - if (!request.journey().transit().enabled() || request.cannotReachTransit()) { + if (request.cannotReachTransit()) { return new TransitRouterResult(List.of(), null); } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 12ed64d4686..f2186f57897 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -279,16 +279,20 @@ public boolean allowTransferOptimization() { } /** - * Returns {@code true} when the request has no way to reach transit on either the access or the - * egress side. A side is unreachable when its street mode is {@link StreetMode#NOT_SET} and the - * corresponding endpoint is not a stop (a stop endpoint provides a zero-distance access/egress, - * so it does not need a street mode). Transit routers can use this to skip their per-request - * data-provider build when neither the access nor the egress side can feed raptor. This guards - * against API callers that supply a direct street mode without explicitly disabling transit - * (e.g. Transmodel queries like {@code modes: { directMode: foot }} that omit - * {@code transportModes}, which is not picked up by the request mappers). + * Returns {@code true} when running a transit search for this request would be pointless, either + * because the caller has explicitly disabled transit or because the request has no way to reach + * transit on at least one of the access and egress sides. A side is unreachable when its street + * mode is {@link StreetMode#NOT_SET} and the corresponding endpoint is not a stop (a stop + * endpoint provides a zero-distance access/egress, so it does not need a street mode). Transit + * routers can use this to skip their per-request data-provider build. This guards against API + * callers that supply a direct street mode without explicitly disabling transit (e.g. Transmodel + * queries like {@code modes: { directMode: foot }} that omit {@code transportModes}, which is + * not picked up by the request mappers). */ public boolean cannotReachTransit() { + if (!journey.transit().enabled()) { + return true; + } boolean accessUnreachable = journey.access().mode() == StreetMode.NOT_SET && (from == null || from.stopId == null); boolean egressUnreachable = From 9b9a5dd2dc46d3e0cce6d09644ff4f38c3c88cac Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Thu, 23 Apr 2026 16:10:44 +0200 Subject: [PATCH 146/177] Update Javadoc --- .../algorithm/raptoradapter/router/TransitRouter.java | 1 + .../opentripplanner/routing/api/request/RouteRequest.java | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index d8aab382bdf..df9e79a226e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -108,6 +108,7 @@ public static TransitRouterResult route( } private TransitRouterResult route() { + // Skip the creation of raptor transit data when the request cannot use transit if (request.cannotReachTransit()) { return new TransitRouterResult(List.of(), null); } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index f2186f57897..d2d945b7433 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -283,11 +283,7 @@ public boolean allowTransferOptimization() { * because the caller has explicitly disabled transit or because the request has no way to reach * transit on at least one of the access and egress sides. A side is unreachable when its street * mode is {@link StreetMode#NOT_SET} and the corresponding endpoint is not a stop (a stop - * endpoint provides a zero-distance access/egress, so it does not need a street mode). Transit - * routers can use this to skip their per-request data-provider build. This guards against API - * callers that supply a direct street mode without explicitly disabling transit (e.g. Transmodel - * queries like {@code modes: { directMode: foot }} that omit {@code transportModes}, which is - * not picked up by the request mappers). + * endpoint provides a zero-distance access/egress, so it does not need a street mode). */ public boolean cannotReachTransit() { if (!journey.transit().enabled()) { From 5fd572eef973dfa3d04c6c388e7054c924922fc9 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 16:20:50 +0200 Subject: [PATCH 147/177] config: Rename fare service config from "default" to "gtfs" --- .../org/opentripplanner/ext/fares/FaresConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java index fdf1df541fb..bd226e30727 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.opentripplanner.ext.fares.service.NoopFareServiceFactory; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.AtlantaFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.CombineInterlinedLegsFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.HSLFareServiceFactory; @@ -68,8 +68,8 @@ public static FareServiceFactory fromConfig(JsonNode config) { private static FareServiceFactory createFactory(String type) { return switch (type) { - case "default" -> new DefaultFareServiceFactory(); case "off" -> new NoopFareServiceFactory(); + case "gtfs" -> new GtfsFareServiceFactory(); case "highest-fare-in-free-transfer-window", "highestFareInFreeTransferWindow" -> new HighestFareInFreeTransferWindowFareServiceFactory(); From 4f3d8a82164aebcb7ead061aa5affb9c195e651c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 16:21:20 +0200 Subject: [PATCH 148/177] refactor: Rename DefaultFareServiceFactory --> GtfsFareServiceFactory --- ...ServiceFactory.java => GtfsFareServiceFactory.java} | 4 ++-- .../gtfs/v1/custom/AtlantaFareServiceFactory.java | 4 ++-- .../gtfs/v1/custom/CombineInterlinedLegsFactory.java | 4 ++-- .../service/gtfs/v1/custom/HSLFareServiceFactory.java | 4 ++-- ...hestFareInFreeTransferWindowFareServiceFactory.java | 4 ++-- .../fares/service/gtfs/v1/custom/OrcaFareFactory.java | 4 ++-- .../service/gtfs/v2/custom/OregonHopFareFactory.java | 4 ++-- .../opentripplanner/gtfs/graphbuilder/GtfsModule.java | 4 ++-- .../gtfs/graphbuilder/GtfsModuleTestFactory.java | 4 ++-- .../java/org/opentripplanner/ConstantsForTests.java | 8 ++++---- .../test/java/org/opentripplanner/TestOtpModel.java | 4 ++-- .../routing/graph/GraphSerializationTest.java | 4 ++-- .../transit/service/TimetableRepositoryTest.java | 10 +++++----- 13 files changed, 31 insertions(+), 31 deletions(-) rename application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/{DefaultFareServiceFactory.java => GtfsFareServiceFactory.java} (96%) diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java similarity index 96% rename from application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java rename to application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java index 2ebb69b99b4..4334a2104bd 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java @@ -30,9 +30,9 @@ * as well as Fares V2. * */ -public class DefaultFareServiceFactory implements FareServiceFactory { +public class GtfsFareServiceFactory implements FareServiceFactory { - private static final Logger LOG = LoggerFactory.getLogger(DefaultFareServiceFactory.class); + private static final Logger LOG = LoggerFactory.getLogger(GtfsFareServiceFactory.class); protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java index c5a26e096a2..35983699092 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java @@ -6,10 +6,10 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; -public class AtlantaFareServiceFactory extends DefaultFareServiceFactory { +public class AtlantaFareServiceFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java index c35dd3cc817..00f3960f410 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.service.gtfs.v1.custom; import com.fasterxml.jackson.databind.JsonNode; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.CombinedInterlinedLegsFareService.CombinationMode; import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; -public class CombineInterlinedLegsFactory extends DefaultFareServiceFactory { +public class CombineInterlinedLegsFactory extends GtfsFareServiceFactory { private CombinationMode mode = CombinationMode.ALWAYS; diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java index 1861a568b3c..7572ad3f6b4 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java @@ -6,14 +6,14 @@ import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRule; import org.opentripplanner.ext.fares.model.FareRuleSet; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model.network.Route; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HSLFareServiceFactory extends DefaultFareServiceFactory { +public class HSLFareServiceFactory extends GtfsFareServiceFactory { private static final Logger LOG = LoggerFactory.getLogger(HSLFareService.class); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java index 281466d26f8..61eb89dd3d0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java @@ -9,7 +9,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -26,7 +26,7 @@ *

        * This calculator is maintained by IBI Group. */ -public class HighestFareInFreeTransferWindowFareServiceFactory extends DefaultFareServiceFactory { +public class HighestFareInFreeTransferWindowFareServiceFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java index a1bad5c376a..988297745ce 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java @@ -6,10 +6,10 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; -public class OrcaFareFactory extends DefaultFareServiceFactory { +public class OrcaFareFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java index 24e13a98862..8d94371693e 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java @@ -11,7 +11,7 @@ import org.opentripplanner.ext.fares.model.TimeLimitType; import org.opentripplanner.ext.fares.service.gtfs.GtfsFaresService; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v2.GtfsFaresV2Service; import org.opentripplanner.model.fare.FareMedium; import org.opentripplanner.model.fare.FareProduct; @@ -20,7 +20,7 @@ import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model.basic.Money; -public class OregonHopFareFactory extends DefaultFareServiceFactory { +public class OregonHopFareFactory extends GtfsFareServiceFactory { static final FeedScopedId TRIMET_ADULT_SINGLE_RIDE = trimetId("TRIMET_ADULT_SINGLE_RIDE"); static final FeedScopedId TRIMET_HC_SINGLE_RIDE = trimetId("TRIMET_HC_SINGLE_RIDE"); diff --git a/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java b/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java index 312aeaca853..04918dacfe7 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java +++ b/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java @@ -19,7 +19,7 @@ import org.opentripplanner.core.framework.deduplicator.DeduplicatorService; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.flex.FlexTripsMapper; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -118,7 +118,7 @@ public static GtfsModule forTest( new Deduplicator(), DataImportIssueStore.NOOP, transitPeriodLimit, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), 150.0, 120 ); diff --git a/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java b/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java index 1fab2524543..b45dbab6ee1 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java @@ -2,7 +2,7 @@ import java.util.List; import org.opentripplanner.core.model.time.LocalDateInterval; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.service.streetdetails.internal.DefaultStreetDetailsRepository; import org.opentripplanner.street.graph.Graph; @@ -25,7 +25,7 @@ public static GtfsModule forTest( new Deduplicator(), DataImportIssueStore.NOOP, transitPeriodLimit, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), 150.0, 120 ); diff --git a/application/src/test/java/org/opentripplanner/ConstantsForTests.java b/application/src/test/java/org/opentripplanner/ConstantsForTests.java index c0d70bd2de1..308086df3de 100644 --- a/application/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/application/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -14,7 +14,7 @@ import org.opentripplanner.datastore.api.CompositeDataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.DirectoryDataSource; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.model.ConfiguredCompositeDataSource; @@ -147,7 +147,7 @@ public static TestOtpModel buildNewPortlandGraph(boolean withElevation) { try { var graph = new Graph(); var timetableRepository = new TimetableRepository(new SiteRepository()); - var fareFactory = new DefaultFareServiceFactory(); + var fareFactory = new GtfsFareServiceFactory(); // Add street data from OSM { var osmModule = OsmModuleTestFactory.of(new DefaultOsmProvider(PORTLAND_CENTRAL_OSM, false)) @@ -239,7 +239,7 @@ public static TestOtpModel buildOsmAndGtfsGraph(File osmPath, File gtfsPath) { otpModel.graph(), otpModel.timetableRepository(), gtfsPath, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ); @@ -250,7 +250,7 @@ public static TestOtpModel buildOsmAndGtfsGraph(File osmPath, File gtfsPath) { } public static TestOtpModel buildGtfsGraph(File gtfsPath) { - return buildGtfsGraph(gtfsPath, new DefaultFareServiceFactory()); + return buildGtfsGraph(gtfsPath, new GtfsFareServiceFactory()); } public static TestOtpModel buildGtfsGraph(File gtfsFile, FareServiceFactory fareServiceFactory) { diff --git a/application/src/test/java/org/opentripplanner/TestOtpModel.java b/application/src/test/java/org/opentripplanner/TestOtpModel.java index 320be49eadc..8c8e56d2c19 100644 --- a/application/src/test/java/org/opentripplanner/TestOtpModel.java +++ b/application/src/test/java/org/opentripplanner/TestOtpModel.java @@ -1,6 +1,6 @@ package org.opentripplanner; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareServiceFactory; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.transfer.regular.TransferRepository; @@ -17,7 +17,7 @@ public TestOtpModel( TimetableRepository timetableRepository, TransferRepository transferRepository ) { - this(graph, timetableRepository, transferRepository, new DefaultFareServiceFactory()); + this(graph, timetableRepository, transferRepository, new GtfsFareServiceFactory()); } public TestOtpModel index() { diff --git a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index e72f5a29ad4..bbbdc836b6d 100644 --- a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -33,7 +33,7 @@ import org.opentripplanner.ext.empiricaldelay.model.EmpiricalDelay; import org.opentripplanner.ext.empiricaldelay.model.TripDelays; import org.opentripplanner.ext.empiricaldelay.model.calendar.EmpiricalDelayCalendar; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.framework.model.Gram; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.model.plan.Emission; @@ -272,7 +272,7 @@ private void testRoundTrip( emissionRepository, empiricalDelayRepository, null, - new DefaultFareServiceFactory() + new GtfsFareServiceFactory() ); serializedObj.save(new FileDataSource(tempFile, FileType.GRAPH)); SerializedGraphObject deserializedGraph = SerializedGraphObject.load(tempFile); diff --git a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java index e8008a87570..98beae64963 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java @@ -14,7 +14,7 @@ import org.opentripplanner.ConstantsForTests; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.graph_builder.module.TimeZoneAdjusterModule; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.test.support.ResourceLoader; @@ -44,7 +44,7 @@ void validateTimeZones() { graph, timetableRepository, ConstantsForTests.SIMPLE_GTFS, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), FAKE_FEED_ID ); @@ -66,7 +66,7 @@ void validateTimeZones() { graph, timetableRepository, RESOURCE_LOADER.file("kcm_gtfs.zip"), - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ), ("The graph contains agencies with different time zones. " + @@ -89,7 +89,7 @@ void validateTimeZonesWithExplicitTimeZone() { graph, timetableRepository, ConstantsForTests.SIMPLE_GTFS, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), FAKE_FEED_ID ); @@ -98,7 +98,7 @@ void validateTimeZonesWithExplicitTimeZone() { graph, timetableRepository, RESOURCE_LOADER.file("kcm_gtfs.zip"), - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ); From 36a67bf3dbedfd26bea6569c5139309231be0f3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:34:52 +0000 Subject: [PATCH 149/177] Update dependency org.openrewrite.recipe:rewrite-static-analysis to v2.33.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3cf5d16e2e3..4f483e9def1 100644 --- a/pom.xml +++ b/pom.xml @@ -351,7 +351,7 @@ org.openrewrite.recipe rewrite-static-analysis - 2.33.0 + 2.33.1 From ec003028ecb8a64be3def5fe0c82cc5525400075 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 23 Apr 2026 14:35:34 +0000 Subject: [PATCH 150/177] Add changelog entry for #7509 [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 fa496b95214..0f3b512b914 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -28,6 +28,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) - Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) - Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrance` to `includeOsmStationEntrance` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) +- Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From dac2088f88a946f5b45c36e879a7eb229e4cf6a1 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 20:54:43 +0200 Subject: [PATCH 151/177] refactor: Simplify dominance in ParetoSet#add --- .../raptor/util/paretoset/ParetoSet.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index 658d0d79a32..fb06e7a05bf 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -97,9 +97,7 @@ public final boolean add(T newValue) { return false; } - boolean mutualDominanceExist = false; - boolean equivalentVectorExist = false; - + loop: for (int i = 0; i < size; ++i) { T it = elements[i]; @@ -107,28 +105,24 @@ public final boolean add(T newValue) { boolean rightDominance = rightDominanceExist(newValue, it); if (leftDominance && rightDominance) { - mutualDominanceExist = true; + continue loop; } else if (leftDominance) { removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); return true; } else if (rightDominance) { - goodElement = elements[i]; + goodElement = it; notifyElementRejected(newValue, it); return false; } else { - equivalentVectorExist = true; + // newValue is equivalent with an existing value + notifyElementRejected(newValue, it); + return false; } } - if (mutualDominanceExist && !equivalentVectorExist) { - assertEnoughSpaceInSet(); - acceptAndAppendValue(newValue); - return true; - } - - // No dominance found, newValue is equivalent with all values in the set - notifyElementRejected(newValue, elements[0]); - return false; + assertEnoughSpaceInSet(); + acceptAndAppendValue(newValue); + return true; } public final void clear() { From 9ae7ab601035600ce1b5295731cae3223fefcf75 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 21:45:16 +0200 Subject: [PATCH 152/177] refactor: Simplify dominance in ParetoSet#qualify --- .../raptor/util/paretoset/ParetoSet.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index fb06e7a05bf..946d2a8f55b 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -153,31 +153,23 @@ public final boolean qualify(T newValue) { return false; } - boolean mutualDominanceExist = false; - boolean equivalentVectorExist = false; - + loop: for (int i = size - 1; i >= 0; --i) { boolean leftDominance = leftDominanceExist(newValue, elements[i]); boolean rightDominance = rightDominanceExist(newValue, elements[i]); if (leftDominance && rightDominance) { - if (equivalentVectorExist) { - return false; - } - mutualDominanceExist = true; + continue loop; } else if (leftDominance) { return true; } else if (rightDominance) { goodElement = elements[i]; return false; } else { - if (mutualDominanceExist) { - return false; - } - equivalentVectorExist = true; + return false; } } - return mutualDominanceExist; + return true; } /** From f93dee4543c3e2a3d73b8090a5d7c274e6f5eaee Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 22:00:19 +0200 Subject: [PATCH 153/177] refactor: Enhance Pareto dominance comparison logic and add tests --- .../util/paretoset/ParetoComparator.java | 36 +++++++---- .../util/paretoset/ParetoDominance.java | 61 +++++++++++++++++++ .../util/paretoset/ParetoComparatorTest.java | 41 +++++++++++++ .../util/paretoset/ParetoDominanceTest.java | 44 +++++++++++++ 4 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java create mode 100644 raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java create mode 100644 raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index 83ba0d982aa..c578f5645af 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -1,22 +1,32 @@ package org.opentripplanner.raptor.util.paretoset; -/** - * Comparator used by the {@link ParetoSet} to compare to elements for dominance. There is 4 - * outcomes of a comparison between a left and right vector: - *

          - *
        • Left dominates right - At least one left criteria dominates, and no right dominance exist - *
        • Right dominates left - At least one right criteria dominates, and no left dominance exist - *
        • Mutual dominance - At least one left criteria dominates right and at least one right criteria dominates left - *
        • No dominance - all criteria is equals or no dominance exist - *
        - * To implement the comparator you only need to implement the comparison in one direction - if dominance exist. - * - * @param The pareto set element type - */ +/// Comparator used by the {@link ParetoSet} to compare to elements for dominance. There is 4 +/// outcomes of a comparison between a left and right vector: +/// +/// - Left dominates right `<` - At least one left criteria dominates, and no right dominance exist +/// - Right dominates left `>` - At least one right criteria dominates, and no left dominance exist +/// - Mutual dominance `||` - At least one left criteria dominates right and at least one right +/// criteria dominates left +/// - No dominance `=` - all criteria is equals or no dominance exist. +/// +/// To implement the comparator you only need to implement the comparison in one direction - if dominance exist. +/// +/// @param The pareto set element type +/// @FunctionalInterface public interface ParetoComparator { /** * At least one of the left criteria dominates one of the corresponding right criteria. */ boolean leftDominanceExist(T left, T right); + + default boolean dominanceExist(T left, T right) { + return leftDominanceExist(left, right) || leftDominanceExist(right, left); + } + + default ParetoDominance compare(T left, T right) { + final boolean l = leftDominanceExist(left, right); + final boolean r = leftDominanceExist(right, left); + return ParetoDominance.of(l, r); + } } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java new file mode 100644 index 00000000000..a092bb40e5a --- /dev/null +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java @@ -0,0 +1,61 @@ +package org.opentripplanner.raptor.util.paretoset; + +/// Pareto Dominance can have 4 values when comparing two values `x`, `y`. Remember +/// x and y can be vectors with one or more criterias. +public enum ParetoDominance { + /// Left-Dominance (`x` ≺ `y`), when `x` is strictly better than`y`. + LEFT('≺'), + /// Right-Dominance (`x` ≻ `y`), when `y` is strictly better than`x`. + RIGHT('≻'), + /// Both Dominate Each Other (Incomparable/Indifferent) (`x` ∥ `y`). Neither solution is + /// superior to the other. Both solutions are part of the Pareto optimal set (Pareto front). For + /// two vectors `v` and `u` this happens when the `v` is better in one criteria, and `u` is + /// better in another: `[1, 7, 3]` ≡ `[7, 1, 3]`. + BOTH('∥'), + /// No Dominate (Pareto Equivalent) (`x` ≡ `y`). Neither `x` dominates `y` nor `y` dominates + /// `x`, AND `x` and `y` are equal in all objective values: `[1, 7, 3]` ≡ `[1, 7, 3]`. + NONE('≡'); + + private char symbol; + + ParetoDominance(char symbol) { + this.symbol = symbol; + } + + /** + * Create a dominance value from two directional dominance flags. + */ + public static ParetoDominance of(boolean leftDominanceExist, boolean rightDominanceExist) { + if (leftDominanceExist) { + return rightDominanceExist ? BOTH : LEFT; + } else { + return rightDominanceExist ? RIGHT : NONE; + } + } + + /** + * Parse a dominance value from either its symbol or enum name. + * + *

        Accepted symbols are `≺`, `≻`, `∥`, and `≡`. If the input is not a single-character symbol, + * the value is parsed as an enum name (case-insensitive). + */ + public static ParetoDominance of(String value) { + if (value.length() == 1) { + char ch = value.charAt(0); + for (var it : values()) { + if (it.symbol == ch) { + return it; + } + } + } + return valueOf(value.toUpperCase()); + } + + /** + * Return the symbolic representation of this dominance value. + */ + @Override + public String toString() { + return Character.toString(symbol); + } +} diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java new file mode 100644 index 00000000000..5da3d900f42 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java @@ -0,0 +1,41 @@ +package org.opentripplanner.raptor.util.paretoset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.BOTH; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; + +import org.junit.jupiter.api.Test; + +public class ParetoComparatorTest { + + private static final ParetoComparator COMPARATOR = (l, r) -> + l.v1 < r.v1 || l.v2 < r.v2; + private static final TestVector A_5_5 = new TestVector("a", 5, 5); + private static final TestVector B_5_5 = new TestVector("b", 5, 5); + private static final TestVector C_3_5 = new TestVector("c", 3, 5); + private static final TestVector D_5_3 = new TestVector("d", 5, 3); + + @Test + public void dominanceExist() { + // Vectors are equal - no dominance + assertFalse(COMPARATOR.dominanceExist(A_5_5, B_5_5)); + + // Dominance exist + assertTrue(COMPARATOR.dominanceExist(C_3_5, B_5_5)); + assertTrue(COMPARATOR.dominanceExist(A_5_5, C_3_5)); + assertTrue(COMPARATOR.dominanceExist(C_3_5, D_5_3)); + } + + @Test + public void compare() { + assertEquals(NONE, COMPARATOR.compare(A_5_5, B_5_5)); + assertEquals(NONE, COMPARATOR.compare(A_5_5, A_5_5)); + assertEquals(LEFT, COMPARATOR.compare(C_3_5, B_5_5)); + assertEquals(RIGHT, COMPARATOR.compare(A_5_5, C_3_5)); + assertEquals(BOTH, COMPARATOR.compare(C_3_5, D_5_3)); + } +} diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java new file mode 100644 index 00000000000..c794c6a6034 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java @@ -0,0 +1,44 @@ +package org.opentripplanner.raptor.util.paretoset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.BOTH; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; + +import org.junit.jupiter.api.Test; + +class ParetoDominanceTest { + + @Test + void testOf() { + assertEquals(BOTH, ParetoDominance.of("both")); + assertEquals(LEFT, ParetoDominance.of("left")); + assertEquals(RIGHT, ParetoDominance.of("right")); + assertEquals(NONE, ParetoDominance.of("none")); + assertEquals(LEFT, ParetoDominance.of("≺")); + assertEquals(RIGHT, ParetoDominance.of("≻")); + assertEquals(NONE, ParetoDominance.of("≡")); + assertEquals(BOTH, ParetoDominance.of("∥")); + + // Mixed case + assertEquals(BOTH, ParetoDominance.of("BOTH")); + assertEquals(BOTH, ParetoDominance.of("bOtH")); + } + + @Test + void testOfLeftRight() { + assertEquals(LEFT, ParetoDominance.of(true, false)); + assertEquals(RIGHT, ParetoDominance.of(false, true)); + assertEquals(BOTH, ParetoDominance.of(true, true)); + assertEquals(NONE, ParetoDominance.of(false, false)); + } + + @Test + void testToSting() { + assertEquals("≺", LEFT.toString()); + assertEquals("≻", RIGHT.toString()); + assertEquals("≡", NONE.toString()); + assertEquals("∥", BOTH.toString()); + } +} From affc79fb4455b05775a56b605231b8a44ac9e175 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 22:05:31 +0200 Subject: [PATCH 154/177] refactor: Use enum switch in ParetoSet --- .../raptor/util/paretoset/ParetoSet.java | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index 946d2a8f55b..c5c03df7f0f 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -97,26 +97,23 @@ public final boolean add(T newValue) { return false; } - loop: - for (int i = 0; i < size; ++i) { + loop: for (int i = 0; i < size; ++i) { T it = elements[i]; - boolean leftDominance = leftDominanceExist(newValue, it); - boolean rightDominance = rightDominanceExist(newValue, it); - - if (leftDominance && rightDominance) { - continue loop; - } else if (leftDominance) { - removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); - return true; - } else if (rightDominance) { - goodElement = it; - notifyElementRejected(newValue, it); - return false; - } else { - // newValue is equivalent with an existing value - notifyElementRejected(newValue, it); - return false; + switch (comparator.compare(newValue, it)) { + case BOTH: + continue loop; + case LEFT: + removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); + return true; + case RIGHT: + goodElement = it; + notifyElementRejected(newValue, it); + return false; + case NONE: + // newValue is equivalent with an existing value + notifyElementRejected(newValue, it); + return false; } } @@ -153,20 +150,19 @@ public final boolean qualify(T newValue) { return false; } - loop: - for (int i = size - 1; i >= 0; --i) { - boolean leftDominance = leftDominanceExist(newValue, elements[i]); - boolean rightDominance = rightDominanceExist(newValue, elements[i]); - - if (leftDominance && rightDominance) { - continue loop; - } else if (leftDominance) { - return true; - } else if (rightDominance) { - goodElement = elements[i]; - return false; - } else { - return false; + loop: for (int i = size - 1; i >= 0; --i) { + var it = elements[i]; + + switch (comparator.compare(newValue, it)) { + case BOTH: + continue loop; + case LEFT: + return true; + case RIGHT: + goodElement = it; + return false; + case NONE: + return false; } } return true; From f99952b69c0a30272d2a2a291a69074480fc3618 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 23 Apr 2026 22:50:08 +0200 Subject: [PATCH 155/177] doc: Improve JavaDoc for ParetoComparator and ParetoDominance --- .../util/paretoset/ParetoComparator.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index c578f5645af..07b7cf7b5bf 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -1,29 +1,41 @@ package org.opentripplanner.raptor.util.paretoset; -/// Comparator used by the {@link ParetoSet} to compare to elements for dominance. There is 4 -/// outcomes of a comparison between a left and right vector: +/// Compares two elements in a {@link ParetoSet} for Pareto dominance. /// -/// - Left dominates right `<` - At least one left criteria dominates, and no right dominance exist -/// - Right dominates left `>` - At least one right criteria dominates, and no left dominance exist -/// - Mutual dominance `||` - At least one left criteria dominates right and at least one right -/// criteria dominates left -/// - No dominance `=` - all criteria is equals or no dominance exist. +/// A comparison between a `left` and `right` element can produce four outcomes: /// -/// To implement the comparator you only need to implement the comparison in one direction - if dominance exist. +/// - **Left dominates right** `≺`: at least one left criterion dominates and no right +/// criterion dominates. +/// - **Right dominates left** `≻`: at least one right criterion dominates and no left +/// criterion dominates. +/// - **Mutual dominance** `∥`: at least one left criteria dominates right and at +/// least one right criteria dominates left +/// - **No dominance** `≡`: all criteria are equal, or neither side dominates. /// -/// @param The pareto set element type +/// Implementations only need to provide one directional check in +/// {@link #leftDominanceExist(Object, Object)}. +/// +/// @param the Pareto set element type /// @FunctionalInterface public interface ParetoComparator { /** - * At least one of the left criteria dominates one of the corresponding right criteria. + * Returns {@code true} if at least one criterion in {@code left} dominates the corresponding + * criterion in {@code right}. */ boolean leftDominanceExist(T left, T right); + /** + * Returns {@code true} if either element dominates the other. + */ default boolean dominanceExist(T left, T right) { return leftDominanceExist(left, right) || leftDominanceExist(right, left); } + /** + * Compares {@code left} and {@code right} and returns the corresponding {@link ParetoDominance} + * outcome. + */ default ParetoDominance compare(T left, T right) { final boolean l = leftDominanceExist(left, right); final boolean r = leftDominanceExist(right, left); From cec3a7d401b66ffcf25ee06eb93185c0c03292f3 Mon Sep 17 00:00:00 2001 From: Heikki Vuorinen Date: Fri, 24 Apr 2026 09:13:09 +0300 Subject: [PATCH 156/177] chore: remove unnecessary test and modify docstring --- .../opentripplanner/apis/gtfs/schema.graphqls | 7 ++----- .../RouteRequestMapperTransitTest.java | 21 ------------------- 2 files changed, 2 insertions(+), 26 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 7e2b30f5361..e0a307ff66d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5104,12 +5104,9 @@ input TransitPreferencesInput { paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - - The relaxation is done using a linear function applied to the generalized cost. - If not defined, a function with a constant 0 and a coefficient of 1.0 is used, - meaning the cost comparison is not relaxed between transit groups. - A relax-cost is used to increase the limit when comparing one cost to another cost. + A relax-cost is used to increase the limit when comparing one cost to another cost + using a linear function applied to the generalized cost. This is used to include more results into the result. A `coefficient=2.0` means a path (itinerary) with twice as high cost as another one, is accepted. A `constant=300` means a "fixed" constant is added to the limit. diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index 36753074eef..39e9867bc11 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -157,25 +157,4 @@ void testRelaxTransitGroupPriority() { assertEquals(constant, transitPreferences.relaxTransitGroupPriority().constant()); assertEquals(coefficient, transitPreferences.relaxTransitGroupPriority().coefficient()); } - - @Test - void testPartialRelaxTransitGroupPriority() { - var args = testCtx.basicRequest(); - var coefficient = 1.2; - var relaxTransitGroupPriority = Map.ofEntries(entry("coefficient", coefficient)); - args.put( - "preferences", - Map.ofEntries( - entry( - "transit", - Map.ofEntries(entry("relaxTransitGroupPriority", relaxTransitGroupPriority)) - ) - ) - ); - var env = testCtx.executionContext(args); - var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); - var transitPreferences = routeRequest.preferences().transit(); - assertEquals(Cost.ZERO, transitPreferences.relaxTransitGroupPriority().constant()); - assertEquals(coefficient, transitPreferences.relaxTransitGroupPriority().coefficient()); - } } From bf36d05018e2c4c27b166db78b1393d0660b01dd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 Apr 2026 15:37:40 +0200 Subject: [PATCH 157/177] Don't cast to an int --- .../org/opentripplanner/street/search/state/StateEditor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index 04a2fc8407c..72de50b0c9c 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -115,7 +115,7 @@ public State makeState() { if (backState != null) { // check that time changes are coherent with edge traversal // direction - int timeDelta = (int) (time_ms - backState.getTimeMilliseconds()); + double timeDelta = time_ms - backState.getTimeMilliseconds(); if (traversingBackward ? (timeDelta > 0) : (timeDelta < 0)) { LOG.trace("Time was incremented the wrong direction during state editing. {}", backEdge); return null; From 9224b0351f260b0be91bbf6cb8be0163a91541c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 25 Apr 2026 02:00:11 +0000 Subject: [PATCH 158/177] Update dependency com.hivemq:hivemq-mqtt-client to v1.3.13 --- application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/pom.xml b/application/pom.xml index a9af197ee94..09d9b92a775 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -337,7 +337,7 @@ com.hivemq hivemq-mqtt-client - 1.3.12 + 1.3.13 io.github.ci-cmg From 3016092e6919d2dd41b5cf8df5759de55c27957a Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 27 Apr 2026 09:39:26 +0000 Subject: [PATCH 159/177] Add changelog entry for #7525 [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 0f3b512b914..84593579de2 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -29,6 +29,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) - Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrance` to `includeOsmStationEntrance` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) - Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) +- Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 2fcf73cb688aace2dce5569c2c1fc6f7305d318b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 27 Apr 2026 12:43:31 +0200 Subject: [PATCH 160/177] Upgrade netex-java-model to 2.0.15.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f483e9def1..f9d89c279f4 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ 1.5.32 10.4.0 1.16.5 - 2.0.15.2 + 2.0.15.3 5.6.0 4.34.1 2.0.6 From 3c546f863c89e6bfc432a0858cffc4953862bdfc Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 27 Apr 2026 13:28:26 +0200 Subject: [PATCH 161/177] Update Gitter URL --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 4 ++-- doc/user/Developers-Guide.md | 6 +++--- doc/user/Product-Overview.md | 2 +- doc/user/index.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 15e9e772595..44f0381f12e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' **NOTE:** this issue system is intended for reporting bugs and tracking progress in software development. For all other usage and software development questions or discussion, please post a -question in our chat room: https://gitter.im/opentripplanner/OpenTripPlanner. +question in our chat room: https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im. ## Expected behavior diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b140423485..b36267ca8bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Please read the guidelines carefully to make sure you follow our contribution pr - If you have any questions about problems you are encountering with code, deployment, documentation, or development coordination, please don't hesitate to post to the -[OpenTripPlanner Gitter chat](https://gitter.im/opentripplanner/OpenTripPlanner). +[OpenTripPlanner Gitter chat](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im). - There are twice-weekly developer meetings to discuss and coordinate contributions in more detail. See the [calendar](https://calendar.google.com/calendar/u/0/embed?src=ormbltvsqb6adl80ejgudt0glc@group.calendar.google.com) or [iCal link](https://calendar.google.com/calendar/ical/ormbltvsqb6adl80ejgudt0glc@group.calendar.google.com/public/basic.ics) diff --git a/README.md b/README.md index 742ab99778b..245fda78079 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Overview -[![Join the chat at https://gitter.im/opentripplanner/OpenTripPLanner](https://badges.gitter.im/opentripplanner/OpenTripPlanner.svg)](https://gitter.im/opentripplanner/OpenTripPlanner) +[![Join the chat at https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im](https://badges.gitter.im/opentripplanner/OpenTripPlanner.svg)](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) [![Matrix](https://img.shields.io/matrix/opentripplanner%3Amatrix.org?label=Matrix%20chat&?cacheSeconds=172800)](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) [![codecov](https://codecov.io/gh/opentripplanner/OpenTripPlanner/branch/dev-2.x/graph/badge.svg?token=ak4PbIKgZ1)](https://codecov.io/gh/opentripplanner/OpenTripPlanner) [![Commit activity](https://img.shields.io/github/commit-activity/y/opentripplanner/OpenTripPlanner)](https://github.com/opentripplanner/OpenTripPlanner/graphs/contributors) @@ -60,7 +60,7 @@ the [main documentation](http://docs.opentripplanner.org/en/dev-2.x/), including ## Getting in Touch -The fastest way to get help is to use our [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) where most of the core developers +The fastest way to get help is to use our [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is used almost exclusively for project announcements. The mailing list and issue tracker are not intended for support questions or discussions. Please use the chat for this purpose. Other details diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md index f2813776d9d..038cb9f2720 100644 --- a/doc/user/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -53,7 +53,7 @@ like "program arguments". OpenTripPlanner is a community based open source project, and we welcome all who wish to contribute. There are several ways to get involved: -* Join the [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) and the +* Join the [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) and the [user mailing list](http://groups.google.com/group/opentripplanner-users). * Fix typos and improve the documentation within the `/doc/user` directory of the project (details @@ -61,7 +61,7 @@ There are several ways to get involved: * [File a bug or new feature request](http://github.com/openplans/OpenTripPlanner/issues/new). If you want a GitHub issue to be addressed, you should also discuss it in - [Gitter](https://gitter.im/opentripplanner/OpenTripPlanner) or in the + [Gitter](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) or in the [developer meetings](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CONTRIBUTING.md#developer-meetings). * Create pull requests citing the relevant issue. @@ -93,7 +93,7 @@ implementation discussions (in the comments). The created issue should be refere request. For really minor and uncontroversial pull requests, it is ok to not create an issue. **If you want a GitHub issue to be addressed, you should also discuss it in -[Gitter](https://gitter.im/opentripplanner/OpenTripPlanner) or in the +[Gitter](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) or in the [developer meetings](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CONTRIBUTING.md#developer-meetings).** ### Unit tests using real OSM data diff --git a/doc/user/Product-Overview.md b/doc/user/Product-Overview.md index dc0cfcc25a1..ae1c5b8898a 100644 --- a/doc/user/Product-Overview.md +++ b/doc/user/Product-Overview.md @@ -69,7 +69,7 @@ be more appropriate for these purposes. ## Talk to an expert about OTP -Everyone interested in OTP is welcome to post questions on the [Gitter chat](https://gitter.im/opentripplanner/OpenTripPlanner) +Everyone interested in OTP is welcome to post questions on the [Gitter chat](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) or [OpenTripPlanner-users group](https://groups.google.com/g/opentripplanner-users). If you’re looking for a conversation with an individual from a similar organizational type, you can diff --git a/doc/user/index.md b/doc/user/index.md index f948c0c486a..70ffa44e3bf 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -64,7 +64,7 @@ your own OTP instance, the best place to start is the [Basic Tutorial](Basic-Tut # Getting help -The fastest way to get help is to use our [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The issue tracker is not intended for support questions or discussions. Please use the chat for this purpose. The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is treated as a legacy communications channel and used almost exclusively for project announcements. Again, please direct development and support discussions to the Gitter chat. +The fastest way to get help is to use our [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The issue tracker is not intended for support questions or discussions. Please use the chat for this purpose. The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is treated as a legacy communications channel and used almost exclusively for project announcements. Again, please direct development and support discussions to the Gitter chat. # Financial and In-Kind Support From a287dba6a1e468a3aed3fe84e00f420c2b546cc3 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 27 Apr 2026 12:17:36 +0000 Subject: [PATCH 162/177] Add changelog entry for #7451 [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 84593579de2..5f93d0b0a69 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -30,6 +30,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrance` to `includeOsmStationEntrance` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) - Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) - Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) +- Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From e8050c8eefa579b99b7186f0ed30bdbdbfdd385b Mon Sep 17 00:00:00 2001 From: Henrik Abrahamsson Date: Mon, 27 Apr 2026 14:57:32 +0200 Subject: [PATCH 163/177] Address review comments --- .../model/GenericLocation.java | 2 +- .../model/GenericLocationTest.java | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index f5998190be1..633b2d57d20 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -22,7 +22,7 @@ public class GenericLocation { private final FeedScopedId stopId; @Nullable - WgsCoordinate coordinate; + private final WgsCoordinate coordinate; private GenericLocation( @Nullable String label, diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 4313d36c848..3f7dfee3e5e 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -1,11 +1,13 @@ package org.opentripplanner.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.street.geometry.WgsCoordinate; class GenericLocationTest { @@ -22,14 +24,28 @@ class GenericLocationTest { private final GenericLocation other = GenericLocation.fromCoordinate(LATITUDE, LONGITUDE, LABEL); + @Test + void fromStopIdWithFallback() { + var location = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); + assertEquals(STOP_ID, location.stopId()); + assertEquals(new WgsCoordinate(LATITUDE, LONGITUDE), location.wgsCoordinate()); + assertEquals(LABEL, location.label()); + } + @Test void fromStopId() { - assertEquals(STOP_ID, subject.stopId()); + var location = GenericLocation.fromStopId(STOP_ID, LABEL); + assertEquals(STOP_ID, location.stopId()); + assertNull(location.wgsCoordinate()); + assertEquals(LABEL, location.label()); } @Test - void getCoordinate() { - assertEquals(STOP_ID, subject.stopId()); + void fromCoordinate() { + var location = GenericLocation.fromCoordinate(LATITUDE, LONGITUDE, LABEL); + assertNull(location.stopId()); + assertEquals(new WgsCoordinate(LATITUDE, LONGITUDE), location.wgsCoordinate()); + assertEquals(LABEL, location.label()); } @Test From 9d2dc124ef180501a6c4bb7d8e7c5d3b0c3c7c31 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 27 Apr 2026 13:37:14 +0000 Subject: [PATCH 164/177] Add changelog entry for #7545 [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 5f93d0b0a69..0504df01f7f 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -31,6 +31,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) - Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) - Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) +- Set default fares to [#7545](https://github.com/opentripplanner/OpenTripPlanner/pull/7545) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 8f791849982bf8c6f301395a6922e4264f37b986 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 27 Apr 2026 13:46:18 +0000 Subject: [PATCH 165/177] Add changelog entry for #7563 [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 0504df01f7f..f2c39150fc5 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 - Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) - Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) - Set default fares to [#7545](https://github.com/opentripplanner/OpenTripPlanner/pull/7545) +- Reduce memory consumption when parsing very large NeTEx files [#7563](https://github.com/opentripplanner/OpenTripPlanner/pull/7563) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 614d6fdbb5252410fbaa7f0f15eb641a044bb99b Mon Sep 17 00:00:00 2001 From: Sigurd Totland Date: Mon, 27 Apr 2026 14:41:08 +0200 Subject: [PATCH 166/177] refactor: Move RequestPreProcessor new() into DefaultRoutingService ctor --- .../routing/service/DefaultRoutingService.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java index 23d06bb04e3..af0b4b36b18 100644 --- a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java +++ b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java @@ -1,6 +1,5 @@ package org.opentripplanner.routing.service; -import java.time.ZoneId; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.Itinerary; @@ -29,11 +28,17 @@ public class DefaultRoutingService implements RoutingService { private final OtpServerRequestContext serverContext; - private final ZoneId timeZone; + private final RequestPreProcessor requestPreProcessor; public DefaultRoutingService(OtpServerRequestContext serverContext) { this.serverContext = serverContext; - this.timeZone = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); + + var timeZone = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); + + this.requestPreProcessor = new RequestPreProcessor( + serverContext.raptorTuningParameters(), + timeZone + ); } @Override @@ -59,9 +64,7 @@ public ViaRoutingResponse route(RouteViaRequest request) { } private RoutingWorkerRequest mapRequest(RouteRequest request) { - return new RequestPreProcessor(serverContext.raptorTuningParameters(), timeZone).computeRequest( - request - ); + return requestPreProcessor.computeRequest(request); } private void logResponse(RoutingResponse response) { From 8510c2b495e6669e2d6ed5c43ce895d137476a38 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 28 Apr 2026 09:58:05 +0200 Subject: [PATCH 167/177] Revert "Update Gitter URL" [ci skip] This reverts commit 3c546f863c89e6bfc432a0858cffc4953862bdfc. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 4 ++-- doc/user/Developers-Guide.md | 6 +++--- doc/user/Product-Overview.md | 2 +- doc/user/index.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 44f0381f12e..15e9e772595 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' **NOTE:** this issue system is intended for reporting bugs and tracking progress in software development. For all other usage and software development questions or discussion, please post a -question in our chat room: https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im. +question in our chat room: https://gitter.im/opentripplanner/OpenTripPlanner. ## Expected behavior diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b36267ca8bd..3b140423485 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Please read the guidelines carefully to make sure you follow our contribution pr - If you have any questions about problems you are encountering with code, deployment, documentation, or development coordination, please don't hesitate to post to the -[OpenTripPlanner Gitter chat](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im). +[OpenTripPlanner Gitter chat](https://gitter.im/opentripplanner/OpenTripPlanner). - There are twice-weekly developer meetings to discuss and coordinate contributions in more detail. See the [calendar](https://calendar.google.com/calendar/u/0/embed?src=ormbltvsqb6adl80ejgudt0glc@group.calendar.google.com) or [iCal link](https://calendar.google.com/calendar/ical/ormbltvsqb6adl80ejgudt0glc@group.calendar.google.com/public/basic.ics) diff --git a/README.md b/README.md index 245fda78079..742ab99778b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Overview -[![Join the chat at https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im](https://badges.gitter.im/opentripplanner/OpenTripPlanner.svg)](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) +[![Join the chat at https://gitter.im/opentripplanner/OpenTripPLanner](https://badges.gitter.im/opentripplanner/OpenTripPlanner.svg)](https://gitter.im/opentripplanner/OpenTripPlanner) [![Matrix](https://img.shields.io/matrix/opentripplanner%3Amatrix.org?label=Matrix%20chat&?cacheSeconds=172800)](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) [![codecov](https://codecov.io/gh/opentripplanner/OpenTripPlanner/branch/dev-2.x/graph/badge.svg?token=ak4PbIKgZ1)](https://codecov.io/gh/opentripplanner/OpenTripPlanner) [![Commit activity](https://img.shields.io/github/commit-activity/y/opentripplanner/OpenTripPlanner)](https://github.com/opentripplanner/OpenTripPlanner/graphs/contributors) @@ -60,7 +60,7 @@ the [main documentation](http://docs.opentripplanner.org/en/dev-2.x/), including ## Getting in Touch -The fastest way to get help is to use our [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) where most of the core developers +The fastest way to get help is to use our [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is used almost exclusively for project announcements. The mailing list and issue tracker are not intended for support questions or discussions. Please use the chat for this purpose. Other details diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md index 038cb9f2720..f2813776d9d 100644 --- a/doc/user/Developers-Guide.md +++ b/doc/user/Developers-Guide.md @@ -53,7 +53,7 @@ like "program arguments". OpenTripPlanner is a community based open source project, and we welcome all who wish to contribute. There are several ways to get involved: -* Join the [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) and the +* Join the [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) and the [user mailing list](http://groups.google.com/group/opentripplanner-users). * Fix typos and improve the documentation within the `/doc/user` directory of the project (details @@ -61,7 +61,7 @@ There are several ways to get involved: * [File a bug or new feature request](http://github.com/openplans/OpenTripPlanner/issues/new). If you want a GitHub issue to be addressed, you should also discuss it in - [Gitter](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) or in the + [Gitter](https://gitter.im/opentripplanner/OpenTripPlanner) or in the [developer meetings](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CONTRIBUTING.md#developer-meetings). * Create pull requests citing the relevant issue. @@ -93,7 +93,7 @@ implementation discussions (in the comments). The created issue should be refere request. For really minor and uncontroversial pull requests, it is ok to not create an issue. **If you want a GitHub issue to be addressed, you should also discuss it in -[Gitter](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) or in the +[Gitter](https://gitter.im/opentripplanner/OpenTripPlanner) or in the [developer meetings](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/CONTRIBUTING.md#developer-meetings).** ### Unit tests using real OSM data diff --git a/doc/user/Product-Overview.md b/doc/user/Product-Overview.md index ae1c5b8898a..dc0cfcc25a1 100644 --- a/doc/user/Product-Overview.md +++ b/doc/user/Product-Overview.md @@ -69,7 +69,7 @@ be more appropriate for these purposes. ## Talk to an expert about OTP -Everyone interested in OTP is welcome to post questions on the [Gitter chat](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) +Everyone interested in OTP is welcome to post questions on the [Gitter chat](https://gitter.im/opentripplanner/OpenTripPlanner) or [OpenTripPlanner-users group](https://groups.google.com/g/opentripplanner-users). If you’re looking for a conversation with an individual from a similar organizational type, you can diff --git a/doc/user/index.md b/doc/user/index.md index 70ffa44e3bf..f948c0c486a 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -64,7 +64,7 @@ your own OTP instance, the best place to start is the [Basic Tutorial](Basic-Tut # Getting help -The fastest way to get help is to use our [Gitter chat room](https://matrix.to/#/#opentripplanner_OpenTripPlanner:gitter.im) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The issue tracker is not intended for support questions or discussions. Please use the chat for this purpose. The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is treated as a legacy communications channel and used almost exclusively for project announcements. Again, please direct development and support discussions to the Gitter chat. +The fastest way to get help is to use our [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) where most of the core developers are. Bug reports may be filed via the Github [issue tracker](https://github.com/openplans/OpenTripPlanner/issues). The issue tracker is not intended for support questions or discussions. Please use the chat for this purpose. The OpenTripPlanner [mailing list](http://groups.google.com/group/opentripplanner-users) is treated as a legacy communications channel and used almost exclusively for project announcements. Again, please direct development and support discussions to the Gitter chat. # Financial and In-Kind Support From bf9515b3a95080da82e11d1eae765372b9794ea8 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 28 Apr 2026 08:09:22 +0000 Subject: [PATCH 168/177] Add changelog entry for #7539 [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 f2c39150fc5..b814c143caa 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 - Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) - Set default fares to [#7545](https://github.com/opentripplanner/OpenTripPlanner/pull/7545) - Reduce memory consumption when parsing very large NeTEx files [#7563](https://github.com/opentripplanner/OpenTripPlanner/pull/7563) +- Remove relation route name application [#7539](https://github.com/opentripplanner/OpenTripPlanner/pull/7539) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 93a96a45858402a7071829bb74f2d3723bc95545 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 28 Apr 2026 10:25:19 +0200 Subject: [PATCH 169/177] Fix formatting [ci skip] --- .../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 e0a307ff66d..e8853cb7e0d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5104,7 +5104,7 @@ input TransitPreferencesInput { paths have a different set of transit-group-priorities, then the generalized-cost comparison is relaxed. The final set of paths are filtered through the normal itinerary-filters. - + A relax-cost is used to increase the limit when comparing one cost to another cost using a linear function applied to the generalized cost. This is used to include more results into the result. A `coefficient=2.0` means a path (itinerary) From 356a026aa646fadd4b93855d9eec5d86d8d21279 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 28 Apr 2026 11:06:58 +0200 Subject: [PATCH 170/177] Apply suggestions from code review Co-authored-by: Sigurd Totland --- .../raptor/util/paretoset/ParetoComparator.java | 6 +++--- .../raptor/util/paretoset/ParetoDominance.java | 4 ++-- .../opentripplanner/raptor/util/paretoset/ParetoSet.java | 2 +- .../raptor/util/paretoset/ParetoDominanceTest.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index 07b7cf7b5bf..f810eccaaa5 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -8,9 +8,9 @@ /// criterion dominates. /// - **Right dominates left** `≻`: at least one right criterion dominates and no left /// criterion dominates. -/// - **Mutual dominance** `∥`: at least one left criteria dominates right and at -/// least one right criteria dominates left -/// - **No dominance** `≡`: all criteria are equal, or neither side dominates. +/// - **Mutual dominance** `∥`: at least one left criterion dominates right and at +/// least one right criterion dominates left +/// - **No dominance** `≡`: all criteria are equal. /// /// Implementations only need to provide one directional check in /// {@link #leftDominanceExist(Object, Object)}. diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java index a092bb40e5a..ccd63e00324 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java @@ -1,7 +1,7 @@ package org.opentripplanner.raptor.util.paretoset; /// Pareto Dominance can have 4 values when comparing two values `x`, `y`. Remember -/// x and y can be vectors with one or more criterias. +/// x and y can be vectors with one or more criteria. public enum ParetoDominance { /// Left-Dominance (`x` ≺ `y`), when `x` is strictly better than`y`. LEFT('≺'), @@ -12,7 +12,7 @@ public enum ParetoDominance { /// two vectors `v` and `u` this happens when the `v` is better in one criteria, and `u` is /// better in another: `[1, 7, 3]` ≡ `[7, 1, 3]`. BOTH('∥'), - /// No Dominate (Pareto Equivalent) (`x` ≡ `y`). Neither `x` dominates `y` nor `y` dominates + /// No Dominate (Strictly equal) (`x` ≡ `y`). Neither `x` dominates `y` nor `y` dominates /// `x`, AND `x` and `y` are equal in all objective values: `[1, 7, 3]` ≡ `[1, 7, 3]`. NONE('≡'); diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index c5c03df7f0f..3d57ff6acb6 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -111,7 +111,7 @@ public final boolean add(T newValue) { notifyElementRejected(newValue, it); return false; case NONE: - // newValue is equivalent with an existing value + // newValue is strictly equal to an existing value notifyElementRejected(newValue, it); return false; } diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java index c794c6a6034..f7528d95c6d 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java @@ -35,7 +35,7 @@ void testOfLeftRight() { } @Test - void testToSting() { + void testToString() { assertEquals("≺", LEFT.toString()); assertEquals("≻", RIGHT.toString()); assertEquals("≡", NONE.toString()); From 74cc828ea1bfd4f02552df3fe576af3b614208dd Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Tue, 28 Apr 2026 11:24:12 +0200 Subject: [PATCH 171/177] Split transit-enabled check back out of cannotReachTransit Move the journey().transit().enabled() check back to the TransitRouter call site as a separate short-circuit disjunct. The cannotReachTransit predicate now reflects only access/egress reachability, matching its name and javadoc. --- .../raptoradapter/router/TransitRouter.java | 2 +- .../routing/api/request/RouteRequest.java | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index df9e79a226e..454516121c3 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -109,7 +109,7 @@ public static TransitRouterResult route( private TransitRouterResult route() { // Skip the creation of raptor transit data when the request cannot use transit - if (request.cannotReachTransit()) { + if (!request.journey().transit().enabled() || request.cannotReachTransit()) { return new TransitRouterResult(List.of(), null); } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index d2d945b7433..d95113419f0 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -279,16 +279,12 @@ public boolean allowTransferOptimization() { } /** - * Returns {@code true} when running a transit search for this request would be pointless, either - * because the caller has explicitly disabled transit or because the request has no way to reach - * transit on at least one of the access and egress sides. A side is unreachable when its street - * mode is {@link StreetMode#NOT_SET} and the corresponding endpoint is not a stop (a stop - * endpoint provides a zero-distance access/egress, so it does not need a street mode). + * Returns {@code true} when the request has no way to reach transit on at least one of the + * access and egress sides. A side is unreachable when its street mode is + * {@link StreetMode#NOT_SET} and the corresponding endpoint is not a stop (a stop endpoint + * provides a zero-distance access/egress, so it does not need a street mode). */ public boolean cannotReachTransit() { - if (!journey.transit().enabled()) { - return true; - } boolean accessUnreachable = journey.access().mode() == StreetMode.NOT_SET && (from == null || from.stopId == null); boolean egressUnreachable = From 944ef38e995b91d5122f8dd29b313456a1437e6c Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 28 Apr 2026 13:27:57 +0200 Subject: [PATCH 172/177] review: Apply suggestions from code review --- .../util/paretoset/ParetoComparator.java | 17 +++++++---------- .../util/paretoset/ParetoDominance.java | 19 +++++++++---------- .../raptor/util/paretoset/ParetoSet.java | 4 ++-- .../util/paretoset/ParetoComparatorTest.java | 4 ++-- .../util/paretoset/ParetoDominanceTest.java | 14 +++++++------- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index f810eccaaa5..7ca0a865d39 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -2,20 +2,17 @@ /// Compares two elements in a {@link ParetoSet} for Pareto dominance. /// -/// A comparison between a `left` and `right` element can produce four outcomes: +/// A comparison between a `left` and `right` element can produce four mutually exclusive outcomes: /// -/// - **Left dominates right** `≺`: at least one left criterion dominates and no right -/// criterion dominates. -/// - **Right dominates left** `≻`: at least one right criterion dominates and no left -/// criterion dominates. -/// - **Mutual dominance** `∥`: at least one left criterion dominates right and at -/// least one right criterion dominates left -/// - **No dominance** `≡`: all criteria are equal. +/// - {@link ParetoDominance#LEFT} +/// - {@link ParetoDominance#RIGHT} +/// - {@link ParetoDominance#MUTUAL} +/// - {@link ParetoDominance#NONE} /// -/// Implementations only need to provide one directional check in +/// Implementations only need to provide one directional check by implementing /// {@link #leftDominanceExist(Object, Object)}. /// -/// @param the Pareto set element type +/// @param the Pareto set element type. /// @FunctionalInterface public interface ParetoComparator { diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java index ccd63e00324..e8a19d44f9f 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java @@ -1,17 +1,16 @@ package org.opentripplanner.raptor.util.paretoset; -/// Pareto Dominance can have 4 values when comparing two values `x`, `y`. Remember -/// x and y can be vectors with one or more criteria. +/// Pareto Dominance can have 4 values when comparing two values `x`, `y`. The 4 values are +/// mutually exclusive. Remember x and y can be vectors with one or more criteria. public enum ParetoDominance { - /// Left-Dominance (`x` ≺ `y`), when `x` is strictly better than`y`. + /// Left-Dominance (`x` ≺ `y`), when `x` is strictly better than`y`. `[1, 1, 3]` ≺ `[1, 7, 3]`. LEFT('≺'), - /// Right-Dominance (`x` ≻ `y`), when `y` is strictly better than`x`. + /// Right-Dominance (`x` ≻ `y`), when `y` is strictly better than`x`. `[1, 7, 3]` ≻ `[1, 1, 3]`. RIGHT('≻'), - /// Both Dominate Each Other (Incomparable/Indifferent) (`x` ∥ `y`). Neither solution is - /// superior to the other. Both solutions are part of the Pareto optimal set (Pareto front). For - /// two vectors `v` and `u` this happens when the `v` is better in one criteria, and `u` is - /// better in another: `[1, 7, 3]` ≡ `[7, 1, 3]`. - BOTH('∥'), + /// Mutual-Dominance (Incomparable/Indifferent) (`x` ∥ `y`). Neither solution is superior to the + /// other. Both solutions are part of the Pareto optimal set (Pareto front). This happens when + /// the `x` is better in one criteria, and `y` is better in another: `[1, 7, 3]` ∥ `[7, 1, 3]`. + MUTUAL('∥'), /// No Dominate (Strictly equal) (`x` ≡ `y`). Neither `x` dominates `y` nor `y` dominates /// `x`, AND `x` and `y` are equal in all objective values: `[1, 7, 3]` ≡ `[1, 7, 3]`. NONE('≡'); @@ -27,7 +26,7 @@ public enum ParetoDominance { */ public static ParetoDominance of(boolean leftDominanceExist, boolean rightDominanceExist) { if (leftDominanceExist) { - return rightDominanceExist ? BOTH : LEFT; + return rightDominanceExist ? MUTUAL : LEFT; } else { return rightDominanceExist ? RIGHT : NONE; } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index 3d57ff6acb6..1d2f0fbc239 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -101,7 +101,7 @@ public final boolean add(T newValue) { T it = elements[i]; switch (comparator.compare(newValue, it)) { - case BOTH: + case MUTUAL: continue loop; case LEFT: removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); @@ -154,7 +154,7 @@ public final boolean qualify(T newValue) { var it = elements[i]; switch (comparator.compare(newValue, it)) { - case BOTH: + case MUTUAL: continue loop; case LEFT: return true; diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java index 5da3d900f42..cc019cf8934 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.BOTH; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.MUTUAL; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; @@ -36,6 +36,6 @@ public void compare() { assertEquals(NONE, COMPARATOR.compare(A_5_5, A_5_5)); assertEquals(LEFT, COMPARATOR.compare(C_3_5, B_5_5)); assertEquals(RIGHT, COMPARATOR.compare(A_5_5, C_3_5)); - assertEquals(BOTH, COMPARATOR.compare(C_3_5, D_5_3)); + assertEquals(MUTUAL, COMPARATOR.compare(C_3_5, D_5_3)); } } diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java index f7528d95c6d..fde3b212896 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.raptor.util.paretoset; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.BOTH; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.MUTUAL; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; @@ -12,25 +12,25 @@ class ParetoDominanceTest { @Test void testOf() { - assertEquals(BOTH, ParetoDominance.of("both")); + assertEquals(MUTUAL, ParetoDominance.of("mutual")); assertEquals(LEFT, ParetoDominance.of("left")); assertEquals(RIGHT, ParetoDominance.of("right")); assertEquals(NONE, ParetoDominance.of("none")); assertEquals(LEFT, ParetoDominance.of("≺")); assertEquals(RIGHT, ParetoDominance.of("≻")); assertEquals(NONE, ParetoDominance.of("≡")); - assertEquals(BOTH, ParetoDominance.of("∥")); + assertEquals(MUTUAL, ParetoDominance.of("∥")); // Mixed case - assertEquals(BOTH, ParetoDominance.of("BOTH")); - assertEquals(BOTH, ParetoDominance.of("bOtH")); + assertEquals(LEFT, ParetoDominance.of("LEFT")); + assertEquals(LEFT, ParetoDominance.of("lEfT")); } @Test void testOfLeftRight() { assertEquals(LEFT, ParetoDominance.of(true, false)); assertEquals(RIGHT, ParetoDominance.of(false, true)); - assertEquals(BOTH, ParetoDominance.of(true, true)); + assertEquals(MUTUAL, ParetoDominance.of(true, true)); assertEquals(NONE, ParetoDominance.of(false, false)); } @@ -39,6 +39,6 @@ void testToString() { assertEquals("≺", LEFT.toString()); assertEquals("≻", RIGHT.toString()); assertEquals("≡", NONE.toString()); - assertEquals("∥", BOTH.toString()); + assertEquals("∥", MUTUAL.toString()); } } From caa7ab7bb8e8fe455316156dc944c87bea90b45d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 28 Apr 2026 13:53:30 +0200 Subject: [PATCH 173/177] doc: Update JavaDoc for ParetoDominance to clarify parameters --- .../raptor/util/paretoset/ParetoComparator.java | 4 ++-- .../raptor/util/paretoset/ParetoDominance.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index 7ca0a865d39..181b8507f03 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -17,8 +17,8 @@ @FunctionalInterface public interface ParetoComparator { /** - * Returns {@code true} if at least one criterion in {@code left} dominates the corresponding - * criterion in {@code right}. + * Returns {@code true} if at least one criterion in {@code left} is better then the + * corresponding criterion in {@code right}. */ boolean leftDominanceExist(T left, T right); diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java index e8a19d44f9f..7ea20cdf52e 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java @@ -21,9 +21,9 @@ public enum ParetoDominance { this.symbol = symbol; } - /** - * Create a dominance value from two directional dominance flags. - */ + /// Create a dominance value from two directional dominance flags. For `x`(left) and `y`(right): + /// @param leftDominanceExist `x` has at least one criteria that is better than `y`. + /// @param rightDominanceExist `y` has at least one criteria that is better than `x`. public static ParetoDominance of(boolean leftDominanceExist, boolean rightDominanceExist) { if (leftDominanceExist) { return rightDominanceExist ? MUTUAL : LEFT; From 6e26c46387630bbf0813975d00767c2b8d8ca59c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 29 Apr 2026 13:19:11 +0000 Subject: [PATCH 174/177] Add changelog entry for #7547 [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 b814c143caa..6710600ba84 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 - Set default fares to [#7545](https://github.com/opentripplanner/OpenTripPlanner/pull/7545) - Reduce memory consumption when parsing very large NeTEx files [#7563](https://github.com/opentripplanner/OpenTripPlanner/pull/7563) - Remove relation route name application [#7539](https://github.com/opentripplanner/OpenTripPlanner/pull/7539) +- Skip transit search when no access or egress mode is set [#7547](https://github.com/opentripplanner/OpenTripPlanner/pull/7547) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) From 69adae6da4afc40d8585b30ebd3fef62f095dda9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 29 Apr 2026 17:16:21 +0300 Subject: [PATCH 175/177] Fix spelling of some config parameters in changelog Corrected the spelling of 'Entrances' in the changelog. --- doc/user/Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 6710600ba84..1f17ce66ca2 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -27,7 +27,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) - De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) - Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) -- Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrance` to `includeOsmStationEntrance` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) +- Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrances` to `includeOsmStationEntrances` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) - Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) - Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) - Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) From 3a32a37f317a02636b8764c773a15bb35c16093f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 29 Apr 2026 20:53:16 +0200 Subject: [PATCH 176/177] Disable Atlanta smoke tests --- .github/workflows/smoke-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index f0932f007ab..fff11b02076 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -19,8 +19,8 @@ jobs: locations: - name: seattle sleep: 30 - - name: atlanta - sleep: 15 + #- name: atlanta + # sleep: 15 - name: houston sleep: 30 - name: denver From 63c3497f61f2294f95bb51b7c41b500b354f1f26 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 29 Apr 2026 21:08:36 +0200 Subject: [PATCH 177/177] Configure fares in Denver and Portland --- smoke-tests/denver/build-config.json | 3 ++- smoke-tests/portland/build-config.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/smoke-tests/denver/build-config.json b/smoke-tests/denver/build-config.json index f1ccbb9197b..2f2be00c7be 100644 --- a/smoke-tests/denver/build-config.json +++ b/smoke-tests/denver/build-config.json @@ -5,5 +5,6 @@ "feedId" : "denver", "source" : "https://www.rtd-denver.com/files/gtfs/google_transit.zip" } - ] + ], + "fares": "gtfs" } diff --git a/smoke-tests/portland/build-config.json b/smoke-tests/portland/build-config.json index 0ae51393c88..32de27dd8b6 100644 --- a/smoke-tests/portland/build-config.json +++ b/smoke-tests/portland/build-config.json @@ -8,5 +8,6 @@ "feedId": "TriMet", "source": "https://developer.trimet.org/schedule/gtfs.zip" } - ] + ], + "fares": "gtfs" } \ No newline at end of file