Repository: graphhopper/graphhopper
Pull request #1853 (2020-01-17 13:34:01)

Replace our polygon code with the JTS implementation
I would like to have this before #1838. 

This PR does the following:

 * avoids our custom Polygon code and uses the JTS implementation which should make the implementation more robust and make it working for more generic polygons like with inner rings
 * it additionally improves the precision of the block_area feature through a new `Shape.intersects(PointList)` method. Now not just a single point of an edge is checked for intersection but the whole point list. Still there is more work (later) #1324.
 * I discovered PackedCoordinateSequence that allows us to be memory efficient with JTS too
 * I've introduced QuerySettings. Not sure if this should be done here, but the parameters for the printTimeOfRouteQuery method got more and more confusing. E.g. recent changes like https://github.com/graphhopper/graphhopper/commit/f69f127434e2bc18a273284d4c9b8985de88353c#diff-cd749cc545f8b84137c2b75b2d174b72 are much easier to understand.


Patch:
diff --git a/api/src/main/java/com/graphhopper/util/Helper.java b/api/src/main/java/com/graphhopper/util/Helper.java
index 48c3aa1d1..60b3b32c6 100644
--- a/api/src/main/java/com/graphhopper/util/Helper.java
+++ b/api/src/main/java/com/graphhopper/util/Helper.java
@@ -294,11 +294,10 @@ public class Helper {
      * Converts into an integer to be compatible with the still limited DataAccess class (accepts
      * only integer values). But this conversion also reduces memory consumption where the precision
      * loss is acceptable. As +- 180° and +-90° are assumed as maximum values.
-     * <p>
      *
      * @return the integer of the specified degree
      */
-    public static final int degreeToInt(double deg) {
+    public static int degreeToInt(double deg) {
         if (deg >= Double.MAX_VALUE)
             return Integer.MAX_VALUE;
         if (deg <= -Double.MAX_VALUE)
@@ -308,11 +307,10 @@ public class Helper {
 
     /**
      * Converts back the integer value.
-     * <p>
      *
      * @return the degree value of the specified integer
      */
-    public static final double intToDegree(int storedInt) {
+    public static double intToDegree(int storedInt) {
         if (storedInt == Integer.MAX_VALUE)
             return Double.MAX_VALUE;
         if (storedInt == -Integer.MAX_VALUE)
@@ -323,7 +321,7 @@ public class Helper {
     /**
      * Converts elevation value (in meters) into integer for storage.
      */
-    public static final int eleToInt(double ele) {
+    public static int eleToInt(double ele) {
         if (ele >= Integer.MAX_VALUE)
             return Integer.MAX_VALUE;
         return (int) (ele * ELE_FACTOR);
@@ -333,7 +331,7 @@ public class Helper {
      * Converts the integer value retrieved from storage into elevation (in meters). Do not expect
      * more precision than meters although it currently is!
      */
-    public static final double intToEle(int integEle) {
+    public static double intToEle(int integEle) {
         if (integEle == Integer.MAX_VALUE)
             return Double.MAX_VALUE;
         return integEle / ELE_FACTOR;
@@ -357,7 +355,7 @@ public class Helper {
     /**
      * This methods returns the value or min if too small or max if too big.
      */
-    public static final double keepIn(double value, double min, double max) {
+    public static double keepIn(double value, double min, double max) {
         return Math.max(min, Math.min(value, max));
     }
 
@@ -369,15 +367,15 @@ public class Helper {
         return Math.round(value * factor) / factor;
     }
 
-    public static final double round6(double value) {
+    public static double round6(double value) {
         return Math.round(value * 1e6) / 1e6;
     }
 
-    public static final double round4(double value) {
+    public static double round4(double value) {
         return Math.round(value * 1e4) / 1e4;
     }
 
-    public static final double round2(double value) {
+    public static double round2(double value) {
         return Math.round(value * 100) / 100d;
     }
 
@@ -402,18 +400,18 @@ public class Helper {
      * This method handles the specified (potentially negative) int as unsigned bit representation
      * and returns the positive converted long.
      */
-    public static final long toUnsignedLong(int x) {
+    public static long toUnsignedLong(int x) {
         return ((long) x) & 0xFFFFffffL;
     }
 
     /**
      * Converts the specified long back into a signed int (reverse method for toUnsignedLong)
      */
-    public static final int toSignedInt(long x) {
+    public static int toSignedInt(long x) {
         return (int) x;
     }
 
-    public static final String camelCaseToUnderScore(String key) {
+    public static String camelCaseToUnderScore(String key) {
         if (key.isEmpty())
             return key;
 
@@ -429,7 +427,7 @@ public class Helper {
         return sb.toString();
     }
 
-    public static final String underScoreToCamelCase(String key) {
+    public static String underScoreToCamelCase(String key) {
         if (key.isEmpty())
             return key;
 
diff --git a/api/src/main/java/com/graphhopper/util/PointList.java b/api/src/main/java/com/graphhopper/util/PointList.java
index 53fed3c0b..69aec762e 100644
--- a/api/src/main/java/com/graphhopper/util/PointList.java
+++ b/api/src/main/java/com/graphhopper/util/PointList.java
@@ -22,11 +22,15 @@ import com.graphhopper.util.shapes.GHPoint3D;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
 
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import static com.graphhopper.util.Helper.round2;
+import static com.graphhopper.util.Helper.round6;
+
 /**
  * Slim list to store several points (without the need for a point object). Be aware that the PointList is closely
  * coupled with the {@link ShallowImmutablePointList} both are not designed for inheritance (but final is not possible if we keep it simple).
@@ -34,6 +38,8 @@ import java.util.NoSuchElementException;
  * @author Peter Karich
  */
 public class PointList implements Iterable<GHPoint3D>, PointAccess {
+    // should be thread-safe according to https://github.com/locationtech/jts/issues/512
+    private static final GeometryFactory factory = new GeometryFactory();
     public static final PointList EMPTY = new PointList(0, true) {
         @Override
         public void set(int index, double lat, double lon, double ele) {
@@ -154,6 +160,7 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
     private double[] longitudes;
     private double[] elevations;
     private boolean isImmutable = false;
+    private LineString cachedLineString;
 
     public PointList() {
         this(10, false);
@@ -399,7 +406,6 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
     }
 
     public LineString toLineString(boolean includeElevation) {
-        GeometryFactory gf = new GeometryFactory();
         Coordinate[] coordinates = new Coordinate[getSize() == 1 ? 2 : getSize()];
         for (int i = 0; i < getSize(); i++) {
             coordinates[i] = includeElevation ?
@@ -415,15 +421,15 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
         // special case as just 1 point is not supported in the specification #1412
         if (getSize() == 1)
             coordinates[1] = coordinates[0];
-        return gf.createLineString(coordinates);
-    }
-
-    public static final double round6(double value) {
-        return Math.round(value * 1e6) / 1e6;
+        return factory.createLineString(new PackedCoordinateSequence.Double(coordinates, includeElevation ? 3 : 2));
     }
 
-    public static final double round2(double value) {
-        return Math.round(value * 100) / 100d;
+    public LineString getCachedLineString(boolean includeElevation) {
+        if (cachedLineString != null)
+            return cachedLineString;
+        if (!isImmutable)
+            throw new IllegalArgumentException("Make PointList immutable before calling getCachedLineString");
+        return cachedLineString = toLineString(includeElevation);
     }
 
     @Override
@@ -622,8 +628,9 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
      * Once immutable, there is no way to make this object mutable again. This is done to ensure the consistency of
      * shallow copies. If you need to modify this object again, you have to create a deep copy of it.
      */
-    public void makeImmutable() {
+    public PointList makeImmutable() {
         this.isImmutable = true;
+        return this;
     }
 
     private void ensureMutability() {
diff --git a/api/src/main/java/com/graphhopper/util/ShallowImmutablePointList.java b/api/src/main/java/com/graphhopper/util/ShallowImmutablePointList.java
index 5902e3860..dafd24954 100644
--- a/api/src/main/java/com/graphhopper/util/ShallowImmutablePointList.java
+++ b/api/src/main/java/com/graphhopper/util/ShallowImmutablePointList.java
@@ -87,8 +87,9 @@ public final class ShallowImmutablePointList extends PointList {
         wrappedPointList.setElevation(fromOffset + index, ele);
     }
 
-    public void makeImmutable() {
+    public PointList makeImmutable() {
         this.wrappedPointList.makeImmutable();
+        return this;
     }
 
     @Override
diff --git a/api/src/main/java/com/graphhopper/util/shapes/BBox.java b/api/src/main/java/com/graphhopper/util/shapes/BBox.java
index 9a73055f9..76f4d7b80 100644
--- a/api/src/main/java/com/graphhopper/util/shapes/BBox.java
+++ b/api/src/main/java/com/graphhopper/util/shapes/BBox.java
@@ -19,6 +19,9 @@ package com.graphhopper.util.shapes;
 
 import com.graphhopper.util.Helper;
 import com.graphhopper.util.NumHelper;
+import com.graphhopper.util.PointList;
+import org.locationtech.jts.algorithm.RectangleLineIntersector;
+import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Envelope;
 
 import java.util.ArrayList;
@@ -161,6 +164,29 @@ public class BBox implements Shape, Cloneable {
         throw new UnsupportedOperationException("unsupported shape");
     }
 
+    public static boolean intersects(RectangleLineIntersector intersector, PointList pointList) {
+        int len = pointList.getSize();
+        if (len == 0)
+            throw new IllegalArgumentException("PointList must not be empty");
+
+        Coordinate coords = new Coordinate(pointList.getLongitude(0), pointList.getLatitude(0));
+        if (len == 1)
+            return intersector.intersects(coords, coords);
+
+        for (int pointIndex = 1; pointIndex < len; pointIndex++) {
+            Coordinate nextCoords = new Coordinate(pointList.getLongitude(pointIndex), pointList.getLatitude(pointIndex));
+            if (intersector.intersects(coords, nextCoords))
+                return true;
+            coords = nextCoords;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean intersects(PointList pointList) {
+        return intersects(new RectangleLineIntersector(toEnvelope(this)), pointList);
+    }
+
     public boolean intersects(Circle s) {
         return s.intersects(this);
     }
@@ -287,6 +313,10 @@ public class BBox implements Shape, Cloneable {
         return new BBox(envelope.getMinX(), envelope.getMaxX(), envelope.getMinY(), envelope.getMaxY());
     }
 
+    public static Envelope toEnvelope(BBox bbox) {
+        return new Envelope(bbox.minLon, bbox.maxLon, bbox.minLat, bbox.maxLat);
+    }
+
     /**
      * @return an estimated area in m^2 using the mean value of latitudes for longitude distance
      */
@@ -312,20 +342,23 @@ public class BBox implements Shape, Cloneable {
 
         double maxLat = Double.parseDouble(splittedObject[2]);
         double maxLon = Double.parseDouble(splittedObject[3]);
+        return BBox.fromPoints(minLat, minLon, maxLat, maxLon);
+    }
 
-        if (minLat > maxLat) {
-            double tmp = minLat;
-            minLat = maxLat;
-            maxLat = tmp;
+    public static BBox fromPoints(double lat1, double lon1, double lat2, double lon2) {
+        if (lat1 > lat2) {
+            double tmp = lat1;
+            lat1 = lat2;
+            lat2 = tmp;
         }
 
-        if (minLon > maxLon) {
-            double tmp = minLon;
-            minLon = maxLon;
-            maxLon = tmp;
+        if (lon1 > lon2) {
+            double tmp = lon1;
+            lon1 = lon2;
+            lon2 = tmp;
         }
 
-        return new BBox(minLon, maxLon, minLat, maxLat);
+        return new BBox(lon1, lon2, lat1, lat2);
     }
 
     /**
diff --git a/api/src/main/java/com/graphhopper/util/shapes/Circle.java b/api/src/main/java/com/graphhopper/util/shapes/Circle.java
index bbb38ee33..738d91c12 100644
--- a/api/src/main/java/com/graphhopper/util/shapes/Circle.java
+++ b/api/src/main/java/com/graphhopper/util/shapes/Circle.java
@@ -20,6 +20,7 @@ package com.graphhopper.util.shapes;
 import com.graphhopper.util.DistanceCalc;
 import com.graphhopper.util.Helper;
 import com.graphhopper.util.NumHelper;
+import com.graphhopper.util.PointList;
 
 /**
  * @author Peter Karich
@@ -30,7 +31,7 @@ public class Circle implements Shape {
     private final double lon;
     private final double normedDist;
     private final BBox bbox;
-    private DistanceCalc calc = Helper.DIST_EARTH;
+    private DistanceCalc calc;
 
     public Circle(double lat, double lon, double radiusInMeter) {
         this(lat, lon, radiusInMeter, Helper.DIST_EARTH);
@@ -83,6 +84,36 @@ public class Circle implements Shape {
         return o.intersects(this);
     }
 
+    @Override
+    public boolean intersects(PointList pointList) {
+        // similar code to LocationIndexTree.checkAdjacent
+        int len = pointList.getSize();
+        if (len == 0)
+            throw new IllegalArgumentException("PointList must not be empty");
+
+        double tmpLat = pointList.getLatitude(0);
+        double tmpLon = pointList.getLongitude(0);
+        if (len == 1)
+            return calc.calcNormalizedDist(lat, lon, tmpLat, tmpLon) <= normedDist;
+
+        for (int pointIndex = 1; pointIndex < len; pointIndex++) {
+            double wayLat = pointList.getLatitude(pointIndex);
+            double wayLon = pointList.getLongitude(pointIndex);
+
+            if (calc.validEdgeDistance(lat, lon, tmpLat, tmpLon, wayLat, wayLon)) {
+                if (calc.calcNormalizedEdgeDistance(lat, lon, tmpLat, tmpLon, wayLat, wayLon) <= normedDist)
+                    return true;
+            } else {
+                if (calc.calcNormalizedDist(lat, lon, tmpLat, tmpLon) <= normedDist
+                        || pointIndex + 1 == len && calc.calcNormalizedDist(lat, lon, wayLat, wayLon) <= normedDist)
+                    return true;
+            }
+            tmpLat = wayLat;
+            tmpLon = wayLon;
+        }
+        return false;
+    }
+
     @Override
     public boolean contains(Shape o) {
         if (o instanceof Circle) {
diff --git a/api/src/main/java/com/graphhopper/util/shapes/GHPlace.java b/api/src/main/java/com/graphhopper/util/shapes/GHPlace.java
deleted file mode 100644
index 9f776b5de..000000000
--- a/api/src/main/java/com/graphhopper/util/shapes/GHPlace.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  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.util.shapes;
-
-import com.graphhopper.util.Helper;
-
-/**
- * Specifies a place by its coordinates or name
- * <p>
- *
- * @author Peter Karich
- */
-public class GHPlace extends GHPoint {
-    private String name = "";
-
-    public GHPlace() {
-    }
-
-    public GHPlace(String name) {
-        setName(name);
-    }
-
-    public GHPlace(double lat, double lon) {
-        super(lat, lon);
-    }
-
-    public void setValue(String t) {
-        setName(t);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public GHPlace setName(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public boolean isValidName() {
-        return !Helper.isEmpty(name);
-    }
-
-    @Override
-    public String toString() {
-        String str = "";
-        if (isValidName())
-            str += name;
-
-        if (isValid())
-            str += " " + lat + ", " + lon;
-
-        return str.trim();
-    }
-}
diff --git a/api/src/main/java/com/graphhopper/util/shapes/Polygon.java b/api/src/main/java/com/graphhopper/util/shapes/Polygon.java
index abf7c731c..ff61f1249 100644
--- a/api/src/main/java/com/graphhopper/util/shapes/Polygon.java
+++ b/api/src/main/java/com/graphhopper/util/shapes/Polygon.java
@@ -17,117 +17,72 @@
  */
 package com.graphhopper.util.shapes;
 
+import com.graphhopper.util.PointList;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
+
+import java.util.Arrays;
+
 /**
  * This class represents a polygon that is defined by a set of points.
  * Every point i is connected to point i-1 and i+1.
- * <p>
- * TODO: Howto design inner rings in the polygon?
  *
  * @author Robin Boldt
  */
 public class Polygon implements Shape {
 
-    private final double[] lat;
-    private final double[] lon;
+    private final GeometryFactory factory = new GeometryFactory();
+    private final PreparedGeometry prepPolygon;
+    private final Envelope envelope;
 
-    private double minLat;
-    private double minLon;
-    private double maxLat;
-    private double maxLon;
+    public Polygon(PreparedGeometry prepPolygon) {
+        this.prepPolygon = prepPolygon;
+        this.envelope = prepPolygon.getGeometry().getEnvelopeInternal();
+    }
 
-    private final double epsilon;
+    public Polygon(double[] lats, double[] lons) {
+        if (lats.length != lons.length)
+            throw new IllegalArgumentException("Points must be of equal length but was " + lats.length + " vs. " + lons.length);
 
-    public Polygon(double[] lat, double[] lon, double growFactor) {
-        if (lat.length != lon.length) {
-            throw new IllegalArgumentException("Points must be of equal length but was " + lat.length + " vs. " + lon.length);
-        }
-        if (lat.length == 0) {
+        if (lats.length == 0)
             throw new IllegalArgumentException("Points must not be empty");
-        }
-        this.lat = lat;
-        this.lon = lon;
-
-        for (int i = 0; i < lat.length; i++) {
-            if (i == 0) {
-                minLat = lat[i];
-                maxLat = lat[i];
-                minLon = lon[i];
-                maxLon = lon[i];
-            } else {
-                if (lat[i] < minLat) {
-                    minLat = lat[i];
-                } else if (lat[i] > maxLat) {
-                    maxLat = lat[i];
-                }
-                if (lon[i] < minLon) {
-                    minLon = lon[i];
-                } else if (lon[i] > maxLon) {
-                    maxLon = lon[i];
-                }
-            }
-        }
 
-        minLat -= growFactor;
-        minLon -= growFactor;
-        maxLat += growFactor;
-        maxLon += growFactor;
-
-        epsilon = (maxLat - minLat) / 10;
-    }
-
-    public Polygon(double[] lat, double[] lon) {
-        this(lat, lon, 0);
-    }
-
-    /**
-     * Lossy conversion to a GraphHopper Polygon.
-     */
-    public static Polygon create(org.locationtech.jts.geom.Polygon polygon) {
-        double[] lats = new double[polygon.getNumPoints()];
-        double[] lons = new double[polygon.getNumPoints()];
-        for (int i = 0; i < polygon.getNumPoints(); i++) {
-            lats[i] = polygon.getCoordinates()[i].y;
-            lons[i] = polygon.getCoordinates()[i].x;
+        Coordinate[] coordinates = new Coordinate[lats.length + 1];
+        for (int i = 0; i < lats.length; i++) {
+            coordinates[i] = new Coordinate(lons[i], lats[i]);
         }
-        return new Polygon(lats, lons);
+        coordinates[lats.length] = coordinates[0];
+        this.prepPolygon = new PreparedPolygon(factory.createPolygon(new PackedCoordinateSequence.Double(coordinates, 2)));
+        this.envelope = prepPolygon.getGeometry().getEnvelopeInternal();
     }
 
-    /**
-     * Wrapper method for {@link Polygon#contains(double, double)}.
-     */
-    public boolean contains(GHPoint point) {
-        return contains(point.lat, point.lon);
+    public static Polygon create(org.locationtech.jts.geom.Polygon polygon) {
+        return new Polygon(new PreparedPolygon(polygon));
     }
 
     @Override
     public boolean intersects(Shape o) {
-        throw new UnsupportedOperationException("Not supported yet.");
+        throw new IllegalArgumentException("Polygon.intersects: shape not supported");
+    }
+
+    public boolean intersects(PointList pointList) {
+        return prepPolygon.intersects(pointList.getCachedLineString(false));
     }
 
     /**
-     * Implements the ray casting algorithm
-     * Code is inspired from here: http://stackoverflow.com/a/218081/1548788
+     * Does the point in polygon check.
      *
      * @param lat Latitude of the point to be checked
      * @param lon Longitude of the point to be checked
      * @return true if point is inside polygon
      */
     public boolean contains(double lat, double lon) {
-        if (lat < minLat || lat > maxLat || lon < minLon || lon > maxLon) {
-            return false;
-        }
-
-        double rayStartLat = maxLat - (minLat / 2);
-        double rayStartLon = minLon - epsilon;
-
-        boolean inside = false;
-        int len = this.lat.length;
-        for (int i = 0; i < len; i++) {
-            if (edgesAreIntersecting(rayStartLon, rayStartLat, lon, lat, this.lon[i], this.lat[i], this.lon[(i + 1) % len], this.lat[(i + 1) % len]))
-                inside = !inside;
-        }
-        return inside;
-
+        return prepPolygon.contains(factory.createPoint(new Coordinate(lon, lat)));
     }
 
     @Override
@@ -137,115 +92,55 @@ public class Polygon implements Shape {
 
     @Override
     public BBox getBounds() {
-        return new BBox(minLon, maxLon, minLat, maxLat);
+        return new BBox(envelope.getMinX(), envelope.getMaxX(), envelope.getMinY(), envelope.getMaxY());
     }
 
     @Override
     public GHPoint getCenter() {
-        return new GHPoint((maxLat + minLat) / 2, (maxLon + minLon) / 2);
+        Point centroid = prepPolygon.getGeometry().getCentroid();
+        return new GHPoint(centroid.getY(), centroid.getX());
     }
 
     @Override
     public double calculateArea() {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    private boolean edgesAreIntersecting(
-            double v1x1, double v1y1, double v1x2, double v1y2,
-            double v2x1, double v2y1, double v2x2, double v2y2
-    ) {
-
-
-        double d1, d2;
-        double a1, a2, b1, b2, c1, c2;
-
-        // Convert vector 1 to a line (line 1) of infinite length.
-        // We want the line in linear equation standard form: A*x + B*y + C = 0
-        // See: http://en.wikipedia.org/wiki/Linear_equation
-        a1 = v1y2 - v1y1;
-        b1 = v1x1 - v1x2;
-        c1 = (v1x2 * v1y1) - (v1x1 * v1y2);
-
-        // Every point (x,y), that solves the equation above, is on the line,
-        // every point that does not solve it, is not. The equation will have a
-        // positive result if it is on one side of the line and a negative one
-        // if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
-        // 2 into the equation above.
-        d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
-        d2 = (a1 * v2x2) + (b1 * v2y2) + c1;
-
-        // If d1 and d2 both have the same sign, they are both on the same side
-        // of our line 1 and in that case no intersection is possible. Careful,
-        // 0 is a special case, that's why we don't test ">=" and "<=",
-        // but "<" and ">".
-        if (d1 > 0 && d2 > 0) return false;
-        if (d1 < 0 && d2 < 0) return false;
-
-        // The fact that vector 2 intersected the infinite line 1 above doesn't
-        // mean it also intersects the vector 1. Vector 1 is only a subset of that
-        // infinite line 1, so it may have intersected that line before the vector
-        // started or after it ended. To know for sure, we have to repeat the
-        // the same test the other way round. We start by calculating the
-        // infinite line 2 in linear equation standard form.
-        a2 = v2y2 - v2y1;
-        b2 = v2x1 - v2x2;
-        c2 = (v2x2 * v2y1) - (v2x1 * v2y2);
-
-        // Calculate d1 and d2 again, this time using points of vector 1.
-        d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
-        d2 = (a2 * v1x2) + (b2 * v1y2) + c2;
-
-        // Again, if both have the same sign (and neither one is 0),
-        // no intersection is possible.
-        if (d1 > 0 && d2 > 0) return false;
-        if (d1 < 0 && d2 < 0) return false;
-
-        // If we get here, only two possibilities are left. Either the two
-        // vectors intersect in exactly one point or they are collinear, which
-        // means they intersect in any number of points from zero to infinite.
-        if ((a1 * b2) - (a2 * b1) == 0) return false;
-
-        // If they are not collinear, they must intersect in exactly one point.
-        return true;
+        // for estimation use bounding box as reference:
+        return getBounds().calculateArea() * envelope.getArea() / prepPolygon.getGeometry().getArea();
     }
 
     public double getMinLat() {
-        return minLat;
+        return envelope.getMinY();
     }
 
     public double getMinLon() {
-        return minLon;
+        return envelope.getMinX();
     }
 
     public double getMaxLat() {
-        return maxLat;
+        return envelope.getMaxY();
     }
 
     public double getMaxLon() {
-        return maxLon;
+        return envelope.getMaxX();
     }
 
     @Override
     public String toString() {
-        return "polygon (" + lat.length + " points)";
+        return "polygon (" + prepPolygon.getGeometry().getNumPoints() + " points," + prepPolygon.getGeometry().getNumGeometries() + " geometries)";
     }
 
-    public static Polygon parsePoints(String pointsStr, double growFactor) {
+    public static Polygon parsePoints(String pointsStr) {
         String[] arr = pointsStr.split(",");
+        if (arr.length % 2 == 1)
+            throw new IllegalArgumentException("incorrect polygon specified: " + Arrays.asList(arr));
 
-        if (arr.length % 2 == 1) throw new IllegalArgumentException("incorrect polygon specified");
-
-        double[] lats = new double[arr.length /2];
-        double[] lons = new double[arr.length /2];
-
-        for (int j = 0; j < arr.length; j++) {
-            if (j % 2 == 0) {
-                lats[j / 2] = Double.parseDouble(arr[j]);
-            } else {
-                lons[(j - 1) / 2] = Double.parseDouble(arr[j]);
-            }
+        Coordinate[] coordinates = new Coordinate[arr.length / 2 + 1];
+        for (int i = 0; i < coordinates.length - 1; i++) {
+            int arrIndex = i * 2;
+            coordinates[i] = new Coordinate(Double.parseDouble(arr[arrIndex + 1]), Double.parseDouble(arr[arrIndex]));
         }
+        coordinates[coordinates.length - 1] = coordinates[0];
 
-        return new Polygon(lats, lons, growFactor);
+        // store more efficient
+        return new Polygon(new PreparedPolygon(new GeometryFactory().createPolygon(new PackedCoordinateSequence.Double(coordinates, 2))));
     }
 }
diff --git a/api/src/main/java/com/graphhopper/util/shapes/Shape.java b/api/src/main/java/com/graphhopper/util/shapes/Shape.java
index 7962c6761..039a00f40 100644
--- a/api/src/main/java/com/graphhopper/util/shapes/Shape.java
+++ b/api/src/main/java/com/graphhopper/util/shapes/Shape.java
@@ -17,9 +17,10 @@
  */
 package com.graphhopper.util.shapes;
 
+import com.graphhopper.util.PointList;
+
 /**
- * A shape interface to implement circles or rectangles.
- * <p>
+ * A shape interface to implement circles, polygons or rectangles.
  *
  * @author Peter Karich
  */
@@ -29,6 +30,8 @@ public interface Shape {
      */
     boolean intersects(Shape o);
 
+    boolean intersects(PointList pointList);
+
     /**
      * @return true only if lat and lon are inside (or on the edge) of this shape
      */
diff --git a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupArray.java b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupArray.java
index 4320767b4..a6d576ff0 100644
--- a/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupArray.java
+++ b/core/src/main/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupArray.java
@@ -182,7 +182,7 @@ public class SpatialRuleLookupArray implements SpatialRuleLookup {
 
                     GHPoint center = getCoordinatesForIndex(xIdx, yIdx);
                     // TODO: Consider creating a new method in Polygon that does the 5 checks - p.partOfTile?
-                    if (polygon.contains(center) ||
+                    if (polygon.contains(center.lat, center.lon) ||
                             polygon.contains(center.getLat() - checkDiff, center.getLon() - checkDiff) ||
                             polygon.contains(center.getLat() - checkDiff, center.getLon() + checkDiff) ||
                             polygon.contains(center.getLat() + checkDiff, center.getLon() - checkDiff) ||
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/BlockAreaWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/BlockAreaWeighting.java
index aa59ccc17..d912800d1 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/BlockAreaWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/BlockAreaWeighting.java
@@ -17,7 +17,7 @@ public class BlockAreaWeighting extends AbstractAdjustedWeighting {
 
     @Override
     public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) {
-        if (blockArea.contains(edgeState))
+        if (blockArea.intersects(edgeState))
             return Double.POSITIVE_INFINITY;
 
         return superWeighting.calcEdgeWeight(edgeState, reverse);
diff --git a/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java b/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
index 1eba43e87..c7c476b14 100644
--- a/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
+++ b/core/src/main/java/com/graphhopper/storage/GraphEdgeIdFinder.java
@@ -28,11 +28,14 @@ import com.graphhopper.util.EdgeIteratorState;
 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.List;
 
+import static com.graphhopper.util.shapes.BBox.toEnvelope;
+
 /**
  * This class allows to find edges (or construct shapes) from shape filter.
  *
@@ -70,9 +73,9 @@ public class GraphEdgeIdFinder {
     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: if there is no street close to the center it'll fail although there are roads covered. Maybe we should check edge points or some random points in the Shape instead?
+        // TODO: this is suboptimal and will be fixed in #1324
         if (!qr.isValid())
-            throw new IllegalArgumentException("Shape " + shape + " does not cover graph");
+            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());
@@ -155,25 +158,34 @@ public class GraphEdgeIdFinder {
             for (int i = 0; i < blockedCircularAreasArr.length; i++) {
                 String objectAsString = blockedCircularAreasArr[i];
                 String[] splittedObject = objectAsString.split(innerObjSep);
+
+                // 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, 0.003);
-                    findEdgesInShape(blockArea.blockedEdges, polygon, filter);
+                    final Polygon polygon = Polygon.parsePoints(objectAsString);
+                    blockArea.add(polygon);
+                    if (polygon.calculateArea() <= useEdgeIdsUntilAreaSize)
+                        findEdgesInShape(blockArea.blockedEdges, polygon, filter);
                 } else if (splittedObject.length == 4) {
                     final BBox bbox = BBox.parseTwoPoints(objectAsString);
-                    if (bbox.calculateArea() > useEdgeIdsUntilAreaSize)
-                        blockArea.add(bbox);
-                    else
-                        findEdgesInShape(blockArea.blockedEdges, bbox, filter);
+                    final RectangleLineIntersector cachedIntersector = new RectangleLineIntersector(toEnvelope(bbox));
+                    BBox preparedBBox = new BBox(bbox.minLon, bbox.maxLon, bbox.minLat, bbox.maxLat) {
+                        @Override
+                        public boolean intersects(PointList pointList) {
+                            return BBox.intersects(cachedIntersector, pointList);
+                        }
+                    };
+                    blockArea.add(preparedBBox);
+                    if (bbox.calculateArea() <= useEdgeIdsUntilAreaSize)
+                        findEdgesInShape(blockArea.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);
-                    if (circle.calculateArea() > useEdgeIdsUntilAreaSize) {
-                        blockArea.add(circle);
-                    } else {
+                    blockArea.add(circle);
+                    if (circle.calculateArea() <= useEdgeIdsUntilAreaSize)
                         findEdgesInShape(blockArea.blockedEdges, circle, filter);
-                    }
+
                 } else if (splittedObject.length == 2) {
                     double lat = Double.parseDouble(splittedObject[0]);
                     double lon = Double.parseDouble(splittedObject[1]);
@@ -210,14 +222,20 @@ public class GraphEdgeIdFinder {
         /**
          * @return true if the specified edgeState is part of this BlockArea
          */
-        public final boolean contains(EdgeIteratorState edgeState) {
+        public final boolean intersects(EdgeIteratorState edgeState) {
             if (!blockedEdges.isEmpty() && blockedEdges.contains(edgeState.getEdge())) {
                 return true;
             }
 
-            if (!blockedShapes.isEmpty() && na != null) {
-                for (Shape shape : blockedShapes) {
-                    if (shape.contains(na.getLatitude(edgeState.getAdjNode()), na.getLongitude(edgeState.getAdjNode())))
+            // 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) {
+                if (shape.getBounds().intersects(bbox)) {
+                    if (pointList == null)
+                        pointList = edgeState.fetchWayGeometry(3).makeImmutable();
+                    if (shape.intersects(pointList))
                         return true;
                 }
             }
diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java
index 45f8a61d4..b5e8be35b 100644
--- a/tools/src/main/java/com/graphhopper/tools/Measurement.java
+++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java
@@ -51,6 +51,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import static com.graphhopper.util.Helper.*;
 import static com.graphhopper.util.Parameters.Algorithms.ALT_ROUTE;
 import static com.graphhopper.util.Parameters.Algorithms.DIJKSTRA_BI;
+import static com.graphhopper.util.Parameters.Routing.BLOCK_AREA;
 
 /**
  * @author Peter Karich
@@ -168,23 +169,30 @@ public class Measurement {
             GHBitSet allowedEdges = printGraphDetails(g, vehicleStr);
             printMiscUnitPerfTests(g, isCH, encoder, count * 100, allowedEdges);
             printLocationIndexQuery(g, hopper.getLocationIndex(), count);
+            String blockAreaStr = "49.394664,11.144428,49.348388,11.144943,49.355768,11.227169,49.411643,11.227512";
             if (runSlow) {
-                printTimeOfRouteQuery(hopper, isCH, isLM, count / 20, "routing", vehicleStr,
-                        true, false, -1, true, false, false, false);
-                printTimeOfRouteQuery(hopper, isCH, isLM, count / 20, "routing_edge", vehicleStr,
-                        true, false, -1, true, true, false, false);
+                printTimeOfRouteQuery(hopper, new QuerySettings("routing", vehicleStr, count / 20, isCH, isLM).
+                        withInstructions());
+                printTimeOfRouteQuery(hopper, new QuerySettings("routing_edge", vehicleStr, count / 20, isCH, isLM).
+                        withInstructions().edgeBased());
+                printTimeOfRouteQuery(hopper, new QuerySettings("routing_block_area", vehicleStr, count / 20, isCH, isLM).
+                        withInstructions().blockArea(blockAreaStr));
             }
 
             if (hopper.getLMFactoryDecorator().isEnabled()) {
                 System.gc();
                 isLM = true;
-                for (int activeLMCount : Arrays.asList(4, 8, 12, 16)) {
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count / 4, "routingLM" + activeLMCount, vehicleStr,
-                            true, false, activeLMCount, true, false, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count / 4, "routingLM" + activeLMCount + "_edge", vehicleStr,
-                            true, false, activeLMCount, true, true, false, false);
+                int activeLMCount = 12;
+                for (; activeLMCount > 3; activeLMCount -= 4) {
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingLM" + activeLMCount, vehicleStr, count / 4, isCH, isLM).
+                            withInstructions().activeLandmarks(activeLMCount));
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingLM" + activeLMCount + "_edge", vehicleStr, count / 4, isCH, isLM).
+                            withInstructions().activeLandmarks(activeLMCount).edgeBased());
                 }
 
+                activeLMCount = 8;
+                printTimeOfRouteQuery(hopper, new QuerySettings("routingLM" + activeLMCount + "_block_area", vehicleStr, count / 4, isCH, isLM).
+                        withInstructions().activeLandmarks(activeLMCount).blockArea(blockAreaStr));
                 // compareRouting(hopper, vehicleStr, count / 5);
             }
 
@@ -196,8 +204,8 @@ public class Measurement {
                     System.gc();
                     // try just one constellation, often ~4-6 is best
                     int lmCount = 5;
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCHLM" + lmCount, vehicleStr,
-                            true, false, lmCount, true, false, false, false);
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCHLM" + lmCount, vehicleStr, count, isCH, isLM).
+                            withInstructions().activeLandmarks(lmCount).sod());
                 }
 
                 isLM = false;
@@ -207,26 +215,26 @@ public class Measurement {
                     CHGraph lg = g.getCHGraph(chProfile);
                     fillAllowedEdges(lg.getAllEdges(), allowedEdges);
                     printMiscUnitPerfTests(lg, isCH, encoder, count * 100, allowedEdges);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH", vehicleStr, true,
-                            false, -1, true, false, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count / 10, "routingCH_alt", vehicleStr, true,
-                            false, -1, true, false, false, true);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_with_hints", vehicleStr, true,
-                            true, -1, true, false, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_sod", vehicleStr, true,
-                            false, -1, false, false, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_instr", vehicleStr, false,
-                            false, -1, true, false, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_full", vehicleStr, true,
-                            true, -1, true, false, true, false);
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH", vehicleStr, count, isCH, isLM).
+                            withInstructions().sod());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_alt", vehicleStr, count / 10, isCH, isLM).
+                            withInstructions().sod().alternative());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_with_hints", vehicleStr, count, isCH, isLM).
+                            withInstructions().sod().withPointHints());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_no_sod", vehicleStr, count, isCH, isLM).
+                            withInstructions());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_no_instr", vehicleStr, count, isCH, isLM).
+                            sod());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_full", vehicleStr, count, isCH, isLM).
+                            withInstructions().withPointHints().sod().simplify());
                 }
                 if (!hopper.getCHFactoryDecorator().getEdgeBasedCHProfiles().isEmpty()) {
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_edge", vehicleStr, true,
-                            false, -1, false, true, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_edge_no_instr", vehicleStr, false,
-                            false, -1, false, true, false, false);
-                    printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_edge_full", vehicleStr, true,
-                            true, -1, false, true, true, false);
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_edge", vehicleStr, count, isCH, isLM).
+                            edgeBased().withInstructions());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_edge_no_instr", vehicleStr, count, isCH, isLM).
+                            edgeBased());
+                    printTimeOfRouteQuery(hopper, new QuerySettings("routingCH_edge_full", vehicleStr, count, isCH, isLM).
+                            edgeBased().withInstructions().withPointHints().simplify());
                 }
             }
         } catch (Exception ex) {
@@ -252,6 +260,63 @@ public class Measurement {
         }
     }
 
+    private static class QuerySettings {
+        private final String prefix, vehicle;
+        private final int count;
+        final boolean ch, lm;
+        int activeLandmarks = -1;
+        boolean withInstructions, withPointHints, sod, edgeBased, simplify, alternative;
+        String blockArea;
+
+        QuerySettings(String prefix, String vehicle, int count, boolean isCH, boolean isLM) {
+            this.vehicle = vehicle;
+            this.prefix = prefix;
+            this.count = count;
+            this.ch = isCH;
+            this.lm = isLM;
+        }
+
+        QuerySettings withInstructions() {
+            this.withInstructions = true;
+            return this;
+        }
+
+        QuerySettings withPointHints() {
+            this.withPointHints = true;
+            return this;
+        }
+
+        QuerySettings sod() {
+            sod = true;
+            return this;
+        }
+
+        QuerySettings activeLandmarks(int alm) {
+            this.activeLandmarks = alm;
+            return this;
+        }
+
+        QuerySettings edgeBased() {
+            this.edgeBased = true;
+            return this;
+        }
+
+        QuerySettings simplify() {
+            this.simplify = true;
+            return this;
+        }
+
+        QuerySettings alternative() {
+            alternative = true;
+            return this;
+        }
+
+        QuerySettings blockArea(String str) {
+            blockArea = str;
+            return this;
+        }
+    }
+
     void fillAllowedEdges(AllEdgesIterator iter, GHBitSet bs) {
         bs.clear();
         while (iter.next()) {
@@ -455,11 +520,7 @@ public class Measurement {
         }
     }
 
-    private void printTimeOfRouteQuery(final GraphHopper hopper, final boolean ch, final boolean lm,
-                                       int count, String prefix, final String vehicle,
-                                       final boolean withInstructions, final boolean withPointHints,
-                                       final int activeLandmarks, final boolean sod, final boolean edgeBased,
-                                       final boolean simplify, final boolean alternative) {
+    private void printTimeOfRouteQuery(final GraphHopper hopper, final QuerySettings querySettings) {
         final Graph g = hopper.getGraphHopperStorage();
         final AtomicLong maxDistance = new AtomicLong(0);
         final AtomicLong minDistance = new AtomicLong(Long.MAX_VALUE);
@@ -469,7 +530,7 @@ public class Measurement {
         final AtomicInteger failedCount = new AtomicInteger(0);
         final DistanceCalc distCalc = new DistanceCalcEarth();
 
-        final EdgeExplorer edgeExplorer = g.createEdgeExplorer(DefaultEdgeFilter.allEdges(hopper.getEncodingManager().getEncoder(vehicle)));
+        final EdgeExplorer edgeExplorer = g.createEdgeExplorer(DefaultEdgeFilter.allEdges(hopper.getEncodingManager().getEncoder(querySettings.vehicle)));
         final AtomicLong visitedNodesSum = new AtomicLong(0);
         final AtomicLong maxVisitedNodes = new AtomicLong(0);
 //        final AtomicLong extractTimeSum = new AtomicLong(0);
@@ -490,29 +551,32 @@ public class Measurement {
                 double toLon = na.getLongitude(to);
                 GHRequest req = new GHRequest(fromLat, fromLon, toLat, toLon).
                         setWeighting("fastest").
-                        setVehicle(vehicle);
+                        setVehicle(querySettings.vehicle);
 
-                req.getHints().put(CH.DISABLE, !ch).
-                        put("stall_on_demand", sod).
-                        put(Parameters.Routing.EDGE_BASED, edgeBased).
-                        put(Landmark.DISABLE, !lm).
-                        put(Landmark.ACTIVE_COUNT, activeLandmarks).
-                        put("instructions", withInstructions);
+                req.getHints().put(CH.DISABLE, !querySettings.ch).
+                        put("stall_on_demand", querySettings.sod).
+                        put(Parameters.Routing.EDGE_BASED, querySettings.edgeBased).
+                        put(Landmark.DISABLE, !querySettings.lm).
+                        put(Landmark.ACTIVE_COUNT, querySettings.activeLandmarks).
+                        put("instructions", querySettings.withInstructions);
 
-                if (alternative)
+                if (querySettings.alternative)
                     req.setAlgorithm(ALT_ROUTE);
 
-                if (withInstructions)
+                if (querySettings.withInstructions)
                     req.setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED));
 
-                if (simplify) {
+                if (querySettings.simplify) {
                     req.setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED, Parameters.Details.EDGE_ID, Parameters.Details.STREET_NAME));
                 } else {
                     // disable path simplification by setting the distance to zero
                     req.getHints().put(Parameters.Routing.WAY_POINT_MAX_DISTANCE, 0);
                 }
 
-                if (withPointHints) {
+                if (querySettings.blockArea != null)
+                    req.getHints().put(BLOCK_AREA, querySettings.blockArea);
+
+                if (querySettings.withPointHints) {
                     EdgeIterator iter = edgeExplorer.setBaseNode(from);
                     if (!iter.next())
                         throw new IllegalArgumentException("wrong 'from' when adding point hint");
@@ -565,21 +629,22 @@ public class Measurement {
                     if (dist < minDistance.get())
                         minDistance.set(dist);
 
-                    if (alternative)
+                    if (querySettings.alternative)
                         altCount.addAndGet(rsp.getAll().size());
                 }
 
                 return arsp.getPoints().getSize();
             }
-        }.setIterations(count).start();
+        }.setIterations(querySettings.count).start();
 
-        count -= failedCount.get();
+        int count = querySettings.count - failedCount.get();
 
         // if using non-bidirectional algorithm make sure you exclude CH routing
-        String algoStr = (ch && !edgeBased) ? Algorithms.DIJKSTRA_BI : Algorithms.ASTAR_BI;
-        if (ch && !sod) {
+        String algoStr = (querySettings.ch && !querySettings.edgeBased) ? Algorithms.DIJKSTRA_BI : Algorithms.ASTAR_BI;
+        if (querySettings.ch && !querySettings.sod) {
             algoStr += "_no_sod";
         }
+        String prefix = querySettings.prefix;
         put(prefix + ".guessed_algorithm", algoStr);
         put(prefix + ".failed_count", failedCount.get());
         put(prefix + ".distance_min", minDistance.get());


Test patch:
diff --git a/api/src/test/java/com/graphhopper/util/shapes/BBoxTest.java b/api/src/test/java/com/graphhopper/util/shapes/BBoxTest.java
index 321e473b5..24cf2beef 100644
--- a/api/src/test/java/com/graphhopper/util/shapes/BBoxTest.java
+++ b/api/src/test/java/com/graphhopper/util/shapes/BBoxTest.java
@@ -19,6 +19,7 @@ package com.graphhopper.util.shapes;
 
 import com.graphhopper.util.DistanceCalc;
 import com.graphhopper.util.DistanceCalcEarth;
+import com.graphhopper.util.PointList;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -86,8 +87,39 @@ public class BBoxTest {
     }
 
     @Test
-    public void testCalculateIntersection() {
+    public void testPointListIntersect() {
+        BBox bbox = new BBox(-0.5, 1, 1, 2);
+        PointList pointList = new PointList();
+        pointList.add(5, 5);
+        pointList.add(5, 0);
+        assertFalse(bbox.intersects(pointList));
+
+        pointList.add(-5, 0);
+        assertTrue(bbox.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 1);
+        pointList.add(-1, 0);
+        assertTrue(bbox.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 0);
+        pointList.add(-1, 3);
+        assertFalse(bbox.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 0);
+        pointList.add(-1, 2);
+        assertTrue(bbox.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(1.5, -2);
+        pointList.add(1.5, 2);
+        assertTrue(bbox.intersects(pointList));
+    }
 
+    @Test
+    public void testCalculateIntersection() {
         BBox b1 = new BBox(0, 2, 0, 1);
         BBox b2 = new BBox(-1, 1, -1, 2);
         BBox expected = new BBox(0, 1, 0, 1);
diff --git a/api/src/test/java/com/graphhopper/util/shapes/CircleTest.java b/api/src/test/java/com/graphhopper/util/shapes/CircleTest.java
index eaf6e1809..b5e7b28f6 100644
--- a/api/src/test/java/com/graphhopper/util/shapes/CircleTest.java
+++ b/api/src/test/java/com/graphhopper/util/shapes/CircleTest.java
@@ -17,11 +17,11 @@
  */
 package com.graphhopper.util.shapes;
 
+import com.graphhopper.util.Helper;
+import com.graphhopper.util.PointList;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * @author Peter Karich
@@ -42,6 +42,38 @@ public class CircleTest {
         assertFalse(new BBox(9, 11, 8, 9).intersects(new Circle(10, 10, 110000)));
     }
 
+    @Test
+    public void testIntersectPointList() {
+        Circle circle = new Circle(1.5, 0.3, Helper.DIST_EARTH.calcDist(0, 0, 0, 0.7));
+        PointList pointList = new PointList();
+        pointList.add(5, 5);
+        pointList.add(5, 0);
+        assertFalse(circle.intersects(pointList));
+
+        pointList.add(-5, 0);
+        assertTrue(circle.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 1);
+        pointList.add(-1, 0);
+        assertTrue(circle.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 0);
+        pointList.add(-1, 3);
+        assertFalse(circle.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(5, 0);
+        pointList.add(2, 0);
+        assertTrue(circle.intersects(pointList));
+
+        pointList = new PointList();
+        pointList.add(1.5, -2);
+        pointList.add(1.5, 2);
+        assertTrue(circle.intersects(pointList));
+    }
+
     @Test
     public void testContains() {
         Circle c = new Circle(10, 10, 120000);
diff --git a/api/src/test/java/com/graphhopper/util/shapes/PolygonTest.java b/api/src/test/java/com/graphhopper/util/shapes/PolygonTest.java
index 9c1b81370..3e874000d 100644
--- a/api/src/test/java/com/graphhopper/util/shapes/PolygonTest.java
+++ b/api/src/test/java/com/graphhopper/util/shapes/PolygonTest.java
@@ -22,7 +22,7 @@ public class PolygonTest {
         assertTrue(square.contains(10,10));
         assertTrue(square.contains(16,10));
         assertFalse(square.contains(10,-20));
-        assertTrue(square.contains(10,0));
+        assertTrue(square.contains(10,0.1));
         assertFalse(square.contains(10,20));
         assertTrue(square.contains(10,16));
         assertFalse(square.contains(20,20));
diff --git a/core/src/test/java/com/graphhopper/storage/GraphEdgeIdFinderTest.java b/core/src/test/java/com/graphhopper/storage/GraphEdgeIdFinderTest.java
index fe38b067b..e943535d9 100644
--- a/core/src/test/java/com/graphhopper/storage/GraphEdgeIdFinderTest.java
+++ b/core/src/test/java/com/graphhopper/storage/GraphEdgeIdFinderTest.java
@@ -18,21 +18,17 @@
 package com.graphhopper.storage;
 
 import com.graphhopper.coll.GHIntHashSet;
-import com.graphhopper.routing.util.CarFlagEncoder;
-import com.graphhopper.routing.util.DefaultEdgeFilter;
-import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.*;
 import com.graphhopper.storage.index.LocationIndex;
 import com.graphhopper.storage.index.LocationIndexTree;
-import com.graphhopper.util.shapes.Circle;
-import com.graphhopper.util.shapes.Shape;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
 import static com.graphhopper.util.GHUtility.updateDistancesFor;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 /**
  * @author Peter Karich
@@ -63,20 +59,14 @@ public class GraphEdgeIdFinderTest {
 
         GraphEdgeIdFinder graphFinder = new GraphEdgeIdFinder(graph, locationIndex);
         GraphEdgeIdFinder.BlockArea blockArea = graphFinder.parseBlockArea("0.01,0.005,1", DefaultEdgeFilter.allEdges(encoder), 1000 * 1000);
+        assertEquals("[0]", blockArea.blockedEdges.toString());
+        assertFalse(blockArea.blockedShapes.isEmpty());
 
-        GHIntHashSet blockedEdges = new GHIntHashSet();
-        blockedEdges.add(0);
-        assertEquals(blockedEdges, blockArea.blockedEdges);
-        List<Shape> blockedShapes = new ArrayList<>();
-        assertEquals(blockedShapes, blockArea.blockedShapes);
-
-        // big area converts into shapes
+        // big area => no edgeIds are collected up-front
         graphFinder = new GraphEdgeIdFinder(graph, locationIndex);
         blockArea = graphFinder.parseBlockArea("0,0,1000", DefaultEdgeFilter.allEdges(encoder), 1000 * 1000);
-        blockedEdges.clear();
-        assertEquals(blockedEdges, blockArea.blockedEdges);
-        blockedShapes.add(new Circle(0, 0, 1000));
-        assertEquals(blockedShapes, blockArea.blockedShapes);
+        assertEquals("[]", blockArea.blockedEdges.toString());
+        assertFalse(blockArea.blockedShapes.isEmpty());
     }
 
     @Test
@@ -121,16 +111,27 @@ public class GraphEdgeIdFinderTest {
                 .prepareIndex();
 
         GraphEdgeIdFinder graphFinder = new GraphEdgeIdFinder(graph, locationIndex);
-        GraphEdgeIdFinder.BlockArea blockArea = graphFinder.parseBlockArea("2,1, 0,2, 2,3", DefaultEdgeFilter.allEdges(encoder), 1000 * 1000);
-
+        // big value => the polygon is small => force edgeId optimization
+        double area = 500_000L * 500_000L;
+        GraphEdgeIdFinder.BlockArea blockArea = graphFinder.parseBlockArea("2.1,1, -1.1,2, 2,3", DefaultEdgeFilter.allEdges(encoder), area);
         GHIntHashSet blockedEdges = new GHIntHashSet();
-        blockedEdges.addAll(1, 2, 6, 7);
+        blockedEdges.addAll(1, 2, 6, 7, 11, 12);
         assertEquals(blockedEdges, blockArea.blockedEdges);
+        assertEdges(graph, "[1, 2, 6, 7, 11, 12]", blockArea);
 
-        blockArea = graphFinder.parseBlockArea("2,1, 1,3, 1,2, 0,1", DefaultEdgeFilter.allEdges(encoder), 1000 * 1000);
+        // small value => same polygon is now "large" => do not pre-calculate edgeId set => check only geometries
+        blockArea = graphFinder.parseBlockArea("2.1,1, 0.9,3, 0.9,2, -0.3,0", DefaultEdgeFilter.allEdges(encoder), 1000 * 1000);
+        assertEquals("[]", blockArea.blockedEdges.toString());
+        assertEdges(graph, "[0, 1, 4, 5, 6, 7, 9, 10]", blockArea);
+    }
 
-        blockedEdges = new GHIntHashSet();
-        blockedEdges.addAll(4, 9, 6, 7);
-        assertEquals(blockedEdges, blockArea.blockedEdges);
+    private void assertEdges(Graph g, String assertSetContent, GraphEdgeIdFinder.BlockArea blockArea) {
+        Set<Integer> blockedEdges = new TreeSet<>();
+        AllEdgesIterator edgeIterator = g.getAllEdges();
+        while (edgeIterator.next()) {
+            if (blockArea.intersects(edgeIterator))
+                blockedEdges.add(edgeIterator.getEdge());
+        }
+        assertEquals(assertSetContent, blockedEdges.toString());
     }
 }
diff --git a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
index aae339f43..afd38ef78 100644
--- a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
+++ b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
@@ -441,25 +441,32 @@ public class GraphHopperIT {
         req.getHints().put(Routing.BLOCK_AREA, someArea);
         rsp = tmpHopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(12173, rsp.getBest().getDistance(), 1);
+        assertEquals(13988, rsp.getBest().getDistance(), 1);
 
         // Add blocked point to above area, to increase detour        
         req.getHints().put(Routing.BLOCK_AREA, "50.017578,11.547527;" + someArea);
         rsp = tmpHopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(12787, rsp.getBest().getDistance(), 1);
+        assertEquals(14602, rsp.getBest().getDistance(), 1);
 
         // block by edge IDs -> i.e. use small circular area
-        req.getHints().put(Routing.BLOCK_AREA, "49.981599,11.517448,100");
+        req.getHints().put(Routing.BLOCK_AREA, "49.979929,11.520066,200");
+        rsp = tmpHopper.route(req);
+        assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
+        assertEquals(12173, rsp.getBest().getDistance(), 1);
+
+        // TODO after #1324 this will work and should block both roads and return 12173m, currently it still routes
+        //  through one of the roads due to "disconnected" roads
+        req.getHints().put(Routing.BLOCK_AREA, "49.981502,11.51762,80");
         rsp = tmpHopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(6879, rsp.getBest().getDistance(), 1);
+        assertEquals(7383, rsp.getBest().getDistance(), 1);
 
         // block by edge IDs -> i.e. use small rectangular area
-        req.getHints().put(Routing.BLOCK_AREA, "49.981875,11.515818,49.981088,11.519423");
+        req.getHints().put(Routing.BLOCK_AREA, "49.981875,11.515818,49.979522,11.521407");
         rsp = tmpHopper.route(req);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(6879, rsp.getBest().getDistance(), 1);
+        assertEquals(12173, rsp.getBest().getDistance(), 1);
 
         // blocking works for all weightings
         req = new GHRequest(50.009504, 11.490669, 50.024726, 11.496162).