You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2017/12/13 21:55:19 UTC

lucene-solr:branch_7x: LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with configurable precision for non-spherical planet models. Some internal refactorings as well.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x 4b4532815 -> caf401c43


LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with configurable precision for non-spherical planet models.
Some internal refactorings as well.

(cherry picked from commit d66d954)


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/caf401c4
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/caf401c4
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/caf401c4

Branch: refs/heads/branch_7x
Commit: caf401c43bbe40771ae633c1a62732823930f35f
Parents: 4b45328
Author: David Smiley <ds...@apache.org>
Authored: Wed Dec 13 16:32:48 2017 -0500
Committer: David Smiley <ds...@apache.org>
Committed: Wed Dec 13 16:55:08 2017 -0500

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   4 +
 .../spatial/spatial4j/Geo3dCircleShape.java     |  15 --
 .../spatial4j/Geo3dDistanceCalculator.java      |  60 +----
 .../lucene/spatial/spatial4j/Geo3dShape.java    |   6 +-
 .../spatial/spatial4j/Geo3dShapeFactory.java    |  43 ++-
 .../Geo3dShapeRectRelationTestCase.java         | 264 -------------------
 .../Geo3dShapeSphereModelRectRelationTest.java  |   8 +-
 .../Geo3dShapeWGS84ModelRectRelationTest.java   |  10 +-
 .../spatial4j/ShapeRectRelationTestCase.java    | 201 ++++++++++++++
 9 files changed, 261 insertions(+), 350 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index e57ed8f..e075448 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -17,6 +17,10 @@ Improvements
   disk. This change adds an expert setting to opt ouf of this behavior unless
   flusing is falling behind. (Simon Willnauer)
 
+* LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with
+  configurable precision for non-spherical planet models.
+  (Ignacio Vera via David Smiley)
+
 ======================= Lucene 7.2.0 =======================
 
 API Changes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java
index ccef92a..d01e2b8 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dCircleShape.java
@@ -20,13 +20,10 @@ package org.apache.lucene.spatial.spatial4j;
 import org.apache.lucene.spatial3d.geom.GeoCircle;
 import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
 import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
-import org.apache.lucene.spatial3d.geom.PlanetModel;
 import org.locationtech.spatial4j.context.SpatialContext;
 import org.locationtech.spatial4j.distance.DistanceUtils;
 import org.locationtech.spatial4j.shape.Circle;
 import org.locationtech.spatial4j.shape.Point;
