Skip to content

Commit 51acef0

Browse files
committed
isochrone: fix wrong termination condition
1 parent 44835f3 commit 51acef0

File tree

2 files changed

+83
-20
lines changed

2 files changed

+83
-20
lines changed

core/src/main/java/com/graphhopper/isochrone/algorithm/ShortestPathTree.java

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.graphhopper.storage.Graph;
2828
import com.graphhopper.util.EdgeIterator;
2929
import com.graphhopper.util.GHUtility;
30+
import org.jetbrains.annotations.NotNull;
3031

3132
import java.util.ArrayList;
3233
import java.util.Collection;
@@ -35,6 +36,7 @@
3536

3637
import static com.graphhopper.isochrone.algorithm.ShortestPathTree.ExploreType.*;
3738
import static java.util.Comparator.comparingDouble;
39+
import static java.util.Comparator.comparingLong;
3840

3941
/**
4042
* Computes a shortest path tree by a given weighting. Terminates when all shortest paths up to
@@ -85,7 +87,8 @@ public String toString() {
8587
}
8688

8789
private final IntObjectHashMap<IsoLabel> fromMap;
88-
private final PriorityQueue<IsoLabel> queueByWeighting;
90+
private final PriorityQueue<IsoLabel> queueByWeighting; // a.k.a. the Dijkstra queue
91+
private PriorityQueue<IsoLabel> queueByZ; // so we know when we are finished
8992
private int visitedNodes;
9093
private double limit = -1;
9194
private ExploreType exploreType = TIME;
@@ -94,6 +97,7 @@ public String toString() {
9497
public ShortestPathTree(Graph g, Weighting weighting, boolean reverseFlow, TraversalMode traversalMode) {
9598
super(g, weighting, traversalMode);
9699
queueByWeighting = new PriorityQueue<>(1000, comparingDouble(l -> l.weight));
100+
queueByZ = new PriorityQueue<>(1000);
97101
fromMap = new GHIntObjectHashMap<>(1000);
98102
this.reverseFlow = reverseFlow;
99103
}
@@ -109,6 +113,7 @@ public Path calcPath(int from, int to) {
109113
public void setTimeLimit(double limit) {
110114
exploreType = TIME;
111115
this.limit = limit;
116+
this.queueByZ = new PriorityQueue<>(1000, comparingLong(l -> l.time));
112117
}
113118

114119
/**
@@ -117,25 +122,30 @@ public void setTimeLimit(double limit) {
117122
public void setDistanceLimit(double limit) {
118123
exploreType = DISTANCE;
119124
this.limit = limit;
125+
this.queueByZ = new PriorityQueue<>(1000, comparingDouble(l -> l.distance));
120126
}
121127

122128
public void setWeightLimit(double limit) {
123129
exploreType = WEIGHT;
124130
this.limit = limit;
131+
this.queueByZ = new PriorityQueue<>(1000, comparingDouble(l -> l.weight));
125132
}
126133

127134
public void search(int from, final Consumer<IsoLabel> consumer) {
128135
checkAlreadyRun();
129136
IsoLabel currentLabel = new IsoLabel(from, -1, 0, 0, 0, null);
130137
queueByWeighting.add(currentLabel);
138+
queueByZ.add(currentLabel);
131139
if (traversalMode == TraversalMode.NODE_BASED) {
132140
fromMap.put(from, currentLabel);
133141
}
134-
while (!queueByWeighting.isEmpty()) {
142+
while (!finished()) {
135143
currentLabel = queueByWeighting.poll();
136144
if (currentLabel.deleted)
137145
continue;
138-
consumer.accept(currentLabel);
146+
if (getExploreValue(currentLabel) <= limit) {
147+
consumer.accept(currentLabel);
148+
}
139149
currentLabel.deleted = true;
140150
visitedNodes++;
141151

@@ -152,31 +162,33 @@ public void search(int from, final Consumer<IsoLabel> consumer) {
152162
double nextDistance = iter.getDistance() + currentLabel.distance;
153163
long nextTime = GHUtility.calcMillisWithTurnMillis(weighting, iter, reverseFlow, currentLabel.edge) + currentLabel.time;
154164
int nextTraversalId = traversalMode.createTraversalId(iter, reverseFlow);
155-
IsoLabel label = fromMap.get(nextTraversalId);
156-
if (label == null) {
157-
label = new IsoLabel(iter.getAdjNode(), iter.getEdge(), nextWeight, nextTime, nextDistance, currentLabel);
158-
fromMap.put(nextTraversalId, label);
159-
if (getExploreValue(label) <= limit) {
160-
queueByWeighting.add(label);
161-
}
162-
} else if (label.weight > nextWeight) {
163-
label.deleted = true;
164-
label = new IsoLabel(iter.getAdjNode(), iter.getEdge(), nextWeight, nextTime, nextDistance, currentLabel);
165-
fromMap.put(nextTraversalId, label);
166-
if (getExploreValue(label) <= limit) {
167-
queueByWeighting.add(label);
168-
}
165+
IsoLabel nextLabel = fromMap.get(nextTraversalId);
166+
if (nextLabel == null) {
167+
nextLabel = new IsoLabel(iter.getAdjNode(), iter.getEdge(), nextWeight, nextTime, nextDistance, currentLabel);
168+
fromMap.put(nextTraversalId, nextLabel);
169+
queueByWeighting.add(nextLabel);
170+
queueByZ.add(nextLabel);
171+
} else if (nextLabel.weight > nextWeight) {
172+
nextLabel.deleted = true;
173+
nextLabel = new IsoLabel(iter.getAdjNode(), iter.getEdge(), nextWeight, nextTime, nextDistance, currentLabel);
174+
fromMap.put(nextTraversalId, nextLabel);
175+
queueByWeighting.add(nextLabel);
176+
queueByZ.add(nextLabel);
169177
}
170178
}
171179
}
172180
}
173181

174182
public Collection<IsoLabel> getIsochroneEdges() {
175183
// assert alreadyRun
184+
return getIsochroneEdges(limit);
185+
}
186+
187+
public ArrayList<IsoLabel> getIsochroneEdges(double z) {
176188
ArrayList<IsoLabel> result = new ArrayList<>();
177189
for (ObjectCursor<IsoLabel> cursor : fromMap.values()) {
178-
if (getExploreValue(cursor.value) > limit) {
179-
assert cursor.value.parent == null || getExploreValue(cursor.value.parent) <= limit;
190+
if (cursor.value.parent != null &&
191+
(getExploreValue(cursor.value) > z ^ getExploreValue(cursor.value.parent) > z)) {
180192
result.add(cursor.value);
181193
}
182194
}
@@ -191,6 +203,14 @@ private double getExploreValue(IsoLabel label) {
191203
return label.distance;
192204
}
193205

206+
protected boolean finished() {
207+
while (queueByZ.peek() != null && queueByZ.peek().deleted)
208+
queueByZ.poll();
209+
if (queueByZ.peek() == null)
210+
return true;
211+
return getExploreValue(queueByZ.peek()) >= limit;
212+
}
213+
194214
@Override
195215
public String getName() {
196216
return "reachability";

core/src/test/java/com/graphhopper/isochrone/algorithm/ShortestPathTreeTest.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
import com.graphhopper.routing.weighting.TurnCostProvider;
1111
import com.graphhopper.routing.weighting.Weighting;
1212
import com.graphhopper.routing.weighting.custom.CustomModelParser;
13+
import com.graphhopper.routing.weighting.custom.CustomWeighting;
1314
import com.graphhopper.storage.BaseGraph;
1415
import com.graphhopper.storage.Graph;
1516
import com.graphhopper.util.CustomModel;
17+
import com.graphhopper.util.EdgeIterator;
18+
import com.graphhopper.util.EdgeIteratorState;
1619
import com.graphhopper.util.GHUtility;
1720
import org.junit.jupiter.api.AfterEach;
1821
import org.junit.jupiter.api.BeforeEach;
@@ -22,6 +25,8 @@
2225
import java.util.Collection;
2326
import java.util.List;
2427

28+
import static com.graphhopper.json.Statement.If;
29+
import static com.graphhopper.json.Statement.Op.MULTIPLY;
2530
import static org.junit.jupiter.api.Assertions.*;
2631

2732
/**
@@ -64,7 +69,8 @@ public long calcTurnMillis(int inEdge, int viaNode, int outEdge) {
6469

6570
private final BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true);
6671
private final DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false);
67-
private final EncodingManager encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).build();
72+
private final BooleanEncodedValue ferryEnc = new SimpleBooleanEncodedValue("ferry", false);
73+
private final EncodingManager encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).add(ferryEnc).build();
6874
private BaseGraph graph;
6975

7076

@@ -169,6 +175,33 @@ public void testNoTimeLimit() {
169175
);
170176
}
171177

178+
@Test
179+
public void testFerry() {
180+
AllEdgesIterator allEdges = graph.getAllEdges();
181+
while (allEdges.next()) {
182+
allEdges.set(ferryEnc, false);
183+
}
184+
EdgeIteratorState edge = findEdge(6, 7);
185+
edge.set(ferryEnc, true);
186+
187+
List<ShortestPathTree.IsoLabel> result = new ArrayList<>();
188+
CustomModel cm = new CustomModel();
189+
cm.addToPriority(If("ferry", MULTIPLY, "0.005"));
190+
CustomWeighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, encodingManager, TurnCostProvider.NO_TURN_COST_PROVIDER, cm);
191+
ShortestPathTree instance = new ShortestPathTree(graph, weighting, false, TraversalMode.NODE_BASED);
192+
instance.setTimeLimit(30_000);
193+
instance.search(0, result::add);
194+
assertEquals(4, result.size());
195+
assertAll(
196+
() -> assertEquals(0, result.get(0).time), () -> assertEquals(0, result.get(0).node),
197+
() -> assertEquals(9000, result.get(1).time), () -> assertEquals(4, result.get(1).node),
198+
() -> assertEquals(18000, result.get(2).time), () -> assertEquals(6, result.get(2).node),
199+
() -> assertEquals(25200, result.get(3).time), () -> assertEquals(1, result.get(3).node)
200+
);
201+
assertEquals(7, instance.getVisitedNodes(), "If this increases, make sure we are not traversing entire graph irl");
202+
}
203+
204+
172205
@Test
173206
public void testEdgeBasedWithFreeUTurns() {
174207
List<ShortestPathTree.IsoLabel> result = new ArrayList<>();
@@ -326,4 +359,14 @@ public void testSearchByDistance() {
326359
);
327360
}
328361

362+
EdgeIteratorState findEdge(int a, int b) {
363+
EdgeIterator edgeIterator = graph.createEdgeExplorer().setBaseNode(a);
364+
while (edgeIterator.next()) {
365+
if (edgeIterator.getAdjNode() == b) {
366+
return edgeIterator;
367+
}
368+
}
369+
throw new RuntimeException("nope");
370+
}
371+
329372
}

0 commit comments

Comments
 (0)