Skip to content

Commit b6ebc9b

Browse files
authored
Adds new fail-fast option to hand-crafted matrix client. (graphhopper#1661)
1 parent 1dc7ff3 commit b6ebc9b

File tree

11 files changed

+519
-26
lines changed

11 files changed

+519
-26
lines changed

api/src/main/java/com/graphhopper/util/exceptions/DetailedIllegalArgumentException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public class DetailedIllegalArgumentException extends IllegalArgumentException i
2424

2525
private final Map<String, Object> details;
2626

27-
public DetailedIllegalArgumentException(String var1, Map<String, Object> details) {
28-
super(var1);
27+
public DetailedIllegalArgumentException(String message, Map<String, Object> details) {
28+
super(message);
2929
this.details = details;
3030
}
3131

client-hc/src/main/java/com/graphhopper/api/GHMRequest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.graphhopper.api;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
34
import com.graphhopper.GHRequest;
45
import com.graphhopper.util.shapes.GHPoint;
56

@@ -17,8 +18,10 @@ public class GHMRequest extends GHRequest {
1718
private List<GHPoint> toPoints;
1819
private List<String> fromPointHints;
1920
private List<String> toPointHints;
21+
private int called = 0;
2022
boolean identicalLists = true;
2123
private final Set<String> outArrays = new HashSet<>(5);
24+
private boolean failFast = true;
2225

2326
public GHMRequest() {
2427
this(10);
@@ -146,7 +149,17 @@ public boolean hasPointHints() {
146149
this.toPointHints.size() == this.toPoints.size() && !toPoints.isEmpty();
147150
}
148151

149-
private int called = 0;
152+
/**
153+
* @param failFast if false the matrix calculation will be continued even when some points are not connected
154+
*/
155+
@JsonProperty("fail_fast")
156+
public void setFailFast(boolean failFast) {
157+
this.failFast = failFast;
158+
}
159+
160+
public boolean getFailFast() {
161+
return failFast;
162+
}
150163

151164
/**
152165
* This method makes it more likely that hasPointHints returns true as often point hints are added although the

client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
*/
2121
public abstract class GHMatrixAbstractRequester {
2222

23-
private GraphHopperWeb web = new GraphHopperWeb();
2423
protected final ObjectMapper objectMapper;
2524
protected final Set<String> ignoreSet = new HashSet<>(10);
2625
protected final String serviceUrl;
@@ -101,11 +100,18 @@ public List<Throwable> readUsableEntityError(List<String> outArraysList, JsonNod
101100
}
102101
}
103102

104-
public void fillResponseFromJson(MatrixResponse matrixResponse, String responseAsString) throws IOException {
105-
fillResponseFromJson(matrixResponse, objectMapper.reader().readTree(responseAsString));
103+
public void fillResponseFromJson(MatrixResponse matrixResponse, String responseAsString, boolean failFast) throws IOException {
104+
fillResponseFromJson(matrixResponse, objectMapper.reader().readTree(responseAsString), failFast);
106105
}
107106

108-
protected void fillResponseFromJson(MatrixResponse matrixResponse, JsonNode solution) {
107+
/**
108+
* @param failFast If false weights/distances/times that are null are interpreted as disconnected points and are
109+
* thus set to their respective maximum values. Furthermore, the indices of the disconnected points
110+
* are added to {@link MatrixResponse#getDisconnectedPoints()} and the indices of the points that
111+
* could not be found are added to {@link MatrixResponse#getInvalidFromPoints()} and/or
112+
* {@link MatrixResponse#getInvalidToPoints()}.
113+
*/
114+
protected void fillResponseFromJson(MatrixResponse matrixResponse, JsonNode solution, boolean failFast) {
109115
final boolean readWeights = solution.has("weights");
110116
final boolean readDistances = solution.has("distances");
111117
final boolean readTimes = solution.has("times");
@@ -155,15 +161,27 @@ protected void fillResponseFromJson(MatrixResponse matrixResponse, JsonNode solu
155161

156162
for (int toIndex = 0; toIndex < toCount; toIndex++) {
157163
if (readWeights) {
158-
weights[toIndex] = weightsFromArray.get(toIndex).asDouble();
164+
if (weightsFromArray.get(toIndex).isNull() && !failFast) {
165+
weights[toIndex] = Double.MAX_VALUE;
166+
} else {
167+
weights[toIndex] = weightsFromArray.get(toIndex).asDouble();
168+
}
159169
}
160170

161171
if (readTimes) {
162-
times[toIndex] = timesFromArray.get(toIndex).asLong() * 1000;
172+
if (timesFromArray.get(toIndex).isNull() && !failFast) {
173+
times[toIndex] = Long.MAX_VALUE;
174+
} else {
175+
times[toIndex] = timesFromArray.get(toIndex).asLong() * 1000;
176+
}
163177
}
164178

165179
if (readDistances) {
166-
distances[toIndex] = (int) Math.round(distancesFromArray.get(toIndex).asDouble());
180+
if (distancesFromArray.get(toIndex).isNull() && !failFast) {
181+
distances[toIndex] = Integer.MAX_VALUE;
182+
} else {
183+
distances[toIndex] = (int) Math.round(distancesFromArray.get(toIndex).asDouble());
184+
}
167185
}
168186
}
169187

@@ -179,6 +197,43 @@ protected void fillResponseFromJson(MatrixResponse matrixResponse, JsonNode solu
179197
matrixResponse.setDistanceRow(fromIndex, distances);
180198
}
181199
}
200+
if (!failFast && solution.has("hints")) {
201+
addProblems(matrixResponse, solution.get("hints"));
202+
}
203+
}
204+
205+
private void addProblems(MatrixResponse matrixResponse, JsonNode hints) {
206+
for (JsonNode hint : hints) {
207+
if (hint.has("point_pairs")) {
208+
matrixResponse.setDisconnectedPoints(readDisconnectedPoints(hint.get("point_pairs")));
209+
}
210+
if (hint.has("invalid_from_points")) {
211+
matrixResponse.setInvalidFromPoints(readInvalidPoints(hint.get("invalid_from_points")));
212+
matrixResponse.setInvalidToPoints(readInvalidPoints(hint.get("invalid_to_points")));
213+
}
214+
}
215+
}
216+
217+
private List<MatrixResponse.PointPair> readDisconnectedPoints(JsonNode pointPairsArray) {
218+
List<MatrixResponse.PointPair> disconnectedPoints = new ArrayList<>(pointPairsArray.size());
219+
for (int i = 0; i < pointPairsArray.size(); i++) {
220+
if (pointPairsArray.get(i).size() != 2) {
221+
throw new IllegalArgumentException("all point_pairs are expected to contain two elements");
222+
}
223+
disconnectedPoints.add(new MatrixResponse.PointPair(
224+
pointPairsArray.get(i).get(0).asInt(),
225+
pointPairsArray.get(i).get(1).asInt()
226+
));
227+
}
228+
return disconnectedPoints;
229+
}
230+
231+
private List<Integer> readInvalidPoints(JsonNode pointsArray) {
232+
List<Integer> result = new ArrayList<>(pointsArray.size());
233+
for (int i = 0; i < pointsArray.size(); i++) {
234+
result.add(pointsArray.get(i).asInt());
235+
}
236+
return result;
182237
}
183238

184239
private static int checkArraySizes(String msg, int len, JsonNode... arrays) {

client-hc/src/main/java/com/graphhopper/api/GHMatrixBatchRequester.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public MatrixResponse route(GHMRequest ghRequest) {
8383
requestJson.putArray("out_arrays").addAll(outArrayListJson);
8484
requestJson.put("vehicle", ghRequest.getVehicle());
8585
requestJson.put("elevation", hasElevation);
86+
requestJson.put("fail_fast", ghRequest.getFailFast());
8687

8788
Map<String, String> hintsMap = ghRequest.getHints().toMap();
8889
for (String hintKey : hintsMap.keySet()) {
@@ -154,7 +155,7 @@ public MatrixResponse route(GHMRequest ghRequest) {
154155
JsonNode solution = getResponseJson.get("solution");
155156
matrixResponse.addErrors(readUsableEntityError(outArraysList, solution));
156157
if (!matrixResponse.hasErrors())
157-
fillResponseFromJson(matrixResponse, solution);
158+
fillResponseFromJson(matrixResponse, solution, ghRequest.getFailFast());
158159

159160
break;
160161
}

client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public MatrixResponse route(GHMRequest ghRequest) {
9191
if (!Helper.isEmpty(ghRequest.getVehicle())) {
9292
url += "&vehicle=" + ghRequest.getVehicle();
9393
}
94+
url += "&fail_fast=" + ghRequest.getFailFast();
9495

9596
boolean withTimes = outArraysList.contains("times");
9697
boolean withDistances = outArraysList.contains("distances");
@@ -109,7 +110,7 @@ public MatrixResponse route(GHMRequest ghRequest) {
109110
}
110111

111112
if (!matrixResponse.hasErrors())
112-
fillResponseFromJson(matrixResponse, getResponseJson);
113+
fillResponseFromJson(matrixResponse, getResponseJson, ghRequest.getFailFast());
113114

114115
} catch (IOException ex) {
115116
throw new RuntimeException(ex);

client-hc/src/main/java/com/graphhopper/api/MatrixResponse.java

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.ArrayList;
44
import java.util.Collection;
55
import java.util.List;
6+
import java.util.Objects;
67

78
/**
89
* This class defines the response for a M-to-N requests.
@@ -13,6 +14,9 @@ public class MatrixResponse {
1314

1415
private String debugInfo = "";
1516
private final List<Throwable> errors = new ArrayList<>(4);
17+
private final List<PointPair> disconnectedPoints = new ArrayList<>(0);
18+
private final List<Integer> invalidFromPoints = new ArrayList<>(0);
19+
private final List<Integer> invalidToPoints = new ArrayList<>(0);
1620
private long[][] times = new long[0][];
1721
private int[][] distances = new int[0][];
1822
private double[][] weights = new double[0][];
@@ -45,7 +49,7 @@ public MatrixResponse(int fromCap, int toCap, boolean withTimes, boolean withDis
4549
throw new IllegalArgumentException("Please specify times, distances or weights that should be calculated by the matrix");
4650
}
4751

48-
public void setFromRow(int row, long timeRow[], int distanceRow[], double weightRow[]) {
52+
public void setFromRow(int row, long[] timeRow, int[] distanceRow, double[] weightRow) {
4953
if (times.length > 0) {
5054
check(timeRow.length, toCount, "to times");
5155
times[row] = timeRow;
@@ -68,7 +72,7 @@ private void check(int currentLength, int expectedLength, String times) {
6872
"Expected " + expectedLength + " was: " + currentLength + ". Matrix: " + fromCount + "x" + toCount);
6973
}
7074

71-
public void setTimeRow(int row, long timeRow[]) {
75+
public void setTimeRow(int row, long[] timeRow) {
7276
if (times.length > 0) {
7377
check(timeRow.length, toCount, "to times");
7478
times[row] = timeRow;
@@ -77,7 +81,7 @@ public void setTimeRow(int row, long timeRow[]) {
7781
}
7882
}
7983

80-
public void setDistanceRow(int row, int distanceRow[]) {
84+
public void setDistanceRow(int row, int[] distanceRow) {
8185
if (distances.length > 0) {
8286
check(distanceRow.length, toCount, "to distances");
8387
distances[row] = distanceRow;
@@ -86,7 +90,7 @@ public void setDistanceRow(int row, int distanceRow[]) {
8690
}
8791
}
8892

89-
public void setWeightRow(int row, double weightRow[]) {
93+
public void setWeightRow(int row, double[] weightRow) {
9094
if (weights.length > 0) {
9195
check(weightRow.length, toCount, "to weights");
9296
weights[row] = weightRow;
@@ -95,12 +99,20 @@ public void setWeightRow(int row, double weightRow[]) {
9599
}
96100
}
97101

102+
public boolean isConnected(int from, int to) {
103+
if (hasErrors()) {
104+
return false;
105+
}
106+
return getWeight(from, to) < Double.MAX_VALUE;
107+
}
108+
98109
/**
99-
* Returns the time for the specific entry (from -&gt; to) in milliseconds.
110+
* Returns the time for the specific entry (from -&gt; to) in milliseconds or {@link Long#MAX_VALUE} in case
111+
* no connection was found (and {@link GHMRequest#setFailFast(boolean)} was set to true).
100112
*/
101113
public long getTime(int from, int to) {
102114
if (hasErrors()) {
103-
throw new IllegalStateException("Cannot return time (" + from + "," + to + ") if errors occured " + getErrors());
115+
throw new IllegalStateException("Cannot return time (" + from + "," + to + ") if errors occurred " + getErrors());
104116
}
105117

106118
if (from >= times.length) {
@@ -112,7 +124,8 @@ public long getTime(int from, int to) {
112124
}
113125

114126
/**
115-
* Returns the distance for the specific entry (from -&gt; to) in meter.
127+
* Returns the distance for the specific entry (from -&gt; to) in meter or {@link Double#MAX_VALUE} in case
128+
* no connection was found (and {@link GHMRequest#setFailFast(boolean)} was set to true).
116129
*/
117130
public double getDistance(int from, int to) {
118131
if (hasErrors()) {
@@ -124,12 +137,13 @@ public double getDistance(int from, int to) {
124137
} else if (to >= distances[from].length) {
125138
throw new IllegalStateException("Cannot get 'to' " + to + " from distances with size " + distances[from].length);
126139
}
127-
return distances[from][to];
140+
return distances[from][to] == Integer.MAX_VALUE ? Double.MAX_VALUE : distances[from][to];
128141
}
129142

130143
/**
131-
* Returns the weight for the specific entry (from -&gt; to) in arbitrary units
132-
* ('costs').
144+
* Returns the weight for the specific entry (from -&gt; to) in arbitrary units ('costs'), or
145+
* {@link Double#MAX_VALUE} in case no connection was found (and {@link GHMRequest#setFailFast(boolean)} was set
146+
* to true).
133147
*/
134148
public double getWeight(int from, int to) {
135149
if (hasErrors()) {
@@ -176,6 +190,44 @@ public MatrixResponse addErrors(Collection<Throwable> errorList) {
176190
return this;
177191
}
178192

193+
/**
194+
* @return true if there are invalid or disconnected points (which both do not yield an error in case we do not fail fast).
195+
* @see GHMRequest#setFailFast(boolean)
196+
*/
197+
public boolean hasProblems() {
198+
return !disconnectedPoints.isEmpty() || !invalidFromPoints.isEmpty() || !invalidToPoints.isEmpty();
199+
}
200+
201+
public MatrixResponse setDisconnectedPoints(List<PointPair> disconnectedPoints) {
202+
this.disconnectedPoints.clear();
203+
this.disconnectedPoints.addAll(disconnectedPoints);
204+
return this;
205+
}
206+
207+
public List<PointPair> getDisconnectedPoints() {
208+
return disconnectedPoints;
209+
}
210+
211+
public MatrixResponse setInvalidFromPoints(List<Integer> invalidFromPoints) {
212+
this.invalidFromPoints.clear();
213+
this.invalidFromPoints.addAll(invalidFromPoints);
214+
return this;
215+
}
216+
217+
public MatrixResponse setInvalidToPoints(List<Integer> invalidToPoints) {
218+
this.invalidToPoints.clear();
219+
this.invalidToPoints.addAll(invalidToPoints);
220+
return this;
221+
}
222+
223+
public List<Integer> getInvalidFromPoints() {
224+
return invalidFromPoints;
225+
}
226+
227+
public List<Integer> getInvalidToPoints() {
228+
return invalidToPoints;
229+
}
230+
179231
@Override
180232
public String toString() {
181233
String addInfo = "";
@@ -188,6 +240,45 @@ public String toString() {
188240
addInfo += ", distances: " + distances.length + "x" + distances[0].length;
189241
}
190242

191-
return "[" + addInfo + "] errors:" + errors.toString();
243+
String result = "[" + addInfo + "] errors:" + errors.toString();
244+
if (!disconnectedPoints.isEmpty()) {
245+
result += ", disconnectedPoints: " + disconnectedPoints.size();
246+
}
247+
if (!invalidFromPoints.isEmpty()) {
248+
result += ", invalidFromPoints: " + invalidFromPoints.size();
249+
}
250+
if (!invalidToPoints.isEmpty()) {
251+
result += ", invalidToPoints: " + invalidToPoints.size();
252+
}
253+
return result;
254+
}
255+
256+
public static class PointPair {
257+
public final int sourceIndex;
258+
public final int targetIndex;
259+
260+
public PointPair(int sourceIndex, int targetIndex) {
261+
this.sourceIndex = sourceIndex;
262+
this.targetIndex = targetIndex;
263+
}
264+
265+
@Override
266+
public boolean equals(Object o) {
267+
if (this == o) return true;
268+
if (o == null || getClass() != o.getClass()) return false;
269+
PointPair pointPair = (PointPair) o;
270+
return sourceIndex == pointPair.sourceIndex &&
271+
targetIndex == pointPair.targetIndex;
272+
}
273+
274+
@Override
275+
public int hashCode() {
276+
return Objects.hash(sourceIndex, targetIndex);
277+
}
278+
279+
@Override
280+
public String toString() {
281+
return "[" + sourceIndex + ", " + targetIndex + "]";
282+
}
192283
}
193284
}

0 commit comments

Comments
 (0)