-import org.locationtech.spatial4j.shape.Shape;
-import org.locationtech.spatial4j.shape.SpatialRelation;
 
 /**
  * Specialization of a {@link Geo3dShape} which represents a {@link Circle}.
@@ -67,16 +64,4 @@ public class Geo3dCircleShape extends Geo3dShape<GeoCircle> implements Circle {
     }
     return center;
   }
-
-  //TODO Improve GeoCircle to properly relate a point with WGS84 model -- LUCENE-7970
-  @Override
-  public SpatialRelation relate(Shape other) {
-    if (shape.getPlanetModel() != PlanetModel.SPHERE && other instanceof Point) {
-      if (spatialcontext.getDistCalc().distance((Point) other, getCenter()) <= getRadius()) {
-        return SpatialRelation.CONTAINS;
-      }
-      return SpatialRelation.DISJOINT;
-    }
-    return super.relate(other);
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java
index 5154de4..8fdb481 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dDistanceCalculator.java
@@ -73,62 +73,20 @@ public class Geo3dDistanceCalculator implements DistanceCalculator {
 
   @Override
   public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
-    // Algorithm using Vincenty's formulae (https://en.wikipedia.org/wiki/Vincenty%27s_formulae)
-    // which takes into account that planets may not be spherical.
-    //Code adaptation from http://www.movable-type.co.uk/scripts/latlong-vincenty.html
     Geo3dPointShape geoFrom = (Geo3dPointShape) from;
     GeoPoint point = (GeoPoint) geoFrom.shape;
-    double lat = point.getLatitude();
-    double lon = point.getLongitude();
     double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
     double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
-
-    double sinα1 = Math.sin(bearing);
-    double cosα1 = Math.cos(bearing);
-
-    double tanU1 = (1 - planetModel.flattening) * Math.tan(lat);
-    double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1));
-    double sinU1 = tanU1 * cosU1;
-
-    double σ1 = Math.atan2(tanU1, cosα1);
-    double sinα = cosU1 * sinα1;
-    double cosSqα = 1 - sinα * sinα;
-    double uSq = cosSqα * planetModel.squareRatio;// (planetModel.ab* planetModel.ab - planetModel.c*planetModel.c) / (planetModel.c*planetModel.c);
-    double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
-    double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
-
-    double cos2σM;
-    double sinσ;
-    double cosσ;
-    double Δσ;
-
-    double σ = dist / (planetModel.c * A);
-    double σʹ;
-    double iterations = 0;
-    do {
-      cos2σM = Math.cos(2 * σ1 + σ);
-      sinσ = Math.sin(σ);
-      cosσ = Math.cos(σ);
-      Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM * cos2σM) -
-          B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM * cos2σM)));
-      σʹ = σ;
-      σ = dist / (planetModel.c * A) + Δσ;
-    } while (Math.abs(σ - σʹ) > 1e-12 && ++iterations < 200);
-
-    if (iterations >= 200) {
-      throw new RuntimeException("Formula failed to converge");
+    GeoPoint newPoint = planetModel.surfacePointOnBearing(point, dist, bearing);
+    double newLat = newPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
+    double newLon = newPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
+    if (reuse != null) {
+      reuse.reset(newLon, newLat);
+      return reuse;
+    }
+    else {
+      return ctx.getShapeFactory().pointXY(newLon, newLat);
     }
-
-    double x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
-    double φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - planetModel.flattening) * Math.sqrt(sinα * sinα + x * x));
-    double λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
-    double C = planetModel.flattening / 16 * cosSqα * (4 + planetModel.flattening * (4 - 3 * cosSqα));
-    double L = λ - (1 - C) * planetModel.flattening * sinα *
-        (σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM)));
-    double λ2 = (lon + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI;  // normalise to -180..+180
-
-    return ctx.getShapeFactory().pointXY(λ2 * DistanceUtils.RADIANS_TO_DEGREES,
-        φ2 * DistanceUtils.RADIANS_TO_DEGREES);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java
index eedf7d6..327ac8f 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java
@@ -112,11 +112,7 @@ public class Geo3dShape<T extends GeoAreaShape> implements Shape {
     if (bbox == null) {
       LatLonBounds bounds = new LatLonBounds();
       shape.getBounds(bounds);
-      double leftLon = bounds.checkNoLongitudeBound() ? -Math.PI : bounds.getLeftLongitude();
-      double rightLon = bounds.checkNoLongitudeBound() ? Math.PI : bounds.getRightLongitude();
-      double minLat = bounds.checkNoBottomLatitudeBound() ? -Math.PI * 0.5 : bounds.getMinLatitude();
-      double maxLat = bounds.checkNoTopLatitudeBound() ? Math.PI * 0.5 : bounds.getMaxLatitude();
-      GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), maxLat, minLat, leftLon, rightLon);
+      GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), bounds);
       bbox = new Geo3dRectangleShape(geoBBox, spatialcontext);
       this.boundingBox = bbox;
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java
index a80a043..282d93b 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShapeFactory.java
@@ -55,6 +55,13 @@ public class Geo3dShapeFactory implements ShapeFactory {
   private SpatialContext context;
   private PlanetModel planetModel;
 
+  /**
+   * Default accuracy for circles when not using the unit sphere.
+   * It is equivalent to ~10m on the surface of the earth.
+   */
+  private static final double DEFAULT_CIRCLE_ACCURACY = 1e-4;
+  private double circleAccuracy = DEFAULT_CIRCLE_ACCURACY;
+
   @SuppressWarnings("unchecked")
   public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
     this.context = context;
@@ -67,6 +74,16 @@ public class Geo3dShapeFactory implements ShapeFactory {
     return context;
   }
 
