-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow multiple states during transfer edge traversals #6238
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,10 @@ | |
|
||
import java.util.Collection; | ||
import java.util.Optional; | ||
import org.opentripplanner.astar.model.ShortestPathTree; | ||
import org.opentripplanner.street.model.edge.Edge; | ||
import org.opentripplanner.street.model.vertex.Vertex; | ||
import org.opentripplanner.street.search.strategy.DominanceFunctions; | ||
|
||
/** | ||
* This is a very reduced version of the A* algorithm: from an initial state a number of edges are | ||
|
@@ -14,24 +17,49 @@ | |
*/ | ||
public class EdgeTraverser { | ||
|
||
public static Optional<State> traverseEdges(final State s, final Collection<Edge> edges) { | ||
var state = s; | ||
public static Optional<State> traverseEdges( | ||
final Collection<State> initialStates, | ||
final Collection<Edge> edges | ||
) { | ||
return traverseEdges(initialStates.toArray(new State[0]), edges); | ||
} | ||
|
||
public static Optional<State> traverseEdges( | ||
final State[] initialStates, | ||
final Collection<Edge> edges | ||
) { | ||
if (edges.isEmpty()) { | ||
return Optional.of(initialStates[0]); | ||
} | ||
|
||
// The shortest path tree is used to prune dominated parallel states. For example, | ||
// CAR_PICKUP can return both a CAR/WALK state after each traversal of which only | ||
// the optimal states need to be continued. | ||
var dominanceFunction = new DominanceFunctions.MinimumWeight(); | ||
var spt = new ShortestPathTree<>(dominanceFunction); | ||
for (State initialState : initialStates) { | ||
spt.add(initialState); | ||
} | ||
|
||
Vertex lastVertex = null; | ||
var isArriveBy = initialStates[0].getRequest().arriveBy(); | ||
for (Edge e : edges) { | ||
var afterTraversal = e.traverse(state); | ||
if (afterTraversal.length > 1) { | ||
throw new IllegalStateException( | ||
"Expected only a single state returned from edge %s but received %s".formatted( | ||
e, | ||
afterTraversal.length | ||
) | ||
); | ||
} | ||
if (State.isEmpty(afterTraversal)) { | ||
var vertex = isArriveBy ? e.getToVertex() : e.getFromVertex(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the reason we need to care about arriveBy is that we now use the SPT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is due to the SPT which stores states by the |
||
var fromStates = spt.getStates(vertex); | ||
if (fromStates == null || fromStates.isEmpty()) { | ||
return Optional.empty(); | ||
} else { | ||
state = afterTraversal[0]; | ||
} | ||
|
||
for (State fromState : fromStates) { | ||
var newToStates = e.traverse(fromState); | ||
for (State newToState : newToStates) { | ||
spt.add(newToState); | ||
} | ||
} | ||
|
||
lastVertex = isArriveBy ? e.getFromVertex() : e.getToVertex(); | ||
} | ||
return Optional.ofNullable(state); | ||
|
||
return Optional.ofNullable(lastVertex).map(spt::getState); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||||||||
package org.opentripplanner.routing.algorithm.mapping; | ||||||||||||
|
||||||||||||
import au.com.origin.snapshots.junit5.SnapshotExtension; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw how is this dependency imported into our project? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's an existing dependency: Lines 466 to 470 in 5b5d92f
|
||||||||||||
import java.util.List; | ||||||||||||
import org.junit.jupiter.api.BeforeAll; | ||||||||||||
import org.junit.jupiter.api.Test; | ||||||||||||
import org.junit.jupiter.api.extension.ExtendWith; | ||||||||||||
import org.opentripplanner.model.GenericLocation; | ||||||||||||
import org.opentripplanner.model.modes.ExcludeAllTransitFilter; | ||||||||||||
import org.opentripplanner.routing.api.request.RouteRequest; | ||||||||||||
import org.opentripplanner.routing.api.request.StreetMode; | ||||||||||||
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter; | ||||||||||||
|
||||||||||||
@ExtendWith(SnapshotExtension.class) | ||||||||||||
public class CarPickupSnapshotTest extends SnapshotTestBase { | ||||||||||||
|
||||||||||||
static GenericLocation p0 = new GenericLocation( | ||||||||||||
"SE Stark St. & SE 17th Ave. (P0)", | ||||||||||||
null, | ||||||||||||
45.519320, | ||||||||||||
-122.648567 | ||||||||||||
); | ||||||||||||
|
||||||||||||
static GenericLocation p1 = new GenericLocation( | ||||||||||||
"SE Morrison St. & SE 17th Ave. (P1)", | ||||||||||||
null, | ||||||||||||
45.51726, | ||||||||||||
-122.64847 | ||||||||||||
); | ||||||||||||
|
||||||||||||
static GenericLocation p2 = new GenericLocation( | ||||||||||||
"NW Northrup St. & NW 22nd Ave. (P2)", | ||||||||||||
null, | ||||||||||||
45.53122, | ||||||||||||
-122.69659 | ||||||||||||
); | ||||||||||||
|
||||||||||||
@BeforeAll | ||||||||||||
public static void beforeClass() { | ||||||||||||
loadGraphBeforeClass(false); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@Test | ||||||||||||
public void test_trip_planning_with_car_pickup_only() { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually don't use the snake case in tests. I know some people like to use them since they might be slightly more readable. Is there some other reason to use them here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing snapshots tests follow the same format, which I built on: Lines 66 to 67 in 5b5d92f
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed this in the dev meeting today. Apparently we had made some decision a couple of years ago that snake case is not allowed in tests. But since it's used in snapshot tests, we don't think you should change it in the scope of this pr but if you could create a follow up pr where you rename all the snake case snapshot tests, that would be appreciated. |
||||||||||||
RouteRequest request = createTestRequest(2009, 11, 17, 10, 0, 0); | ||||||||||||
|
||||||||||||
request.journey().direct().setMode(StreetMode.CAR_PICKUP); | ||||||||||||
request.journey().transit().setFilters(List.of(ExcludeAllTransitFilter.of())); | ||||||||||||
|
||||||||||||
request.setFrom(p0); | ||||||||||||
request.setTo(p2); | ||||||||||||
|
||||||||||||
expectRequestResponseToMatchSnapshot(request); | ||||||||||||
} | ||||||||||||
|
||||||||||||
@Test | ||||||||||||
public void test_trip_planning_with_car_pickup_transfer() { | ||||||||||||
RouteRequest request = createTestRequest(2009, 11, 17, 10, 0, 0); | ||||||||||||
|
||||||||||||
request.journey().access().setMode(StreetMode.WALK); | ||||||||||||
request.journey().egress().setMode(StreetMode.WALK); | ||||||||||||
request.journey().direct().setMode(StreetMode.WALK); | ||||||||||||
request.journey().transfer().setMode(StreetMode.CAR_PICKUP); | ||||||||||||
request.journey().transit().setFilters(List.of(AllowAllTransitFilter.of())); | ||||||||||||
|
||||||||||||
request.setFrom(p0); | ||||||||||||
request.setTo(p2); | ||||||||||||
|
||||||||||||
expectRequestResponseToMatchSnapshot(request); | ||||||||||||
} | ||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one didn't use the EdgeTraverser before. What is the reason for using it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple states may also occur here, which need to be handled in a similar manner:
As an example, the
CAR_PICKUP
transfers resulted in an NPE, since the initialCAR/IN_CAR
state couldn't traverse theStreetTransitLink
entities.By using
EdgeTraverser
traversal results in the same state as when creating the actual transfer.