You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2016/03/22 22:12:34 UTC

lucene-solr:branch_6x: LUCENE-7127: remove epsilon-based testing from lucene/spatial, fix distance bugs.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x c5da271b9 -> ee1aca864


LUCENE-7127: remove epsilon-based testing from lucene/spatial, fix distance bugs.


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

Branch: refs/heads/branch_6x
Commit: ee1aca86435e501d02c23a93f480f076a8b72f34
Parents: c5da271
Author: Robert Muir <rm...@apache.org>
Authored: Tue Mar 22 16:53:25 2016 -0400
Committer: Robert Muir <rm...@apache.org>
Committed: Tue Mar 22 16:55:33 2016 -0400

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   4 +
 .../document/TestLatLonPointDistanceQuery.java  | 108 -------
 .../lucene/search/TestLatLonPointQueries.java   |  45 ---
 .../search/GeoPointDistanceQueryImpl.java       |  24 +-
 .../lucene/spatial/util/GeoDistanceUtils.java   |   8 +-
 .../lucene/spatial/util/GeoEncodingUtils.java   |   6 -
 .../lucene/spatial/util/GeoProjectionUtils.java | 158 ----------
 .../lucene/spatial/util/GeoRelationUtils.java   | 250 ---------------
 .../apache/lucene/spatial/util/GeoUtils.java    |  44 +--
 .../geopoint/search/TestGeoPointQuery.java      |  84 +----
 .../search/TestLegacyGeoPointQuery.java         |  83 ++---
 .../spatial/util/BaseGeoPointTestCase.java      | 206 ++++++++++---
 .../lucene/spatial/util/TestGeoUtils.java       | 308 -------------------
 13 files changed, 234 insertions(+), 1094 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 138f6bd..6dbacef 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -29,6 +29,10 @@ Optimizations
 * LUCENE-7115: Speed up FieldCache.CacheEntry toString by setting initial
   StringBuilder capacity (Gregory Chanan)
 
+Bug Fixes
+
+* LUCENE-7127: Fix corner case bugs in GeoPointDistanceQuery. (Robert Muir)
+
 ======================= Lucene 6.0.0 =======================
 
 System Requirements

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java
index 983b1a7..f015780 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceQuery.java
@@ -16,29 +16,11 @@
  */
 package org.apache.lucene.document;
 
-import java.io.IOException;
-import java.util.BitSet;
-
-import org.apache.lucene.codecs.FilterCodec;
-import org.apache.lucene.codecs.PointsFormat;
-import org.apache.lucene.codecs.PointsReader;
-import org.apache.lucene.codecs.PointsWriter;
-import org.apache.lucene.codecs.lucene60.Lucene60PointsReader;
-import org.apache.lucene.codecs.lucene60.Lucene60PointsWriter;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.index.SegmentReadState;
-import org.apache.lucene.index.SegmentWriteState;
 import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.TestUtil;
-import org.apache.lucene.util.bkd.BKDWriter;
 
 /** Simple tests for {@link LatLonPoint#newDistanceQuery} */
 public class TestLatLonPointDistanceQuery extends LuceneTestCase {
@@ -97,94 +79,4 @@ public class TestLatLonPointDistanceQuery extends LuceneTestCase {
     assertTrue(expected.getMessage().contains("radiusMeters"));
     assertTrue(expected.getMessage().contains("is invalid"));
   }
-  
-  /** Run a few iterations with just 10 docs, hopefully easy to debug */
-  public void testRandom() throws Exception {
-    for (int iters = 0; iters < 100; iters++) {
-      doRandomTest(10, 100);
-    }
-  }
-  
-  /** Runs with thousands of docs */
-  @Nightly
-  public void testRandomHuge() throws Exception {
-    for (int iters = 0; iters < 10; iters++) {
-      doRandomTest(2000, 100);
-    }
-  }
-  
-  private void doRandomTest(int numDocs, int numQueries) throws IOException {
-    Directory dir = newDirectory();
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    int pointsInLeaf = 2 + random().nextInt(4);
-    iwc.setCodec(new FilterCodec("Lucene60", TestUtil.getDefaultCodec()) {
-      @Override
-      public PointsFormat pointsFormat() {
-        return new PointsFormat() {
-          @Override
-          public PointsWriter fieldsWriter(SegmentWriteState writeState) throws IOException {
-            return new Lucene60PointsWriter(writeState, pointsInLeaf, BKDWriter.DEFAULT_MAX_MB_SORT_IN_HEAP);
-          }
-
-          @Override
-          public PointsReader fieldsReader(SegmentReadState readState) throws IOException {
-            return new Lucene60PointsReader(readState);
-          }
-        };
-      }
-    });
-    RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
-
-    for (int i = 0; i < numDocs; i++) {
-      double latRaw = -90 + 180.0 * random().nextDouble();
-      double lonRaw = -180 + 360.0 * random().nextDouble();
-      // pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
-      double lat = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(latRaw));
-      double lon = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lonRaw));
-      Document doc = new Document();
-      doc.add(new LatLonPoint("field", lat, lon));
-      doc.add(new StoredField("lat", lat));
-      doc.add(new StoredField("lon", lon));
-      writer.addDocument(doc);
-    }
-    IndexReader reader = writer.getReader();
-    IndexSearcher searcher = newSearcher(reader);
-
-    for (int i = 0; i < numQueries; i++) {
-      double lat = -90 + 180.0 * random().nextDouble();
-      double lon = -180 + 360.0 * random().nextDouble();
-      double radius = 50000000 * random().nextDouble();
-
-      BitSet expected = new BitSet();
-      for (int doc = 0; doc < reader.maxDoc(); doc++) {
-        double docLatitude = reader.document(doc).getField("lat").numericValue().doubleValue();
-        double docLongitude = reader.document(doc).getField("lon").numericValue().doubleValue();
-        double distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
-        if (distance <= radius) {
-          expected.set(doc);
-        }
-      }
-
-      TopDocs topDocs = searcher.search(LatLonPoint.newDistanceQuery("field", lat, lon, radius), reader.maxDoc(), Sort.INDEXORDER);
-      BitSet actual = new BitSet();
-      for (ScoreDoc doc : topDocs.scoreDocs) {
-        actual.set(doc.doc);
-      }
-
-      try {
-        assertEquals(expected, actual);
-      } catch (AssertionError e) {
-        for (int doc = 0; doc < reader.maxDoc(); doc++) {
-          double docLatitude = reader.document(doc).getField("lat").numericValue().doubleValue();
-          double docLongitude = reader.document(doc).getField("lon").numericValue().doubleValue();
-          double distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
-          System.out.println("" + doc + ": (" + docLatitude + "," + docLongitude + "), distance=" + distance);
-        }
-        throw e;
-      }
-    }
-    reader.close();
-    writer.close();
-    dir.close();
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
index df34940..71ef7ef 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -20,8 +20,6 @@ import org.apache.lucene.document.Document;
 import org.apache.lucene.document.LatLonPoint;
 import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
 import org.apache.lucene.spatial.util.GeoRect;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
-import org.apache.lucene.util.SloppyMath;
 
 public class TestLatLonPointQueries extends BaseGeoPointTestCase {
 
@@ -51,19 +49,6 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
   }
 
   @Override
-  protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    assert Double.isNaN(pointLat) == false;
-
-    if (rect.minLon < rect.maxLon) {
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
-    } else {
-      // Rect crosses dateline:
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, -180.0, rect.maxLon)
-        || GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, 180.0);
-    }
-  }
-
-  @Override
   protected double quantizeLat(double latRaw) {
     return LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(latRaw));
   }
@@ -72,34 +57,4 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
   protected double quantizeLon(double lonRaw) {
     return LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lonRaw));
   }
