Skip to content

Commit ab5f7f2

Browse files
authored
Merge pull request opentripplanner#7309 from HSLdevcom/fix-multimode-linking
Use only the closest vertices for each mode when linking with multiple street modes to improve performance
2 parents 6875be3 + 73342c0 commit ab5f7f2

2 files changed

Lines changed: 106 additions & 4 deletions

File tree

application/src/main/java/org/opentripplanner/routing/linking/VertexLinker.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ private Set<DistanceTo<StreetEdge>> getClosestEdgesPerMode(
348348
// other half lost. It seems like this was based on some incorrect premises about floating point calculations
349349
// being non-deterministic.
350350

351-
Set<DistanceTo<StreetEdge>> closesEdges = new HashSet<>();
351+
Set<DistanceTo<StreetEdge>> closestEdges = new HashSet<>();
352352
for (TraverseMode mode : traverseModeSet.getModes()) {
353353
TraverseModeSet modeSet = new TraverseModeSet(mode);
354354
// There is at least one appropriate edge within range.
@@ -369,14 +369,15 @@ private Set<DistanceTo<StreetEdge>> getClosestEdgesPerMode(
369369
.getAsDouble();
370370

371371
// Because this is a set, each instance of DistanceTo<StreetEdge> will only be added once
372-
closesEdges.addAll(
373-
candidateEdges
372+
// Note: add only closest edges of each mode
373+
closestEdges.addAll(
374+
candidateEdgesForMode
374375
.stream()
375376
.filter(ce -> ce.distanceDegreesLat <= closestDistance + DUPLICATE_WAY_EPSILON_DEGREES)
376377
.collect(Collectors.toSet())
377378
);
378379
}
379-
return closesEdges;
380+
return closestEdges;
380381
}
381382

382383
/* Snap a vertex to and edge if necessary, create required linking and return the applied entry vertex */

application/src/test/java/org/opentripplanner/routing/linking/VertexLinkerTest.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,35 @@
44
import static org.opentripplanner.street.model.edge.LinkingDirection.BIDIRECTIONAL;
55
import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id;
66

7+
import java.text.DecimalFormat;
8+
import java.text.DecimalFormatSymbols;
79
import java.util.List;
810
import java.util.Set;
911
import org.junit.jupiter.api.Test;
1012
import org.opentripplanner.core.model.id.FeedScopedId;
1113
import org.opentripplanner.framework.application.OTPFeature;
1214
import org.opentripplanner.routing.graph.Graph;
1315
import org.opentripplanner.street.model.StreetModelForTest;
16+
import org.opentripplanner.street.model.StreetTraversalPermission;
1417
import org.opentripplanner.street.model.edge.StreetEdge;
18+
import org.opentripplanner.street.model.edge.TemporaryPartialStreetEdge;
1519
import org.opentripplanner.street.model.vertex.IntersectionVertex;
1620
import org.opentripplanner.street.model.vertex.SplitterVertex;
1721
import org.opentripplanner.street.model.vertex.StreetVertex;
22+
import org.opentripplanner.street.model.vertex.Vertex;
1823
import org.opentripplanner.street.search.TraverseModeSet;
1924

2025
class VertexLinkerTest {
2126

2227
public static final FeedScopedId AREA_STOP_1 = id("area-stop-1");
2328
public static final FeedScopedId AREA_STOP_2 = id("area-stop-2");
29+
public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.######");
30+
public static final DecimalFormatSymbols SYMBOLS = DECIMAL_FORMAT.getDecimalFormatSymbols();
31+
32+
{
33+
SYMBOLS.setDecimalSeparator('.');
34+
DECIMAL_FORMAT.setDecimalFormatSymbols(SYMBOLS);
35+
}
2436

2537
@Test
2638
void flex() {
@@ -108,6 +120,95 @@ void splitRealtime() {
108120
assertThat(model.graph().getEdgesOfType(StreetEdge.class)).hasSize(1);
109121
}
110122

123+
@Test
124+
void multiModeLinking() {
125+
// test model has 3 parallel horizontal edges, of which uppermost allows car driving
126+
IntersectionVertex[] vertices = {
127+
StreetModelForTest.intersectionVertex(0.0, 0.0),
128+
StreetModelForTest.intersectionVertex(0.01, 0.0),
129+
StreetModelForTest.intersectionVertex(0.0, 0.0001),
130+
StreetModelForTest.intersectionVertex(0.01, 0.0001),
131+
StreetModelForTest.intersectionVertex(0.0, 0.0002),
132+
StreetModelForTest.intersectionVertex(0.01, 0.0002),
133+
};
134+
135+
var walkEdge1 = StreetModelForTest.streetEdge(
136+
vertices[0],
137+
vertices[1],
138+
0.01,
139+
StreetTraversalPermission.PEDESTRIAN
140+
);
141+
var walkEdge2 = StreetModelForTest.streetEdge(
142+
vertices[2],
143+
vertices[3],
144+
0.01,
145+
StreetTraversalPermission.PEDESTRIAN
146+
);
147+
var carEdge = StreetModelForTest.streetEdge(
148+
vertices[4],
149+
vertices[5],
150+
0.01,
151+
StreetTraversalPermission.CAR
152+
);
153+
154+
// link point below all edges, in the middle
155+
var split = StreetModelForTest.intersectionVertex(0.005, -0.0001);
156+
157+
var g = new Graph();
158+
for (IntersectionVertex vertex : vertices) {
159+
g.addVertex(vertex);
160+
}
161+
g.index();
162+
g.insert(walkEdge1, Scope.PERMANENT);
163+
g.insert(walkEdge2, Scope.PERMANENT);
164+
g.insert(carEdge, Scope.PERMANENT);
165+
assertThat(g.getEdgesOfType(StreetEdge.class)).hasSize(3);
166+
var linker = VertexLinkerTestFactory.of(g);
167+
var temp = linker.linkVertexForRequest(
168+
split,
169+
TraverseModeSet.allModes(),
170+
BIDIRECTIONAL,
171+
(v1, v2) -> List.of()
172+
);
173+
// vertex is linked to closest walk edge and to the car edge, not to all 3 edges
174+
assertThat(summarizeLinks(g)).containsExactly(
175+
"(0,0) → (0.005,0) PEDESTRIAN ♿✅",
176+
"(0,0.0002) → (0.005,0.0002) CAR ♿✅"
177+
);
178+
temp.disposeEdges();
179+
assertThat(summarizeLinks(g)).isEmpty();
180+
}
181+
182+
private static List<String> summarizeLinks(Graph graph) {
183+
return graph
184+
.getEdgesOfType(TemporaryPartialStreetEdge.class)
185+
.stream()
186+
.map(e ->
187+
String.format(
188+
"%s → %s %s ♿%s",
189+
summarizeVertex(e.getFromVertex()),
190+
summarizeVertex(e.getToVertex()),
191+
e.getPermission(),
192+
summarizeBoolean(e.isWheelchairAccessible())
193+
)
194+
)
195+
.toList();
196+
}
197+
198+
private static String summarizeBoolean(boolean b) {
199+
if (b) {
200+
return "✅";
201+
} else {
202+
return "❌";
203+
}
204+
}
205+
206+
private static String summarizeVertex(Vertex e) {
207+
return String.format(
208+
"(%s,%s)".formatted(DECIMAL_FORMAT.format(e.getLat()), DECIMAL_FORMAT.format(e.getLon()))
209+
);
210+
}
211+
111212
private static TestModel buildModel() {
112213
var v1 = StreetModelForTest.intersectionVertex(0.0, 0.0);
113214
var v2 = StreetModelForTest.intersectionVertex(0.1, 0.1);

0 commit comments

Comments
 (0)