Skip to content

Commit df3bdae

Browse files
committed
add u-turn test demonstrating issue opentripplanner#5955
1 parent de56d00 commit df3bdae

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package org.opentripplanner.street.model;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
import static org.opentripplanner.street.model._data.StreetModelForTest.intersectionVertex;
7+
8+
import java.util.List;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
import org.locationtech.jts.geom.Coordinate;
12+
import org.locationtech.jts.geom.LineString;
13+
import org.opentripplanner.astar.model.GraphPath;
14+
import org.opentripplanner.astar.model.ShortestPathTree;
15+
import org.opentripplanner.framework.geometry.GeometryUtils;
16+
import org.opentripplanner.model.GenericLocation;
17+
import org.opentripplanner.routing.api.request.RouteRequest;
18+
import org.opentripplanner.routing.api.request.StreetMode;
19+
import org.opentripplanner.routing.api.request.request.StreetRequest;
20+
import org.opentripplanner.routing.graph.Graph;
21+
import org.opentripplanner.routing.linking.LinkingDirection;
22+
import org.opentripplanner.street.model.edge.Edge;
23+
import org.opentripplanner.street.model.edge.StreetEdge;
24+
import org.opentripplanner.street.model.edge.StreetEdgeBuilder;
25+
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
26+
import org.opentripplanner.street.model.vertex.StreetVertex;
27+
import org.opentripplanner.street.model.vertex.TransitStopVertex;
28+
import org.opentripplanner.street.model.vertex.Vertex;
29+
import org.opentripplanner.street.search.StreetSearchBuilder;
30+
import org.opentripplanner.street.search.TraverseMode;
31+
import org.opentripplanner.street.search.TraverseModeSet;
32+
import org.opentripplanner.street.search.state.State;
33+
import org.opentripplanner.street.search.strategy.EuclideanRemainingWeightHeuristic;
34+
import org.opentripplanner.transit.model._data.TransitModelForTest;
35+
36+
public class UTurnTest {
37+
38+
private Graph graph;
39+
private Vertex topRight;
40+
41+
private Vertex topLeft;
42+
43+
private StreetEdge maple_main1, main_broad1;
44+
45+
/*
46+
This test constructs a simplified graph to test u turn avoidance.
47+
Note: the coordinates are smaller than for other tests, as their distance is
48+
important, especially for isCloseToStartOrEnd checks of the dominance function.
49+
50+
b1 <--100-- ma1 <--100-- mp1
51+
^ ^ I
52+
100 100 100
53+
I v v
54+
b2 <--300-- ma2 <--800-- mp2
55+
56+
*/
57+
@BeforeEach
58+
public void before() {
59+
graph = new Graph();
60+
// Graph for a fictional grid city with turn restrictions
61+
StreetVertex maple1 = intersectionVertex("maple_1st", 0.002, 0.002);
62+
graph.addVertex(maple1);
63+
StreetVertex maple2 = intersectionVertex("maple_2nd", 0.001, 0.002);
64+
graph.addVertex(maple2);
65+
66+
StreetVertex main1 = intersectionVertex("main_1st", 0.002, 0.001);
67+
graph.addVertex(main1);
68+
StreetVertex main2 = intersectionVertex("main_2nd", 0.001, 0.001);
69+
graph.addVertex(main2);
70+
StreetVertex broad1 = intersectionVertex("broad_1st", 0.002, 0.0);
71+
graph.addVertex(broad1);
72+
StreetVertex broad2 = intersectionVertex("broad_2nd", 0.001, 0.0);
73+
graph.addVertex(broad2);
74+
75+
// Each block along the main streets has unit length and is one-way
76+
StreetEdge maple1_2 = edge(maple1, maple2, 100.0, false);
77+
StreetEdge main1_2 = edge(main1, main2, 100.0, false);
78+
StreetEdge main2_1 = edge(main2, main1, 100.0, true);
79+
StreetEdge broad2_1 = edge(broad2, broad1, 100.0, false);
80+
81+
// Each cross-street connects
82+
maple_main1 = edge(maple1, main1, 100.0, false);
83+
main_broad1 = edge(main1, broad1, 100.0, false);
84+
85+
StreetEdge maple_main2 = edge(maple2, main2, 800.0, false);
86+
StreetEdge main_broad2 = edge(main2, broad2, 300.0, false);
87+
88+
graph.index(null);
89+
// Hold onto some vertices for the tests
90+
topRight = maple1;
91+
topLeft = broad1;
92+
}
93+
94+
@Test
95+
public void testDefault() {
96+
GraphPath<State, Edge, Vertex> path = getPath();
97+
98+
// The shortest path is 1st to Main, Main to Broad, 1st to 2nd.
99+
100+
assertVertexSequence(path, new String[] { "maple_1st", "main_1st", "broad_1st" });
101+
}
102+
103+
@Test
104+
public void testNoUTurn() {
105+
DisallowTurn(maple_main1, main_broad1);
106+
107+
GraphPath<State, Edge, Vertex> path = getPath();
108+
109+
// Since there is a turn restrictions applied car mode,
110+
// the shortest path is 1st to Main, Main to 2nd, 2nd to Broad.
111+
// U turns usually are prevented by StreetEdge.doTraverse's isReversed check and
112+
// the dominanceFunction which usually prevents that the same vertex is visited multiple times
113+
// with the same mode.
114+
115+
assertVertexSequence(
116+
path,
117+
new String[] { "maple_1st", "main_1st", "main_2nd", "broad_2nd", "broad_1st" }
118+
);
119+
}
120+
121+
@Test
122+
public void testNoUTurnWithLinkedStop() {
123+
DisallowTurn(maple_main1, main_broad1);
124+
TransitStopVertex stop = TransitStopVertex
125+
.of()
126+
.withStop(TransitModelForTest.of().stop("UTurnTest:1234", 0.0015, 0.0011).build())
127+
.build();
128+
129+
// Stop linking splits forward and backward edge, currently with to distinct split vertices.
130+
graph
131+
.getLinker()
132+
.linkVertexPermanently(
133+
stop,
134+
new TraverseModeSet(TraverseMode.WALK),
135+
LinkingDirection.BOTH_WAYS,
136+
(vertex, streetVertex) ->
137+
List.of(
138+
StreetTransitStopLink.createStreetTransitStopLink(
139+
(TransitStopVertex) vertex,
140+
streetVertex
141+
),
142+
StreetTransitStopLink.createStreetTransitStopLink(
143+
streetVertex,
144+
(TransitStopVertex) vertex
145+
)
146+
)
147+
);
148+
149+
GraphPath<State, Edge, Vertex> path = getPath();
150+
151+
// Since there is a turn restrictions applied car mode,
152+
// the shortest path (without u-turn) should be 1st to Main, Main to 2nd, 2nd to Broad, back to 1st.
153+
154+
assertVertexSequence(
155+
path,
156+
new String[] { "maple_1st", "main_1st", "split_", "main_2nd", "broad_2nd", "broad_1st" }
157+
);
158+
}
159+
160+
private GraphPath<State, Edge, Vertex> getPath() {
161+
var request = new RouteRequest();
162+
// We set From/To explicitly, so that fromEnvelope/toEnvelope
163+
request.setFrom(new GenericLocation(topRight.getLat(), topRight.getLon()));
164+
request.setTo(new GenericLocation(topLeft.getLat(), topLeft.getLon()));
165+
166+
ShortestPathTree<State, Edge, Vertex> tree = StreetSearchBuilder
167+
.of()
168+
.setHeuristic(new EuclideanRemainingWeightHeuristic())
169+
.setRequest(request)
170+
.setStreetRequest(new StreetRequest(StreetMode.CAR))
171+
// It is necessary to set From/To explicitly, though it is provided via request already
172+
.setFrom(topRight)
173+
.setTo(topLeft)
174+
.getShortestPathTree();
175+
176+
return tree.getPath(topLeft);
177+
}
178+
179+
private void assertVertexSequence(GraphPath<State, Edge, Vertex> path, String[] vertexLabels) {
180+
assertNotNull(path);
181+
List<State> states = path.states;
182+
assertEquals(vertexLabels.length, states.size());
183+
184+
for (int i = 0; i < vertexLabels.length; i++) {
185+
// we check via startsWith, as splitting order is not deterministic. In consequence split_0 / split_1 both
186+
// would be possible names of a visited node.
187+
188+
String labelString = states.get(i).getVertex().getLabelString();
189+
assertTrue(
190+
labelString.startsWith(vertexLabels[i]),
191+
"state " +
192+
i +
193+
" does not match expected state: " +
194+
labelString +
195+
" should start with " +
196+
vertexLabels[i]
197+
);
198+
}
199+
}
200+
201+
/**
202+
* Create an edge. If twoWay, create two edges (back and forth).
203+
*
204+
* @param back true if this is a reverse edge
205+
*/
206+
private StreetEdge edge(StreetVertex vA, StreetVertex vB, double length, boolean back) {
207+
var labelA = vA.getLabel();
208+
var labelB = vB.getLabel();
209+
String name = String.format("%s_%s", labelA, labelB);
210+
Coordinate[] coords = new Coordinate[2];
211+
coords[0] = vA.getCoordinate();
212+
coords[1] = vB.getCoordinate();
213+
LineString geom = GeometryUtils.getGeometryFactory().createLineString(coords);
214+
215+
StreetTraversalPermission perm = StreetTraversalPermission.ALL;
216+
return new StreetEdgeBuilder<>()
217+
.withFromVertex(vA)
218+
.withToVertex(vB)
219+
.withGeometry(geom)
220+
.withName(name)
221+
.withMeterLength(length)
222+
.withPermission(perm)
223+
.withBack(back)
224+
.buildAndConnect();
225+
}
226+
227+
private void DisallowTurn(StreetEdge from, StreetEdge to) {
228+
TurnRestrictionType rType = TurnRestrictionType.NO_TURN;
229+
TraverseModeSet restrictedModes = new TraverseModeSet(TraverseMode.CAR);
230+
TurnRestriction restrict = new TurnRestriction(from, to, rType, restrictedModes, null);
231+
from.addTurnRestriction(restrict);
232+
}
233+
}

0 commit comments

Comments
 (0)