-
-  @Override
-  protected Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    // TODO write better random polygon tests
-    
-    // note: logic must be slightly different than rectContainsPoint, to satisfy
-    // insideness for cases exactly on boundaries.
-    
-    assert Double.isNaN(pointLat) == false;
-    assert rect.crossesDateline() == false;
-    double polyLats[] = new double[] { rect.minLat, rect.maxLat, rect.maxLat, rect.minLat, rect.minLat };
-    double polyLons[] = new double[] { rect.minLon, rect.minLon, rect.maxLon, rect.maxLon, rect.minLon };
-
-    // TODO: separately test this method is 100% correct, here treat it like a black box (like haversin)
-    return GeoRelationUtils.pointInPolygon(polyLats, polyLons, pointLat, pointLon);
-  }
-
-  @Override
-  protected Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
-    double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
-    boolean result = distanceMeters <= radiusMeters;
-    //System.out.println("  shouldMatch?  centerLon=" + centerLon + " centerLat=" + centerLat + " pointLon=" + pointLon + " pointLat=" + pointLat + " result=" + result + " distanceMeters=" + (distanceKM * 1000));
-    return result;
-  }
-
-  @Override
-  protected Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
-    final double d = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
-    return d >= minRadiusMeters && d <= radiusMeters;
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
index 8f50db5..d4272a3 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointDistanceQueryImpl.java
@@ -19,7 +19,6 @@ package org.apache.lucene.spatial.geopoint.search;
 import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.GeoRect;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
 import org.apache.lucene.util.SloppyMath;
 
 /** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
@@ -54,15 +53,28 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
 
     @Override
     protected boolean cellCrosses(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      return GeoRelationUtils.rectCrossesCircle(minLat, maxLat, minLon, maxLon,
-                                                distanceQuery.centerLat, centerLon, distanceQuery.radiusMeters, true);
+      if (maxLat < GeoPointDistanceQueryImpl.this.minLat ||
+          maxLon < GeoPointDistanceQueryImpl.this.minLon ||
+          minLat > GeoPointDistanceQueryImpl.this.maxLat ||
+          minLon > GeoPointDistanceQueryImpl.this.maxLon) {
+        return false;
+      } else {
+        return true;
+      }
     }
 
     @Override
     protected boolean cellWithin(final double minLat, final double maxLat, final double minLon, final double maxLon) {
-      return GeoRelationUtils.rectWithinCircle(minLat, maxLat, minLon, maxLon,
-                                               distanceQuery.centerLat, centerLon,
-                                               distanceQuery.radiusMeters, true);
+      // TODO: we call cellCrosses because of how the termsEnum logic works, helps us avoid some haversin() calls here.
+      if (cellCrosses(minLat, maxLat, minLon, maxLon) && maxLon - centerLon < 90 && centerLon - minLon < 90 &&
+          SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, minLat, minLon) <= distanceQuery.radiusMeters &&
+          SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, minLat, maxLon) <= distanceQuery.radiusMeters &&
+          SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, maxLat, minLon) <= distanceQuery.radiusMeters &&
+          SloppyMath.haversinMeters(distanceQuery.centerLat, centerLon, maxLat, maxLon) <= distanceQuery.radiusMeters) {
+        // we are fully enclosed, collect everything within this subtree
+        return true;
+      }
+      return false;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
index 8956c4b..32a16cd 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
@@ -18,16 +18,12 @@ package org.apache.lucene.spatial.util;
 
 import org.apache.lucene.util.SloppyMath;
 
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
 /**
  * Reusable geo-spatial distance utility methods.
  *
  * @lucene.experimental
  */
 public class GeoDistanceUtils {
-  /** error threshold for point-distance queries (in percent) NOTE: Guideline from USGS is 0.005 **/
-  public static final double DISTANCE_PCT_ERR = 0.005;
 
   // No instance:
   private GeoDistanceUtils() {
@@ -58,7 +54,7 @@ public class GeoDistanceUtils {
     lat = StrictMath.toRadians(lat);
 
     // get the diameter at the latitude
-    final double diameter = 2 * GeoProjectionUtils.SEMIMAJOR_AXIS;
+    final double diameter = 2 * GeoUtils.SEMIMAJOR_AXIS;
 
     // compute inverse haversine
     double a = StrictMath.sin(distance/diameter);
@@ -125,7 +121,7 @@ public class GeoDistanceUtils {
    */
   public static double distanceToDegreesLat(double lat, double distance) {
     // get the diameter at the latitude
-    final double diameter = 2 * GeoProjectionUtils.SEMIMAJOR_AXIS;
+    final double diameter = 2 * GeoUtils.SEMIMAJOR_AXIS;
 
     // compute inverse haversine
     double a = StrictMath.sin(distance/diameter);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java
index 38eef04..7bed892 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoEncodingUtils.java
@@ -84,12 +84,6 @@ public final class GeoEncodingUtils {
     return (val / LAT_SCALE) + MIN_LAT_INCL;
   }
 
-  /** Compare two position values within a {@link GeoEncodingUtils#TOLERANCE} factor */
-  public static double compare(final double v1, final double v2) {
-    final double delta = v1-v2;
-    return Math.abs(delta) <= TOLERANCE ? 0 : delta;
-  }
-
   /** Convert a geocoded morton long into a prefix coded geo term */
   public static void geoCodedToPrefixCoded(long hash, int shift, BytesRefBuilder bytes) {
     geoCodedToPrefixCodedBytes(hash, shift, bytes);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
deleted file mode 100644
index 61aa09f..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
+++ /dev/null
@@ -1,158 +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.util;
-
-import static java.lang.StrictMath.sqrt;
-
-import static org.apache.lucene.util.SloppyMath.asin;
-import static org.apache.lucene.util.SloppyMath.cos;
-import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-import static org.apache.lucene.spatial.util.GeoUtils.MAX_LAT_INCL;
-import static org.apache.lucene.spatial.util.GeoUtils.MAX_LON_INCL;
-import static org.apache.lucene.spatial.util.GeoUtils.MIN_LAT_INCL;
-import static org.apache.lucene.spatial.util.GeoUtils.MIN_LON_INCL;
-import static org.apache.lucene.spatial.util.GeoUtils.PIO2;
-import static org.apache.lucene.spatial.util.GeoUtils.normalizeLat;
-import static org.apache.lucene.spatial.util.GeoUtils.normalizeLon;
-import static org.apache.lucene.spatial.util.GeoUtils.sloppySin;
-import static org.apache.lucene.spatial.util.GeoUtils.sloppyTan;
-
-/**
- * Reusable geo-spatial projection utility methods.
- *
- * @lucene.experimental
- */
-public class GeoProjectionUtils {
-  // WGS84 earth-ellipsoid parameters
-  /** major (a) axis in meters */
-  public static final double SEMIMAJOR_AXIS = 6_378_137; // [m]
-  /** earth flattening factor (f) */
-  public static final double FLATTENING = 1.0/298.257223563;
-  /** minor (b) axis in meters */
-  public static final double SEMIMINOR_AXIS = SEMIMAJOR_AXIS * (1.0 - FLATTENING); //6_356_752.31420; // [m]
-  /** first eccentricity (e) */
-  public static final double ECCENTRICITY = sqrt((2.0 - FLATTENING) * FLATTENING);
-  /** major axis squared (a2) */
-  public static final double SEMIMAJOR_AXIS2 = SEMIMAJOR_AXIS * SEMIMAJOR_AXIS;
-  /** minor axis squared (b2) */
-  public static final double SEMIMINOR_AXIS2 = SEMIMINOR_AXIS * SEMIMINOR_AXIS;
-  private static final double E2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2);
-  private static final double EP2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMINOR_AXIS2);
-
-  /** min longitude value in radians */
-  public static final double MIN_LON_RADIANS = TO_RADIANS * MIN_LON_INCL;
-  /** min latitude value in radians */
-  public static final double MIN_LAT_RADIANS = TO_RADIANS * MIN_LAT_INCL;
-  /** max longitude value in radians */
-  public static final double MAX_LON_RADIANS = TO_RADIANS * MAX_LON_INCL;
-  /** max latitude value in radians */
-  public static final double MAX_LAT_RADIANS = TO_RADIANS * MAX_LAT_INCL;
-
-  // No instance:
-  private GeoProjectionUtils() {
-  }
-
-  /**
-   * Converts from geodesic lat lon alt to geocentric earth-centered earth-fixed
-   * @param lat geodesic latitude
-   * @param lon geodesic longitude
-   * @param alt geodesic altitude
-   * @param ecf reusable earth-centered earth-fixed result
-   * @return either a new ecef array or the reusable ecf parameter
-   */
-  public static final double[] llaToECF(double lat, double lon, double alt, double[] ecf) {
-    lon = TO_RADIANS * lon;
-    lat = TO_RADIANS * lat;
-
-    final double sl = sloppySin(lat);
-    final double s2 = sl*sl;
-    final double cl = cos(lat);
-
-    if (ecf == null) {
-      ecf = new double[3];
-    }
-
-    if (lat < -PIO2 && lat > -1.001D * PIO2) {
-      lat = -PIO2;
-    } else if (lat > PIO2 && lat < 1.001D * PIO2) {
-      lat = PIO2;
-    }
-    assert (lat >= -PIO2) || (lat <= PIO2);
-
-    if (lon > StrictMath.PI) {
-      lon -= (2*StrictMath.PI);
-    }
-
-    final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * s2);
-    ecf[0] = (rn+alt) * cl * cos(lon);
-    ecf[1] = (rn+alt) * cl * sloppySin(lon);
-    ecf[2] = ((rn*(1.0-E2))+alt)*sl;
-
-    return ecf;
-  }
-
-  /**
-   * Finds a point along a bearing from a given lat,lon geolocation using great circle arc
-   *
-   * @param lat origin latitude in degrees
-   * @param lon origin longitude in degrees
-   * @param bearing azimuthal bearing in degrees
-   * @param dist distance in meters
-   * @param pt resulting point
-   * @return the point along a bearing at a given distance in meters
-   */
-  public static final double[] pointFromLonLatBearingGreatCircle(double lat, double lon, double bearing, double dist, double[] pt) {
-
-    if (pt == null) {
-      pt = new double[2];
-    }
-
-    lat *= TO_RADIANS;
-    lon *= TO_RADIANS;
-    bearing *= TO_RADIANS;
-
-    final double cLat = cos(lat);
-    final double sLat = sloppySin(lat);
-    final double sinDoR = sloppySin(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
-    final double cosDoR = cos(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
-
-    pt[1] = asin(sLat*cosDoR + cLat * sinDoR * cos(bearing));
-    pt[0] = TO_DEGREES * (lon + Math.atan2(sloppySin(bearing) * sinDoR * cLat, cosDoR - sLat * sloppySin(pt[1])));
-    pt[1] *= TO_DEGREES;
-
-    return pt;
-  }
-
-  /**
-   * Finds the bearing (in degrees) between 2 geo points (lat, lon) using great circle arc
-   * @param lat1 first point latitude in degrees
-   * @param lon1 first point longitude in degrees
-   * @param lat2 second point latitude in degrees
-   * @param lon2 second point longitude in degrees
-   * @return the bearing (in degrees) between the two provided points
-   */
-  public static double bearingGreatCircle(double lat1, double lon1, double lat2, double lon2) {
-    double dLon = (lon2 - lon1) * TO_RADIANS;
-    lat2 *= TO_RADIANS;
-    lat1 *= TO_RADIANS;
-    double y = sloppySin(dLon) * cos(lat2);
-    double x = cos(lat1) * sloppySin(lat2) - sloppySin(lat1) * cos(lat2) * cos(dLon);
-    return Math.atan2(y, x) * TO_DEGREES;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
index 4a31202..ba94e9d 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
@@ -16,8 +16,6 @@
  */
 package org.apache.lucene.spatial.util;
 
-import org.apache.lucene.util.SloppyMath;
-
 /**
  * Reusable geo-relation utility methods
  */
@@ -277,252 +275,4 @@ public class GeoRelationUtils {
      return !(rectCrossesPolyApprox(rMinLat, rMaxLat, rMinLon, rMaxLon, shapeLats, shapeLons, sMinLat, sMaxLat, sMinLon, sMaxLon)
          || !pointInPolygon(shapeLats, shapeLons, rMinLat, rMinLon));
   }
-
-  /////////////////////////
-  // Circle relations
-  /////////////////////////
-
-  private static boolean rectAnyCornersInCircle(final double rMinLat, final double rMaxLat, final double rMinLon,
-                                                final double rMaxLon, final double centerLat, final double centerLon,
-                                                final double radiusMeters, final boolean approx) {
-    if (approx == true) {
-      return rectAnyCornersInCircleSloppy(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters);
-    }
-    double w = Math.abs(rMaxLon - rMinLon);
-    if (w <= 90.0) {
-      return SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMinLon) <= radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMinLon) <= radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMaxLon) <= radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMaxLon) <= radiusMeters;
-    }
-    // partition
-    w /= 4;
-    final double p1 = rMinLon + w;
-    final double p2 = p1 + w;
-    final double p3 = p2 + w;
-
-    return SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMinLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMinLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, p1) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, p1) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, p2) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, p2) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, p3) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, p3) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMaxLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMaxLon) <= radiusMeters;
-  }
-
-  private static boolean rectAnyCornersInCircleSloppy(final double rMinLat, final double rMaxLat, final double rMinLon, final double rMaxLon,
-                                                      final double centerLat, final double centerLon, final double radiusMeters) {
-    return SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMinLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMinLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMaxLon) <= radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMaxLon) <= radiusMeters;
-  }
-
-  /**
-   * Compute whether any of the 4 corners of the rectangle (defined by min/max X/Y) are outside the circle (defined
-   * by centerLon, centerLat, radiusMeters)
-   *
-   * Note: exotic rectangles at the poles (e.g., those whose lat/lon distance ratios greatly deviate from 1) can not
-   * be determined by using distance alone. For this reason the approx flag may be set to false, in which case the
-   * space will be further divided to more accurately compute whether the rectangle crosses the circle
-   */
-  private static boolean rectAnyCornersOutsideCircle(final double rMinLat, final double rMaxLat, final double rMinLon,
-                                                     final double rMaxLon, final double centerLat, final double centerLon,
-                                                     final double radiusMeters, final boolean approx) {
-    if (approx == true) {
-      return rectAnyCornersOutsideCircleSloppy(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters);
-    }
-    // if span is less than 70 degrees we can approximate using distance alone
-    if (Math.abs(rMaxLon - rMinLon) <= 70.0) {
-      return SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMinLon) > radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMinLon) > radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMaxLon) > radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMaxLon) > radiusMeters;
-    }
-    return rectCrossesOblateCircle(centerLat, centerLon,
-                                   radiusMeters,
-                                   rMinLat, rMaxLat,
-                                   rMinLon, rMaxLon);
-  }
-
-  /**
-   * Compute whether the rectangle (defined by min/max Lat/Lon) crosses a potentially oblate circle
-   *
-   * TODO benchmark for replacing existing rectCrossesCircle.
-   */
-  private static boolean rectCrossesOblateCircle(double centerLat, double centerLon,
-                                                 double radiusMeters,
-                                                 double rMinLat, double rMaxLat,
-                                                 double rMinLon, double rMaxLon) {
-    double w = Math.abs(rMaxLon - rMinLon);
-    final int segs = (int)Math.ceil(w / 45.0);
-    w /= segs;
-    short i = 1;
-    double p1 = rMinLon;
-    double maxLon, midLon;
-    double[] pt = new double[2];
-
-    do {
-      maxLon = (i == segs) ? rMaxLon : p1 + w;
-
-      final double d1, d2;
-      // short-circuit if we find a corner outside the circle
-      if ( (d1 = SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, p1)) > radiusMeters
-          || (d2 = SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, maxLon)) > radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, p1) > radiusMeters
-          || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, maxLon) > radiusMeters) {
-        return true;
-      }
-
-      // else we treat as an oblate circle by slicing the longitude space and checking the azimuthal range
-      // OPTIMIZATION: this is only executed for latitude values "closeTo" the poles (e.g., 88.0 > lat < -88.0)
-      if ( (rMaxLat > 88.0 || rMinLat < -88.0)
-           && (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(rMinLat, p1,
-                                                                          GeoProjectionUtils.bearingGreatCircle(rMinLat, p1, rMaxLat, p1), radiusMeters - d1, pt))[1] < rMinLat || pt[1] < rMaxLat
-           || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(rMinLat, maxLon,
-                                                                          GeoProjectionUtils.bearingGreatCircle(rMinLat, maxLon, rMaxLat, maxLon), radiusMeters - d2, pt))[1] < rMinLat || pt[1] < rMaxLat
-           || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(rMinLat, maxLon,
-                                                                          GeoProjectionUtils.bearingGreatCircle(rMinLat, maxLon, rMaxLat, (midLon = p1 + 0.5*(maxLon - p1))),
-                                                                          radiusMeters - SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, midLon), pt))[1] < rMinLat
-           || pt[1] < rMaxLat == false ) {
-        return true;
-      }
-      p1 += w;
-    } while (++i <= segs);
-    return false;
-  }
-
-  private static boolean rectAnyCornersOutsideCircleSloppy(final double rMinLat, final double rMaxLat, final double rMinLon, final double rMaxLon,
-                                                           final double centerLat, final double centerLon, final double radiusMeters) {
-    return SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMinLon) > radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMinLon) > radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMaxLat, rMaxLon) > radiusMeters
-        || SloppyMath.haversinMeters(centerLat, centerLon, rMinLat, rMaxLon) > radiusMeters;
-  }
-
-  /**
-   * Computes whether a rectangle is within a circle. Note: approx == true will be faster but less precise and may
-   * fail on large rectangles
-   */
-  public static boolean rectWithinCircle(final double rMinLat, final double rMaxLat, final double rMinLon, final double rMaxLon,
-                                         final double centerLat, final double centerLon, final double radiusMeters,
-                                         final boolean approx) {
-    return rectAnyCornersOutsideCircle(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters, approx) == false;
-  }
-
-  /**
-   * Computes whether a rectangle crosses a circle. Note: approx == true will be faster but less precise and may
-   * fail on large rectangles
-   *
-   * <p>NOTE: this is basic method that does not handle dateline or pole crossing. Unwrapping must be done before
-   * calling this method.
-   */
-  public static boolean rectCrossesCircle(final double rMinLat, final double rMaxLat, final double rMinLon, final double rMaxLon,
-                                          final double centerLat, final double centerLon, final double radiusMeters,
-                                          final boolean approx) {
-    if (approx == true) {
-      if (rectAnyCornersInCircle(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters, approx)) {
-        return true;
-      }
-    } else {
-      if (rectAnyCornersInCircle(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters, approx) &&
-          rectAnyCornersOutsideCircle(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters, approx)) {
-        return true;
-      }
-    }
-    if (isClosestPointOnRectWithinRange(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, radiusMeters, approx)) {
-      return true;
-    }
-    return false;
-  }
-
-  private static boolean isClosestPointOnRectWithinRange(final double rMinLat, final double rMaxLat,
-                                                         final double rMinLon, final double rMaxLon,
-                                                         final double centerLat, final double centerLon,
-                                                         final double radiusMeters,
-                                                         final boolean approx) {
-    double[] closestPt = {0, 0};
-    GeoDistanceUtils.closestPointOnBBox(rMinLat, rMaxLat, rMinLon, rMaxLon, centerLat, centerLon, closestPt);
-    boolean haverShortCut = SloppyMath.haversinMeters(centerLat, centerLon, closestPt[0], closestPt[1]) <= radiusMeters;
-    if (approx == true || haverShortCut == true) {
-      return haverShortCut;
-    }
-    double lon1 = rMinLon;
-    double lon2 = rMaxLon;
-    double lat1 = rMinLat;
-    double lat2 = rMaxLat;
-    if (closestPt[1] == rMinLon || closestPt[1] == rMaxLon) {
-      lon1 = closestPt[1];
-      lon2 = lon1;
-    } else if (closestPt[0] == rMinLat || closestPt[0] == rMaxLat) {
-      lat1 = closestPt[0];
-      lat2 = lat1;
-    }
-
-    return lineCrossesSphere(lat1, lon1, 0,
-                             lat2, lon2, 0,
-                             centerLat, centerLon, 0,
-                             radiusMeters);
-  }
-
-  /**
-   * Computes whether or a 3dimensional line segment intersects or crosses a sphere
-   *
-   * @param lon1 longitudinal location of the line segment start point (in degrees)
-   * @param lat1 latitudinal location of the line segment start point (in degrees)
-   * @param alt1 altitude of the line segment start point (in degrees)
-   * @param lon2 longitudinal location of the line segment end point (in degrees)
-   * @param lat2 latitudinal location of the line segment end point (in degrees)
-   * @param alt2 altitude of the line segment end point (in degrees)
-   * @param centerLon longitudinal location of center search point (in degrees)
-   * @param centerLat latitudinal location of center search point (in degrees)
-   * @param centerAlt altitude of the center point (in meters)
-   * @param radiusMeters search sphere radius (in meters)
-   * @return whether the provided line segment is a secant of the
-   */
-  private static boolean lineCrossesSphere(double lat1, double lon1, double alt1,
-                                           double lat2, double lon2, double alt2,
-                                           double centerLat, double centerLon, double centerAlt,
-                                           double radiusMeters) {
-    // convert to cartesian 3d (in meters)
-    double[] ecf1 = GeoProjectionUtils.llaToECF(lat1, lon1, alt1, null);
-    double[] ecf2 = GeoProjectionUtils.llaToECF(lat2, lon2, alt2, null);
-    double[] cntr = GeoProjectionUtils.llaToECF(centerLat, centerLon, centerAlt, null);
-
-    // convert radius from arc radius to cartesian radius
-    double[] oneEighty = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(centerLat, centerLon, 180.0d, radiusMeters, new double[3]);
-    GeoProjectionUtils.llaToECF(oneEighty[1], oneEighty[0], 0, oneEighty);
-
-    radiusMeters = GeoDistanceUtils.linearDistance(oneEighty, cntr);//   Math.sqrt(oneEighty[0]*cntr[0] + oneEighty[1]*cntr[1] + oneEighty[2]*cntr[2]);
-
-    final double dX = ecf2[0] - ecf1[0];
-    final double dY = ecf2[1] - ecf1[1];
-    final double dZ = ecf2[2] - ecf1[2];
-    final double fX = ecf1[0] - cntr[0];
-    final double fY = ecf1[1] - cntr[1];
-    final double fZ = ecf1[2] - cntr[2];
-
-    final double a = dX*dX + dY*dY + dZ*dZ;
-    final double b = 2 * (fX*dX + fY*dY + fZ*dZ);
-    final double c = (fX*fX + fY*fY + fZ*fZ) - (radiusMeters*radiusMeters);
-
-    double discrim = (b*b)-(4*a*c);
-    if (discrim < 0) {
-      return false;
-    }
-
-    discrim = StrictMath.sqrt(discrim);
-    final double a2 = 2*a;
-    final double t1 = (-b - discrim)/a2;
-    final double t2 = (-b + discrim)/a2;
-
-    if ( (t1 < 0 || t1 > 1) ) {
-      return !(t2 < 0 || t2 > 1);
-    }
-
-    return true;
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
index 935da60..a07ea72 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
@@ -16,10 +16,6 @@
  */
 package org.apache.lucene.spatial.util;
 
-import java.util.ArrayList;
-
-import org.apache.lucene.util.SloppyMath;
-
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 import static java.lang.Math.PI;
@@ -30,11 +26,6 @@ import static org.apache.lucene.util.SloppyMath.cos;
 import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
 import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
 import static org.apache.lucene.spatial.util.GeoEncodingUtils.TOLERANCE;
-import static org.apache.lucene.spatial.util.GeoProjectionUtils.MAX_LAT_RADIANS;
-import static org.apache.lucene.spatial.util.GeoProjectionUtils.MAX_LON_RADIANS;
-import static org.apache.lucene.spatial.util.GeoProjectionUtils.MIN_LAT_RADIANS;
-import static org.apache.lucene.spatial.util.GeoProjectionUtils.MIN_LON_RADIANS;
-import static org.apache.lucene.spatial.util.GeoProjectionUtils.SEMIMAJOR_AXIS;
 
 /**
  * Basic reusable geo-spatial utility methods
@@ -53,6 +44,19 @@ public final class GeoUtils {
 
   /** Maximum latitude value. */
   public static final double MAX_LAT_INCL = 90.0D;
+  
+  /** min longitude value in radians */
+  public static final double MIN_LON_RADIANS = TO_RADIANS * MIN_LON_INCL;
+  /** min latitude value in radians */
+  public static final double MIN_LAT_RADIANS = TO_RADIANS * MIN_LAT_INCL;
+  /** max longitude value in radians */
+  public static final double MAX_LON_RADIANS = TO_RADIANS * MAX_LON_INCL;
+  /** max latitude value in radians */
+  public static final double MAX_LAT_RADIANS = TO_RADIANS * MAX_LAT_INCL;
+  
+  // WGS84 earth-ellipsoid parameters
+  /** major (a) axis in meters */
+  public static final double SEMIMAJOR_AXIS = 6_378_137; // [m]
 
   // No instance:
   private GeoUtils() {
@@ -153,7 +157,7 @@ public final class GeoUtils {
   
   // some sloppyish stuff, do we really need this to be done in a sloppy way?
   // unless it is performance sensitive, we should try to remove.
-  static final double PIO2 = Math.PI / 2D;
+  private static final double PIO2 = Math.PI / 2D;
 
   /**
    * Returns the trigonometric sine of an angle converted as a cos operation.
@@ -169,26 +173,8 @@ public final class GeoUtils {
    * @see Math#sin(double)
    */
   // TODO: deprecate/remove this? at least its no longer public.
-  static double sloppySin(double a) {
+  private static double sloppySin(double a) {
     return cos(a - PIO2);
   }
-  
 
-  /**
-   * Returns the trigonometric tangent of an angle converted in terms of a sin/cos operation.
-   * <p>
-   * Note that this is probably not quite right (untested)
-   * <p>
-   * Special cases:
-   * <ul>
-   *  <li>If the argument is {@code NaN} or an infinity, then the result is {@code NaN}.
-   * </ul>
-   * @param a an angle, in radians.
-   * @return the tangent of the argument.
-   * @see Math#sin(double) aand Math#cos(double)
-   */
-  // TODO: deprecate/remove this? at least its no longer public.
-  static double sloppyTan(double a) {
-    return sloppySin(a) / cos(a);
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
index 53ff25c..747cd1f 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
@@ -22,23 +22,30 @@ import org.apache.lucene.spatial.util.GeoEncodingUtils;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
 import org.apache.lucene.spatial.util.GeoRect;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
-import org.apache.lucene.util.SloppyMath;
-
-import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
 
 /**
  * random testing for GeoPoint query logic
  *
  * @lucene.experimental
  */
-
 public class TestGeoPointQuery extends BaseGeoPointTestCase {
 
   @Override
-  protected boolean forceSmall() {
-    return false;
+  protected double maxRadius(double latitude, double longitude) {
+    // TODO: clean this up
+    return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
+  }
+  
+  @Override
+  protected double quantizeLat(double lat) {
+    return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
+  }
+  
+  @Override
+  protected double quantizeLon(double lon) {
+    return GeoEncodingUtils.mortonUnhashLon(GeoEncodingUtils.mortonHash(0, lon));
   }
 
   @Override
@@ -58,7 +65,9 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
 
   @Override
   protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
-    return new GeoPointDistanceRangeQuery(field, TermEncoding.PREFIX, centerLat, centerLon, minRadiusMeters, radiusMeters);
+    // LUCENE-7126: currently not valid for multi-valued documents, because it rewrites to a BooleanQuery!
+    // return new GeoPointDistanceRangeQuery(field, TermEncoding.PREFIX, centerLat, centerLon, minRadiusMeters, radiusMeters);
+    return null;
   }
 
   @Override
@@ -66,63 +75,4 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
     return new GeoPointInPolygonQuery(field, TermEncoding.PREFIX, lats, lons);
   }
 
-  @Override
-  protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    if (GeoEncodingUtils.compare(pointLon, rect.minLon) == 0.0 ||
-        GeoEncodingUtils.compare(pointLon, rect.maxLon) == 0.0 ||
-        GeoEncodingUtils.compare(pointLat, rect.minLat) == 0.0 ||
-        GeoEncodingUtils.compare(pointLat, rect.maxLat) == 0.0) {
-      // Point is very close to rect boundary
-      return null;
-    }
-
-    if (rect.minLon < rect.maxLon) {
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
-    } else {
-      // Rect crosses dateline:
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, -180.0, rect.maxLon)
-        || GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, 180.0);
-    }
-  }
-
-  @Override
-  protected Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    return rectContainsPoint(rect, pointLat, pointLon);
-  }
-
-  @Override
-  protected Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
-    if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
-      return null;
-    } else {
-      return SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon) <= radiusMeters;
-    }
-  }
-
-  @Override
-  protected Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
-    if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, minRadiusMeters)
-        || radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
-      return null;
-    } else {
-      final double d = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
-      return d >= minRadiusMeters && d <= radiusMeters;
-    }
-  }
-
-  private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
-                                               final double radius) {
-    final long hashedCntr = GeoEncodingUtils.mortonHash(centerLat, centerLon);
-    centerLon = GeoEncodingUtils.mortonUnhashLon(hashedCntr);
-    centerLat = GeoEncodingUtils.mortonUnhashLat(hashedCntr);
-    final long hashedPt = GeoEncodingUtils.mortonHash(ptLat, ptLon);
-    ptLon = GeoEncodingUtils.mortonUnhashLon(hashedPt);
-    ptLat = GeoEncodingUtils.mortonUnhashLat(hashedPt);
-
-    double ptDistance = SloppyMath.haversinMeters(centerLat, centerLon, ptLat, ptLon);
-    double delta = StrictMath.abs(ptDistance - radius);
-
-    // if its within the distance error then it can be wrong
-    return delta < (ptDistance*DISTANCE_PCT_ERR);
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
index 90bfb10..73b3082 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
@@ -22,11 +22,8 @@ import org.apache.lucene.spatial.util.GeoEncodingUtils;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
 import org.apache.lucene.spatial.util.GeoRect;
-import org.apache.lucene.spatial.util.GeoRelationUtils;
-import org.apache.lucene.util.SloppyMath;
-
-import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
 
 /**
  * random testing for GeoPoint query logic (with deprecated numeric encoding)
@@ -36,8 +33,19 @@ import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
 public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
 
   @Override
-  protected boolean forceSmall() {
-    return false;
+  protected double maxRadius(double latitude, double longitude) {
+    // TODO: clean this up
+    return GeoDistanceUtils.maxRadialDistanceMeters(latitude, longitude);
+  }
+  
+  @Override
+  protected double quantizeLat(double lat) {
+    return GeoEncodingUtils.mortonUnhashLat(GeoEncodingUtils.mortonHash(lat, 0));
+  }
+  
+  @Override
+  protected double quantizeLon(double lon) {
+    return GeoEncodingUtils.mortonUnhashLon(GeoEncodingUtils.mortonHash(0, lon));
   }
 
   @Override
@@ -57,7 +65,9 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
 
   @Override
   protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
-    return new GeoPointDistanceRangeQuery(field, TermEncoding.NUMERIC, centerLat, centerLon, minRadiusMeters, radiusMeters);
+    // LUCENE-7126: currently not valid for multi-valued documents, because it rewrites to a BooleanQuery!
+    // return new GeoPointDistanceRangeQuery(field, TermEncoding.NUMERIC, centerLat, centerLon, minRadiusMeters, radiusMeters);
+    return null;
   }
 
   @Override
@@ -65,63 +75,14 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
     return new GeoPointInPolygonQuery(field, TermEncoding.NUMERIC, lats, lons);
   }
 
+  // legacy encoding is too slow somehow for this random test, its not up to the task.
   @Override
-  protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    if (GeoEncodingUtils.compare(pointLon, rect.minLon) == 0.0 ||
-        GeoEncodingUtils.compare(pointLon, rect.maxLon) == 0.0 ||
-        GeoEncodingUtils.compare(pointLat, rect.minLat) == 0.0 ||
-        GeoEncodingUtils.compare(pointLat, rect.maxLat) == 0.0) {
-      // Point is very close to rect boundary
-      return null;
-    }
-
-    if (rect.minLon < rect.maxLon) {
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
-    } else {
-      // Rect crosses dateline:
-      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, -180.0, rect.maxLon)
-        || GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, 180.0);
-    }
-  }
-
-  @Override
-  protected Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    return rectContainsPoint(rect, pointLat, pointLon);
-  }
-
-  @Override
-  protected Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
-    if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
-      return null;
-    } else {
-      return SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon) <= radiusMeters;
-    }
+  public void testRandomDistance() throws Exception {
+    assumeTrue("legacy encoding is too slow/hangs on this test", false);
   }
 
   @Override
-  protected Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
-    if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, minRadiusMeters)
-        || radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
-      return null;
-    } else {
-      final double d = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
-      return d >= minRadiusMeters && d <= radiusMeters;
-    }
-  }
-
-  private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
-                                               final double radius) {
-    final long hashedCntr = GeoEncodingUtils.mortonHash(centerLat, centerLon);
-    centerLon = GeoEncodingUtils.mortonUnhashLon(hashedCntr);
-    centerLat = GeoEncodingUtils.mortonUnhashLat(hashedCntr);
-    final long hashedPt = GeoEncodingUtils.mortonHash(ptLat, ptLon);
-    ptLon = GeoEncodingUtils.mortonUnhashLon(hashedPt);
-    ptLat = GeoEncodingUtils.mortonUnhashLat(hashedPt);
-
-    double ptDistance = SloppyMath.haversinMeters(centerLat, centerLon, ptLat, ptLon);
-    double delta = StrictMath.abs(ptDistance - radius);
-
-    // if its within the distance error then it can be wrong
-    return delta < (ptDistance*DISTANCE_PCT_ERR);
+  public void testRandomDistanceHuge() throws Exception {
+    assumeTrue("legacy encoding is too slow/hangs on this test", false);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
index a4f70d9..6f3a73c 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
@@ -21,6 +21,7 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -28,9 +29,16 @@ import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.lucene.codecs.FilterCodec;
+import org.apache.lucene.codecs.PointsFormat;
+import org.apache.lucene.codecs.PointsReader;
+import org.apache.lucene.codecs.PointsWriter;
+import org.apache.lucene.codecs.lucene60.Lucene60PointsReader;
+import org.apache.lucene.codecs.lucene60.Lucene60PointsWriter;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.StoredField;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
@@ -39,16 +47,22 @@ import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.MultiDocValues;
 import org.apache.lucene.index.NumericDocValues;
 import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.SegmentReadState;
+import org.apache.lucene.index.SegmentWriteState;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.SloppyMath;
 import org.apache.lucene.util.TestUtil;
+import org.apache.lucene.util.bkd.BKDWriter;
 import org.junit.BeforeClass;
 
 // TODO: cutover TestGeoUtils too?
@@ -72,17 +86,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     originLat = GeoUtils.normalizeLat(GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble());
   }
 
-  /** Return true when testing on a non-small region may be too slow (GeoPoint*Query) */
-  protected boolean forceSmall() {
-    return false;
-  }
-
   // A particularly tricky adversary for BKD tree:
   public void testSamePointManyTimes() throws Exception {
-
-    // For GeoPointQuery, only run this test nightly:
-    assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
     int numPoints = atLeast(1000);
     boolean small = random().nextBoolean();
 
@@ -100,12 +105,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
 
   public void testAllLatEqual() throws Exception {
-
-    // For GeoPointQuery, only run this test nightly:
-    assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
     int numPoints = atLeast(10000);
-    boolean small = forceSmall() || random().nextBoolean();
+    boolean small = random().nextBoolean();
     double lat = randomLat(small);
     double[] lats = new double[numPoints];
     double[] lons = new double[numPoints];
@@ -151,12 +152,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
 
   public void testAllLonEqual() throws Exception {
-
-    // For GeoPointQuery, only run this test nightly:
-    assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
     int numPoints = atLeast(10000);
-    boolean small = forceSmall() || random().nextBoolean();
+    boolean small = random().nextBoolean();
     double theLon = randomLon(small);
     double[] lats = new double[numPoints];
     double[] lons = new double[numPoints];
@@ -204,10 +201,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
 
   public void testMultiValued() throws Exception {
-
-    // For GeoPointQuery, only run this test nightly:
-    assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
     int numPoints = atLeast(10000);
     // Every doc has 2 points:
     double[] lats = new double[2*numPoints];
@@ -289,19 +282,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
         double latDoc2 = lats[2*docID+1];
         double lonDoc2 = lons[2*docID+1];
         
-        Boolean result1 = rectContainsPoint(rect, latDoc1, lonDoc1);
-        if (result1 == null) {
-          // borderline case: cannot test
-          continue;
-        }
-
-        Boolean result2 = rectContainsPoint(rect, latDoc2, lonDoc2);
-        if (result2 == null) {
-          // borderline case: cannot test
-          continue;
-        }
+        boolean result1 = rectContainsPoint(rect, latDoc1, lonDoc1);
+        boolean result2 = rectContainsPoint(rect, latDoc2, lonDoc2);
 
-        boolean expected = result1 == Boolean.TRUE || result2 == Boolean.TRUE;
+        boolean expected = result1 || result2;
 
         if (hits.get(docID) != expected) {
           String id = s.doc(docID).get("id");
@@ -447,6 +431,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   protected double quantizeLon(double lon) {
     return lon;
   }
+  
+  protected double maxRadius(double latitude, double longitude) {
+    return 50000000D; // bigger than earth, shouldnt matter
+  }
 
   protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
     double lat0 = randomLat(small);
@@ -482,16 +470,44 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
   protected abstract Query newPolygonQuery(String field, double[] lats, double[] lons);
 
-  /** Returns null if it's borderline case */
-  protected abstract Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon);
+  static final boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
+    assert Double.isNaN(pointLat) == false;
 
-  /** Returns null if it's borderline case */
-  protected abstract Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon);
+    if (rect.minLon < rect.maxLon) {
+      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
+    } else {
+      // Rect crosses dateline:
+      return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, -180.0, rect.maxLon)
+        || GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, 180.0);
+    }
+  }
+  
+  static final boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
+    // TODO write better random polygon tests
+    
+    // note: logic must be slightly different than rectContainsPoint, to satisfy
+    // insideness for cases exactly on boundaries.
+    
+    assert Double.isNaN(pointLat) == false;
+    assert rect.crossesDateline() == false;
+    double polyLats[] = new double[] { rect.minLat, rect.maxLat, rect.maxLat, rect.minLat, rect.minLat };
+    double polyLons[] = new double[] { rect.minLon, rect.minLon, rect.maxLon, rect.maxLon, rect.minLon };
+
+    // TODO: separately test this method is 100% correct, here treat it like a black box (like haversin)
+    return GeoRelationUtils.pointInPolygon(polyLats, polyLons, pointLat, pointLon);
+  }
 
