diff --git a/ors-api/src/main/java/org/heigit/ors/api/requests/export/ExportApiRequest.java b/ors-api/src/main/java/org/heigit/ors/api/requests/export/ExportApiRequest.java index 9de239c205..9a22282edf 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/requests/export/ExportApiRequest.java +++ b/ors-api/src/main/java/org/heigit/ors/api/requests/export/ExportApiRequest.java @@ -18,8 +18,7 @@ public class ExportApiRequest extends APIRequest { public static final String PARAM_PROFILE = "profile"; public static final String PARAM_FORMAT = "format"; public static final String PARAM_GEOMETRY = "geometry"; - - public static final String PARAM_DEBUG = "debug"; + public static final String PARAM_ADDITIONAL_INFO = "additional_info"; @Schema(name = PARAM_ID, description = "Arbitrary identification string of the request reflected in the meta information.", example = "export_request") @@ -45,9 +44,9 @@ public class ExportApiRequest extends APIRequest { @JsonProperty(PARAM_GEOMETRY) private boolean geometry = true; - @Schema(name = PARAM_DEBUG, hidden = true) - @JsonProperty(PARAM_DEBUG) - private boolean debug; + @Schema(name = PARAM_ADDITIONAL_INFO, hidden = true) + @JsonProperty(PARAM_ADDITIONAL_INFO) + private boolean additionalInfo; @JsonCreator public ExportApiRequest(@JsonProperty(value = PARAM_BBOX, required = true) List> bbox) { @@ -67,8 +66,8 @@ public void setId(String id) { this.hasId = true; } - public boolean debug() { - return debug; + public boolean additionalInfo() { + return additionalInfo; } public List> getBbox() { diff --git a/ors-api/src/main/java/org/heigit/ors/api/responses/export/topojson/TopoJsonExportResponse.java b/ors-api/src/main/java/org/heigit/ors/api/responses/export/topojson/TopoJsonExportResponse.java index 0e25b8b328..4729c16044 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/responses/export/topojson/TopoJsonExportResponse.java +++ b/ors-api/src/main/java/org/heigit/ors/api/responses/export/topojson/TopoJsonExportResponse.java @@ -47,7 +47,7 @@ public static TopoJsonExportResponse fromExportResult(ExportResult exportResult) for (Map.Entry, Double> edgeWeight : exportResult.getEdgeWeights().entrySet()) { Pair fromTo = edgeWeight.getKey(); - LineString lineString = (LineString) exportResult.getEdgeExtras().get(fromTo).get("geometry"); + LineString lineString = exportResult.getEdgeGeometries().get(fromTo); arcsLocal.add(Arc.builder().coordinates(makeCoordinateList(lineString, bbox)).build()); arcCount++; diff --git a/ors-api/src/main/java/org/heigit/ors/api/services/ExportService.java b/ors-api/src/main/java/org/heigit/ors/api/services/ExportService.java index 1b79b58a13..e47cfd9fd1 100644 --- a/ors-api/src/main/java/org/heigit/ors/api/services/ExportService.java +++ b/ors-api/src/main/java/org/heigit/ors/api/services/ExportService.java @@ -59,7 +59,7 @@ private org.heigit.ors.export.ExportRequest convertExportRequest(ExportApiReques } exportRequest.setBoundingBox(convertBBox(exportApiRequest.getBbox())); - exportRequest.setDebug(exportApiRequest.debug()); + exportRequest.setAdditionalEdgeInfo(exportApiRequest.additionalInfo()); exportRequest.setTopoJson(exportApiRequest.getResponseType().equals(APIEnums.ExportResponseType.TOPOJSON)); exportRequest.setUseRealGeometry(exportApiRequest.getGeometry()); diff --git a/ors-api/src/test/java/org/heigit/ors/apitests/export/ParamsTest.java b/ors-api/src/test/java/org/heigit/ors/apitests/export/ParamsTest.java index b689f3e19f..eb0c5368e0 100644 --- a/ors-api/src/test/java/org/heigit/ors/apitests/export/ParamsTest.java +++ b/ors-api/src/test/java/org/heigit/ors/apitests/export/ParamsTest.java @@ -150,6 +150,7 @@ void expectNodesAndEdges() { .body("containsKey('edges')", is(true)) .body("containsKey('nodes_count')", is(true)) .body("containsKey('edges_count')", is(true)) + .body("containsKey('edges_extra')", is(false)) .statusCode(200); } @@ -246,4 +247,27 @@ void expectTopoJsonFallbackNoOsmIdMode() { .body("objects.network.geometries[0].properties.containsKey('weight')", is(true)) .statusCode(200); } + + @Test + void expectAdditionalInfo() { + JSONObject body = new JSONObject(); + body.put("bbox", getParameter("bboxProper")); + body.put("additional_info", true); + given() + .headers(jsonContent) + .pathParam("profile", "wheelchair") + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/json") + .then().log().ifValidationFails() + .assertThat() + .body("containsKey('nodes')", is(true)) + .body("containsKey('edges')", is(true)) + .body("containsKey('edges_extra')", is(true)) + .body("edges_extra[0].extra.containsKey('osm_id')", is(true)) + .body("edges_extra[0].extra.containsKey('ors_id')", is(true)) + .body("nodes_count", is(59)) + .body("edges_count", is(128)) + .statusCode(200); + } } diff --git a/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java b/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java index 45dd469b80..8f848a1c9c 100644 --- a/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java +++ b/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java @@ -39,11 +39,14 @@ public class ExportRequest extends ServiceRequest { private String profileName; private int profileType = -1; - private boolean debug; + private boolean additionalEdgeInfo; private boolean topoJson; private boolean useRealGeometry; private static final int NO_TIME = -1; + private OsmIdGraphStorage osmIdGraphStorage; + private WheelchairAttributesGraphStorage wheelchairAttributesGraphStorage; + private Weighting weighting; public String getProfileName() { return profileName; @@ -53,24 +56,16 @@ public void setProfileName(String profileName) { this.profileName = profileName; } - public BBox getBoundingBox() { - return this.boundingBox; - } - public void setBoundingBox(BBox bbox) { this.boundingBox = bbox; } - public int getProfileType() { - return profileType; - } - public void setProfileType(int profileType) { this.profileType = profileType; } - public void setDebug(boolean debug) { - this.debug = debug; + public void setAdditionalEdgeInfo(boolean additionalEdgeInfo) { + this.additionalEdgeInfo = additionalEdgeInfo; } public void setTopoJson(boolean equals) { @@ -84,113 +79,108 @@ public void setUseRealGeometry(boolean useRealGeometry) { public ExportResult computeExport(RoutingProfile routingProfile) { ExportResult res = new ExportResult(); + // Prepare graph data access GraphHopper gh = routingProfile.getGraphhopper(); - String encoderName = RoutingProfileType.getEncoderName(getProfileType()); + String encoderName = RoutingProfileType.getEncoderName(profileType); Graph graph = gh.getGraphHopperStorage().getBaseGraph(); - + NodeAccess nodeAccess = graph.getNodeAccess(); PMap hintsMap = new PMap(); - int weightingMethod = WeightingMethod.FASTEST; - ProfileTools.setWeightingMethod(hintsMap, weightingMethod, getProfileType(), false); - String localProfileName = ProfileTools.makeProfileName(encoderName, hintsMap.getString("weighting_method", ""), false); - Weighting weighting = gh.createWeighting(gh.getProfile(localProfileName), hintsMap); - - FlagEncoder flagEncoder = gh.getEncodingManager().getEncoder(encoderName); - EdgeExplorer explorer = graph.createEdgeExplorer(AccessFilter.outEdges(flagEncoder.getAccessEnc())); + ProfileTools.setWeightingMethod(hintsMap, WeightingMethod.FASTEST, profileType, false); + weighting = gh.createWeighting(gh.getProfile(ProfileTools.makeProfileName(encoderName, hintsMap.getString("weighting_method", ""), false)), hintsMap); + osmIdGraphStorage = GraphStorageUtils.getGraphExtension(gh.getGraphHopperStorage(), OsmIdGraphStorage.class); + wheelchairAttributesGraphStorage = GraphStorageUtils.getGraphExtension(gh.getGraphHopperStorage(), WheelchairAttributesGraphStorage.class); // filter graph for nodes in Bounding Box - LocationIndex index = gh.getLocationIndex(); - NodeAccess nodeAccess = graph.getNodeAccess(); - - Set nodesInBBox = new HashSet<>(); - index.query(boundingBox, edgeId -> { - // According to GHUtility.getEdgeFromEdgeKey, edgeIds are calculated as edgeKey/2. - EdgeIteratorState edge = graph.getEdgeIteratorStateForKey(edgeId * 2); - int baseNode = edge.getBaseNode(); - int adjNode = edge.getAdjNode(); - if (boundingBox.contains(nodeAccess.getLat(baseNode), nodeAccess.getLon(baseNode))) { - nodesInBBox.add(baseNode); - } - if (boundingBox.contains(nodeAccess.getLat(adjNode), nodeAccess.getLon(adjNode))) { - nodesInBBox.add(adjNode); - } - }); - + Set nodesInBBox = nodesInBBox(gh.getLocationIndex(), nodeAccess, graph); LOGGER.debug("Found %d nodes in bbox.".formatted(nodesInBBox.size())); - - if (nodesInBBox.isEmpty()) { - // without nodes, no export can be calculated + if (nodesInBBox.isEmpty()) { // without nodes, no export can be calculated res.setWarning(new ExportWarning(ExportWarning.EMPTY_BBOX)); return res; } - Map topoGeometries = res.getTopoGeometries(); - OsmIdGraphStorage osmIdGraphStorage = GraphStorageUtils.getGraphExtension(gh.getGraphHopperStorage(), OsmIdGraphStorage.class); - boolean osmIdsAvailable = osmIdGraphStorage != null; - // calculate node coordinates + // iterate over all edges and add them to the result object for (int from : nodesInBBox) { Coordinate fromCoords = new Coordinate(nodeAccess.getLon(from), nodeAccess.getLat(from)); res.addLocation(from, fromCoords); - EdgeIterator iter = explorer.setBaseNode(from); + EdgeIterator iter = graph.createEdgeExplorer(AccessFilter.outEdges(gh.getEncodingManager().getEncoder(encoderName).getAccessEnc())).setBaseNode(from); while (iter.next()) { int to = iter.getAdjNode(); if (nodesInBBox.contains(to)) { - Pair p = new Pair<>(from, to); - Map extra = new HashMap<>(); - res.addEdge(p, weighting.calcEdgeWeight(iter, false, NO_TIME)); LOGGER.debug("Edge %d: from %d to %d".formatted(iter.getEdge(), from, to)); - - if (topoJson) { - LineString geo; - if (useRealGeometry) { - geo = iter.fetchWayGeometry(FetchMode.ALL).toLineString(false); - } else { - Coordinate toCoords = new Coordinate(nodeAccess.getLon(to), nodeAccess.getLat(to)); - geo = geometryFactory.createLineString(new Coordinate[]{fromCoords, toCoords}); - } - if (osmIdsAvailable) { - boolean reverse = iter.getEdgeKey() % 2 == 1; - TopoGeometry topoGeometry = topoGeometries.computeIfAbsent(osmIdGraphStorage.getEdgeValue(iter.getEdge()), x -> - new TopoGeometry(weighting.getSpeedCalculator().getSpeed(iter, reverse, NO_TIME), - weighting.getSpeedCalculator().getSpeed(iter, !reverse, NO_TIME)) - ); - topoGeometry.getArcs().compute(iter.getEdge(), (k, v) -> { - if (v != null) { - topoGeometry.setBothDirections(true); - return v; - } else { - return reverse ? new TopoArc(geo.reverse(), iter.getDistance(), iter.getAdjNode(), iter.getBaseNode()) : - new TopoArc(geo, iter.getDistance(), iter.getBaseNode(), iter.getAdjNode()); - } - }); - } else { - extra.put("geometry", geo); - } - } - - if (debug) { - extra.put("edge_id", iter.getEdge()); - WheelchairAttributesGraphStorage storage = GraphStorageUtils.getGraphExtension(gh.getGraphHopperStorage(), WheelchairAttributesGraphStorage.class); - if (storage != null) { - WheelchairAttributes attributes = new WheelchairAttributes(); - byte[] buffer = new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]; - storage.getEdgeValues(iter.getEdge(), attributes, buffer); - if (attributes.hasValues()) { - extra.put("incline", attributes.getIncline()); - extra.put("surface_quality_known", attributes.isSurfaceQualityKnown()); - extra.put("suitable", attributes.isSuitable()); - } - } - if (osmIdsAvailable) { - extra.put("osm_id", osmIdGraphStorage.getEdgeValue(iter.getEdge())); - } + LineString geo; + if (useRealGeometry) { + geo = iter.fetchWayGeometry(FetchMode.ALL).toLineString(false); + } else { + Coordinate toCoords = new Coordinate(nodeAccess.getLon(to), nodeAccess.getLat(to)); + geo = geometryFactory.createLineString(new Coordinate[]{fromCoords, toCoords}); } - if (!extra.isEmpty()) { - res.addEdgeExtra(p, extra); + if (topoJson && osmIdGraphStorage != null) { + addEdgeToTopoGeometries(res, iter, geo); + } else { + addEdgeToResultObject(res, iter, geo, from, to); } } } } return res; } + + private Set nodesInBBox(LocationIndex index, NodeAccess nodeAccess, Graph graph) { + Set ret = new HashSet<>(); + index.query(boundingBox, edgeId -> { + // According to GHUtility.getEdgeFromEdgeKey, edgeIds are calculated as edgeKey/2. + EdgeIteratorState edge = graph.getEdgeIteratorStateForKey(edgeId * 2); + int baseNode = edge.getBaseNode(); + int adjNode = edge.getAdjNode(); + if (this.boundingBox.contains(nodeAccess.getLat(baseNode), nodeAccess.getLon(baseNode))) { + ret.add(baseNode); + } + if (this.boundingBox.contains(nodeAccess.getLat(adjNode), nodeAccess.getLon(adjNode))) { + ret.add(adjNode); + } + }); + return ret; + } + + private void addEdgeToTopoGeometries(ExportResult res, EdgeIterator iter, LineString geo) { + boolean reverse = iter.getEdgeKey() % 2 == 1; + TopoGeometry topoGeometry = res.getTopoGeometries().computeIfAbsent(osmIdGraphStorage.getEdgeValue(iter.getEdge()), x -> + new TopoGeometry(weighting.getSpeedCalculator().getSpeed(iter, reverse, NO_TIME), + weighting.getSpeedCalculator().getSpeed(iter, !reverse, NO_TIME)) + ); + topoGeometry.getArcs().compute(iter.getEdge(), (k, v) -> { + if (v != null) { + topoGeometry.setBothDirections(true); + return v; + } else { + return reverse ? new TopoArc(geo.reverse(), iter.getDistance(), iter.getAdjNode(), iter.getBaseNode()) : + new TopoArc(geo, iter.getDistance(), iter.getBaseNode(), iter.getAdjNode()); + } + }); + } + + private void addEdgeToResultObject(ExportResult res, EdgeIterator iter, LineString geo, int from, int to) { + Pair p = new Pair<>(from, to); + res.addEdge(p, weighting.calcEdgeWeight(iter, false, NO_TIME)); + res.getEdgeGeometries().put(p, geo); + if (additionalEdgeInfo) { + Map extra = new HashMap<>(); + if (osmIdGraphStorage != null) { + extra.put("osm_id", osmIdGraphStorage.getEdgeValue(iter.getEdge())); + } + extra.put("ors_id", iter.getEdge()); + if (wheelchairAttributesGraphStorage != null) { + WheelchairAttributes attributes = new WheelchairAttributes(); + byte[] buffer = new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]; + wheelchairAttributesGraphStorage.getEdgeValues(iter.getEdge(), attributes, buffer); + if (attributes.hasValues()) { + extra.put("incline", attributes.getIncline()); + extra.put("surface_quality_known", attributes.isSurfaceQualityKnown()); + extra.put("suitable", attributes.isSuitable()); + } + } + res.addEdgeExtra(p, extra); + } + } } diff --git a/ors-engine/src/main/java/org/heigit/ors/export/ExportResult.java b/ors-engine/src/main/java/org/heigit/ors/export/ExportResult.java index 78e534c515..a1494be928 100644 --- a/ors-engine/src/main/java/org/heigit/ors/export/ExportResult.java +++ b/ors-engine/src/main/java/org/heigit/ors/export/ExportResult.java @@ -13,6 +13,7 @@ public class ExportResult { private final Map locations; private final Map, Double> edgeWeights; + private final Map, LineString> edgeGeometries; private final Map topoGeometries; private Map, Map> edgeExtras; @Setter @@ -22,6 +23,7 @@ public class ExportResult { public ExportResult() { this.locations = new HashMap<>(); this.edgeWeights = new HashMap<>(); + this.edgeGeometries = new HashMap<>(); this.topoGeometries = new HashMap<>(); this.warning = null; }