+  /**
+   * Set the accuracy for circles in decimal degrees. Note that accuracy has no effect
+   * when the planet model is a sphere. In that case, circles are always fully precise.
+   *
+   * @param circleAccuracy the provided accuracy in decimal degrees.
+   */
+  public void setCircleAccuracy(double circleAccuracy) {
+    this.circleAccuracy = circleAccuracy;
+  }
+
   @Override
   public boolean isNormWrapLongitude() {
     return normWrapLongitude;
@@ -150,10 +167,23 @@ public class Geo3dShapeFactory implements ShapeFactory {
 
   @Override
   public Circle circle(double x, double y, double distance) {
-    GeoCircle circle = GeoCircleFactory.makeGeoCircle(planetModel,
-        y * DistanceUtils.DEGREES_TO_RADIANS,
-        x * DistanceUtils.DEGREES_TO_RADIANS,
-        distance * DistanceUtils.DEGREES_TO_RADIANS);
+    GeoCircle circle;
+    if (planetModel.isSphere()) {
+      circle = GeoCircleFactory.makeGeoCircle(planetModel,
+          y * DistanceUtils.DEGREES_TO_RADIANS,
+          x * DistanceUtils.DEGREES_TO_RADIANS,
+          distance * DistanceUtils.DEGREES_TO_RADIANS);
+    }
+    else {
+      //accuracy is defined as a linear distance in this class. At tiny distances, linear distance
+      //can be approximated to surface distance in radians.
+      circle = GeoCircleFactory.makeExactGeoCircle(planetModel,
+          y * DistanceUtils.DEGREES_TO_RADIANS,
+          x * DistanceUtils.DEGREES_TO_RADIANS,
+          distance * DistanceUtils.DEGREES_TO_RADIANS,
+          circleAccuracy * DistanceUtils.DEGREES_TO_RADIANS);
+
+    }
     return new Geo3dCircleShape(circle, context);
   }
 
@@ -238,8 +268,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
 
   /**
    * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate
-   * nine Strings. Note that GeoPath needs a buffer so we set the
-   * buffer to 1e-10.
+   * line strings.
    */
   private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {
 
@@ -373,7 +402,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
 
   /**
    * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
-   * geometry collections
+   * geometry collections.
    *
    * @param <T> is the type of shapes.
    */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
deleted file mode 100644
index 9873012..0000000
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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 org.apache.lucene.spatial.spatial4j;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.lucene.spatial3d.geom.GeoPath;
-import org.apache.lucene.spatial3d.geom.GeoPolygon;
-import org.locationtech.spatial4j.TestLog;
-import org.locationtech.spatial4j.context.SpatialContext;
-import org.locationtech.spatial4j.distance.DistanceUtils;
-import org.locationtech.spatial4j.shape.Circle;
-import org.locationtech.spatial4j.shape.Point;
-import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
-import org.apache.lucene.spatial3d.geom.LatLonBounds;
-import org.apache.lucene.spatial3d.geom.GeoBBox;
-import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
-import org.apache.lucene.spatial3d.geom.GeoCircle;
-import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
-import org.apache.lucene.spatial3d.geom.GeoPathFactory;
-import org.apache.lucene.spatial3d.geom.GeoPoint;
-import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
-import org.apache.lucene.spatial3d.geom.GeoShape;
-import org.apache.lucene.spatial3d.geom.PlanetModel;
-import org.junit.Rule;
-import org.junit.Test;
-
-import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
-
-public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTestCase {
-  protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
-
-  @Rule
-  public final TestLog testLog = TestLog.instance;
-
-  protected final PlanetModel planetModel;
-
-  public Geo3dShapeRectRelationTestCase(PlanetModel planetModel) {
-    super(SpatialContext.GEO);
-    this.planetModel = planetModel;
-  }
-
-  protected GeoBBox getBoundingBox(final GeoShape path) {
-    LatLonBounds bounds = new LatLonBounds();
-    path.getBounds(bounds);
-
-    double leftLon;
-    double rightLon;
-    if (bounds.checkNoLongitudeBound()) {
-      leftLon = -Math.PI;
-      rightLon = Math.PI;
-    } else {
-      leftLon = bounds.getLeftLongitude().doubleValue();
-      rightLon = bounds.getRightLongitude().doubleValue();
-    }
-    double minLat;
-    if (bounds.checkNoBottomLatitudeBound()) {
-      minLat = -Math.PI * 0.5;
-    } else {
-      minLat = bounds.getMinLatitude().doubleValue();
-    }
-    double maxLat;
-    if (bounds.checkNoTopLatitudeBound()) {
-      maxLat = Math.PI * 0.5;
-    } else {
-      maxLat = bounds.getMaxLatitude().doubleValue();
-    }
-    return GeoBBoxFactory.makeGeoBBox(planetModel, maxLat, minLat, leftLon, rightLon);
-  }
-
-  public abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {
-
-    public Geo3dRectIntersectionTestHelper(SpatialContext ctx) {
-      super(ctx);
-    }
-
-    //20 times each -- should be plenty
-
-    protected int getContainsMinimum(int laps) {
-      return 20;
-    }
-
-    protected int getIntersectsMinimum(int laps) {
-      return 20;
-    }
-
-    // producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
-    protected int getWithinMinimum(int laps) {
-      return 2;
-    }
-
-    protected int getDisjointMinimum(int laps) {
-      return 20;
-    }
-
-    protected int getBoundingMinimum(int laps) {
-      return 20;
-    }
-  }
-
-  @Test
-  public void testGeoCircleRect() {
-    new Geo3dRectIntersectionTestHelper(ctx) {
-
-      @Override
-      protected Geo3dShape generateRandomShape(Point nearP) {
-        final int circleRadius = 180 - random().nextInt(180);//no 0-radius
-        final Point point = nearP;
-        final GeoCircle shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
-            circleRadius * DEGREES_TO_RADIANS);
-        return new Geo3dShape(shape, ctx);
-      }
-
-      @Override
-      protected Point randomPointInEmptyShape(Geo3dShape shape) {
-        GeoPoint geoPoint = ((GeoCircle)shape.shape).getCenter();
-        return geoPointToSpatial4jPoint(geoPoint);
-      }
-
-    }.testRelateWithRectangle();
-  }
-
-  @Test
-  public void testGeoBBoxRect() {
-    new Geo3dRectIntersectionTestHelper(ctx) {
-
-      @Override
-      protected boolean isRandomShapeRectangular() {
-        return true;
-      }
-
-      @Override
-      protected Geo3dShape generateRandomShape(Point nearP) {
-        // (ignoring nearP)
-        Point ulhcPoint = randomPoint();
-        Point lrhcPoint = randomPoint();
-        if (ulhcPoint.getY() < lrhcPoint.getY()) {
-          //swap
-          Point temp = ulhcPoint;
-          ulhcPoint = lrhcPoint;
-          lrhcPoint = temp;
-        }
-        final GeoBBox shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS,
-            lrhcPoint.getY() * DEGREES_TO_RADIANS,
-            ulhcPoint.getX() * DEGREES_TO_RADIANS,
-            lrhcPoint.getX() * DEGREES_TO_RADIANS);
-        return new Geo3dShape(shape, ctx);
-      }
-
-      @Override
-      protected Point randomPointInEmptyShape(Geo3dShape shape) {
-        return shape.getBoundingBox().getCenter();
-      }
-    }.testRelateWithRectangle();
-  }
-
-  @Test
-  public void testGeoPolygonRect() {
-    new Geo3dRectIntersectionTestHelper(ctx) {
-
-      @Override
-      protected Geo3dShape generateRandomShape(Point nearP) {
-        final Point centerPoint = randomPoint();
-        final int maxDistance = random().nextInt(160) + 20;
-        final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
-        final int vertexCount = random().nextInt(3) + 3;
-        while (true) {
-          final List<GeoPoint> geoPoints = new ArrayList<>();
-          while (geoPoints.size() < vertexCount) {
-            final Point point = randomPointIn(pointZone);
-            final GeoPoint gPt = new GeoPoint(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
-            geoPoints.add(gPt);
-          }
-          try {
-            final GeoPolygon shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints);
-            if (shape == null) {
-              continue;
-            }
-            return new Geo3dShape(shape, ctx);
-          } catch (IllegalArgumentException e) {
-            // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
-            // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
-            continue;
-          }
-        }
-      }
-
-      @Override
-      protected Point randomPointInEmptyShape(Geo3dShape shape) {
-        throw new IllegalStateException("unexpected; need to finish test code");
-      }
-
-      @Override
-      protected int getWithinMinimum(int laps) {
-        // Long/thin so lets just find 1.
-        return 1;
-      }
-
-    }.testRelateWithRectangle();
-  }
-
-  @Test
-  public void testGeoPathRect() {
-    new Geo3dRectIntersectionTestHelper(ctx) {
-
-      @Override
-      protected Geo3dShape generateRandomShape(Point nearP) {
-        final Point centerPoint = randomPoint();
-        final int maxDistance = random().nextInt(160) + 20;
-        final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
-        final int pointCount = random().nextInt(5) + 1;
-        final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
-        final GeoPoint[] points = new GeoPoint[pointCount];
-        while (true) {
-          for (int i = 0; i < pointCount; i++) {
-            final Point nextPoint = randomPointIn(pointZone);
-            points[i] = new GeoPoint(planetModel, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
-          }
-          
-          try {
-            final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, width, points);
-            return new Geo3dShape(path, ctx);
-          } catch (IllegalArgumentException e) {
-            // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
-            // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
-            continue;
-          }
-        }
-      }
-
-      @Override
-      protected Point randomPointInEmptyShape(Geo3dShape shape) {
-        throw new IllegalStateException("unexpected; need to finish test code");
-      }
-
-      @Override
-      protected int getWithinMinimum(int laps) {
-        // Long/thin so lets just find 1.
-        return 1;
-      }
-
-    }.testRelateWithRectangle();
-  }
-
-  private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
-    return ctx.makePoint(geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES,
-        geoPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java
index 20db21c..bf152b7 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeSphereModelRectRelationTest.java
@@ -34,13 +34,13 @@ import org.locationtech.spatial4j.shape.Point;
 import org.locationtech.spatial4j.shape.Rectangle;
 import org.locationtech.spatial4j.shape.SpatialRelation;
 
-public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
+public class Geo3dShapeSphereModelRectRelationTest extends ShapeRectRelationTestCase {
+
+  PlanetModel planetModel = PlanetModel.SPHERE;
 
   public Geo3dShapeSphereModelRectRelationTest() {
-    super(PlanetModel.SPHERE);
     Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
-    factory.planetModel = PlanetModel.SPHERE;
-    //factory.distCalc = new GeodesicSphereDistCalc.Haversine();
+    factory.planetModel = planetModel;
     this.ctx = factory.newSpatialContext();
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
index 22d7bd4..5a7b4b5 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
@@ -30,14 +30,16 @@ import org.locationtech.spatial4j.shape.Circle;
 import org.locationtech.spatial4j.shape.Point;
 import org.locationtech.spatial4j.shape.SpatialRelation;
 
-public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
+public class Geo3dShapeWGS84ModelRectRelationTest extends ShapeRectRelationTestCase {
+
+  PlanetModel planetModel = PlanetModel.WGS84;
 
   public Geo3dShapeWGS84ModelRectRelationTest() {
-    super(PlanetModel.WGS84);
     Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
-    factory.planetModel = PlanetModel.WGS84;
-    //factory.distCalc = new GeodesicSphereDistCalc.Haversine();
+    factory.planetModel = planetModel;
     this.ctx = factory.newSpatialContext();
+    this.maxRadius = 178;
+    ((Geo3dShapeFactory)ctx.getShapeFactory()).setCircleAccuracy(1e-6);
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/caf401c4/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/ShapeRectRelationTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/ShapeRectRelationTestCase.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/ShapeRectRelationTestCase.java
new file mode 100644
index 0000000..7ec2a2b
--- /dev/null
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/ShapeRectRelationTestCase.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.lucene.spatial.spatial4j;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.locationtech.spatial4j.TestLog;
+import org.locationtech.spatial4j.context.SpatialContext;
+import org.locationtech.spatial4j.shape.Circle;
+import org.locationtech.spatial4j.shape.Point;
+import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
+import org.locationtech.spatial4j.shape.Shape;
+import org.locationtech.spatial4j.shape.ShapeFactory;
+
+import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
+
+public abstract class ShapeRectRelationTestCase extends RandomizedShapeTestCase {
+  protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
+
+  @Rule
+  public final TestLog testLog = TestLog.instance;
+
+  protected int maxRadius = 180;
+
+  public ShapeRectRelationTestCase() {
+    super(SpatialContext.GEO);
+  }
+
+  public abstract class AbstractRectIntersectionTestHelper extends RectIntersectionTestHelper<Shape> {
+
+    public AbstractRectIntersectionTestHelper(SpatialContext ctx) {
+      super(ctx);
+    }
+
+    //20 times each -- should be plenty
+
+    protected int getContainsMinimum(int laps) {
+      return 20;
+    }
+
+    protected int getIntersectsMinimum(int laps) {
+      return 20;
+    }
+
+    // producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
+    protected int getWithinMinimum(int laps) {
+      return 2;
+    }
+
+    protected int getDisjointMinimum(int laps) {
+      return 20;
+    }
+
+    protected int getBoundingMinimum(int laps) {
+      return 20;
+    }
+  }
+
+  @Test
+  public void testGeoCircleRect() {
+    new AbstractRectIntersectionTestHelper(ctx) {
+
+      @Override
+      protected Shape generateRandomShape(Point nearP) {
+        final int circleRadius = maxRadius - random().nextInt(maxRadius);//no 0-radius
+        return ctx.getShapeFactory().circle(nearP, circleRadius);
+      }
+
+      @Override
+      protected Point randomPointInEmptyShape(Shape shape) {
+        return shape.getCenter();
+      }
+
+    }.testRelateWithRectangle();
+  }
+
+  @Test
+  public void testGeoBBoxRect() {
+    new AbstractRectIntersectionTestHelper(ctx) {
+
+      @Override
+      protected boolean isRandomShapeRectangular() {
+        return true;
+      }
+
+      @Override
+      protected Shape generateRandomShape(Point nearP) {
+        Point upperRight = randomPoint();
+        Point lowerLeft = randomPoint();
+        if (upperRight.getY() < lowerLeft.getY()) {
+          //swap
+          Point temp = upperRight;
+          upperRight = lowerLeft;
+          lowerLeft = temp;
+        }
+        return ctx.getShapeFactory().rect(lowerLeft, upperRight);
+      }
+
+      @Override
+      protected Point randomPointInEmptyShape(Shape shape) {
+        return shape.getCenter();
+      }
+    }.testRelateWithRectangle();
+  }
+
+  @Test
+  public void testGeoPolygonRect() {
+    new AbstractRectIntersectionTestHelper(ctx) {
+
+      @Override
+      protected Shape generateRandomShape(Point nearP) {
+        final Point centerPoint = randomPoint();
+        final int maxDistance = random().nextInt(maxRadius -20) + 20;
+        final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
+        final int vertexCount = random().nextInt(3) + 3;
+        while (true) {
+          ShapeFactory.PolygonBuilder builder = ctx.getShapeFactory().polygon();
+          for (int i = 0; i < vertexCount; i++) {
+            final Point point = randomPointIn(pointZone);
+            builder.pointXY(point.getX(), point.getY());
+          }
+          try {
+            return builder.build();
+          } catch (IllegalArgumentException e) {
+            // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
+            // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
+            continue;
+          }
+        }
+      }
+
+      @Override
+      protected Point randomPointInEmptyShape(Shape shape) {
+        throw new IllegalStateException("unexpected; need to finish test code");
+      }
+
+      @Override
+      protected int getWithinMinimum(int laps) {
+        // Long/thin so lets just find 1.
+        return 1;
+      }
+
+    }.testRelateWithRectangle();
+  }
+
+  @Test
+  public void testGeoPathRect() {
+    new AbstractRectIntersectionTestHelper(ctx) {
+
+      @Override
+      protected Shape generateRandomShape(Point nearP) {
+        final Point centerPoint = randomPoint();
+        final int maxDistance = random().nextInt(maxRadius -20) + 20;
+        final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
+        final int pointCount = random().nextInt(5) + 1;
+        final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
+        final ShapeFactory.LineStringBuilder builder = ctx.getShapeFactory().lineString();
+        while (true) {
+          for (int i = 0; i < pointCount; i++) {
+            final Point nextPoint = randomPointIn(pointZone);
+            builder.pointXY(nextPoint.getX(), nextPoint.getY());
+          }
+          builder.buffer(width);
+          try {
+            return builder.build();
+          } catch (IllegalArgumentException e) {
+            // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
+            // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
+            continue;
+          }
+        }
+      }
+
+      @Override
+      protected Point randomPointInEmptyShape(Shape shape) {
+        throw new IllegalStateException("unexpected; need to finish test code");
+      }
+
+      @Override
+      protected int getWithinMinimum(int laps) {
+        // Long/thin so lets just find 1.
+        return 1;
+      }
+
+    }.testRelateWithRectangle();
+  }
+}