-  /** Returns null if it's borderline case */
-  protected abstract Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon);
+  static final boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
+    double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
+    boolean result = distanceMeters <= radiusMeters;
+    //System.out.println("  shouldMatch?  centerLon=" + centerLon + " centerLat=" + centerLat + " pointLon=" + pointLon + " pointLat=" + pointLat + " result=" + result + " distanceMeters=" + (distanceKM * 1000));
+    return result;
+  }
 
-  protected abstract Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon);
+  static final boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
+    final double d = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
+    return d >= minRadiusMeters && d <= radiusMeters;
+  }
 
   private static abstract class VerifyHits {
 
@@ -525,7 +541,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
       for(int docID=0;docID<maxDoc;docID++) {
         int id = (int) docIDToID.get(docID);
-        Boolean expected;
+        boolean expected;
         if (deleted.contains(id)) {
           expected = false;
         } else if (Double.isNaN(lats[id])) {
@@ -534,8 +550,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
           expected = shouldMatch(lats[id], lons[id]);
         }
 
-        // null means it's a borderline case which is allowed to be wrong:
-        if (expected != null && hits.get(docID) != expected) {
+        if (hits.get(docID) != expected) {
 
           // Print only one failed hit; add a true || in here to see all failures:
           if (failFast == false || failed.getAndSet(true) == false) {
@@ -568,7 +583,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     /** Return true if we definitely should match, false if we definitely
      *  should not match, and null if it's a borderline case which might
      *  go either way. */
-    protected abstract Boolean shouldMatch(double lat, double lon);
+    protected abstract boolean shouldMatch(double lat, double lon);
 
     protected abstract void describe(int docID, double lat, double lon);
   }
@@ -666,7 +681,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
                 verifyHits = new VerifyHits() {
                     @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                    protected boolean shouldMatch(double pointLat, double pointLon) {
                       return rectContainsPoint(rect, pointLat, pointLon);
                     }
                     @Override
@@ -688,7 +703,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
                   radiusMeters = random().nextDouble() * 333000 + 1.0;
                 } else {
                   // So the query can cover at most 50% of the earth's surface:
-                  radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
+                  radiusMeters = random().nextDouble() * GeoUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
                 }
 
                 // generate a random minimum radius between 1% and 95% the max radius
@@ -715,7 +730,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
                 verifyHits = new VerifyHits() {
                     @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                    protected boolean shouldMatch(double pointLat, double pointLon) {
                       if (rangeQuery == false) {
                         return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
                       } else {
@@ -755,7 +770,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
 
                 verifyHits = new VerifyHits() {
                     @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                    protected boolean shouldMatch(double pointLat, double pointLon) {
                       return polyRectContainsPoint(bbox, pointLat, pointLon);
                     }
 
@@ -834,4 +849,95 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     w.close();
     dir.close();
   }
+  
+  /** Run a few iterations with just 10 docs, hopefully easy to debug */
+  public void testRandomDistance() throws Exception {
+    for (int iters = 0; iters < 100; iters++) {
+      doRandomDistanceTest(10, 100);
+    }
+  }
+    
+  /** Runs with thousands of docs */
+  @Nightly
+  public void testRandomDistanceHuge() throws Exception {
+    for (int iters = 0; iters < 10; iters++) {
+      doRandomDistanceTest(2000, 100);
+    }
+  }
+    
+  private void doRandomDistanceTest(int numDocs, int numQueries) throws IOException {
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    int pointsInLeaf = 2 + random().nextInt(4);
+    iwc.setCodec(new FilterCodec("Lucene60", TestUtil.getDefaultCodec()) {
+      @Override
+      public PointsFormat pointsFormat() {
+        return new PointsFormat() {
+          @Override
+          public PointsWriter fieldsWriter(SegmentWriteState writeState) throws IOException {
+            return new Lucene60PointsWriter(writeState, pointsInLeaf, BKDWriter.DEFAULT_MAX_MB_SORT_IN_HEAP);
+          }
+  
+          @Override
+          public PointsReader fieldsReader(SegmentReadState readState) throws IOException {
+            return new Lucene60PointsReader(readState);
+          }
+        };
+      }
+    });
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
+  
+    for (int i = 0; i < numDocs; i++) {
+      double latRaw = -90 + 180.0 * random().nextDouble();
+      double lonRaw = -180 + 360.0 * random().nextDouble();
+      // pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
+      double lat = quantizeLat(latRaw);
+      double lon = quantizeLon(lonRaw);
+      Document doc = new Document();
+      addPointToDoc("field", doc, lat, lon);
+      doc.add(new StoredField("lat", lat));
+      doc.add(new StoredField("lon", lon));
+      writer.addDocument(doc);
+    }
+    IndexReader reader = writer.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+  
+    for (int i = 0; i < numQueries; i++) {
+      double lat = -90 + 180.0 * random().nextDouble();
+      double lon = -180 + 360.0 * random().nextDouble();
+      double radius = maxRadius(lat, lon) * random().nextDouble();
+  
+      BitSet expected = new BitSet();
+      for (int doc = 0; doc < reader.maxDoc(); doc++) {
+        double docLatitude = reader.document(doc).getField("lat").numericValue().doubleValue();
+        double docLongitude = reader.document(doc).getField("lon").numericValue().doubleValue();
+        double distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
+        if (distance <= radius) {
+          expected.set(doc);
+        }
+      }
+  
+      TopDocs topDocs = searcher.search(newDistanceQuery("field", lat, lon, radius), reader.maxDoc(), Sort.INDEXORDER);
+      BitSet actual = new BitSet();
+      for (ScoreDoc doc : topDocs.scoreDocs) {
+        actual.set(doc.doc);
+      }
+      
+      try {
+        assertEquals(expected, actual);
+      } catch (AssertionError e) {
+        System.out.println("center: (" + lat + "," + lon + "), radius=" + radius);
+        for (int doc = 0; doc < reader.maxDoc(); doc++) {
+          double docLatitude = reader.document(doc).getField("lat").numericValue().doubleValue();
+          double docLongitude = reader.document(doc).getField("lon").numericValue().doubleValue();
+          double distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
+          System.out.println("" + doc + ": (" + docLatitude + "," + docLongitude + "), distance=" + distance);
+        }
+        throw e;
+      }
+    }
+    reader.close();
+    writer.close();
+    dir.close();
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ee1aca86/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
index f9dde50..fe8444f 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
@@ -16,23 +16,11 @@
  */
 package org.apache.lucene.spatial.util;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.SloppyMath;
 import org.junit.BeforeClass;
 
-import com.carrotsearch.randomizedtesting.generators.RandomInts;
-
-import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
-
 /**
  * Tests class for methods in GeoUtils
  *
@@ -77,41 +65,6 @@ public class TestGeoUtils extends LuceneTestCase {
     assertEquals(0.0, result[1], 0.0);
   }
 
-  private static class Cell {
-    static int nextCellID;
-
-    final Cell parent;
-    final int cellID;
-    final double minLon, maxLon;
-    final double minLat, maxLat;
-    final int splitCount;
-
-    public Cell(Cell parent,
-                double minLon, double minLat,
-                double maxLon, double maxLat,
-                int splitCount) {
-      assert maxLon >= minLon;
-      assert maxLat >= minLat;
-      this.parent = parent;
-      this.minLon = minLon;
-      this.minLat = minLat;
-      this.maxLon = maxLon;
-      this.maxLat = maxLat;
-      this.cellID = nextCellID++;
-      this.splitCount = splitCount;
-    }
-
-    /** Returns true if the quantized point lies within this cell, inclusive on all bounds. */
-    public boolean contains(double lon, double lat) {
-      return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
-    }
-
-    @Override
-    public String toString() {
-      return "cell=" + cellID + (parent == null ? "" : " parentCellID=" + parent.cellID) + " lon: " + minLon + " TO " + maxLon + ", lat: " + minLat + " TO " + maxLat + ", splits: " + splitCount;
-    }
-  }
-
   public long scaleLon(final double val) {
     return (long) ((val-GeoUtils.MIN_LON_INCL) * LON_SCALE);
   }
@@ -148,263 +101,6 @@ public class TestGeoUtils extends LuceneTestCase {
     return result;
   }
 
-  private void findMatches(Set<Integer> hits, PrintWriter log, Cell root,
-                           double centerLon, double centerLat, double radiusMeters,
-                           double[] docLons, double[] docLats) {
-
-    if (VERBOSE) {
-      log.println("  root cell: " + root);
-    }
-
-    List<Cell> queue = new ArrayList<>();
-    queue.add(root);
-
-    int recurseDepth = RandomInts.randomIntBetween(random(), 5, 15);
-
-    while (queue.size() > 0) {
-      Cell cell = queue.get(queue.size()-1);
-      queue.remove(queue.size()-1);
-      if (VERBOSE) {
-        log.println("  cycle: " + cell + " queue.size()=" + queue.size());
-      }
-
-      if (random().nextInt(10) == 7 || cell.splitCount > recurseDepth) {
-        if (VERBOSE) {
-          log.println("    leaf");
-        }
-        // Leaf cell: brute force check all docs that fall within this cell:
-        for(int docID=0;docID<docLons.length;docID++) {
-          if (cell.contains(docLons[docID], docLats[docID])) {
-            double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, docLats[docID], docLons[docID]);
-            if (distanceMeters <= radiusMeters) {
-              if (VERBOSE) {
-                log.println("    check doc=" + docID + ": match!");
-              }
-              hits.add(docID);
-            } else {
-              if (VERBOSE) {
-                log.println("    check doc=" + docID + ": no match");
-              }
-            }
-          }
-        }
-      } else {
-
-        if (GeoRelationUtils.rectWithinCircle(cell.minLat, cell.maxLat, cell.minLon, cell.maxLon, centerLat, centerLon, radiusMeters, false)) {
-          // Query circle fully contains this cell, just addAll:
-          if (VERBOSE) {
-            log.println("    circle fully contains cell: now addAll");
-          }
-          for(int docID=0;docID<docLons.length;docID++) {
-            if (cell.contains(docLons[docID], docLats[docID])) {
-              if (VERBOSE) {
-                log.println("    addAll doc=" + docID);
-              }
-              hits.add(docID);
-            }
-          }
-          continue;
-        } else if (GeoRelationUtils.rectWithin(root.minLat, root.maxLat, root.minLon, root.maxLon,
-                                               cell.minLat, cell.maxLat, cell.minLon, cell.maxLon)) {
-          // Fall through below to "recurse"
-          if (VERBOSE) {
-            log.println("    cell fully contains circle: keep splitting");
-          }
-        } else if (GeoRelationUtils.rectCrossesCircle(cell.minLat, cell.maxLat, cell.minLon, cell.maxLon,
-                                                      centerLat, centerLon, radiusMeters, false)) {
-          // Fall through below to "recurse"
-          if (VERBOSE) {
-            log.println("    cell overlaps circle: keep splitting");
-          }
-        } else {
-          if (VERBOSE) {
-            log.println("    no overlap: drop this cell");
-            for(int docID=0;docID<docLons.length;docID++) {
-              if (cell.contains(docLons[docID], docLats[docID])) {
-                if (VERBOSE) {
-                  log.println("    skip doc=" + docID);
-                }
-              }
-            }
-          }
-          continue;
-        }
-          
-        // Randomly split:
-        if (random().nextBoolean()) {
-
-          // Split on lon:
-          double splitValue = cell.minLon + (cell.maxLon - cell.minLon) * random().nextDouble();
-          if (VERBOSE) {
-            log.println("    now split on lon=" + splitValue);
-          }
-          Cell cell1 = new Cell(cell,
-                                cell.minLon, cell.minLat,
-                                splitValue, cell.maxLat,
-                                cell.splitCount+1);
-          Cell cell2 = new Cell(cell,
-                                splitValue, cell.minLat,
-                                cell.maxLon, cell.maxLat,
-                                cell.splitCount+1);
-          if (VERBOSE) {
-            log.println("    split cell1: " + cell1);
-            log.println("    split cell2: " + cell2);
-          }
-          queue.add(cell1);
-          queue.add(cell2);
-        } else {
-
-          // Split on lat:
-          double splitValue = cell.minLat + (cell.maxLat - cell.minLat) * random().nextDouble();
-          if (VERBOSE) {
-            log.println("    now split on lat=" + splitValue);
-          }
-          Cell cell1 = new Cell(cell,
-                                cell.minLon, cell.minLat,
-                                cell.maxLon, splitValue,
-                                cell.splitCount+1);
-          Cell cell2 = new Cell(cell,
-                                cell.minLon, splitValue,
-                                cell.maxLon, cell.maxLat,
-                                cell.splitCount+1);
-          if (VERBOSE) {
-            log.println("    split cells:\n      " + cell1 + "\n      " + cell2);
-          }
-          queue.add(cell1);
-          queue.add(cell2);
-        }
-      }
-    }
-  }
-
-  /** Tests consistency of GeoEncodingUtils.rectWithinCircle, .rectCrossesCircle, .rectWithin and SloppyMath.haversine distance check */
-  public void testGeoRelations() throws Exception {
-
-    int numDocs = atLeast(1000);
-    
-    boolean useSmallRanges = random().nextBoolean();
-
-    if (VERBOSE) {
-      System.out.println("TEST: " + numDocs + " docs useSmallRanges=" + useSmallRanges);
-    }
-
-    double[] docLons = new double[numDocs];
-    double[] docLats = new double[numDocs];
-    for(int docID=0;docID<numDocs;docID++) {
-      docLons[docID] = randomLon(useSmallRanges);
-      docLats[docID] = randomLat(useSmallRanges);
-      if (VERBOSE) {
-        System.out.println("  doc=" + docID + ": lon=" + docLons[docID] + " lat=" + docLats[docID]);
-      }
-    }
-
-    int iters = atLeast(10);
-
-    iters = atLeast(50);
-    
-    for(int iter=0;iter<iters;iter++) {
-
-      Cell.nextCellID = 0;
-
-      double centerLon = randomLon(useSmallRanges);
-      double centerLat = randomLat(useSmallRanges);
-
-      // So the circle covers at most 50% of the earth's surface:
-
-      double radiusMeters;
-
-      // TODO: large exotic rectangles created by BKD may be inaccurate up to 2 times DISTANCE_PCT_ERR.
-      // restricting size until LUCENE-6994 can be addressed
-      if (true || useSmallRanges) {
-        // Approx 3 degrees lon at the equator:
-        radiusMeters = random().nextDouble() * 333000;
-      } else {
-        radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0;
-      }
-
-      StringWriter sw = new StringWriter();
-      PrintWriter log = new PrintWriter(sw, true);
-
-      if (VERBOSE) {
-        log.println("\nTEST: iter=" + iter + " radiusMeters=" + radiusMeters + " centerLon=" + centerLon + " centerLat=" + centerLat);
-      }
-
-      GeoRect bbox = GeoUtils.circleToBBox(centerLat, centerLon, radiusMeters);
-      
-      Set<Integer> hits = new HashSet<>();
-
-      if (bbox.maxLon < bbox.minLon) {
-        // Crosses dateline
-        log.println("  circle crosses dateline; first left query");
-        double unwrappedLon = centerLon;
-        if (unwrappedLon > bbox.maxLon) {
-          // unwrap left
-          unwrappedLon += -360.0D;
-        }
-        findMatches(hits, log,
-                    new Cell(null,
-                             -180, bbox.minLat,
-                             bbox.maxLon, bbox.maxLat,
-                             0),
-                    unwrappedLon, centerLat, radiusMeters, docLons, docLats);
-        log.println("  circle crosses dateline; now right query");
-        if (unwrappedLon < bbox.maxLon) {
-          // unwrap right
-          unwrappedLon += 360.0D;
-        }
-        findMatches(hits, log,
-                    new Cell(null,
-                             bbox.minLon, bbox.minLat,
-                             180, bbox.maxLat,
-                             0),
-                    unwrappedLon, centerLat, radiusMeters, docLons, docLats);
-      } else {
-        // Start with the root cell that fully contains the shape:
-        findMatches(hits, log,
-                    new Cell(null,
-                             bbox.minLon, bbox.minLat,
-                             bbox.maxLon, bbox.maxLat,
-                             0),
-                    centerLon, centerLat, radiusMeters,
-                    docLons, docLats);
-      }
-
-      if (VERBOSE) {
-        log.println("  " + hits.size() + " hits");
-      }
-
-      int failCount = 0;
-
-      // Done matching, now verify:
-      for(int docID=0;docID<numDocs;docID++) {
-        double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, docLats[docID], docLons[docID]);
-        final Boolean expected;
-        final double percentError = Math.abs(distanceMeters - radiusMeters) / distanceMeters;
-        if (percentError <= DISTANCE_PCT_ERR) {
-          expected = null;
-        } else {
-          expected = distanceMeters <= radiusMeters;
-        }
-
-        boolean actual = hits.contains(docID);
-        if (expected != null && actual != expected) {
-          if (actual) {
-            log.println("doc=" + docID + " matched but should not with distance error " + percentError + " on iteration " + iter);
-          } else {
-            log.println("doc=" + docID + " did not match but should with distance error " + percentError + " on iteration " + iter);
-          }
-          log.println("  lon=" + docLons[docID] + " lat=" + docLats[docID] + " distanceMeters=" + distanceMeters + " vs radiusMeters=" + radiusMeters);
-          failCount++;
-        }
-      }
-
-      if (failCount != 0) {
-        System.out.print(sw.toString());
-        fail(failCount + " incorrect hits (see above)");
-      }
-    }
-  }
-
   /**
    * Tests stability of {@link GeoEncodingUtils#geoCodedToPrefixCoded}
    */
@@ -613,8 +309,4 @@ public class TestGeoUtils extends LuceneTestCase {
       assertFalse(rect.crossesDateline());
     }
   }
-
-  public void testRectCrossesCircle() throws Exception {
-    assertTrue(GeoRelationUtils.rectCrossesCircle(-90, 0.0, -180, 180, 0.0, 0.667, 88000.0, false));
-  }
 }