Skip to content

Commit 0cfb588

Browse files
committed
Add support for direct routing with coordinate via locations
1 parent 7e12ad7 commit 0cfb588

6 files changed

Lines changed: 352 additions & 63 deletions

File tree

application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays;
2424
import org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty;
2525
import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter;
26-
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DefaultDirectStreetRouter;
2726
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectFlexRouter;
27+
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.DirectStreetRouterFactory;
2828
import org.opentripplanner.routing.api.request.RouteRequest;
2929
import org.opentripplanner.routing.api.request.StreetMode;
3030
import org.opentripplanner.routing.api.request.request.StreetRequest;
@@ -226,14 +226,6 @@ private Duration searchWindowUsed() {
226226
}
227227

228228
private RoutingResult routeDirectStreet() {
229-
// TODO: Add support for via search to the direct-street search and remove this.
230-
// The direct search is used to prune away silly transit results and it
231-
// would be nice to also support via as a feature in the direct-street
232-
// search.
233-
if (request.isViaSearch()) {
234-
return RoutingResult.empty();
235-
}
236-
237229
// If no direct mode is set, then we set one.
238230
// See {@link FilterTransitWhenDirectModeIsEmpty}
239231
var emptyDirectModeHandler = new FilterTransitWhenDirectModeIsEmpty(
@@ -253,7 +245,7 @@ private RoutingResult routeDirectStreet() {
253245

254246
debugTimingAggregator.startedDirectStreetRouter();
255247
try {
256-
var directRouter = new DefaultDirectStreetRouter();
248+
var directRouter = DirectStreetRouterFactory.create(request);
257249
return RoutingResult.ok(
258250
directRouter
259251
.route(serverContext, directBuilder.buildRequest(), linkingContext())

application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,14 @@ public static boolean isFloatingRentalDropoff(State state) {
102102
}
103103

104104
/**
105-
* Generates a TripPlan from a set of paths
105+
* Generates a TripPlan from a set of paths. Each path generates a leg in the itinerary.
106106
*/
107+
107108
public Optional<Itinerary> mapToItinerary(
108-
GraphPath<State, Edge, Vertex> path,
109+
List<GraphPath<State, Edge, Vertex>> paths,
109110
RouteRequest request
110111
) {
111-
Itinerary itinerary = generateItinerary(path, request);
112+
Itinerary itinerary = generateItinerary(paths, request);
112113
if (itinerary.legs().isEmpty()) {
113114
return Optional.empty();
114115
}
@@ -117,43 +118,77 @@ public Optional<Itinerary> mapToItinerary(
117118
}
118119

119120
/**
120-
* Generate an itinerary from a {@link GraphPath}. This method first slices the list of states at
121-
* the leg boundaries. These smaller state arrays are then used to generate legs.
121+
* Generates a TripPlan from a path.
122+
*/
123+
public Optional<Itinerary> mapToItinerary(
124+
GraphPath<State, Edge, Vertex> path,
125+
RouteRequest request
126+
) {
127+
return mapToItinerary(List.of(path), request);
128+
}
129+
130+
/**
131+
* Generate an itinerary from a list {@link GraphPath}s. Each path generates one more or more
132+
* legs. This method first slices the list of states at the leg boundaries. These smaller state
133+
* arrays are then used to generate legs.
122134
*
123-
* @param path The graph path to base the itinerary on
135+
* @param paths The graph paths to base the itinerary on
124136
* @return The generated itinerary
125137
*/
126-
public Itinerary generateItinerary(GraphPath<State, Edge, Vertex> path, RouteRequest request) {
138+
public Itinerary generateItinerary(
139+
List<GraphPath<State, Edge, Vertex>> paths,
140+
RouteRequest request
141+
) {
127142
List<Leg> legs = new ArrayList<>();
128-
WalkStep previousStep = null;
129-
for (List<State> legStates : sliceStates(path.states)) {
130-
if (OTPFeature.FlexRouting.isOn() && legStates.get(1).backEdge instanceof FlexTripEdge) {
131-
legs.add(generateFlexLeg(legStates));
132-
previousStep = null;
133-
continue;
134-
}
135-
StreetLeg leg = generateLeg(legStates, previousStep, request);
136-
legs.add(leg);
137-
138-
List<WalkStep> walkSteps = leg.listWalkSteps();
139-
if (walkSteps.size() > 0) {
140-
previousStep = walkSteps.get(walkSteps.size() - 1);
141-
} else {
142-
previousStep = null;
143+
for (GraphPath<State, Edge, Vertex> path : paths) {
144+
WalkStep previousStep = null;
145+
for (List<State> legStates : sliceStates(path.states)) {
146+
if (OTPFeature.FlexRouting.isOn() && legStates.get(1).backEdge instanceof FlexTripEdge) {
147+
legs.add(generateFlexLeg(legStates));
148+
previousStep = null;
149+
continue;
150+
}
151+
StreetLeg leg = generateLeg(legStates, previousStep, request);
152+
legs.add(leg);
153+
154+
List<WalkStep> walkSteps = leg.listWalkSteps();
155+
if (walkSteps.size() > 0) {
156+
previousStep = walkSteps.get(walkSteps.size() - 1);
157+
} else {
158+
previousStep = null;
159+
}
143160
}
144161
}
145162

146-
State lastState = path.states.getLast();
147-
var cost = Cost.costOfSeconds(lastState.weight);
163+
var cost = Cost.costOfSeconds(
164+
paths.stream().map(GraphPath::getWeight).reduce(0.0, Double::sum)
165+
);
148166
var builder = Itinerary.ofDirect(legs).withGeneralizedCost(cost);
149167

150-
builder.withArrivedAtDestinationWithRentedVehicle(lastState.isRentingVehicleFromStation());
168+
builder.withArrivedAtDestinationWithRentedVehicle(
169+
paths.getLast().states.getLast().isRentingVehicleFromStation()
170+
);
151171

152-
calculateElevations(builder, path.edges);
172+
var allEdges = paths
173+
.stream()
174+
.flatMap(p -> p.edges.stream())
175+
.toList();
176+
calculateElevations(builder, allEdges);
153177

154178
return builder.build();
155179
}
156180

181+
/**
182+
* Generate an itinerary from a {@link GraphPath}. This method first slices the list of states at
183+
* the leg boundaries. These smaller state arrays are then used to generate legs.
184+
*
185+
* @param path The graph path to base the itinerary on
186+
* @return The generated itinerary
187+
*/
188+
public Itinerary generateItinerary(GraphPath<State, Edge, Vertex> path, RouteRequest request) {
189+
return generateItinerary(List.of(path), request);
190+
}
191+
157192
/**
158193
* Slice a {@link State} list at the leg boundaries.
159194
*

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DefaultDirectStreetRouter.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package org.opentripplanner.routing.algorithm.raptoradapter.router.street;
22

3+
import java.util.List;
34
import org.opentripplanner.astar.model.GraphPath;
45
import org.opentripplanner.framework.geometry.SphericalDistanceLibrary;
56
import org.opentripplanner.routing.api.request.RouteRequest;
67
import org.opentripplanner.routing.api.request.StreetMode;
78
import org.opentripplanner.routing.impl.GraphPathFinder;
89
import org.opentripplanner.routing.linking.LinkingContext;
9-
import org.opentripplanner.standalone.api.OtpServerRequestContext;
1010
import org.opentripplanner.street.model.edge.Edge;
1111
import org.opentripplanner.street.model.vertex.Vertex;
1212
import org.opentripplanner.street.search.state.State;
@@ -17,26 +17,19 @@
1717
*/
1818
public class DefaultDirectStreetRouter extends DirectStreetRouter {
1919

20-
GraphPath<State, Edge, Vertex> findPath(
21-
OtpServerRequestContext serverContext,
20+
List<GraphPath<State, Edge, Vertex>> findPaths(
21+
GraphPathFinder graphPathFinder,
2222
LinkingContext linkingContext,
23-
RouteRequest request,
24-
float maxCarSpeed
23+
RouteRequest request
2524
) {
26-
// we could also get a persistent router-scoped GraphPathFinder but there's no setup cost here
27-
GraphPathFinder gpFinder = new GraphPathFinder(
28-
serverContext.traverseVisitor(),
29-
serverContext.listExtensionRequestContexts(request),
30-
maxCarSpeed
31-
);
32-
return gpFinder.graphPathFinderEntryPoint(request, linkingContext);
25+
return List.of(graphPathFinder.graphPathFinderEntryPoint(request, linkingContext));
3326
}
3427

35-
boolean isRequestValidForRouting(RouteRequest request) {
28+
boolean isRequestInvalidForRouting(RouteRequest request) {
3629
return request.journey().direct().mode() == StreetMode.NOT_SET;
3730
}
3831

39-
boolean isStraightLineDistanceIsWithinLimit(
32+
boolean isStraightLineDistanceWithinLimit(
4033
LinkingContext linkingContext,
4134
RouteRequest request,
4235
double maxDistanceLimit

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.opentripplanner.routing.algorithm.raptoradapter.router.street;
22

3+
import java.util.List;
34
import java.util.Optional;
45
import org.locationtech.jts.geom.Coordinate;
56
import org.opentripplanner.astar.model.GraphPath;
@@ -12,6 +13,7 @@
1213
import org.opentripplanner.routing.api.request.StreetMode;
1314
import org.opentripplanner.routing.error.PathNotFoundException;
1415
import org.opentripplanner.routing.graphfinder.TransitServiceResolver;
16+
import org.opentripplanner.routing.impl.GraphPathFinder;
1517
import org.opentripplanner.routing.linking.LinkingContext;
1618
import org.opentripplanner.standalone.api.OtpServerRequestContext;
1719
import org.opentripplanner.street.model.edge.Edge;
@@ -33,20 +35,26 @@ public Optional<Itinerary> route(
3335
RouteRequest request,
3436
LinkingContext linkingContext
3537
) {
36-
if (isRequestValidForRouting(request)) {
38+
if (isRequestInvalidForRouting(request)) {
3739
return Optional.empty();
3840
}
3941
OTPRequestTimeoutException.checkForTimeout();
4042

4143
var maxCarSpeed = serverContext.streetLimitationParametersService().maxCarSpeed();
4244
var maxDistanceLimit = calculateDistanceMaxLimit(request, maxCarSpeed);
43-
if (!isStraightLineDistanceIsWithinLimit(linkingContext, request, maxDistanceLimit)) {
45+
if (!isStraightLineDistanceWithinLimit(linkingContext, request, maxDistanceLimit)) {
4446
return Optional.empty();
4547
}
4648

4749
try {
48-
var path = findPath(serverContext, linkingContext, request, maxCarSpeed);
49-
return mapToItinerary(serverContext, request, path);
50+
// we could also get a persistent router-scoped GraphPathFinder but there's no setup cost here
51+
GraphPathFinder gpFinder = new GraphPathFinder(
52+
serverContext.traverseVisitor(),
53+
serverContext.listExtensionRequestContexts(request),
54+
maxCarSpeed
55+
);
56+
var paths = findPaths(gpFinder, linkingContext, request);
57+
return mapToItinerary(serverContext, request, paths);
5058
} catch (PathNotFoundException e) {
5159
return Optional.empty();
5260
}
@@ -55,26 +63,27 @@ public Optional<Itinerary> route(
5563
/**
5664
* Checks that the route request is configured to allow direct street results.
5765
*/
58-
abstract boolean isRequestValidForRouting(RouteRequest request);
66+
abstract boolean isRequestInvalidForRouting(RouteRequest request);
5967

6068
/**
6169
* Checks that as the crow flies distance between locations in the search are within the maximum
6270
* distance limit.
6371
*/
64-
abstract boolean isStraightLineDistanceIsWithinLimit(
72+
abstract boolean isStraightLineDistanceWithinLimit(
6573
LinkingContext linkingContext,
6674
RouteRequest request,
6775
double maxDistanceLimit
6876
);
6977

7078
/**
71-
* Find a graph path between the locations in the request.
79+
* Find an ordered set of graph paths between the locations in the request starting from the
80+
* origin and ending in the destination. If there are no via locations, there is exactly one path.
81+
* With via locations, there is one path between each location.
7282
*/
73-
abstract GraphPath<State, Edge, Vertex> findPath(
74-
OtpServerRequestContext serverContext,
83+
abstract List<GraphPath<State, Edge, Vertex>> findPaths(
84+
GraphPathFinder graphPathFinder,
7585
LinkingContext linkingContext,
76-
RouteRequest request,
77-
float maxCarSpeed
86+
RouteRequest request
7887
);
7988

8089
static Coordinate getFirstCoordinateForLocation(
@@ -110,10 +119,13 @@ private static double calculateDistanceMaxLimit(RouteRequest request, float maxC
110119
throw new IllegalStateException("Could not set max limit for StreetMode");
111120
}
112121

122+
/**
123+
* Creates an itinerary where one graph path generates one or more legs.
124+
*/
113125
private static Optional<Itinerary> mapToItinerary(
114126
OtpServerRequestContext serverContext,
115127
RouteRequest request,
116-
GraphPath<State, Edge, Vertex> path
128+
List<GraphPath<State, Edge, Vertex>> paths
117129
) {
118130
final GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper(
119131
new TransitServiceResolver(serverContext.transitService()),
@@ -122,7 +134,7 @@ private static Optional<Itinerary> mapToItinerary(
122134
serverContext.streetDetailsService(),
123135
serverContext.graph().ellipsoidToGeoidDifference
124136
);
125-
var response = graphPathToItineraryMapper.mapToItinerary(path, request);
137+
var response = graphPathToItineraryMapper.mapToItinerary(paths, request);
126138
return response.map(itinerary ->
127139
ItinerariesHelper.decorateItineraryWithRequestData(
128140
itinerary,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.opentripplanner.routing.algorithm.raptoradapter.router.street;
2+
3+
import org.opentripplanner.routing.api.request.RouteRequest;
4+
5+
/**
6+
* This factory encapsulates the logic for deciding which direct street router to use.
7+
*/
8+
public class DirectStreetRouterFactory {
9+
10+
/**
11+
* @return {@link DefaultDirectStreetRouter} if there are no via locations, otherwise
12+
* {@link ViaDirectStreetRouter}.
13+
*/
14+
public static DirectStreetRouter create(RouteRequest request) {
15+
return request.isViaSearch() ? new ViaDirectStreetRouter() : new DefaultDirectStreetRouter();
16+
}
17+
}

0 commit comments

Comments
 (0)