com.graphhopper.api.GHMatrixAbstractRequester Maven / Gradle / Ivy
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.graphhopper.util.Helper;
import com.graphhopper.util.shapes.GHPoint;
import okhttp3.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.graphhopper.api.GraphHopperMatrixWeb.*;
import static com.graphhopper.api.GraphHopperWeb.X_GH_CLIENT_VERSION;
/**
* @author Peter Karich
*/
public abstract class GHMatrixAbstractRequester {
static final String MATRIX_URL = "https://graphhopper.com/api/1/matrix";
protected final ObjectMapper objectMapper;
protected final String serviceUrl;
private final Set ignoreSet = new HashSet<>();
private OkHttpClient downloader;
int maxUnzippedLength = 1000;
public GHMatrixAbstractRequester() {
this(MATRIX_URL);
}
public GHMatrixAbstractRequester(String serviceUrl) {
this(serviceUrl, new OkHttpClient.Builder().
connectTimeout(5, TimeUnit.SECONDS).
readTimeout(5, TimeUnit.SECONDS).build(), true);
}
public GHMatrixAbstractRequester(String serviceUrl, OkHttpClient client, boolean doRequestGzip) {
if (serviceUrl.endsWith("/")) {
serviceUrl = serviceUrl.substring(0, serviceUrl.length() - 1);
}
this.downloader = doRequestGzip ? client.newBuilder().addInterceptor(new GzipRequestInterceptor()).build() : client;
this.serviceUrl = serviceUrl;
ignoreSet.add("key");
ignoreSet.add("service_url");
this.objectMapper = new ObjectMapper();
}
public abstract MatrixResponse route(GHMRequest request);
public GHMatrixAbstractRequester setDownloader(OkHttpClient downloader) {
this.downloader = downloader;
return this;
}
public OkHttpClient getDownloader() {
return downloader;
}
protected JsonNode createPostRequest(GHMRequest ghRequest) {
if (ghRequest.getHints().getObject("profile", null) != null)
throw new IllegalArgumentException("use setProfile instead of hint 'profile'");
if (ghRequest.getProfile() == null)
throw new IllegalArgumentException("profile cannot be empty");
if (ghRequest.getHints().getObject("fail_fast", null) != null)
throw new IllegalArgumentException("use setFailFast instead of hint 'fail_fast'");
ObjectNode requestJson = objectMapper.createObjectNode();
if (ghRequest.getPoints() != null) {
if (ghRequest.getFromPoints() != null)
throw new IllegalArgumentException("if points are set do not use setFromPoints");
if (ghRequest.getToPoints() != null)
throw new IllegalArgumentException("if points are set do not use setToPoints");
putPoints(requestJson, "points", ghRequest.getPoints());
putStrings(requestJson, "point_hints", ghRequest.getPointHints());
putStrings(requestJson, "curbsides", ghRequest.getCurbsides());
} else {
if (ghRequest.getFromPoints() == null)
throw new IllegalArgumentException("if points are not set you have to use setFromPoints but was null");
if (ghRequest.getToPoints() == null)
throw new IllegalArgumentException("if points are not set you have to use setToPoints but was null");
putPoints(requestJson, "from_points", ghRequest.getFromPoints());
putStrings(requestJson, "from_point_hints", ghRequest.getFromPointHints());
putPoints(requestJson, "to_points", ghRequest.getToPoints());
putStrings(requestJson, "to_point_hints", ghRequest.getToPointHints());
putStrings(requestJson, "from_curbsides", ghRequest.getFromCurbsides());
putStrings(requestJson, "to_curbsides", ghRequest.getToCurbsides());
}
putStrings(requestJson, "snap_preventions", ghRequest.getSnapPreventions());
putStrings(requestJson, "out_arrays", ghRequest.getOutArrays());
requestJson.put("fail_fast", ghRequest.getFailFast());
requestJson.put("profile", ghRequest.getProfile());
Map hintsMap = ghRequest.getHints().toMap();
for (String hintKey : hintsMap.keySet()) {
if (ignoreSet.contains(hintKey))
continue;
Object hint = hintsMap.get(hintKey);
if (hint instanceof String)
requestJson.put(hintKey, (String) hint);
else
requestJson.putPOJO(hintKey, hint);
}
return requestJson;
}
protected JsonNode fromStringToJSON(String url, String str) {
try {
return objectMapper.readTree(str);
} catch (Exception ex) {
throw new RuntimeException("Cannot parse json " + str + " from " + url);
}
}
public List readUsableEntityError(Collection outArraysList, JsonNode solution) {
boolean readWeights = outArraysList.contains("weights") && solution.has("weights");
boolean readDistances = outArraysList.contains("distances") && solution.has("distances");
boolean readTimes = outArraysList.contains("times") && solution.has("times");
if (!readWeights && !readDistances && !readTimes) {
return Collections.singletonList(new RuntimeException("Cannot find usable entity like weights, distances or times in JSON"));
} else {
return Collections.emptyList();
}
}
/**
* @param failFast If false weights/distances/times that are null are interpreted as disconnected points and are
* thus set to their respective maximum values. Furthermore, the indices of the disconnected points
* are added to {@link MatrixResponse#getDisconnectedPoints()} and the indices of the points that
* could not be found are added to {@link MatrixResponse#getInvalidFromPoints()} and/or
* {@link MatrixResponse#getInvalidToPoints()}.
*/
protected void fillResponseFromJson(MatrixResponse matrixResponse, JsonNode solution, boolean failFast) {
final boolean readWeights = solution.has("weights");
final boolean readDistances = solution.has("distances");
final boolean readTimes = solution.has("times");
int fromCount = 0;
JsonNode weightsArray = null;
if (readWeights) {
weightsArray = solution.get("weights");
fromCount = checkArraySizes("weights", weightsArray.size());
}
JsonNode timesArray = null;
if (readTimes) {
timesArray = solution.get("times");
fromCount = checkArraySizes("times", timesArray.size(), weightsArray);
}
JsonNode distancesArray = null;
if (readDistances) {
distancesArray = solution.get("distances");
fromCount = checkArraySizes("distances", distancesArray.size(), weightsArray, timesArray);
}
for (int fromIndex = 0; fromIndex < fromCount; fromIndex++) {
int toCount = 0;
JsonNode weightsFromArray = null;
double[] weights = null;
if (readWeights) {
weightsFromArray = weightsArray.get(fromIndex);
weights = new double[weightsFromArray.size()];
toCount = checkArraySizes("weights", weightsFromArray.size());
}
JsonNode timesFromArray = null;
long[] times = null;
if (readTimes) {
timesFromArray = timesArray.get(fromIndex);
times = new long[timesFromArray.size()];
toCount = checkArraySizes("times", timesFromArray.size(), weightsFromArray);
}
JsonNode distancesFromArray = null;
int[] distances = null;
if (readDistances) {
distancesFromArray = distancesArray.get(fromIndex);
distances = new int[distancesFromArray.size()];
toCount = checkArraySizes("distances", distancesFromArray.size(), weightsFromArray, timesFromArray);
}
for (int toIndex = 0; toIndex < toCount; toIndex++) {
if (readWeights) {
if (weightsFromArray.get(toIndex).isNull() && !failFast) {
weights[toIndex] = Double.MAX_VALUE;
} else {
weights[toIndex] = weightsFromArray.get(toIndex).asDouble();
}
}
if (readTimes) {
if (timesFromArray.get(toIndex).isNull() && !failFast) {
times[toIndex] = Long.MAX_VALUE;
} else {
times[toIndex] = timesFromArray.get(toIndex).asLong() * 1000;
}
}
if (readDistances) {
if (distancesFromArray.get(toIndex).isNull() && !failFast) {
distances[toIndex] = Integer.MAX_VALUE;
} else {
distances[toIndex] = (int) Math.round(distancesFromArray.get(toIndex).asDouble());
}
}
}
if (readWeights) {
matrixResponse.setWeightRow(fromIndex, weights);
}
if (readTimes) {
matrixResponse.setTimeRow(fromIndex, times);
}
if (readDistances) {
matrixResponse.setDistanceRow(fromIndex, distances);
}
}
if (!failFast && solution.has("hints")) {
addProblems(matrixResponse, solution.get("hints"));
}
}
private void addProblems(MatrixResponse matrixResponse, JsonNode hints) {
for (JsonNode hint : hints) {
if (hint.has("point_pairs")) {
matrixResponse.setDisconnectedPoints(readDisconnectedPoints(hint.get("point_pairs")));
}
if (hint.has("invalid_from_points")) {
matrixResponse.setInvalidFromPoints(readInvalidPoints(hint.get("invalid_from_points")));
matrixResponse.setInvalidToPoints(readInvalidPoints(hint.get("invalid_to_points")));
}
}
}
private List readDisconnectedPoints(JsonNode pointPairsArray) {
List disconnectedPoints = new ArrayList<>(pointPairsArray.size());
for (int i = 0; i < pointPairsArray.size(); i++) {
if (pointPairsArray.get(i).size() != 2) {
throw new IllegalArgumentException("all point_pairs are expected to contain two elements");
}
disconnectedPoints.add(new MatrixResponse.PointPair(
pointPairsArray.get(i).get(0).asInt(),
pointPairsArray.get(i).get(1).asInt()
));
}
return disconnectedPoints;
}
private List readInvalidPoints(JsonNode pointsArray) {
List result = new ArrayList<>(pointsArray.size());
for (int i = 0; i < pointsArray.size(); i++) {
result.add(pointsArray.get(i).asInt());
}
return result;
}
private static int checkArraySizes(String msg, int len, JsonNode... arrays) {
for (JsonNode other : arrays) {
if (len <= 0)
throw new IllegalArgumentException("Size " + len + " of '" + msg + "' array is too small");
if (other != null && len != other.size())
throw new IllegalArgumentException("Size " + len + " of '" + msg + "' array is has to be equal to other arrays but wasn't");
}
return len;
}
protected String buildURLNoHints(String path, GHMRequest ghRequest) {
// allow per request service URLs
String url = ghRequest.getHints().getString(SERVICE_URL, serviceUrl) + path + "?";
String key = ghRequest.getHints().getString(KEY, "");
if (!Helper.isEmpty(key)) {
url += "key=" + key;
}
return url;
}
protected record JsonResult(String body, int statusCode, Map> headers) {
}
protected JsonResult postJson(String url, JsonNode data) throws IOException {
String stringData = data.toString();
Request.Builder builder = new Request.Builder().url(url).post(RequestBody.create(MT_JSON, stringData));
builder.header(X_GH_CLIENT_VERSION, Version.GH_VERSION_FROM_MAVEN);
// force avoiding our GzipRequestInterceptor for smaller requests ~30 locations
if (stringData.length() < maxUnzippedLength)
builder.header("Content-Encoding", "identity");
Request okRequest = builder.build();
ResponseBody body = null;
try {
Response rsp = getDownloader().newCall(okRequest).execute();
body = rsp.body();
return new JsonResult(body.string(), rsp.code(), rsp.headers().toMultimap());
} finally {
Helper.close(body);
}
}
private void putStrings(ObjectNode requestJson, String name, Collection stringList) {
if (stringList == null || stringList.isEmpty())
return;
ArrayNode outList = objectMapper.createArrayNode();
for (String str : stringList) {
outList.add(str);
}
requestJson.putArray(name).addAll(outList);
}
private void putPoints(ObjectNode requestJson, String name, List pList) {
if (pList.isEmpty())
return;
ArrayNode outList = objectMapper.createArrayNode();
for (GHPoint p : pList) {
ArrayNode entry = objectMapper.createArrayNode();
entry.add(p.lon);
entry.add(p.lat);
outList.add(entry);
}
requestJson.putArray(name).addAll(outList);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy