Skip to content

Commit 1e0526a

Browse files
committed
Add support for direct routing with coordinate via locations
1 parent 03defce commit 1e0526a

File tree

6 files changed

+349
-63
lines changed

6 files changed

+349
-63
lines changed

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;
@@ -222,14 +222,6 @@ private Duration searchWindowUsed() {
222222
}
223223

224224
private RoutingResult routeDirectStreet() {
225-
// TODO: Add support for via search to the direct-street search and remove this.
226-
// The direct search is used to prune away silly transit results and it
227-
// would be nice to also support via as a feature in the direct-street
228-
// search.
229-
if (request.isViaSearch()) {
230-
return RoutingResult.empty();
231-
}
232-
233225
// If no direct mode is set, then we set one.
234226
// See {@link FilterTransitWhenDirectModeIsEmpty}
235227
var emptyDirectModeHandler = new FilterTransitWhenDirectModeIsEmpty(
@@ -244,7 +236,7 @@ private RoutingResult routeDirectStreet() {
244236

245237
debugTimingAggregator.startedDirectStreetRouter();
246238
try {
247-
var directRouter = new DefaultDirectStreetRouter();
239+
var directRouter = DirectStreetRouterFactory.create(request);
248240
return RoutingResult.ok(
249241
directRouter
250242
.route(serverContext, directBuilder.buildRequest(), linkingContext())

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

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ public static boolean isFloatingRentalDropoff(State state) {
9898
}
9999

100100
/**
101-
* Generates a TripPlan from a set of paths
101+
* Generates a TripPlan from a set of paths. Each path generates a leg in the itinerary.
102102
*/
103+
103104
public Optional<Itinerary> mapToItinerary(
104-
GraphPath<State, Edge, Vertex> path,
105+
List<GraphPath<State, Edge, Vertex>> paths,
105106
RouteRequest request
106107
) {
107-
Itinerary itinerary = generateItinerary(path, request);
108+
Itinerary itinerary = generateItinerary(paths, request);
108109
if (itinerary.legs().isEmpty()) {
109110
return Optional.empty();
110111
}
@@ -113,43 +114,74 @@ public Optional<Itinerary> mapToItinerary(
113114
}
114115

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

142-
State lastState = path.states.getLast();
143-
var cost = Cost.costOfSeconds(lastState.weight);
159+
var cost = Cost.costOfSeconds(
160+
paths.stream().map(GraphPath::getWeight).reduce(0.0, Double::sum)
161+
);
144162
var builder = Itinerary.ofDirect(legs).withGeneralizedCost(cost);
145163

146-
builder.withArrivedAtDestinationWithRentedVehicle(lastState.isRentingVehicleFromStation());
164+
builder.withArrivedAtDestinationWithRentedVehicle(
165+
paths.getLast().states.getLast().isRentingVehicleFromStation()
166+
);
147167

148-
calculateElevations(builder, path.edges);
168+
var allEdges = paths.stream().flatMap(p -> p.edges.stream()).toList();
169+
calculateElevations(builder, allEdges);
149170

150171
return builder.build();
151172
}
152173

174+
/**
175+
* Generate an itinerary from a {@link GraphPath}. This method first slices the list of states at
176+
* the leg boundaries. These smaller state arrays are then used to generate legs.
177+
*
178+
* @param path The graph path to base the itinerary on
179+
* @return The generated itinerary
180+
*/
181+
public Itinerary generateItinerary(GraphPath<State, Edge, Vertex> path, RouteRequest request) {
182+
return generateItinerary(List.of(path), request);
183+
}
184+
153185
/**
154186
* Slice a {@link State} list at the leg boundaries.
155187
*

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,18 +119,21 @@ 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()),
120132
serverContext.transitService().getTimeZone(),
121133
serverContext.graph().streetNotesService,
122134
serverContext.graph().ellipsoidToGeoidDifference
123135
);
124-
var response = graphPathToItineraryMapper.mapToItinerary(path, request);
136+
var response = graphPathToItineraryMapper.mapToItinerary(paths, request);
125137
return response.map(itinerary ->
126138
ItinerariesHelper.decorateItineraryWithRequestData(
127139
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)