Skip to content

Commit

Permalink
Fix problems with block_area (#1917)
Browse files Browse the repository at this point in the history
* do not use edgeId cache for virtual edges, #1127

* replaced BFS with locationIndex query; speed up: return immediately if edgeIds are known; proper cached edgeId handling if multiple shapes, fixes #1324

* re-enable block_area for /spt and /isochrone, fixes #1905
  • Loading branch information
karussell committed Feb 22, 2020
1 parent b57a55e commit 55408ce
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 151 deletions.
20 changes: 8 additions & 12 deletions core/src/main/java/com/graphhopper/GraphHopper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,9 @@ public List<Path> calcPaths(GHRequest request, GHResponse ghRsp) {
Graph graph = ghStorage;
if (chPreparationHandler.isEnabled() && !disableCH) {
if (algorithmFactory instanceof CHRoutingAlgorithmFactory) {
if (hints.has(Routing.BLOCK_AREA))
throw new IllegalArgumentException("When CH is enabled the " + Parameters.Routing.BLOCK_AREA + " cannot be specified");

CHProfile chProfile = ((CHRoutingAlgorithmFactory) algorithmFactory).getCHProfile();
weighting = chProfile.getWeighting();
graph = ghStorage.getCHGraph(chProfile);
Expand All @@ -1030,7 +1033,8 @@ public List<Path> calcPaths(GHRequest request, GHResponse ghRsp) {
checkNonChMaxWaypointDistance(points);
weighting = createWeighting(hints, encoder, turnCostProvider);
if (hints.has(Routing.BLOCK_AREA))
weighting = new BlockAreaWeighting(weighting, createBlockArea(points, hints, DefaultEdgeFilter.allEdges(encoder)));
weighting = new BlockAreaWeighting(weighting, GraphEdgeIdFinder.createBlockArea(ghStorage, locationIndex,
points, hints, DefaultEdgeFilter.allEdges(encoder)));
}
ghRsp.addDebugInfo("tmode:" + tMode.toString());

Expand All @@ -1049,6 +1053,9 @@ else if (ALT_ROUTE.equalsIgnoreCase(algoStr))
return Collections.emptyList();

QueryGraph queryGraph = QueryGraph.lookup(graph, qResults);
if (weighting instanceof BlockAreaWeighting)
((BlockAreaWeighting) weighting).setQueryGraph(queryGraph);

int maxVisitedNodesForRequest = hints.getInt(Routing.MAX_VISITED_NODES, routingConfig.getMaxVisitedNodes());
if (maxVisitedNodesForRequest > routingConfig.getMaxVisitedNodes())
throw new IllegalArgumentException("The max_visited_nodes parameter has to be below or equal to:" + routingConfig.getMaxVisitedNodes());
Expand Down Expand Up @@ -1112,17 +1119,6 @@ protected ChangeGraphHelper createChangeGraphHelper(Graph graph, LocationIndex l
return new ChangeGraphHelper(graph, locationIndex);
}

private GraphEdgeIdFinder.BlockArea createBlockArea(List<GHPoint> points, HintsMap hints, EdgeFilter edgeFilter) {
String blockAreaStr = hints.get(Parameters.Routing.BLOCK_AREA, "");
GraphEdgeIdFinder.BlockArea blockArea = new GraphEdgeIdFinder(ghStorage, locationIndex).
parseBlockArea(blockAreaStr, edgeFilter, hints.getDouble(Routing.BLOCK_AREA + ".edge_id_max_area", 1000 * 1000));
for (GHPoint p : points) {
if (blockArea.contains(p))
throw new IllegalArgumentException("Request with " + Routing.BLOCK_AREA + " contained query point " + p + ". This is not allowed.");
}
return blockArea;
}

private void checkIfPointsAreInBounds(List<GHPoint> points) {
BBox bounds = ghStorage.getBounds();
for (int i = 0; i < points.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.graphhopper.routing.weighting;

import com.graphhopper.routing.querygraph.QueryGraph;
import com.graphhopper.storage.GraphEdgeIdFinder;
import com.graphhopper.util.EdgeIteratorState;

Expand All @@ -15,6 +16,11 @@ public BlockAreaWeighting(Weighting superWeighting, GraphEdgeIdFinder.BlockArea
this.blockArea = blockArea;
}

public BlockAreaWeighting setQueryGraph(QueryGraph queryGraph) {
blockArea.setQueryGraph(queryGraph);
return this;
}

@Override
public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) {
if (blockArea.intersects(edgeState))
Expand Down
185 changes: 89 additions & 96 deletions core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@
*/
package com.graphhopper.storage;

import com.graphhopper.coll.GHBitSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import com.graphhopper.coll.GHIntHashSet;
import com.graphhopper.coll.GHTBitSet;
import com.graphhopper.routing.querygraph.QueryGraph;
import com.graphhopper.routing.util.EdgeFilter;
import com.graphhopper.routing.util.HintsMap;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.BreadthFirstSearch;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.Parameters;
import com.graphhopper.util.PointList;
import com.graphhopper.util.shapes.Polygon;
import com.graphhopper.util.shapes.*;
import org.locationtech.jts.algorithm.RectangleLineIntersector;
import org.locationtech.jts.geom.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.graphhopper.util.shapes.BBox.toEnvelope;
Expand All @@ -43,6 +44,7 @@
*/
public class GraphEdgeIdFinder {

private final static int P_RADIUS = 5;
private final Graph graph;
private final LocationIndex locationIndex;

Expand All @@ -51,97 +53,53 @@ public GraphEdgeIdFinder(Graph graph, LocationIndex locationIndex) {
this.locationIndex = locationIndex;
}

/**
* This method fills the edgeIds hash with edgeIds found close (exact match) to the specified point
*/
public void findClosestEdgeToPoint(GHIntHashSet edgeIds, GHPoint point, EdgeFilter filter) {
findClosestEdge(edgeIds, point.getLat(), point.getLon(), filter);
}

/**
* This method fills the edgeIds hash with edgeIds found close (exact match) to the specified lat,lon
*/
public void findClosestEdge(GHIntHashSet edgeIds, double lat, double lon, EdgeFilter filter) {
QueryResult qr = locationIndex.findClosest(lat, lon, filter);
if (qr.isValid())
edgeIds.add(qr.getClosestEdge().getEdge());
}

/**
* This method fills the edgeIds hash with edgeIds found inside the specified shape
*/
public void findEdgesInShape(final GHIntHashSet edgeIds, final Shape shape, EdgeFilter filter) {
GHPoint center = shape.getCenter();
QueryResult qr = locationIndex.findClosest(center.getLat(), center.getLon(), filter);
// TODO: this is suboptimal and will be fixed in #1324
if (!qr.isValid())
throw new IllegalArgumentException("Shape '" + shape + "' does not cover graph. Center: " + center);

if (shape.contains(qr.getSnappedPoint().lat, qr.getSnappedPoint().lon))
edgeIds.add(qr.getClosestEdge().getEdge());

final boolean isPolygon = shape instanceof Polygon;

BreadthFirstSearch bfs = new BreadthFirstSearch() {
final NodeAccess na = graph.getNodeAccess();
final Shape localShape = shape;

@Override
protected GHBitSet createBitSet() {
return new GHTBitSet();
}

locationIndex.query(shape.getBounds(), new LocationIndex.EdgeVisitor(graph.createEdgeExplorer(filter)) {
@Override
protected boolean goFurther(int nodeId) {
if (isPolygon) return isInsideBBox(nodeId);

return localShape.contains(na.getLatitude(nodeId), na.getLongitude(nodeId));
}

@Override
protected boolean checkAdjacent(EdgeIteratorState edge) {
int adjNodeId = edge.getAdjNode();

if (localShape.contains(na.getLatitude(adjNodeId), na.getLongitude(adjNodeId))) {
public void onEdge(EdgeIteratorState edge, int nodeA, int nodeB) {
if (shape.intersects(edge.fetchWayGeometry(3).makeImmutable()))
edgeIds.add(edge.getEdge());
return true;
}
return isPolygon && isInsideBBox(adjNodeId);
}

private boolean isInsideBBox(int nodeId) {
BBox bbox = localShape.getBounds();
double lat = na.getLatitude(nodeId);
double lon = na.getLongitude(nodeId);
return lat <= bbox.maxLat && lat >= bbox.minLat && lon <= bbox.maxLon && lon >= bbox.minLon;
}
};
bfs.start(graph.createEdgeExplorer(filter), qr.getClosestNode());
});
}

/**
* This method fills the edgeIds hash with edgeIds found inside the specified geometry
*/
public void fillEdgeIDs(GHIntHashSet edgeIds, Geometry geometry, EdgeFilter filter) {
public void fillEdgeIDs(final GHIntHashSet edgeIds, final Geometry geometry, EdgeFilter filter) {
if (geometry instanceof Point) {
GHPoint point = GHPoint.create((Point) geometry);
findClosestEdgeToPoint(edgeIds, point, filter);
Point p = (Point) geometry;
findEdgesInShape(edgeIds, new Circle(p.getY(), p.getX(), P_RADIUS), filter);
} else if (geometry instanceof LineString) {
PointList pl = PointList.fromLineString((LineString) geometry);
// TODO do map matching or routing
int lastIdx = pl.size() - 1;
if (pl.size() >= 2) {
double meanLat = (pl.getLatitude(0) + pl.getLatitude(lastIdx)) / 2;
double meanLon = (pl.getLongitude(0) + pl.getLongitude(lastIdx)) / 2;
findClosestEdge(edgeIds, meanLat, meanLon, filter);
}
locationIndex.query(BBox.fromEnvelope(geometry.getEnvelopeInternal()), new LocationIndex.EdgeVisitor(graph.createEdgeExplorer(filter)) {
@Override
public void onEdge(EdgeIteratorState edge, int nodeA, int nodeB) {
if (geometry.intersects(edge.fetchWayGeometry(3).toLineString(false)))
edgeIds.add(edge.getEdge());
}
});
} else if (geometry instanceof MultiPoint) {
for (Coordinate coordinate : geometry.getCoordinates()) {
findClosestEdge(edgeIds, coordinate.y, coordinate.x, filter);
findEdgesInShape(edgeIds, new Circle(coordinate.y, coordinate.x, P_RADIUS), filter);
}
}
}

public static GraphEdgeIdFinder.BlockArea createBlockArea(Graph graph, LocationIndex locationIndex,
List<GHPoint> points, HintsMap hints, EdgeFilter edgeFilter) {
String blockAreaStr = hints.get(Parameters.Routing.BLOCK_AREA, "");
GraphEdgeIdFinder.BlockArea blockArea = new GraphEdgeIdFinder(graph, locationIndex).
parseBlockArea(blockAreaStr, edgeFilter, hints.getDouble(Parameters.Routing.BLOCK_AREA + ".edge_id_max_area", 1000 * 1000));
for (GHPoint p : points) {
if (blockArea.contains(p))
throw new IllegalArgumentException("Request with " + Parameters.Routing.BLOCK_AREA + " contained query point " + p + ". This is not allowed.");
}
return blockArea;
}

/**
* This method reads the blockAreaString and creates a Collection of Shapes or a set of found edges if area is small enough.
*
Expand All @@ -162,9 +120,9 @@ public BlockArea parseBlockArea(String blockAreaString, EdgeFilter filter, doubl
// always add the shape as we'll need this for virtual edges and for debugging.
if (splittedObject.length > 4) {
final Polygon polygon = Polygon.parsePoints(objectAsString);
blockArea.add(polygon);
GHIntHashSet blockedEdges = blockArea.add(polygon);
if (polygon.calculateArea() <= useEdgeIdsUntilAreaSize)
findEdgesInShape(blockArea.blockedEdges, polygon, filter);
findEdgesInShape(blockedEdges, polygon, filter);
} else if (splittedObject.length == 4) {
final BBox bbox = BBox.parseTwoPoints(objectAsString);
final RectangleLineIntersector cachedIntersector = new RectangleLineIntersector(toEnvelope(bbox));
Expand All @@ -174,22 +132,24 @@ public boolean intersects(PointList pointList) {
return BBox.intersects(cachedIntersector, pointList);
}
};
blockArea.add(preparedBBox);
GHIntHashSet blockedEdges = blockArea.add(preparedBBox);
if (bbox.calculateArea() <= useEdgeIdsUntilAreaSize)
findEdgesInShape(blockArea.blockedEdges, preparedBBox, filter);
findEdgesInShape(blockedEdges, preparedBBox, filter);
} else if (splittedObject.length == 3) {
double lat = Double.parseDouble(splittedObject[0]);
double lon = Double.parseDouble(splittedObject[1]);
int radius = Integer.parseInt(splittedObject[2]);
Circle circle = new Circle(lat, lon, radius);
blockArea.add(circle);
GHIntHashSet blockedEdges = blockArea.add(circle);
if (circle.calculateArea() <= useEdgeIdsUntilAreaSize)
findEdgesInShape(blockArea.blockedEdges, circle, filter);
findEdgesInShape(blockedEdges, circle, filter);

} else if (splittedObject.length == 2) {
double lat = Double.parseDouble(splittedObject[0]);
double lon = Double.parseDouble(splittedObject[1]);
findClosestEdge(blockArea.blockedEdges, lat, lon, filter);
Circle circle = new Circle(lat, lon, P_RADIUS);
GHIntHashSet blockedEdges = blockArea.add(circle);
findEdgesInShape(blockedEdges, circle, filter);
} else {
throw new IllegalArgumentException(objectAsString + " at index " + i + " need to be defined as lat,lon "
+ "or as a circle lat,lon,radius or rectangular lat1,lon1,lat2,lon2");
Expand All @@ -203,20 +163,35 @@ public boolean intersects(PointList pointList) {
* This class handles edges and areas where access should be blocked.
*/
public static class BlockArea {
final GHIntHashSet blockedEdges = new GHIntHashSet();
final List<Shape> blockedShapes = new ArrayList<>();
private final NodeAccess na;
private final List<GHIntHashSet> edgesList = new ArrayList<>();
private final List<Shape> blockedShapes = new ArrayList<>();
private NodeAccess na;
private final int baseEdgeCount;
private boolean prepared;

public BlockArea(Graph g) {
baseEdgeCount = g.getAllEdges().length();
na = g.getNodeAccess();
}

public void add(int edgeId) {
blockedEdges.addAll(edgeId);
public boolean hasCachedEdgeIds(int shapeIndex) {
return !edgesList.get(shapeIndex).isEmpty();
}

public String toString(int shapeIndex) {
List<Integer> returnList = new ArrayList<>();
for (IntCursor intCursor : edgesList.get(shapeIndex)) {
returnList.add(intCursor.value);
}
Collections.sort(returnList);
return returnList.toString();
}

public void add(Shape shape) {
public GHIntHashSet add(Shape shape) {
blockedShapes.add(shape);
GHIntHashSet set = new GHIntHashSet();
edgesList.add(set);
return set;
}

public final boolean contains(GHPoint point) {
Expand All @@ -231,15 +206,19 @@ public final boolean contains(GHPoint point) {
* @return true if the specified edgeState is part of this BlockArea
*/
public final boolean intersects(EdgeIteratorState edgeState) {
if (!blockedEdges.isEmpty() && blockedEdges.contains(edgeState.getEdge())) {
return true;
}

// compromise: mostly avoid expensive fetchWayGeometry which isn't yet fast for being used in Weighting.calc
BBox bbox = BBox.fromPoints(na.getLatitude(edgeState.getBaseNode()), na.getLongitude(edgeState.getBaseNode()),
na.getLatitude(edgeState.getAdjNode()), na.getLongitude(edgeState.getAdjNode()));
PointList pointList = null;
for (Shape shape : blockedShapes) {
for (int shapeIdx = 0; shapeIdx < blockedShapes.size(); shapeIdx++) {
GHIntHashSet blockedEdges = edgesList.get(shapeIdx);
// blockedEdges acts as cache that is only useful when filled and for non-virtual edges
if (!blockedEdges.isEmpty() && edgeState.getEdge() < baseEdgeCount) {
if (blockedEdges.contains(edgeState.getEdge()))
return true;
continue;
}

// compromise: mostly avoid expensive fetchWayGeometry which isn't yet fast for being used in Weighting.calc
BBox bbox = createBBox(na, edgeState);
Shape shape = blockedShapes.get(shapeIdx);
if (shape.getBounds().intersects(bbox)) {
if (pointList == null)
pointList = edgeState.fetchWayGeometry(3).makeImmutable();
Expand All @@ -249,5 +228,19 @@ public final boolean intersects(EdgeIteratorState edgeState) {
}
return false;
}

public BlockArea setQueryGraph(QueryGraph queryGraph) {
if (prepared)
throw new IllegalStateException("setGraph cannot be called multiple times");

prepared = true;
na = queryGraph.getNodeAccess();
return this;
}
}

private static BBox createBBox(NodeAccess na, EdgeIteratorState edgeState) {
return BBox.fromPoints(na.getLatitude(edgeState.getBaseNode()), na.getLongitude(edgeState.getBaseNode()),
na.getLatitude(edgeState.getAdjNode()), na.getLongitude(edgeState.getAdjNode()));
}
}
Loading

0 comments on commit 55408ce

Please sign in to comment.