You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by nk...@apache.org on 2016/02/05 18:11:32 UTC

[1/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Repository: lucene-solr
Updated Branches:
  refs/heads/lucene-6997 [created] 665041c52


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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
new file mode 100644
index 0000000..7dc9318
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
@@ -0,0 +1,546 @@
+/*
+ * 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 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.LuceneTestCase;
+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
+ *
+ * @lucene.experimental
+ */
+public class TestGeoUtils extends LuceneTestCase {
+
+  private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
+  private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
+
+  // Global bounding box we will "cover" in the random test; we have to make this "smallish" else the queries take very long:
+  private static double originLat;
+  private static double originLon;
+  //  private static double range;
+  private static double lonRange;
+  private static double latRange;
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    // Between 1.0 and 3.0:
+    lonRange = 2 * (random().nextDouble() + 0.5);
+    latRange = 2 * (random().nextDouble() + 0.5);
+
+    originLon = GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble();
+    originLon = GeoUtils.normalizeLon(originLon);
+    originLat = GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble();
+    originLat = GeoUtils.normalizeLat(originLat);
+
+    if (VERBOSE) {
+      System.out.println("TEST: originLon=" + originLon + " lonRange= " + lonRange + " originLat=" + originLat + " latRange=" + latRange);
+    }
+  }
+
+  public void testGeoHash() {
+    int numPoints = atLeast(100);
+    String randomGeoHashString;
+    String mortonGeoHash;
+    long mortonLongFromGHLong, geoHashLong, mortonLongFromGHString;
+    int randomLevel;
+    for (int i = 0; i < numPoints; ++i) {
+      // random point
+      double lat = randomLat(false);
+      double lon = randomLon(false);
+
+      // compute geohash straight from lat/lon and from morton encoded value to ensure they're the same
+      randomGeoHashString = GeoHashUtils.stringEncode(lon, lat, randomLevel = random().nextInt(12 - 1) + 1);
+      mortonGeoHash = GeoHashUtils.stringEncodeFromMortonLong(GeoUtils.mortonHash(lon, lat), randomLevel);
+      assertEquals(randomGeoHashString, mortonGeoHash);
+
+      // v&v conversion from lat/lon or geohashstring to geohash long and back to geohash string
+      geoHashLong = (random().nextBoolean()) ? GeoHashUtils.longEncode(lon, lat, randomLevel) : GeoHashUtils.longEncode(randomGeoHashString);
+      assertEquals(randomGeoHashString, GeoHashUtils.stringEncode(geoHashLong));
+
+      // v&v conversion from geohash long to morton long
+      mortonLongFromGHString = GeoHashUtils.mortonEncode(randomGeoHashString);
+      mortonLongFromGHLong = GeoHashUtils.mortonEncode(geoHashLong);
+      assertEquals(mortonLongFromGHLong, mortonLongFromGHString);
+
+      // v&v lat/lon from geohash string and geohash long
+      assertEquals(GeoUtils.mortonUnhashLat(mortonLongFromGHString), GeoUtils.mortonUnhashLat(mortonLongFromGHLong), 0);
+      assertEquals(GeoUtils.mortonUnhashLon(mortonLongFromGHString), GeoUtils.mortonUnhashLon(mortonLongFromGHLong), 0);
+    }
+  }
+
+  /**
+   * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
+   * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
+   */
+  public void testEncode() {
+    String hash = GeoHashUtils.stringEncode(-5.6, 42.6, 12);
+    assertEquals("ezs42e44yx96", hash);
+
+    hash = GeoHashUtils.stringEncode(10.40744, 57.64911, 12);
+    assertEquals("u4pruydqqvj8", hash);
+  }
+
+  /**
+   * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then
+   * decoded within 0.00001 of the original value
+   */
+  public void testDecodePreciseLongitudeLatitude() {
+    final String geohash = GeoHashUtils.stringEncode(4.8909347, 52.3738007);
+    final long hash = GeoHashUtils.mortonEncode(geohash);
+
+    assertEquals(52.3738007, GeoUtils.mortonUnhashLat(hash), 0.00001D);
+    assertEquals(4.8909347, GeoUtils.mortonUnhashLon(hash), 0.00001D);
+  }
+
+  /**
+   * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded
+   * within 0.00001 of the original value
+   */
+  public void testDecodeImpreciseLongitudeLatitude() {
+    final String geohash = GeoHashUtils.stringEncode(10.5, 84.6);
+
+    final long hash = GeoHashUtils.mortonEncode(geohash);
+
+    assertEquals(84.6, GeoUtils.mortonUnhashLat(hash), 0.00001D);
+    assertEquals(10.5, GeoUtils.mortonUnhashLon(hash), 0.00001D);
+  }
+
+  public void testDecodeEncode() {
+    final String geoHash = "u173zq37x014";
+    assertEquals(geoHash, GeoHashUtils.stringEncode(4.8909347, 52.3738007));
+    final long mortonHash = GeoHashUtils.mortonEncode(geoHash);
+    final double lon = GeoUtils.mortonUnhashLon(mortonHash);
+    final double lat = GeoUtils.mortonUnhashLat(mortonHash);
+    assertEquals(52.37380061d, GeoUtils.mortonUnhashLat(mortonHash), 0.000001d);
+    assertEquals(4.8909343d, GeoUtils.mortonUnhashLon(mortonHash), 0.000001d);
+
+    assertEquals(geoHash, GeoHashUtils.stringEncode(lon, lat));
+  }
+
+  public void testNeighbors() {
+    String geohash = "gcpv";
+    List<String> expectedNeighbors = new ArrayList<>();
+    expectedNeighbors.add("gcpw");
+    expectedNeighbors.add("gcpy");
+    expectedNeighbors.add("u10n");
+    expectedNeighbors.add("gcpt");
+    expectedNeighbors.add("u10j");
+    expectedNeighbors.add("gcps");
+    expectedNeighbors.add("gcpu");
+    expectedNeighbors.add("u10h");
+    Collection<? super String> neighbors = new ArrayList<>();
+    GeoHashUtils.addNeighbors(geohash, neighbors );
+    assertEquals(expectedNeighbors, neighbors);
+
+    // Border odd geohash
+    geohash = "u09x";
+    expectedNeighbors = new ArrayList<>();
+    expectedNeighbors.add("u0c2");
+    expectedNeighbors.add("u0c8");
+    expectedNeighbors.add("u0cb");
+    expectedNeighbors.add("u09r");
+    expectedNeighbors.add("u09z");
+    expectedNeighbors.add("u09q");
+    expectedNeighbors.add("u09w");
+    expectedNeighbors.add("u09y");
+    neighbors = new ArrayList<>();
+    GeoHashUtils.addNeighbors(geohash, neighbors);
+    assertEquals(expectedNeighbors, neighbors);
+
+    // Border even geohash
+    geohash = "u09tv";
+    expectedNeighbors = new ArrayList<>();
+    expectedNeighbors.add("u09wh");
+    expectedNeighbors.add("u09wj");
+    expectedNeighbors.add("u09wn");
+    expectedNeighbors.add("u09tu");
+    expectedNeighbors.add("u09ty");
+    expectedNeighbors.add("u09ts");
+    expectedNeighbors.add("u09tt");
+    expectedNeighbors.add("u09tw");
+    neighbors = new ArrayList<>();
+    GeoHashUtils.addNeighbors(geohash, neighbors );
+    assertEquals(expectedNeighbors, neighbors);
+
+    // Border even and odd geohash
+    geohash = "ezzzz";
+    expectedNeighbors = new ArrayList<>();
+    expectedNeighbors.add("gbpbn");
+    expectedNeighbors.add("gbpbp");
+    expectedNeighbors.add("u0000");
+    expectedNeighbors.add("ezzzy");
+    expectedNeighbors.add("spbpb");
+    expectedNeighbors.add("ezzzw");
+    expectedNeighbors.add("ezzzx");
+    expectedNeighbors.add("spbp8");
+    neighbors = new ArrayList<>();
+    GeoHashUtils.addNeighbors(geohash, neighbors );
+    assertEquals(expectedNeighbors, neighbors);
+  }
+
+  public void testClosestPointOnBBox() {
+    double[] result = new double[2];
+    GeoDistanceUtils.closestPointOnBBox(20, 30, 40, 50, 70, 70, result);
+    assertEquals(40.0, result[0], 0.0);
+    assertEquals(50.0, result[1], 0.0);
+
+    GeoDistanceUtils.closestPointOnBBox(-20, -20, 0, 0, 70, 70, result);
+    assertEquals(0.0, result[0], 0.0);
+    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);
+  }
+
+  public long scaleLat(final double val) {
+    return (long) ((val-GeoUtils.MIN_LAT_INCL) * LAT_SCALE);
+  }
+
+  public double unscaleLon(final long val) {
+    return (val / LON_SCALE) + GeoUtils.MIN_LON_INCL;
+  }
+
+  public double unscaleLat(final long val) {
+    return (val / LAT_SCALE) + GeoUtils.MIN_LAT_INCL;
+  }
+
+  public double randomLat(boolean small) {
+    double result;
+    if (small) {
+      result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
+    } else {
+      result = -90 + 180.0 * random().nextDouble();
+    }
+    return result;
+  }
+
+  public double randomLon(boolean small) {
+    double result;
+    if (small) {
+      result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
+    } else {
+      result = -180 + 360.0 * random().nextDouble();
+    }
+    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 = GeoDistanceUtils.haversin(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.minLon, cell.minLat, cell.maxLon, cell.maxLat, centerLon, centerLat, radiusMeters)) {
+          // 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.minLon, root.minLat, root.maxLon, root.maxLat,
+                                       cell.minLon, cell.minLat, cell.maxLon, cell.maxLat)) {
+          // Fall through below to "recurse"
+          if (VERBOSE) {
+            log.println("    cell fully contains circle: keep splitting");
+          }
+        } else if (GeoRelationUtils.rectCrossesCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat,
+                                              centerLon, centerLat, radiusMeters)) {
+          // 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 GeoUtils.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(centerLon, centerLat, 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 = GeoDistanceUtils.haversin(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)");
+      }
+    }
+  }
+}


[2/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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
new file mode 100644
index 0000000..1911720
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
@@ -0,0 +1,520 @@
+/*
+ * 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 org.apache.lucene.util.SloppyMath;
+
+/**
+ * Reusable geo-relation utility methods
+ */
+public class GeoRelationUtils {
+
+  // No instance:
+  private GeoRelationUtils() {
+  }
+
+  /**
+   * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
+   * NOTE: this is a basic method that does not handle dateline or pole crossing. Unwrapping must be done before
+   * calling this method.
+   */
+  public static boolean pointInRectPrecise(final double lon, final double lat, final double minLon,
+                                           final double minLat, final double maxLon, final double maxLat) {
+    return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
+  }
+
+  /**
+   * simple even-odd point in polygon computation
+   *    1.  Determine if point is contained in the longitudinal range
+   *    2.  Determine whether point crosses the edge by computing the latitudinal delta
+   *        between the end-point of a parallel vector (originating at the point) and the
+   *        y-component of the edge sink
+   *
+   * NOTE: Requires polygon point (x,y) order either clockwise or counter-clockwise
+   */
+  public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) {
+    assert x.length == y.length;
+    boolean inPoly = false;
+    /**
+     * Note: This is using a euclidean coordinate system which could result in
+     * upwards of 110KM error at the equator.
+     * TODO convert coordinates to cylindrical projection (e.g. mercator)
+     */
+    for (int i = 1; i < x.length; i++) {
+      if (x[i] <= lon && x[i-1] >= lon || x[i-1] <= lon && x[i] >= lon) {
+        if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) <= lat) {
+          inPoly = !inPoly;
+        }
+      }
+    }
+    return inPoly;
+  }
+
+  /////////////////////////
+  // Rectangle relations
+  /////////////////////////
+
+  /**
+   * Computes whether two rectangles are disjoint
+   */
+  public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+                                     final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+    return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
+  }
+
+  /**
+   * Computes whether the first (a) rectangle is wholly within another (b) rectangle (shared boundaries allowed)
+   */
+  public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+                                   final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+    return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY);
+  }
+
+  /**
+   * Computes whether two rectangles cross
+   */
+  public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+                                    final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+    return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) ||
+        rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY));
+  }
+
+  /**
+   * Computes whether rectangle a contains rectangle b (touching allowed)
+   */
+  public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+                                     final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+    return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY);
+  }
+
+  /**
+   * Computes whether a rectangle intersects another rectangle (crosses, within, touching, etc)
+   */
+  public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+                                       final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+    return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) );
+  }
+
+  /////////////////////////
+  // Polygon relations
+  /////////////////////////
+
+  /**
+   * Convenience method for accurately computing whether a rectangle crosses a poly
+   */
+  public static boolean rectCrossesPolyPrecise(final double rMinX, final double rMinY, final double rMaxX,
+                                        final double rMaxY, final double[] shapeX, final double[] shapeY,
+                                        final double sMinX, final double sMinY, final double sMaxX,
+                                        final double sMaxY) {
+    // short-circuit: if the bounding boxes are disjoint then the shape does not cross
+    if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
+      return false;
+    }
+    return rectCrossesPoly(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY);
+  }
+
+  /**
+   * Compute whether a rectangle crosses a shape. (touching not allowed) Includes a flag for approximating the
+   * relation.
+   */
+  public static boolean rectCrossesPolyApprox(final double rMinX, final double rMinY, final double rMaxX,
+                                              final double rMaxY, final double[] shapeX, final double[] shapeY,
+                                              final double sMinX, final double sMinY, final double sMaxX,
+                                              final double sMaxY) {
+    // short-circuit: if the bounding boxes are disjoint then the shape does not cross
+    if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
+      return false;
+    }
+
+    final int polyLength = shapeX.length-1;
+    for (short p=0; p<polyLength; ++p) {
+      if (lineCrossesRect(shapeX[p], shapeY[p], shapeX[p+1], shapeY[p+1], rMinX, rMinY, rMaxX, rMaxY) == true) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Accurately compute (within restrictions of cartesian decimal degrees) whether a rectangle crosses a polygon
+   */
+  private static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX,
+                                         final double rMaxY, final double[] shapeX, final double[] shapeY) {
+    final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} };
+    final int polyLength = shapeX.length-1;
+    double d, s, t, a1, b1, c1, a2, b2, c2;
+    double x00, y00, x01, y01, x10, y10, x11, y11;
+
+    // computes the intersection point between each bbox edge and the polygon edge
+    for (short b=0; b<4; ++b) {
+      a1 = bbox[b+1][1]-bbox[b][1];
+      b1 = bbox[b][0]-bbox[b+1][0];
+      c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1];
+      for (int p=0; p<polyLength; ++p) {
+        a2 = shapeY[p+1]-shapeY[p];
+        b2 = shapeX[p]-shapeX[p+1];
+        // compute determinant
+        d = a1*b2 - a2*b1;
+        if (d != 0) {
+          // lines are not parallel, check intersecting points
+          c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
+          s = (1/d)*(b2*c1 - b1*c2);
+          t = (1/d)*(a1*c2 - a2*c1);
+          x00 = StrictMath.min(bbox[b][0], bbox[b+1][0]) - GeoUtils.TOLERANCE;
+          x01 = StrictMath.max(bbox[b][0], bbox[b+1][0]) + GeoUtils.TOLERANCE;
+          y00 = StrictMath.min(bbox[b][1], bbox[b+1][1]) - GeoUtils.TOLERANCE;
+          y01 = StrictMath.max(bbox[b][1], bbox[b+1][1]) + GeoUtils.TOLERANCE;
+          x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - GeoUtils.TOLERANCE;
+          x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + GeoUtils.TOLERANCE;
+          y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - GeoUtils.TOLERANCE;
+          y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + GeoUtils.TOLERANCE;
+          // check whether the intersection point is touching one of the line segments
+          boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
+              || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
+          // if line segments are not touching and the intersection point is within the range of either segment
+          if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
+            return true;
+          }
+        }
+      } // for each poly edge
+    } // for each bbox edge
+    return false;
+  }
+
+  private static boolean lineCrossesRect(double aX1, double aY1, double aX2, double aY2,
+                                         final double rMinX, final double rMinY, final double rMaxX, final double rMaxY) {
+    // short-circuit: if one point inside rect, other outside
+    if (pointInRectPrecise(aX1, aY1, rMinX, rMinY, rMaxX, rMaxY) ?
+        !pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY) : pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)) {
+      return true;
+    }
+
+    return lineCrossesLine(aX1, aY1, aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)
+        || lineCrossesLine(aX1, aY1, aX2, aY2, rMaxX, rMinY, rMinX, rMaxY);
+  }
+
+  private static boolean lineCrossesLine(final double aX1, final double aY1, final double aX2, final double aY2,
+                                         final double bX1, final double bY1, final double bX2, final double bY2) {
+    // determine if three points are ccw (right-hand rule) by computing the determinate
+    final double aX2X1d = aX2 - aX1;
+    final double aY2Y1d = aY2 - aY1;
+    final double bX2X1d = bX2 - bX1;
+    final double bY2Y1d = bY2 - bY1;
+
+    final double t1B = aX2X1d * (bY2 - aY1) - aY2Y1d * (bX2 - aX1);
+    final double test1 = (aX2X1d * (bY1 - aY1) - aY2Y1d * (bX1 - aX1)) * t1B;
+    final double t2B = bX2X1d * (aY2 - bY1) - bY2Y1d * (aX2 - bX1);
+    final double test2 = (bX2X1d * (aY1 - bY1) - bY2Y1d * (aX1 - bX1)) * t2B;
+
+    if (test1 < 0 && test2 < 0) {
+      return true;
+    }
+
+    if (test1 == 0 || test2 == 0) {
+      // vertically collinear
+      if (aX1 == aX2 || bX1 == bX2) {
+        final double minAy = Math.min(aY1, aY2);
+        final double maxAy = Math.max(aY1, aY2);
+        final double minBy = Math.min(bY1, bY2);
+        final double maxBy = Math.max(bY1, bY2);
+
+        return !(minBy >= maxAy || maxBy <= minAy);
+      }
+      // horizontally collinear
+      final double minAx = Math.min(aX1, aX2);
+      final double maxAx = Math.max(aX1, aX2);
+      final double minBx = Math.min(bX1, bX2);
+      final double maxBx = Math.max(bX1, bX2);
+
+      return !(minBx >= maxAx || maxBx <= minAx);
+    }
+    return false;
+  }
+
+  public static boolean rectWithinPolyPrecise(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                       final double[] shapeX, final double[] shapeY, final double sMinX,
+                                       final double sMinY, final double sMaxX, final double sMaxY) {
+    // check if rectangle crosses poly (to handle concave/pacman polys), then check that all 4 corners
+    // are contained
+    return !(rectCrossesPolyPrecise(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY) ||
+        !pointInPolygon(shapeX, shapeY, rMinY, rMinX) || !pointInPolygon(shapeX, shapeY, rMinY, rMaxX) ||
+        !pointInPolygon(shapeX, shapeY, rMaxY, rMaxX) || !pointInPolygon(shapeX, shapeY, rMaxY, rMinX));
+  }
+
+  /**
+   * Computes whether a rectangle is within a given polygon (shared boundaries allowed)
+   */
+  public static boolean rectWithinPolyApprox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                       final double[] shapeX, final double[] shapeY, final double sMinX,
+                                       final double sMinY, final double sMaxX, final double sMaxY) {
+    // approximation: check if rectangle crosses poly (to handle concave/pacman polys), then check one of the corners
+    // are contained
+
+    // short-cut: if bounding boxes cross, rect is not within
+     if (rectCrosses(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY) == true) {
+       return false;
+     }
+
+     return !(rectCrossesPolyApprox(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY)
+         || !pointInPolygon(shapeX, shapeY, rMinY, rMinX));
+  }
+
+  /////////////////////////
+  // Circle relations
+  /////////////////////////
+
+  private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX,
+                                                final double rMaxY, final double centerLon, final double centerLat,
+                                                final double radiusMeters, final boolean approx) {
+    if (approx == true) {
+      return rectAnyCornersInCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
+    }
+    double w = Math.abs(rMaxX - rMinX);
+    if (w <= 90.0) {
+      return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
+    }
+    // partition
+    w /= 4;
+    final double p1 = rMinX + w;
+    final double p2 = p1 + w;
+    final double p3 = p2 + w;
+
+    return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p1) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p1) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p2) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p2) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p3) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p3) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
+        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
+  }
+
+  private static boolean rectAnyCornersInCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                                      final double centerLon, final double centerLat, final double radiusMeters) {
+    return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= 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 lon/lat 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 rMinX, final double rMinY, final double rMaxX,
+                                                     final double rMaxY, final double centerLon, final double centerLat,
+                                                     final double radiusMeters, final boolean approx) {
+    if (approx == true) {
+      return rectAnyCornersOutsideCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
+    }
+    // if span is less than 70 degrees we can approximate using distance alone
+    if (Math.abs(rMaxX - rMinX) <= 70.0) {
+      return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) > radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) > radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) > radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) > radiusMeters;
+    }
+    return rectCrossesOblateCircle(centerLon, centerLat, radiusMeters, rMinX, rMinY, rMaxX, rMaxY);
+  }
+
+  /**
+   * Compute whether the rectangle (defined by min/max Lon/Lat) crosses a potentially oblate circle
+   *
+   * TODO benchmark for replacing existing rectCrossesCircle.
+   */
+  public static boolean rectCrossesOblateCircle(double centerLon, double centerLat, double radiusMeters, double rMinLon, double rMinLat, double  rMaxLon, double rMaxLat) {
+    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 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, p1)) > radiusMeters
+          || (d2 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, maxLon)) > radiusMeters
+          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, p1) > radiusMeters
+          || GeoDistanceUtils.haversin(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(p1, rMinLat,
+          GeoProjectionUtils.bearingGreatCircle(p1, rMinLat, p1, rMaxLat), radiusMeters - d1, pt))[1] < rMinLat || pt[1] < rMaxLat
+          || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
+          GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, maxLon, rMaxLat), radiusMeters - d2, pt))[1] < rMinLat || pt[1] < rMaxLat
+          || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
+          GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, (midLon = p1 + 0.5*(maxLon - p1)), rMaxLat),
+          radiusMeters - GeoDistanceUtils.haversin(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 rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                                           final double centerLon, final double centerLat, final double radiusMeters) {
+    return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 > radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radiusMeters
+        || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radiusMeters;
+  }
+
+  /**
+   * Convenience method for computing whether a rectangle is within a circle using additional precision checks
+   */
+  public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                         final double centerLon, final double centerLat, final double radiusMeters) {
+    return rectWithinCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
+  }
+
+  /**
+   * 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 rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                         final double centerLon, final double centerLat, final double radiusMeters,
+                                         final boolean approx) {
+    return rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) == false;
+  }
+
+  /**
+   * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
+   * 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 rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                          final double centerLon, final double centerLat, final double radiusMeters) {
+    return rectCrossesCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
+  }
+
+  /**
+   * Computes whether a rectangle crosses a circle. Note: approx == true will be faster but less precise and may
+   * fail on large rectangles
+   */
+  public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                          final double centerLon, final double centerLat, final double radiusMeters,
+                                          final boolean approx) {
+    if (approx == true) {
+      return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx)
+          || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
+    }
+
+    return (rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) &&
+        rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx))
+        || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
+  }
+
+  private static boolean isClosestPointOnRectWithinRange(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                                         final double centerLon, final double centerLat, final double radiusMeters,
+                                                         final boolean approx) {
+    double[] closestPt = {0, 0};
+    GeoDistanceUtils.closestPointOnBBox(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, closestPt);
+    boolean haverShortCut = GeoDistanceUtils.haversin(centerLat, centerLon, closestPt[1], closestPt[0]) <= radiusMeters;
+    if (approx == true || haverShortCut == true) {
+      return haverShortCut;
+    }
+    double lon1 = rMinX;
+    double lon2 = rMaxX;
+    double lat1 = rMinY;
+    double lat2 = rMaxY;
+    if (closestPt[0] == rMinX || closestPt[0] == rMaxX) {
+      lon1 = closestPt[0];
+      lon2 = lon1;
+    } else if (closestPt[1] == rMinY || closestPt[1] == rMaxY) {
+      lat1 = closestPt[1];
+      lat2 = lat1;
+    }
+
+    return lineCrossesSphere(lon1, lat1, 0, lon2, lat2, 0, centerLon, centerLat, 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 lon1, double lat1, double alt1, double lon2,
+                                           double lat2, double alt2, double centerLon, double centerLat,
+                                           double centerAlt, double radiusMeters) {
+    // convert to cartesian 3d (in meters)
+    double[] ecf1 = GeoProjectionUtils.llaToECF(lon1, lat1, alt1, null);
+    double[] ecf2 = GeoProjectionUtils.llaToECF(lon2, lat2, alt2, null);
+    double[] cntr = GeoProjectionUtils.llaToECF(centerLon, centerLat, centerAlt, null);
+
+    // convert radius from arc radius to cartesian radius
+    double[] oneEighty = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(centerLon, centerLat, 180.0d, radiusMeters, new double[3]);
+    GeoProjectionUtils.llaToECF(oneEighty[0], oneEighty[1], 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/665041c5/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
new file mode 100644
index 0000000..8899170
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
@@ -0,0 +1,248 @@
+/*
+ * 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 java.util.ArrayList;
+
+import org.apache.lucene.util.BitUtil;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.lang.Math.PI;
+import static java.lang.Math.abs;
+
+import static org.apache.lucene.util.SloppyMath.asin;
+import static org.apache.lucene.util.SloppyMath.cos;
+import static org.apache.lucene.util.SloppyMath.sin;
+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.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.pointFromLonLatBearingGreatCircle;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.SEMIMAJOR_AXIS;
+
+/**
+ * Basic reusable geo-spatial utility methods
+ *
+ * @lucene.experimental
+ */
+public final class GeoUtils {
+  /** number of bits used for quantizing latitude and longitude values */
+  public static final short BITS = 31;
+  private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
+  private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
+  /** rounding error for quantized latitude and longitude values */
+  public static final double TOLERANCE = 1E-6;
+
+  /** Minimum longitude value. */
+  public static final double MIN_LON_INCL = -180.0D;
+
+  /** Maximum longitude value. */
+  public static final double MAX_LON_INCL = 180.0D;
+
+  /** Minimum latitude value. */
+  public static final double MIN_LAT_INCL = -90.0D;
+
+  /** Maximum latitude value. */
+  public static final double MAX_LAT_INCL = 90.0D;
+
+  // No instance:
+  private GeoUtils() {
+  }
+
+  /**
+   * encode longitude, latitude geopoint values using morton encoding method
+   * https://en.wikipedia.org/wiki/Z-order_curve
+   */
+  public static final Long mortonHash(final double lon, final double lat) {
+    return BitUtil.interleave(scaleLon(lon), scaleLat(lat));
+  }
+
+  /** decode longitude value from morton encoded geo point */
+  public static final double mortonUnhashLon(final long hash) {
+    return unscaleLon(BitUtil.deinterleave(hash));
+  }
+
+  /** decode latitude value from morton encoded geo point */
+  public static final double mortonUnhashLat(final long hash) {
+    return unscaleLat(BitUtil.deinterleave(hash >>> 1));
+  }
+
+  private static final long scaleLon(final double val) {
+    return (long) ((val-MIN_LON_INCL) * LON_SCALE);
+  }
+
+  private static final long scaleLat(final double val) {
+    return (long) ((val-MIN_LAT_INCL) * LAT_SCALE);
+  }
+
+  private static final double unscaleLon(final long val) {
+    return (val / LON_SCALE) + MIN_LON_INCL;
+  }
+
+  private static final double unscaleLat(final long val) {
+    return (val / LAT_SCALE) + MIN_LAT_INCL;
+  }
+
+  /** Compare two position values within a {@link GeoUtils#TOLERANCE} factor */
+  public static double compare(final double v1, final double v2) {
+    final double delta = v1-v2;
+    return abs(delta) <= TOLERANCE ? 0 : delta;
+  }
+
+  /** Puts longitude in range of -180 to +180. */
+  public static double normalizeLon(double lon_deg) {
+    if (lon_deg >= -180 && lon_deg <= 180) {
+      return lon_deg; //common case, and avoids slight double precision shifting
+    }
+    double off = (lon_deg + 180) % 360;
+    if (off < 0) {
+      return 180 + off;
+    } else if (off == 0 && lon_deg > 0) {
+      return 180;
+    } else {
+      return -180 + off;
+    }
+  }
+
+  /** Puts latitude in range of -90 to 90. */
+  public static double normalizeLat(double lat_deg) {
+    if (lat_deg >= -90 && lat_deg <= 90) {
+      return lat_deg; //common case, and avoids slight double precision shifting
+    }
+    double off = abs((lat_deg + 90) % 360);
+    return (off <= 180 ? off : 360-off) - 90;
+  }
+
+  /** Converts long value to bit string (useful for debugging) */
+  public static String geoTermToString(long term) {
+    StringBuilder s = new StringBuilder(64);
+    final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term);
+    for (int i = 0; i < numberOfLeadingZeros; i++) {
+      s.append('0');
+    }
+    if (term != 0) {
+      s.append(Long.toBinaryString(term));
+    }
+    return s.toString();
+  }
+
+  /**
+   * Converts a given circle (defined as a point/radius) to an approximated line-segment polygon
+   *
+   * @param lon longitudinal center of circle (in degrees)
+   * @param lat latitudinal center of circle (in degrees)
+   * @param radiusMeters distance radius of circle (in meters)
+   * @return a list of lon/lat points representing the circle
+   */
+  @SuppressWarnings({"unchecked","rawtypes"})
+  public static ArrayList<double[]> circleToPoly(final double lon, final double lat, final double radiusMeters) {
+    double angle;
+    // a little under-sampling (to limit the number of polygonal points): using archimedes estimation of pi
+    final int sides = 25;
+    ArrayList<double[]> geometry = new ArrayList();
+    double[] lons = new double[sides];
+    double[] lats = new double[sides];
+
+    double[] pt = new double[2];
+    final int sidesLen = sides-1;
+    for (int i=0; i<sidesLen; ++i) {
+      angle = (i*360/sides);
+      pt = pointFromLonLatBearingGreatCircle(lon, lat, angle, radiusMeters, pt);
+      lons[i] = pt[0];
+      lats[i] = pt[1];
+    }
+    // close the poly
+    lons[sidesLen] = lons[0];
+    lats[sidesLen] = lats[0];
+    geometry.add(lons);
+    geometry.add(lats);
+
+    return geometry;
+  }
+
+  /** Compute Bounding Box for a circle using WGS-84 parameters */
+  public static GeoRect circleToBBox(final double centerLon, final double centerLat, final double radiusMeters) {
+    final double radLat = TO_RADIANS * centerLat;
+    final double radLon = TO_RADIANS * centerLon;
+    double radDistance = radiusMeters / SEMIMAJOR_AXIS;
+    double minLat = radLat - radDistance;
+    double maxLat = radLat + radDistance;
+    double minLon;
+    double maxLon;
+
+    if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) {
+      double deltaLon = asin(sin(radDistance) / cos(radLat));
+      minLon = radLon - deltaLon;
+      if (minLon < MIN_LON_RADIANS) {
+        minLon += 2d * PI;
+      }
+      maxLon = radLon + deltaLon;
+      if (maxLon > MAX_LON_RADIANS) {
+        maxLon -= 2d * PI;
+      }
+    } else {
+      // a pole is within the distance
+      minLat = max(minLat, MIN_LAT_RADIANS);
+      maxLat = min(maxLat, MAX_LAT_RADIANS);
+      minLon = MIN_LON_RADIANS;
+      maxLon = MAX_LON_RADIANS;
+    }
+
+    return new GeoRect(TO_DEGREES * minLon, TO_DEGREES * maxLon, TO_DEGREES * minLat, TO_DEGREES * maxLat);
+  }
+
+  /** Compute Bounding Box for a polygon using WGS-84 parameters */
+  public static GeoRect polyToBBox(double[] polyLons, double[] polyLats) {
+    if (polyLons.length != polyLats.length) {
+      throw new IllegalArgumentException("polyLons and polyLats must be equal length");
+    }
+
+    double minLon = Double.POSITIVE_INFINITY;
+    double maxLon = Double.NEGATIVE_INFINITY;
+    double minLat = Double.POSITIVE_INFINITY;
+    double maxLat = Double.NEGATIVE_INFINITY;
+
+    for (int i=0;i<polyLats.length;i++) {
+      if (GeoUtils.isValidLon(polyLons[i]) == false) {
+        throw new IllegalArgumentException("invalid polyLons[" + i + "]=" + polyLons[i]);
+      }
+      if (GeoUtils.isValidLat(polyLats[i]) == false) {
+        throw new IllegalArgumentException("invalid polyLats[" + i + "]=" + polyLats[i]);
+      }
+      minLon = min(polyLons[i], minLon);
+      maxLon = max(polyLons[i], maxLon);
+      minLat = min(polyLats[i], minLat);
+      maxLat = max(polyLats[i], maxLat);
+    }
+    // expand bounding box by TOLERANCE factor to handle round-off error
+    return new GeoRect(max(minLon - TOLERANCE, MIN_LON_INCL), min(maxLon + TOLERANCE, MAX_LON_INCL),
+        max(minLat - TOLERANCE, MIN_LAT_INCL), min(maxLat + TOLERANCE, MAX_LAT_INCL));
+  }
+
+  /** validates latitude value is within standard +/-90 coordinate bounds */
+  public static boolean isValidLat(double lat) {
+    return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL;
+  }
+
+  /** validates longitude value is within standard +/-180 coordinate bounds */
+  public static boolean isValidLon(double lon) {
+    return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
new file mode 100644
index 0000000..4002531
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
@@ -0,0 +1,389 @@
+/*
+ * 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.search;
+
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.spatial.document.GeoPointField;
+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.spatial.util.GeoUtils;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
+
+/**
+ * Unit testing for basic GeoPoint query logic
+ *
+ * @lucene.experimental
+ */
+
+public class TestGeoPointQuery extends BaseGeoPointTestCase {
+
+  private static Directory directory = null;
+  private static IndexReader reader = null;
+  private static IndexSearcher searcher = null;
+
+  @Override
+  protected boolean forceSmall() {
+    return false;
+  }
+
+  @Override
+  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
+    doc.add(new GeoPointField(field, lon, lat, Field.Store.NO));
+  }
+
+  @Override
+  protected Query newRectQuery(String field, GeoRect rect) {
+    return new GeoPointInBBoxQuery(field, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
+  }
+
+  @Override
+  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
+    return new GeoPointDistanceQuery(field, centerLon, centerLat, radiusMeters);
+  }
+
+  @Override
+  protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
+    return new GeoPointDistanceRangeQuery(field, centerLon, centerLat, minRadiusMeters, radiusMeters);
+  }
+
+  @Override
+  protected Query newPolygonQuery(String field, double[] lats, double[] lons) {
+    return new GeoPointInPolygonQuery(field, lons, lats);
+  }
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    directory = newDirectory();
+
+    RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
+            newIndexWriterConfig(new MockAnalyzer(random()))
+                    .setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
+                    .setMergePolicy(newLogMergePolicy()));
+
+    // create some simple geo points
+    final FieldType storedPoint = new FieldType(GeoPointField.TYPE_STORED);
+    // this is a simple systematic test
+    GeoPointField[] pts = new GeoPointField[] {
+         new GeoPointField(FIELD_NAME, -96.774, 32.763420, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.7759895324707, 32.7559529921407, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.77701950073242, 32.77866942010977, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.7706036567688, 32.7756745755423, storedPoint),
+         new GeoPointField(FIELD_NAME, -139.73458170890808, 27.703618681345585, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.4538113027811, 32.94823588839368, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.65084838867188, 33.06047141970814, storedPoint),
+         new GeoPointField(FIELD_NAME, -96.7772, 32.778650, storedPoint),
+         new GeoPointField(FIELD_NAME, -177.23537676036358, -88.56029371730983, storedPoint),
+         new GeoPointField(FIELD_NAME, -26.779373834241003, 33.541429799076354, storedPoint),
+         new GeoPointField(FIELD_NAME, -77.35379276106497, 26.774024500421728, storedPoint),
+         new GeoPointField(FIELD_NAME, -14.796283808944777, -90.0, storedPoint),
+         new GeoPointField(FIELD_NAME, -178.8538113027811, 32.94823588839368, storedPoint),
+         new GeoPointField(FIELD_NAME, 178.8538113027811, 32.94823588839368, storedPoint),
+         new GeoPointField(FIELD_NAME, -73.998776, 40.720611, storedPoint),
+         new GeoPointField(FIELD_NAME, -179.5, -44.5, storedPoint)};
+
+    for (GeoPointField p : pts) {
+        Document doc = new Document();
+        doc.add(p);
+        writer.addDocument(doc);
+    }
+
+    // add explicit multi-valued docs
+    for (int i=0; i<pts.length; i+=2) {
+      Document doc = new Document();
+      doc.add(pts[i]);
+      doc.add(pts[i+1]);
+      writer.addDocument(doc);
+    }
+
+    // index random string documents
+    for (int i=0; i<random().nextInt(10); ++i) {
+      Document doc = new Document();
+      doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
+      writer.addDocument(doc);
+    }
+
+    reader = writer.getReader();
+    searcher = newSearcher(reader);
+    writer.close();
+  }
+
+  @AfterClass
+  public static void afterClass() throws Exception {
+    searcher = null;
+    reader.close();
+    reader = null;
+    directory.close();
+    directory = null;
+  }
+
+  private TopDocs bboxQuery(double minLon, double minLat, double maxLon, double maxLat, int limit) throws Exception {
+    GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, minLon, minLat, maxLon, maxLat);
+    return searcher.search(q, limit);
+  }
+
+  private TopDocs polygonQuery(double[] lon, double[] lat, int limit) throws Exception {
+    GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, lon, lat);
+    return searcher.search(q, limit);
+  }
+
+  private TopDocs geoDistanceQuery(double lon, double lat, double radius, int limit) throws Exception {
+    GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, lon, lat, radius);
+    return searcher.search(q, limit);
+  }
+
+  @Override
+  protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
+    if (GeoUtils.compare(pointLon, rect.minLon) == 0.0 ||
+        GeoUtils.compare(pointLon, rect.maxLon) == 0.0 ||
+        GeoUtils.compare(pointLat, rect.minLat) == 0.0 ||
+        GeoUtils.compare(pointLat, rect.maxLat) == 0.0) {
+      // Point is very close to rect boundary
+      return null;
+    }
+
+    if (rect.minLon < rect.maxLon) {
+      return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
+    } else {
+      // Rect crosses dateline:
+      return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, -180.0, rect.minLat, rect.maxLon, rect.maxLat)
+          || GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, 180.0, rect.maxLat);
+    }
+  }
+
+  @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.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0 <= 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.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0;
+      return d >= minRadiusMeters && d <= radiusMeters;
+    }
+  }
+
+  private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
+                                               final double radius) {
+    final long hashedCntr = GeoUtils.mortonHash(centerLon, centerLat);
+    centerLon = GeoUtils.mortonUnhashLon(hashedCntr);
+    centerLat = GeoUtils.mortonUnhashLat(hashedCntr);
+    final long hashedPt = GeoUtils.mortonHash(ptLon, ptLat);
+    ptLon = GeoUtils.mortonUnhashLon(hashedPt);
+    ptLat = GeoUtils.mortonUnhashLat(hashedPt);
+
+    double ptDistance = SloppyMath.haversin(centerLat, centerLon, ptLat, ptLon)*1000.0;
+    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 testRectCrossesCircle() throws Exception {
+    assertTrue(GeoRelationUtils.rectCrossesCircle(-180, -90, 180, 0.0, 0.667, 0.0, 88000.0));
+  }
+
+  private TopDocs geoDistanceRangeQuery(double lon, double lat, double minRadius, double maxRadius, int limit)
+      throws Exception {
+    GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, lon, lat, minRadius, maxRadius);
+    return searcher.search(q, limit);
+  }
+
+  public void testBBoxQuery() throws Exception {
+    TopDocs td = bboxQuery(-96.7772, 32.778650, -96.77690000, 32.778950, 5);
+    assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
+  }
+
+  public void testPolyQuery() throws Exception {
+    TopDocs td = polygonQuery(new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
+            -96.6041564, -96.7449188, -96.76826477, -96.7682647},
+        new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
+            33.1369762, 33.1162747, 33.073130, 33.073130}, 5);
+    assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
+  }
+
+  public void testPacManPolyQuery() throws Exception {
+    // pacman
+    double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
+    double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
+
+    // shape bbox
+    double xMinA = -10;
+    double xMaxA = 10;
+    double yMinA = -10;
+    double yMaxA = 10;
+
+    // candidate crosses cell
+    double xMin = 2;//-5;
+    double xMax = 11;//0.000001;
+    double yMin = -1;//0;
+    double yMax = 1;//5;
+
+    // test cell crossing poly
+    assertTrue(GeoRelationUtils.rectCrossesPolyApprox(xMin, yMin, xMax, yMax, px, py, xMinA, yMinA, xMaxA, yMaxA));
+    assertFalse(GeoRelationUtils.rectCrossesPolyApprox(-5, 0,  0.000001, 5, px, py, xMin, yMin, xMax, yMax));
+    assertTrue(GeoRelationUtils.rectWithinPolyApprox(-5, 0, -2, 5, px, py, xMin, yMin, xMax, yMax));
+  }
+
+  public void testBBoxCrossDateline() throws Exception {
+    TopDocs td = bboxQuery(179.0, -45.0, -179.0, -44.0, 20);
+    assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
+  }
+
+  public void testWholeMap() throws Exception {
+    TopDocs td = bboxQuery(GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LAT_INCL, 20);
+    assertEquals("testWholeMap failed", 24, td.totalHits);
+    td = polygonQuery(new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
+        new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL}, 20);
+    assertEquals("testWholeMap failed", 24, td.totalHits);
+  }
+
+  public void smallTest() throws Exception {
+    TopDocs td = geoDistanceQuery(-73.998776, 40.720611, 1, 20);
+    assertEquals("smallTest failed", 2, td.totalHits);
+  }
+
+  public void testInvalidBBox() throws Exception {
+    try {
+      bboxQuery(179.0, -92.0, 181.0, -91.0, 20);
+    } catch(Exception e) {
+      return;
+    }
+    throw new Exception("GeoBoundingBox should not accept invalid lat/lon");
+  }
+
+  public void testGeoDistanceQuery() throws Exception {
+    TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000, 20);
+    assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
+  }
+
+  /** see https://issues.apache.org/jira/browse/LUCENE-6905 */
+  public void testNonEmptyTermsEnum() throws Exception {
+    TopDocs td = geoDistanceQuery(-177.23537676036358, -88.56029371730983, 7757.999232959935, 20);
+    assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
+  }
+
+  public void testMultiValuedQuery() throws Exception {
+    TopDocs td = bboxQuery(-96.4538113027811, 32.7559529921407, -96.7706036567688, 32.7756745755423, 20);
+    // 3 single valued docs + 2 multi-valued docs
+    assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
+  }
+
+  public void testTooBigRadius() throws Exception {
+    try {
+      geoDistanceQuery(0.0, 85.0, 4000000, 20);
+    } catch (IllegalArgumentException e) {
+      e.getMessage().contains("exceeds maxRadius");
+    }
+  }
+
+  /**
+   * Explicitly large
+   */
+  public void testGeoDistanceQueryHuge() throws Exception {
+    TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000000, 20);
+    assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
+  }
+
+  public void testGeoDistanceQueryCrossDateline() throws Exception {
+    TopDocs td = geoDistanceQuery(-179.9538113027811, 32.94823588839368, 120000, 20);
+    assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
+  }
+
+  public void testInvalidGeoDistanceQuery() throws Exception {
+    try {
+      geoDistanceQuery(181.0, 92.0, 120000, 20);
+    } catch (Exception e) {
+      return;
+    }
+    throw new Exception("GeoDistanceQuery should not accept invalid lat/lon as origin");
+  }
+
+  public void testMaxDistanceRangeQuery() throws Exception {
+    TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
+    assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
+  }
+
+  public void testMortonEncoding() throws Exception {
+    long hash = GeoUtils.mortonHash(180, 90);
+    assertEquals(180.0, GeoUtils.mortonUnhashLon(hash), 0);
+    assertEquals(90.0, GeoUtils.mortonUnhashLat(hash), 0);
+  }
+
+  public void testEncodeDecode() throws Exception {
+    int iters = atLeast(10000);
+    boolean small = random().nextBoolean();
+    for(int iter=0;iter<iters;iter++) {
+      double lat = randomLat(small);
+      double lon = randomLon(small);
+
+      long enc = GeoUtils.mortonHash(lon, lat);
+      double latEnc = GeoUtils.mortonUnhashLat(enc);
+      double lonEnc = GeoUtils.mortonUnhashLon(enc);
+
+      assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoUtils.TOLERANCE);
+      assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoUtils.TOLERANCE);
+    }
+  }
+
+  public void testScaleUnscaleIsStable() throws Exception {
+    int iters = atLeast(1000);
+    boolean small = random().nextBoolean();
+    for(int iter=0;iter<iters;iter++) {
+      double lat = randomLat(small);
+      double lon = randomLon(small);
+
+      long enc = GeoUtils.mortonHash(lon, lat);
+      double latEnc = GeoUtils.mortonUnhashLat(enc);
+      double lonEnc = GeoUtils.mortonUnhashLon(enc);
+
+      long enc2 = GeoUtils.mortonHash(lon, lat);
+      double latEnc2 = GeoUtils.mortonUnhashLat(enc2);
+      double lonEnc2 = GeoUtils.mortonUnhashLon(enc2);
+      assertEquals(latEnc, latEnc2, 0.0);
+      assertEquals(lonEnc, lonEnc2, 0.0);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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
new file mode 100644
index 0000000..9f6edda
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
@@ -0,0 +1,769 @@
+/*
+ * 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 java.io.IOException;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+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.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MockDirectoryWrapper;
+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.junit.BeforeClass;
+
+// TODO: cutover TestGeoUtils too?
+
+public abstract class BaseGeoPointTestCase extends LuceneTestCase {
+
+  protected static final String FIELD_NAME = "point";
+
+  private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
+  private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
+
+  private static double originLat;
+  private static double originLon;
+  private static double lonRange;
+  private static double latRange;
+
+  @BeforeClass
+  public static void beforeClassBase() throws Exception {
+    // Between 1.0 and 3.0:
+    lonRange = 2 * (random().nextDouble() + 0.5);
+    latRange = 2 * (random().nextDouble() + 0.5);
+
+    originLon = GeoUtils.normalizeLon(GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble());
+    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();
+
+    // Every doc has 2 points:
+    double theLat = randomLat(small);
+    double theLon = randomLon(small);
+
+    double[] lats = new double[numPoints];
+    Arrays.fill(lats, theLat);
+
+    double[] lons = new double[numPoints];
+    Arrays.fill(lons, theLon);
+
+    verify(small, lats, lons);
+  }
+
+  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();
+    double lat = randomLat(small);
+    double[] lats = new double[numPoints];
+    double[] lons = new double[numPoints];
+
+    boolean haveRealDoc = false;
+
+    for(int docID=0;docID<numPoints;docID++) {
+      int x = random().nextInt(20);
+      if (x == 17) {
+        // Some docs don't have a point:
+        lats[docID] = Double.NaN;
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " is missing");
+        }
+        continue;
+      }
+
+      if (docID > 0 && x == 14 && haveRealDoc) {
+        int oldDocID;
+        while (true) {
+          oldDocID = random().nextInt(docID);
+          if (Double.isNaN(lats[oldDocID]) == false) {
+            break;
+          }
+        }
+            
+        // Fully identical point:
+        lons[docID] = lons[oldDocID];
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " lat=" + lat + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
+        }
+      } else {
+        lons[docID] = randomLon(small);
+        haveRealDoc = true;
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " lat=" + lat + " lon=" + lons[docID]);
+        }
+      }
+      lats[docID] = lat;
+    }
+
+    verify(small, lats, lons);
+  }
+
+  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();
+    double theLon = randomLon(small);
+    double[] lats = new double[numPoints];
+    double[] lons = new double[numPoints];
+
+    boolean haveRealDoc = false;
+
+    //System.out.println("theLon=" + theLon);
+
+    for(int docID=0;docID<numPoints;docID++) {
+      int x = random().nextInt(20);
+      if (x == 17) {
+        // Some docs don't have a point:
+        lats[docID] = Double.NaN;
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " is missing");
+        }
+        continue;
+      }
+
+      if (docID > 0 && x == 14 && haveRealDoc) {
+        int oldDocID;
+        while (true) {
+          oldDocID = random().nextInt(docID);
+          if (Double.isNaN(lats[oldDocID]) == false) {
+            break;
+          }
+        }
+            
+        // Fully identical point:
+        lats[docID] = lats[oldDocID];
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon + " (same lat/lon as doc=" + oldDocID + ")");
+        }
+      } else {
+        lats[docID] = randomLat(small);
+        haveRealDoc = true;
+        if (VERBOSE) {
+          System.out.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon);
+        }
+      }
+      lons[docID] = theLon;
+    }
+
+    verify(small, lats, lons);
+  }
+
+  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];
+    double[] lons = new double[2*numPoints];
+    Directory dir = newDirectory();
+    noVirusChecker(dir);
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    initIndexWriterConfig(FIELD_NAME, iwc);
+
+    // We rely on docID order:
+    iwc.setMergePolicy(newLogMergePolicy());
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
+
+    boolean small = random().nextBoolean();
+
+    for (int id=0;id<numPoints;id++) {
+      Document doc = new Document();
+      lats[2*id] = randomLat(small);
+      lons[2*id] = randomLon(small);
+      doc.add(newStringField("id", ""+id, Field.Store.YES));
+      addPointToDoc(FIELD_NAME, doc, lats[2*id], lons[2*id]);
+      lats[2*id+1] = randomLat(small);
+      lons[2*id+1] = randomLon(small);
+      addPointToDoc(FIELD_NAME, doc, lats[2*id+1], lons[2*id+1]);
+
+      if (VERBOSE) {
+        System.out.println("id=" + id);
+        System.out.println("  lat=" + lats[2*id] + " lon=" + lons[2*id]);
+        System.out.println("  lat=" + lats[2*id+1] + " lon=" + lons[2*id+1]);
+      }
+      w.addDocument(doc);
+    }
+
+    // TODO: share w/ verify; just need parallel array of the expected ids
+    if (random().nextBoolean()) {
+      w.forceMerge(1);
+    }
+    IndexReader r = w.getReader();
+    w.close();
+
+    // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
+    IndexSearcher s = newSearcher(r, false);
+
+    int iters = atLeast(75);
+    for (int iter=0;iter<iters;iter++) {
+      GeoRect rect = randomRect(small, small == false);
+
+      if (VERBOSE) {
+        System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
+      }
+
+      Query query = newRectQuery(FIELD_NAME, rect);
+
+      final FixedBitSet hits = new FixedBitSet(r.maxDoc());
+      s.search(query, new SimpleCollector() {
+
+          private int docBase;
+
+          @Override
+          public boolean needsScores() {
+            return false;
+          }
+
+          @Override
+          protected void doSetNextReader(LeafReaderContext context) throws IOException {
+            docBase = context.docBase;
+          }
+
+          @Override
+          public void collect(int doc) {
+            hits.set(docBase+doc);
+          }
+        });
+
+      boolean fail = false;
+
+      for(int docID=0;docID<lats.length/2;docID++) {
+        double latDoc1 = lats[2*docID];
+        double lonDoc1 = lons[2*docID];
+        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 expected = result1 == Boolean.TRUE || result2 == Boolean.TRUE;
+
+        if (hits.get(docID) != expected) {
+          String id = s.doc(docID).get("id");
+          if (expected) {
+            System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should match but did not");
+          } else {
+            System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should not match but did");
+          }
+          System.out.println("  rect=" + rect);
+          System.out.println("  lat=" + latDoc1 + " lon=" + lonDoc1 + "\n  lat=" + latDoc2 + " lon=" + lonDoc2);
+          System.out.println("  result1=" + result1 + " result2=" + result2);
+          fail = true;
+        }
+      }
+
+      if (fail) {
+        fail("some hits were wrong");
+      }
+    }
+    r.close();
+    dir.close();
+  }
+
+  public void testRandomTiny() throws Exception {
+    // Make sure single-leaf-node case is OK:
+    doTestRandom(10);
+  }
+
+  public void testRandomMedium() throws Exception {
+    doTestRandom(10000);
+  }
+
+  @Nightly
+  public void testRandomBig() throws Exception {
+    assumeFalse("Direct codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Direct"));
+    assumeFalse("Memory codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Memory"));
+    doTestRandom(200000);
+  }
+
+  private void doTestRandom(int count) throws Exception {
+
+    int numPoints = atLeast(count);
+
+    if (VERBOSE) {
+      System.out.println("TEST: numPoints=" + numPoints);
+    }
+
+    double[] lats = new double[numPoints];
+    double[] lons = new double[numPoints];
+
+    boolean small = random().nextBoolean();
+
+    boolean haveRealDoc = false;
+
+    for (int id=0;id<numPoints;id++) {
+      int x = random().nextInt(20);
+      if (x == 17) {
+        // Some docs don't have a point:
+        lats[id] = Double.NaN;
+        if (VERBOSE) {
+          System.out.println("  id=" + id + " is missing");
+        }
+        continue;
+      }
+
+      if (id > 0 && x < 3 && haveRealDoc) {
+        int oldID;
+        while (true) {
+          oldID = random().nextInt(id);
+          if (Double.isNaN(lats[oldID]) == false) {
+            break;
+          }
+        }
+            
+        if (x == 0) {
+          // Identical lat to old point
+          lats[id] = lats[oldID];
+          lons[id] = randomLon(small);
+          if (VERBOSE) {
+            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat as doc=" + oldID + ")");
+          }
+        } else if (x == 1) {
+          // Identical lon to old point
+          lats[id] = randomLat(small);
+          lons[id] = lons[oldID];
+          if (VERBOSE) {
+            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lon as doc=" + oldID + ")");
+          }
+        } else {
+          assert x == 2;
+          // Fully identical point:
+          lats[id] = lats[oldID];
+          lons[id] = lons[oldID];
+          if (VERBOSE) {
+            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat/lon as doc=" + oldID + ")");
+          }
+        }
+      } else {
+        lats[id] = randomLat(small);
+        lons[id] = randomLon(small);
+        haveRealDoc = true;
+        if (VERBOSE) {
+          System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id]);
+        }
+      }
+    }
+
+    verify(small, lats, lons);
+  }
+
+  public double randomLat(boolean small) {
+    double result;
+    if (small) {
+      result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
+    } else {
+      result = -90 + 180.0 * random().nextDouble();
+    }
+    return result;
+  }
+
+  public double randomLon(boolean small) {
+    double result;
+    if (small) {
+      result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
+    } else {
+      result = -180 + 360.0 * random().nextDouble();
+    }
+    return result;
+  }
+
+  protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
+    double lat0 = randomLat(small);
+    double lat1 = randomLat(small);
+    double lon0 = randomLon(small);
+    double lon1 = randomLon(small);
+
+    if (lat1 < lat0) {
+      double x = lat0;
+      lat0 = lat1;
+      lat1 = x;
+    }
+
+    if (canCrossDateLine == false && lon1 < lon0) {
+      double x = lon0;
+      lon0 = lon1;
+      lon1 = x;
+    }
+
+    return new GeoRect(lon0, lon1, lat0, lat1);
+  }
+
+  protected void initIndexWriterConfig(String field, IndexWriterConfig iwc) {
+  }
+
+  protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
+
+  protected abstract Query newRectQuery(String field, GeoRect bbox);
+
+  protected abstract Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters);
+
+  protected abstract Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters);
+
+  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);
+
+  /** Returns null if it's borderline case */
+  protected abstract Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon);
+
+  /** Returns null if it's borderline case */
+  protected abstract Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon);
+
+  protected abstract Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon);
+
+  private static abstract class VerifyHits {
+
+    public void test(AtomicBoolean failed, boolean small, IndexSearcher s, NumericDocValues docIDToID, Set<Integer> deleted, Query query, double[] lats, double[] lons) throws Exception {
+      int maxDoc = s.getIndexReader().maxDoc();
+      final FixedBitSet hits = new FixedBitSet(maxDoc);
+      s.search(query, new SimpleCollector() {
+
+          private int docBase;
+
+          @Override
+          public boolean needsScores() {
+            return false;
+          }
+
+          @Override
+          protected void doSetNextReader(LeafReaderContext context) throws IOException {
+            docBase = context.docBase;
+          }
+
+          @Override
+          public void collect(int doc) {
+            hits.set(docBase+doc);
+          }
+        });
+
+      boolean fail = false;
+
+      for(int docID=0;docID<maxDoc;docID++) {
+        int id = (int) docIDToID.get(docID);
+        Boolean expected;
+        if (deleted.contains(id)) {
+          expected = false;
+        } else if (Double.isNaN(lats[id])) {
+          expected = false;
+        } else {
+          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 (expected) {
+            System.out.println(Thread.currentThread().getName() + ": id=" + id + " should match but did not");
+          } else {
+            System.out.println(Thread.currentThread().getName() + ": id=" + id + " should not match but did");
+          }
+          System.out.println("  small=" + small + " query=" + query +
+                             " docID=" + docID + "\n  lat=" + lats[id] + " lon=" + lons[id] +
+                             "\n  deleted?=" + deleted.contains(id));
+          if (Double.isNaN(lats[id]) == false) {
+            describe(docID, lats[id], lons[id]);
+          }
+          fail = true;
+        }
+      }
+
+      if (fail) {
+        failed.set(true);
+        fail("some hits were wrong");
+      }
+    }
+
+    /** 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 void describe(int docID, double lat, double lon);
+  }
+
+  protected void verify(boolean small, double[] lats, double[] lons) throws Exception {
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    // Else we can get O(N^2) merging:
+    int mbd = iwc.getMaxBufferedDocs();
+    if (mbd != -1 && mbd < lats.length/100) {
+      iwc.setMaxBufferedDocs(lats.length/100);
+    }
+    Directory dir;
+    if (lats.length > 100000) {
+      dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
+    } else {
+      dir = newDirectory();
+    }
+    noVirusChecker(dir);
+
+    Set<Integer> deleted = new HashSet<>();
+    // RandomIndexWriter is too slow here:
+    IndexWriter w = new IndexWriter(dir, iwc);
+    for(int id=0;id<lats.length;id++) {
+      Document doc = new Document();
+      doc.add(newStringField("id", ""+id, Field.Store.NO));
+      doc.add(new NumericDocValuesField("id", id));
+      if (Double.isNaN(lats[id]) == false) {
+        addPointToDoc(FIELD_NAME, doc, lats[id], lons[id]);
+      }
+      w.addDocument(doc);
+      if (id > 0 && random().nextInt(100) == 42) {
+        int idToDelete = random().nextInt(id);
+        w.deleteDocuments(new Term("id", ""+idToDelete));
+        deleted.add(idToDelete);
+        if (VERBOSE) {
+          System.out.println("  delete id=" + idToDelete);
+        }
+      }
+    }
+
+    if (random().nextBoolean()) {
+      w.forceMerge(1);
+    }
+    final IndexReader r = DirectoryReader.open(w);
+    w.close();
+
+    // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
+    IndexSearcher s = newSearcher(r, false);
+
+    // Make sure queries are thread safe:
+    int numThreads = TestUtil.nextInt(random(), 2, 5);
+
+    List<Thread> threads = new ArrayList<>();
+    final int iters = atLeast(75);
+
+    final CountDownLatch startingGun = new CountDownLatch(1);
+    final AtomicBoolean failed = new AtomicBoolean();
+
+    for(int i=0;i<numThreads;i++) {
+      Thread thread = new Thread() {
+          @Override
+          public void run() {
+            try {
+              _run();
+            } catch (Exception e) {
+              failed.set(true);
+              throw new RuntimeException(e);
+            }
+          }
+
+          private void _run() throws Exception {
+            startingGun.await();
+
+            NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
+
+            for (int iter=0;iter<iters && failed.get() == false;iter++) {
+
+              if (VERBOSE) {
+                System.out.println("\nTEST: iter=" + iter + " s=" + s);
+              }
+              Query query;
+              VerifyHits verifyHits;
+
+              if (random().nextBoolean()) {
+                // Rect: don't allow dateline crossing when testing small:
+                final GeoRect rect = randomRect(small, small == false);
+
+                query = newRectQuery(FIELD_NAME, rect);
+
+                verifyHits = new VerifyHits() {
+                    @Override
+                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                      return rectContainsPoint(rect, pointLat, pointLon);
+                    }
+                    @Override
+                    protected void describe(int docID, double lat, double lon) {
+                    }
+                  };
+
+              } else if (random().nextBoolean()) {
+                // Distance
+                final boolean rangeQuery = random().nextBoolean();
+                final double centerLat = randomLat(small);
+                final double centerLon = randomLon(small);
+
+                double radiusMeters;
+                double minRadiusMeters;
+
+                if (small) {
+                  // Approx 3 degrees lon at the equator:
+                  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;
+                }
+
+                // generate a random minimum radius between 1% and 95% the max radius
+                minRadiusMeters = (0.01 + 0.94 * random().nextDouble()) * radiusMeters;
+
+                if (VERBOSE) {
+                  final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+                  System.out.println("  radiusMeters = " + df.format(radiusMeters)
+                      + ((rangeQuery == true) ? " minRadiusMeters = " + df.format(minRadiusMeters) : ""));
+                }
+
+                try {
+                  if (rangeQuery == true) {
+                    query = newDistanceRangeQuery(FIELD_NAME, centerLat, centerLon, minRadiusMeters, radiusMeters);
+                  } else {
+                    query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
+                  }
+                } catch (IllegalArgumentException e) {
+                  if (e.getMessage().contains("exceeds maxRadius")) {
+                    continue;
+                  }
+                  throw e;
+                }
+
+                verifyHits = new VerifyHits() {
+                    @Override
+                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                      if (rangeQuery == false) {
+                        return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
+                      } else {
+                        return distanceRangeContainsPoint(centerLat, centerLon, minRadiusMeters, radiusMeters, pointLat, pointLon);
+                      }
+                    }
+
+                    @Override
+                    protected void describe(int docID, double pointLat, double pointLon) {
+                      double distanceKM = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon);
+                      System.out.println("  docID=" + docID + " centerLon=" + centerLon + " centerLat=" + centerLat
+                          + " pointLon=" + pointLon + " pointLat=" + pointLat + " distanceMeters=" + (distanceKM * 1000)
+                          + " vs" + ((rangeQuery == true) ? " minRadiusMeters=" + minRadiusMeters : "") + " radiusMeters=" + radiusMeters);
+                    }
+                   };
+
+              // TODO: get poly query working with dateline crossing too (how?)!
+              } else {
+
+                // TODO: poly query can't handle dateline crossing yet:
+                final GeoRect bbox = randomRect(small, false);
+
+                // Polygon
+                double[] lats = new double[5];
+                double[] lons = new double[5];
+                lats[0] = bbox.minLat;
+                lons[0] = bbox.minLon;
+                lats[1] = bbox.maxLat;
+                lons[1] = bbox.minLon;
+                lats[2] = bbox.maxLat;
+                lons[2] = bbox.maxLon;
+                lats[3] = bbox.minLat;
+                lons[3] = bbox.maxLon;
+                lats[4] = bbox.minLat;
+                lons[4] = bbox.minLon;
+                query = newPolygonQuery(FIELD_NAME, lats, lons);
+
+                verifyHits = new VerifyHits() {
+                    @Override
+                    protected Boolean shouldMatch(double pointLat, double pointLon) {
+                      return polyRectContainsPoint(bbox, pointLat, pointLon);
+                    }
+
+                    @Override
+                    protected void describe(int docID, double lat, double lon) {
+                    }
+                  };
+              }
+
+              if (query != null) {
+
+                if (VERBOSE) {
+                  System.out.println("  query=" + query);
+                }
+
+                verifyHits.test(failed, small, s, docIDToID, deleted, query, lats, lons);
+              }
+            }
+          }
+      };
+      thread.setName("T" + i);
+      thread.start();
+      threads.add(thread);
+    }
+    startingGun.countDown();
+    for(Thread thread : threads) {
+      thread.join();
+    }
+    IOUtils.close(r, dir);
+    assertFalse(failed.get());
+  }
+
+  protected Directory noVirusChecker(Directory dir) {
+    if (dir instanceof MockDirectoryWrapper) {
+      ((MockDirectoryWrapper) dir).setEnableVirusScanner(false);
+    }
+    return dir;
+  }
+}


[6/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Posted by nk...@apache.org.
LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module


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

Branch: refs/heads/lucene-6997
Commit: 665041c52f088310c5bd3414607aa683a1d8813b
Parents: 4569fd7
Author: nknize <nk...@apache.org>
Authored: Fri Feb 5 10:58:39 2016 -0600
Committer: nknize <nk...@apache.org>
Committed: Fri Feb 5 10:58:39 2016 -0600

----------------------------------------------------------------------
 dev-tools/idea/lucene/sandbox/sandbox.iml       |   1 +
 .../java/org/apache/lucene/util/SloppyMath.java |   6 +-
 lucene/sandbox/build.xml                        |  25 +
 .../apache/lucene/document/GeoPointField.java   | 131 ----
 .../org/apache/lucene/document/LatLonPoint.java |   2 +-
 .../apache/lucene/search/GeoBoundingBox.java    |  46 --
 .../lucene/search/GeoPointDistanceQuery.java    | 170 ----
 .../search/GeoPointDistanceQueryImpl.java       | 131 ----
 .../search/GeoPointDistanceRangeQuery.java      | 102 ---
 .../lucene/search/GeoPointInBBoxQuery.java      | 152 ----
 .../lucene/search/GeoPointInBBoxQueryImpl.java  | 160 ----
 .../lucene/search/GeoPointInPolygonQuery.java   | 194 -----
 .../apache/lucene/search/GeoPointTermQuery.java |  70 --
 .../GeoPointTermQueryConstantScoreWrapper.java  | 129 ----
 .../apache/lucene/search/GeoPointTermsEnum.java | 249 ------
 .../lucene/search/PointInPolygonQuery.java      |   4 +-
 .../apache/lucene/search/PointInRectQuery.java  |   2 +-
 .../apache/lucene/util/GeoDistanceUtils.java    | 217 ------
 .../org/apache/lucene/util/GeoHashUtils.java    | 273 -------
 .../apache/lucene/util/GeoProjectionUtils.java  | 437 -----------
 .../java/org/apache/lucene/util/GeoRect.java    |  66 --
 .../apache/lucene/util/GeoRelationUtils.java    | 497 ------------
 .../java/org/apache/lucene/util/GeoUtils.java   | 237 ------
 .../java/org/apache/lucene/util/package.html    |  28 -
 .../apache/lucene/search/TestGeoPointQuery.java | 386 ----------
 .../lucene/search/TestLatLonPointQueries.java   |   6 +-
 .../lucene/util/BaseGeoPointTestCase.java       | 764 ------------------
 .../org/apache/lucene/util/TestGeoUtils.java    | 545 -------------
 .../lucene/spatial/document/GeoPointField.java  | 136 ++++
 .../lucene/spatial/document/package-info.java   |  21 +
 .../lucene/spatial/search/GeoBoundingBox.java   |  53 ++
 .../spatial/search/GeoPointDistanceQuery.java   | 182 +++++
 .../search/GeoPointDistanceQueryImpl.java       | 131 ++++
 .../search/GeoPointDistanceRangeQuery.java      | 110 +++
 .../spatial/search/GeoPointInBBoxQuery.java     | 166 ++++
 .../spatial/search/GeoPointInBBoxQueryImpl.java | 161 ++++
 .../spatial/search/GeoPointInPolygonQuery.java  | 196 +++++
 .../spatial/search/GeoPointTermQuery.java       | 114 +++
 .../GeoPointTermQueryConstantScoreWrapper.java  | 138 ++++
 .../spatial/search/GeoPointTermsEnum.java       | 249 ++++++
 .../lucene/spatial/search/package-info.java     |  21 +
 .../lucene/spatial/util/GeoDistanceUtils.java   | 223 ++++++
 .../lucene/spatial/util/GeoHashUtils.java       | 283 +++++++
 .../lucene/spatial/util/GeoProjectionUtils.java | 465 +++++++++++
 .../org/apache/lucene/spatial/util/GeoRect.java |  66 ++
 .../lucene/spatial/util/GeoRelationUtils.java   | 520 +++++++++++++
 .../apache/lucene/spatial/util/GeoUtils.java    | 248 ++++++
 .../spatial/search/TestGeoPointQuery.java       | 389 ++++++++++
 .../spatial/util/BaseGeoPointTestCase.java      | 769 +++++++++++++++++++
 .../lucene/spatial/util/TestGeoUtils.java       | 546 +++++++++++++
 50 files changed, 5223 insertions(+), 4994 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/dev-tools/idea/lucene/sandbox/sandbox.iml
----------------------------------------------------------------------
diff --git a/dev-tools/idea/lucene/sandbox/sandbox.iml b/dev-tools/idea/lucene/sandbox/sandbox.iml
index 05caa9f..47cf8da 100644
--- a/dev-tools/idea/lucene/sandbox/sandbox.iml
+++ b/dev-tools/idea/lucene/sandbox/sandbox.iml
@@ -23,6 +23,7 @@
     <orderEntry type="library" scope="TEST" name="JUnit" level="project" />
     <orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
     <orderEntry type="module" scope="TEST" module-name="codecs" />
+    <orderEntry type="module" module-name="spatial" />
     <orderEntry type="module" module-name="lucene-core" />
   </component>
 </module>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java b/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
index 9a05704..8da8276 100644
--- a/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
+++ b/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
@@ -180,15 +180,15 @@ public class SloppyMath {
   }
 
   // haversin
-  static final double TO_RADIANS = Math.PI / 180D;
-  static final double TO_DEGREES = 180D / Math.PI;
+  public static final double TO_RADIANS = Math.PI / 180D;
+  public static final double TO_DEGREES = 180D / Math.PI;
   
   // cos/asin
   private static final double ONE_DIV_F2 = 1/2.0;
   private static final double ONE_DIV_F3 = 1/6.0;
   private static final double ONE_DIV_F4 = 1/24.0;
 
-  static final double PIO2 = Math.PI / 2D;
+  public static final double PIO2 = Math.PI / 2D;
   private static final double PIO2_HI = Double.longBitsToDouble(0x3FF921FB54400000L); // 1.57079632673412561417e+00 first 33 bits of pi/2
   private static final double PIO2_LO = Double.longBitsToDouble(0x3DD0B4611A626331L); // 6.07710050650619224932e-11 pi/2 - PIO2_HI
   private static final double TWOPI_HI = 4*PIO2_HI;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/build.xml
----------------------------------------------------------------------
diff --git a/lucene/sandbox/build.xml b/lucene/sandbox/build.xml
index 93bc275..d53a6ac 100644
--- a/lucene/sandbox/build.xml
+++ b/lucene/sandbox/build.xml
@@ -23,4 +23,29 @@
 
   <import file="../module-build.xml"/>
 
+  <target name="compile-test-spatial" depends="init" if="module.has.tests">
+    <ant dir="${common.dir}/spatial" target="compile-test" inheritAll="false"/>
+  </target>
+
+  <path id="classpath">
+    <path refid="base.classpath"/>
+    <pathelement path="${spatial.jar}"/>
+  </path>
+  <target name="compile-core" depends="jar-spatial,common.compile-core" />
+
+  <path id="test.classpath">
+    <pathelement location="${build.dir}/classes/java"/>
+    <pathelement location="${build.dir}/classes/test"/>
+    <pathelement location="${common.dir}/build/spatial/classes/test"/>
+    <path refid="test.base.classpath"/>
+    <pathelement path="${spatial.jar}"/>
+    <path refid="junit-path"/>
+  </path>
+
+  <path id="junit.classpath">
+    <path refid="test.classpath"/>
+    <pathelement path="${java.class.path}"/>
+  </path>
+  <target name="compile-test" depends="jar-spatial,compile-test-spatial,common.compile-test" />
+
 </project>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java b/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
deleted file mode 100644
index ad1483d..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
+++ /dev/null
@@ -1,131 +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.document;
-
-import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * <p>
- * Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
- * for efficient encoding, sorting, and querying. This Geo capability is intended
- * to provide a basic and efficient out of the box field type for indexing and
- * querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
- *
- * <pre class="prettyprint">
- *  document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
- * </pre>
- *
- * <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
- * see {@link org.apache.lucene.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.search.GeoPointInPolygonQuery},
- * or {@link org.apache.lucene.search.GeoPointDistanceQuery}
- *
- * NOTE: This indexes only high precision encoded terms which may result in visiting a high number
- * of terms for large queries. See LUCENE-6481 for a future improvement.
- *
- * @lucene.experimental
- */
-public final class GeoPointField extends Field {
-  public static final int PRECISION_STEP = 9;
-
-  /**
-   * Type for an GeoPointField that is not stored:
-   * normalization factors, frequencies, and positions are omitted.
-   */
-  public static final FieldType TYPE_NOT_STORED = new FieldType();
-  static {
-    TYPE_NOT_STORED.setTokenized(false);
-    TYPE_NOT_STORED.setOmitNorms(true);
-    TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_NOT_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
-    TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
-    TYPE_NOT_STORED.freeze();
-  }
-
-  /**
-   * Type for a stored GeoPointField:
-   * normalization factors, frequencies, and positions are omitted.
-   */
-  public static final FieldType TYPE_STORED = new FieldType();
-  static {
-    TYPE_STORED.setTokenized(false);
-    TYPE_STORED.setOmitNorms(true);
-    TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
-    TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
-    TYPE_STORED.setStored(true);
-    TYPE_STORED.freeze();
-  }
-
-  /** Creates a stored or un-stored GeoPointField with the provided value
-   *  and default <code>precisionStep</code> set to 64 to avoid wasteful
-   *  indexing of lower precision terms.
-   *  @param name field name
-   *  @param lon longitude double value [-180.0 : 180.0]
-   *  @param lat latitude double value [-90.0 : 90.0]
-   *  @param stored Store.YES if the content should also be stored
-   *  @throws IllegalArgumentException if the field name is null.
-   */
-  public GeoPointField(String name, double lon, double lat, Store stored) {
-    super(name, stored == Store.YES ? TYPE_STORED : TYPE_NOT_STORED);
-    fieldsData = GeoUtils.mortonHash(lon, lat);
-  }
-
-  /** Expert: allows you to customize the {@link
-   *  FieldType}.
-   *  @param name field name
-   *  @param lon longitude double value [-180.0 : 180.0]
-   *  @param lat latitude double value [-90.0 : 90.0]
-   *  @param type customized field type: must have {@link FieldType#numericType()}
-   *         of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
-   *  @throws IllegalArgumentException if the field name or type is null, or
-   *          if the field type does not have a LONG numericType()
-   */
-  public GeoPointField(String name, double lon, double lat, FieldType type) {
-    super(name, type);
-    if (type.numericType() != FieldType.LegacyNumericType.LONG) {
-      throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
-    }
-    if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
-      throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
-    }
-    fieldsData = GeoUtils.mortonHash(lon, lat);
-  }
-
-  public double getLon() {
-    return GeoUtils.mortonUnhashLon((long) fieldsData);
-  }
-
-  public double getLat() {
-    return GeoUtils.mortonUnhashLat((long) fieldsData);
-  }
-
-  @Override
-  public String toString() {
-    if (fieldsData == null) {
-      return null;
-    }
-    StringBuilder sb = new StringBuilder();
-    sb.append(GeoUtils.mortonUnhashLon((long) fieldsData));
-    sb.append(',');
-    sb.append(GeoUtils.mortonUnhashLat((long) fieldsData));
-    return sb.toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
index 71d9527..77f7d32 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -17,8 +17,8 @@
 package org.apache.lucene.document;
 
 import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.GeoUtils;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
 
 /** Add this to a document to index lat/lon point dimensionally */
 public class LatLonPoint extends Field {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
deleted file mode 100644
index c2c92ea..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
+++ /dev/null
@@ -1,46 +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.search;
-
-import org.apache.lucene.util.GeoUtils;
-
-/** NOTE: package private; just used so {@link GeoPointInPolygonQuery} can communicate its bounding box to {@link GeoPointInBBoxQuery}. */
-class GeoBoundingBox {
-  public final double minLon;
-  public final double maxLon;
-  public final double minLat;
-  public final double maxLat;
-
-  public GeoBoundingBox(double minLon, double maxLon, double minLat, double maxLat) {
-    if (GeoUtils.isValidLon(minLon) == false) {
-      throw new IllegalArgumentException("invalid minLon " + minLon);
-    }
-    if (GeoUtils.isValidLon(maxLon) == false) {
-      throw new IllegalArgumentException("invalid maxLon " + minLon);
-    }
-    if (GeoUtils.isValidLat(minLat) == false) {
-      throw new IllegalArgumentException("invalid minLat " + minLat);
-    }
-    if (GeoUtils.isValidLat(maxLat) == false) {
-      throw new IllegalArgumentException("invalid maxLat " + minLat);
-    }
-    this.minLon = minLon;
-    this.maxLon = maxLon;
-    this.minLat = minLat;
-    this.maxLat = maxLat;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
deleted file mode 100644
index a77798b..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
+++ /dev/null
@@ -1,170 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoDistanceUtils;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple point distance query on a GeoPoint field. This is based on
- * {@link org.apache.lucene.search.GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
- * like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
- * the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
- * circle. Terms
- * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
- * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversin}.
- * All morton value comparisons are subject to the same precision tolerance defined in
- * {@value org.apache.lucene.util.GeoUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
- * haversine formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
- *
- * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
- * queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
- * using the spheroid
- *
- * @lucene.experimental */
-public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
-  protected final double centerLon;
-  protected final double centerLat;
-  protected final double radiusMeters;
-
-  /** NOTE: radius is in meters. */
-  public GeoPointDistanceQuery(final String field, final double centerLon, final double centerLat, final double radiusMeters) {
-    this(field, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
-  }
-
-  private GeoPointDistanceQuery(final String field, GeoRect bbox, final double centerLon,
-                                final double centerLat, final double radiusMeters) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
-    {
-      // check longitudinal overlap (limits radius)
-      final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLon, centerLat);
-      if (radiusMeters > maxRadius) {
-        throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
-            + "] at location [" + centerLon + " " + centerLat + "]");
-      }
-    }
-
-    if (GeoUtils.isValidLon(centerLon) == false) {
-      throw new IllegalArgumentException("invalid centerLon " + centerLon);
-    }
-
-    if (GeoUtils.isValidLat(centerLat) == false) {
-      throw new IllegalArgumentException("invalid centerLat " + centerLat);
-    }
-
-    if (radiusMeters <= 0.0) {
-      throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
-    }
-
-    this.centerLon = centerLon;
-    this.centerLat = centerLat;
-    this.radiusMeters = radiusMeters;
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) {
-    // query crosses dateline; split into left and right queries
-    if (maxLon < minLon) {
-      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
-      // unwrap the longitude iff outside the specified min/max lon range
-      double unwrappedLon = centerLon;
-      if (unwrappedLon > maxLon) {
-        // unwrap left
-        unwrappedLon += -360.0D;
-      }
-      GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
-          new GeoRect(GeoUtils.MIN_LON_INCL, maxLon, minLat, maxLat));
-      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-
-      if (unwrappedLon < maxLon) {
-        // unwrap right
-        unwrappedLon += 360.0D;
-      }
-      GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
-          new GeoRect(minLon, GeoUtils.MAX_LON_INCL, minLat, maxLat));
-      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-
-      return bqb.build();
-    }
-    return new GeoPointDistanceQueryImpl(field, this, centerLon,
-        new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoPointDistanceQuery)) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
-
-    if (Double.compare(that.centerLat, centerLat) != 0) return false;
-    if (Double.compare(that.centerLon, centerLon) != 0) return false;
-    if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(centerLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(centerLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(radiusMeters);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!this.field.equals(field)) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    return sb.append( " Center: [")
-        .append(centerLon)
-        .append(',')
-        .append(centerLat)
-        .append(']')
-        .append(" Distance: ")
-        .append(radiusMeters)
-        .append(" meters")
-        .append("]")
-        .toString();
-  }
-
-  public double getCenterLon() {
-    return this.centerLon;
-  }
-
-  public double getCenterLat() {
-    return this.centerLat;
-  }
-
-  public double getRadiusMeters() {
-    return this.radiusMeters;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
deleted file mode 100644
index ef2155b..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
+++ /dev/null
@@ -1,131 +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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.SloppyMath;
-
-/** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
- *
- *    @lucene.experimental
- */
-final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
-  private final GeoPointDistanceQuery query;
-  private final double centerLon;
-
-  GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q, final double centerLonUnwrapped,
-                            final GeoRect bbox) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
-    query = q;
-    centerLon = centerLonUnwrapped;
-  }
-
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPointRadiusTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
-  }
-
-  @Override
-  public void setRewriteMethod(RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  private final class GeoPointRadiusTermsEnum extends GeoPointTermsEnum {
-    GeoPointRadiusTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                            final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
-
-    /**
-     * Computes the maximum shift for the given pointDistanceQuery. This prevents unnecessary depth traversal
-     * given the size of the distance query.
-     */
-    @Override
-    protected short computeMaxShift() {
-      final short shiftFactor;
-
-      if (query.radiusMeters > 1000000) {
-        shiftFactor = 5;
-      } else {
-        shiftFactor = 4;
-      }
-
-      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
-    }
-
-    @Override
-    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectCrossesCircle(minLon, minLat, maxLon, maxLat,
-          centerLon, query.centerLat, query.radiusMeters, true);
-    }
-
-    @Override
-    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectWithinCircle(minLon, minLat, maxLon, maxLat,
-          centerLon, query.centerLat, query.radiusMeters, true);
-    }
-
-    @Override
-    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return (cellContains(minLon, minLat, maxLon, maxLat)
-          || cellWithin(minLon, minLat, maxLon, maxLat) || cellCrosses(minLon, minLat, maxLon, maxLat));
-    }
-
-    /**
-     * The two-phase query approach. The parent {@link org.apache.lucene.search.GeoPointTermsEnum} class matches
-     * encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
-     * the initial bounding box filter are then post filter compared to the provided distance using the
-     * {@link org.apache.lucene.util.SloppyMath#haversin} method.
-     */
-    @Override
-    protected boolean postFilter(final double lon, final double lat) {
-      return (SloppyMath.haversin(query.centerLat, centerLon, lat, lon) * 1000.0 <= query.radiusMeters);
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
-
-    if (!query.equals(that.query)) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    result = 31 * result + query.hashCode();
-    return result;
-  }
-
-  public double getRadiusMeters() {
-    return query.getRadiusMeters();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
deleted file mode 100644
index 5324a6d..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
+++ /dev/null
@@ -1,102 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoProjectionUtils;
-
-/** Implements a point distance range query on a GeoPoint field. This is based on
- * {@code org.apache.lucene.search.GeoPointDistanceQuery} and is implemented using a
- * {@code org.apache.lucene.search.BooleanClause.MUST_NOT} clause to exclude any points that fall within
- * minRadiusMeters from the provided point.
- *
- *    @lucene.experimental
- */
-public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
-  protected final double minRadiusMeters;
-
-  public GeoPointDistanceRangeQuery(final String field, final double centerLon, final double centerLat,
-                                    final double minRadiusMeters, final double maxRadius) {
-    super(field, centerLon, centerLat, maxRadius);
-    this.minRadiusMeters = minRadiusMeters;
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) {
-    Query q = super.rewrite(reader);
-    if (minRadiusMeters == 0.0) {
-      return q;
-    }
-
-    // add an exclusion query
-    BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
-    // create a new exclusion query
-    GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, centerLon, centerLat, minRadiusMeters);
-    // full map search
-//    if (radiusMeters >= GeoProjectionUtils.SEMIMINOR_AXIS) {
-//      bqb.add(new BooleanClause(new GeoPointInBBoxQuery(this.field, -180.0, -90.0, 180.0, 90.0), BooleanClause.Occur.MUST));
-//    } else {
-      bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
-//    }
-    bqb.add(new BooleanClause(exclude, BooleanClause.Occur.MUST_NOT));
-
-    return bqb.build();
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!this.field.equals(field)) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    return sb.append( " Center: [")
-        .append(centerLon)
-        .append(',')
-        .append(centerLat)
-        .append(']')
-        .append(" From Distance: ")
-        .append(minRadiusMeters)
-        .append(" m")
-        .append(" To Distance: ")
-        .append(radiusMeters)
-        .append(" m")
-        .append(" Lower Left: [")
-        .append(minLon)
-        .append(',')
-        .append(minLat)
-        .append(']')
-        .append(" Upper Right: [")
-        .append(maxLon)
-        .append(',')
-        .append(maxLat)
-        .append("]")
-        .toString();
-  }
-
-  public double getMinRadiusMeters() {
-    return this.minRadiusMeters;
-  }
-
-  public double getMaxRadiusMeters() {
-    return this.radiusMeters;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
deleted file mode 100644
index 0926a4f..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
+++ /dev/null
@@ -1,152 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple bounding box query on a GeoPoint field. This is inspired by
- * {@link LegacyNumericRangeQuery} and is implemented using a
- * two phase approach. First, candidate terms are queried using a numeric
- * range based on the morton codes of the min and max lat/lon pairs. Terms
- * passing this initial filter are passed to a final check that verifies whether
- * the decoded lat/lon falls within (or on the boundary) of the query bounding box.
- * The value comparisons are subject to a precision tolerance defined in
- * {@value org.apache.lucene.util.GeoUtils#TOLERANCE}
- *
- * NOTES:
- *    1.  All latitude/longitude values must be in decimal degrees.
- *    2.  Complex computational geometry (e.g., dateline wrapping) is not supported
- *    3.  For more advanced GeoSpatial indexing and query operations see spatial module
- *    4.  This is well suited for small rectangles, large bounding boxes may result
- *        in many terms, depending whether the bounding box falls on the boundary of
- *        many cells (degenerate case)
- *
- * @lucene.experimental
- */
-public class GeoPointInBBoxQuery extends Query {
-  protected final String field;
-  protected final double minLon;
-  protected final double minLat;
-  protected final double maxLon;
-  protected final double maxLat;
-
-  public GeoPointInBBoxQuery(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    this.field = field;
-    this.minLon = minLon;
-    this.minLat = minLat;
-    this.maxLon = maxLon;
-    this.maxLat = maxLat;
-  }
-
-  @Override
-  public Query rewrite(IndexReader reader) {
-    // short-circuit to match all if specifying the whole map
-    if (minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL
-        && minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL) {
-      // FieldValueQuery is valid since DocValues are *required* for GeoPointField
-      return new FieldValueQuery(field);
-    }
-
-    if (maxLon < minLon) {
-      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
-      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
-      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
-      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-      return bqb.build();
-    }
-    return new GeoPointInBBoxQueryImpl(field, minLon, minLat, maxLon, maxLat);
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!this.field.equals(field)) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    return sb.append(" Lower Left: [")
-        .append(minLon)
-        .append(',')
-        .append(minLat)
-        .append(']')
-        .append(" Upper Right: [")
-        .append(maxLon)
-        .append(',')
-        .append(maxLat)
-        .append("]")
-        .toString();
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof GeoPointInBBoxQuery)) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointInBBoxQuery that = (GeoPointInBBoxQuery) o;
-
-    if (Double.compare(that.maxLat, maxLat) != 0) return false;
-    if (Double.compare(that.maxLon, maxLon) != 0) return false;
-    if (Double.compare(that.minLat, minLat) != 0) return false;
-    if (Double.compare(that.minLon, minLon) != 0) return false;
-    if (!field.equals(that.field)) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    result = 31 * result + field.hashCode();
-    temp = Double.doubleToLongBits(minLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  public final String getField() {
-    return this.field;
-  }
-
-  public final double getMinLon() {
-    return this.minLon;
-  }
-
-  public final double getMinLat() {
-    return this.minLat;
-  }
-
-  public final double getMaxLon() {
-    return this.maxLon;
-  }
-
-  public final double getMaxLat() {
-    return this.maxLat;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
deleted file mode 100644
index b385a54..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
+++ /dev/null
@@ -1,160 +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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.SloppyMath;
-
-/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
- *
- *    @lucene.experimental
- */
-class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
-  /**
-   * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
-   * of the bounding box defined by the input parameters
-   * @param field the field name
-   * @param minLon lower longitude (x) value of the bounding box
-   * @param minLat lower latitude (y) value of the bounding box
-   * @param maxLon upper longitude (x) value of the bounding box
-   * @param maxLat upper latitude (y) value of the bounding box
-   */
-  GeoPointInBBoxQueryImpl(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    super(field, minLon, minLat, maxLon, maxLat);
-  }
-
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPointInBBoxTermsEnum(terms.iterator(), minLon, minLat, maxLon, maxLat);
-  }
-
-  @Override
-  public void setRewriteMethod(RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  protected class GeoPointInBBoxTermsEnum extends GeoPointTermsEnum {
-    protected GeoPointInBBoxTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                            final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
-
-    @Override
-    protected short computeMaxShift() {
-      final short shiftFactor;
-
-      // compute diagonal radius
-      double midLon = (minLon + maxLon) * 0.5;
-      double midLat = (minLat + maxLat) * 0.5;
-
-      if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
-        shiftFactor = 5;
-      } else {
-        shiftFactor = 4;
-      }
-
-      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
-    }
-
-    /**
-     * Determine whether the quad-cell crosses the shape
-     */
-    @Override
-    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
-    }
-
-    /**
-     * Determine whether quad-cell is within the shape
-     */
-    @Override
-    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
-    }
-
-    @Override
-    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return cellIntersectsMBR(minLon, minLat, maxLon, maxLat);
-    }
-
-    @Override
-    protected boolean postFilter(final double lon, final double lat) {
-      return GeoRelationUtils.pointInRectPrecise(lon, lat, minLon, minLat, maxLon, maxLat);
-    }
-  }
-
-  @Override
-  @SuppressWarnings({"unchecked","rawtypes"})
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
-
-    if (Double.compare(that.maxLat, maxLat) != 0) return false;
-    if (Double.compare(that.maxLon, maxLon) != 0) return false;
-    if (Double.compare(that.minLat, minLat) != 0) return false;
-    if (Double.compare(that.minLon, minLon) != 0) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(minLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!getField().equals(field)) {
-      sb.append(" field=");
-      sb.append(getField());
-      sb.append(':');
-    }
-    return sb.append(" Lower Left: [")
-        .append(minLon)
-        .append(',')
-        .append(minLat)
-        .append(']')
-        .append(" Upper Right: [")
-        .append(maxLon)
-        .append(',')
-        .append(maxLat)
-        .append("]")
-        .toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
deleted file mode 100644
index abdcf95..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
+++ /dev/null
@@ -1,194 +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.search;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple point in polygon query on a GeoPoint field. This is based on
- * {@code GeoPointInBBoxQueryImpl} and is implemented using a
- * three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
- * candidate terms are queried using a numeric range based on the morton codes
- * of the min and max lat/lon pairs. Terms passing this initial filter are passed
- * to a secondary filter that verifies whether the decoded lat/lon point falls within
- * (or on the boundary) of the bounding box query. Finally, the remaining candidate
- * term is passed to the final point in polygon check. All value comparisons are subject
- * to the same precision tolerance defined in {@value org.apache.lucene.util.GeoUtils#TOLERANCE}
- *
- * <p>NOTES:
- *    1.  The polygon coordinates need to be in either clockwise or counter-clockwise order.
- *    2.  The polygon must not be self-crossing, otherwise the query may result in unexpected behavior
- *    3.  All latitude/longitude values must be in decimal degrees.
- *    4.  Complex computational geometry (e.g., dateline wrapping, polygon with holes) is not supported
- *    5.  For more advanced GeoSpatial indexing and query operations see spatial module
- *
- * @lucene.experimental
- */
-public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
-  // polygon position arrays - this avoids the use of any objects or
-  // or geo library dependencies
-  private final double[] x;
-  private final double[] y;
-
-  /**
-   * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.document.GeoPointField} terms
-   * that fall within or on the boundary of the polygon defined by the input parameters.
-   */
-  public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
-    this(field, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
-  }
-
-  /** Common constructor, used only internally. */
-  private GeoPointInPolygonQuery(final String field, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
-    if (polyLats.length != polyLons.length) {
-      throw new IllegalArgumentException("polyLats and polyLons must be equal length");
-    }
-    if (polyLats.length < 4) {
-      throw new IllegalArgumentException("at least 4 polygon points required");
-    }
-    if (polyLats[0] != polyLats[polyLats.length-1]) {
-      throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLats[0]=" + polyLats[0] + " polyLats[" + (polyLats.length-1) + "]=" + polyLats[polyLats.length-1]);
-    }
-    if (polyLons[0] != polyLons[polyLons.length-1]) {
-      throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLons[0]=" + polyLons[0] + " polyLons[" + (polyLons.length-1) + "]=" + polyLons[polyLons.length-1]);
-    }
-
-    this.x = polyLons;
-    this.y = polyLats;
-  }
-
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPolygonTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
-  }
-
-  @Override
-  public void setRewriteMethod(RewriteMethod method) {
-    throw new UnsupportedOperationException("cannot change rewrite method");
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointInPolygonQuery that = (GeoPointInPolygonQuery) o;
-
-    if (!Arrays.equals(x, that.x)) return false;
-    if (!Arrays.equals(y, that.y)) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    result = 31 * result + (x != null ? Arrays.hashCode(x) : 0);
-    result = 31 * result + (y != null ? Arrays.hashCode(y) : 0);
-    return result;
-  }
-
-  @Override
-  public String toString(String field) {
-    assert x.length == y.length;
-
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (!getField().equals(field)) {
-      sb.append(" field=");
-      sb.append(getField());
-      sb.append(':');
-    }
-    sb.append(" Points: ");
-    for (int i=0; i<x.length; ++i) {
-      sb.append("[")
-        .append(x[i])
-        .append(", ")
-        .append(y[i])
-        .append("] ");
-    }
-
-    return sb.toString();
-  }
-
-  /**
-   * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
-   * the provided polygon.
-   */
-  private final class GeoPolygonTermsEnum extends GeoPointTermsEnum {
-    GeoPolygonTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                        final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
-
-    @Override
-    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
-          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
-    }
-
-    @Override
-    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
-          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
-    }
-
-    @Override
-    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
-          || cellCrosses(minLon, minLat, maxLon, maxLat);
-    }
-
-    /**
-     * The two-phase query approach. The parent
-     * {@link org.apache.lucene.search.GeoPointTermsEnum#accept} method is called to match
-     * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
-     * bounding box filter are then compared to the provided polygon using the
-     * {@link org.apache.lucene.util.GeoRelationUtils#pointInPolygon} method.
-     */
-    @Override
-    protected boolean postFilter(final double lon, final double lat) {
-      return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
-    }
-  }
-
-  /**
-   * API utility method for returning the array of longitudinal values for this GeoPolygon
-   * The returned array is not a copy so do not change it!
-   */
-  public double[] getLons() {
-    return this.x;
-  }
-
-  /**
-   * API utility method for returning the array of latitudinal values for this GeoPolygon
-   * The returned array is not a copy so do not change it!
-   */
-  public double[] getLats() {
-    return this.y;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
deleted file mode 100644
index 5ec0774..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
+++ /dev/null
@@ -1,70 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
- * Geospatial operations
- *
- * @lucene.experimental
- */
-abstract class GeoPointTermQuery extends MultiTermQuery {
-  // simple bounding box optimization - no objects used to avoid dependencies
-  protected final double minLon;
-  protected final double minLat;
-  protected final double maxLon;
-  protected final double maxLat;
-
-  /**
-   * Constructs a query matching terms that cannot be represented with a single
-   * Term.
-   */
-  public GeoPointTermQuery(String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    super(field);
-
-    if (GeoUtils.isValidLon(minLon) == false) {
-      throw new IllegalArgumentException("invalid minLon " + minLon);
-    }
-    if (GeoUtils.isValidLon(maxLon) == false) {
-      throw new IllegalArgumentException("invalid maxLon " + maxLon);
-    }
-    if (GeoUtils.isValidLat(minLat) == false) {
-      throw new IllegalArgumentException("invalid minLat " + minLat);
-    }
-    if (GeoUtils.isValidLat(maxLat) == false) {
-      throw new IllegalArgumentException("invalid maxLat " + maxLat);
-    }
-    this.minLon = minLon;
-    this.minLat = minLat;
-    this.maxLon = maxLon;
-    this.maxLat = maxLat;
-
-    this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
-  }
-
-  public static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
-    @Override
-    public Query rewrite(IndexReader reader, MultiTermQuery query) {
-      return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointTermQuery)query);
-    }
-  };
-
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
deleted file mode 100644
index e00bb72..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
+++ /dev/null
@@ -1,129 +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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PostingsEnum;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * Custom ConstantScoreWrapper for {@code GeoPointTermQuery} that cuts over to DocValues
- * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
- *
- * @lucene.experimental
- */
-final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery> extends Query {
-  protected final Q query;
-
-  protected GeoPointTermQueryConstantScoreWrapper(Q query) {
-    this.query = query;
-  }
-
-  @Override
-  public String toString(String field) {
-    return query.toString();
-  }
-
-  @Override
-  public final boolean equals(final Object o) {
-    if (super.equals(o) == false) {
-      return false;
-    }
-    final GeoPointTermQueryConstantScoreWrapper<?> that = (GeoPointTermQueryConstantScoreWrapper<?>) o;
-    return this.query.equals(that.query);
-  }
-
-  @Override
-  public final int hashCode() {
-    return 31 * super.hashCode() + query.hashCode();
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
-    return new ConstantScoreWeight(this) {
-
-      private DocIdSet getDocIDs(LeafReaderContext context) throws IOException {
-        final Terms terms = context.reader().terms(query.field);
-        if (terms == null) {
-          return DocIdSet.EMPTY;
-        }
-
-        final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms));
-        assert termsEnum != null;
-
-        LeafReader reader = context.reader();
-        DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc());
-        PostingsEnum docs = null;
-        SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.field);
-
-        while (termsEnum.next() != null) {
-          docs = termsEnum.postings(docs, PostingsEnum.NONE);
-          // boundary terms need post filtering by
-          if (termsEnum.boundaryTerm()) {
-            int docId = docs.nextDoc();
-            long hash;
-            do {
-              sdv.setDocument(docId);
-              for (int i=0; i<sdv.count(); ++i) {
-                hash = sdv.valueAt(i);
-                if (termsEnum.postFilter(GeoUtils.mortonUnhashLon(hash), GeoUtils.mortonUnhashLat(hash))) {
-                  builder.add(docId);
-                  break;
-                }
-              }
-            } while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS);
-          } else {
-            builder.add(docs);
-          }
-        }
-
-        return builder.build();
-      }
-
-      private Scorer scorer(DocIdSet set) throws IOException {
-        if (set == null) {
-          return null;
-        }
-        final DocIdSetIterator disi = set.iterator();
-        if (disi == null) {
-          return null;
-        }
-        return new ConstantScoreScorer(this, score(), disi);
-      }
-
-      @Override
-      public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
-        final Scorer scorer = scorer(getDocIDs(context));
-        if (scorer == null) {
-          return null;
-        }
-        return new DefaultBulkScorer(scorer);
-      }
-
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        return scorer(getDocIDs(context));
-      }
-    };
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
deleted file mode 100644
index e2f96d4..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
+++ /dev/null
@@ -1,249 +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.search;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.FilteredTermsEnum;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.LegacyNumericUtils;
-
-/**
- * computes all ranges along a space-filling curve that represents
- * the given bounding box and enumerates all terms contained within those ranges
- *
- *  @lucene.experimental
- */
-abstract class GeoPointTermsEnum extends FilteredTermsEnum {
-  protected final double minLon;
-  protected final double minLat;
-  protected final double maxLon;
-  protected final double maxLat;
-
-  protected Range currentRange;
-  private final BytesRefBuilder currentCell = new BytesRefBuilder();
-  private final BytesRefBuilder nextSubRange = new BytesRefBuilder();
-
-  private final List<Range> rangeBounds = new LinkedList<>();
-
-  // detail level should be a factor of PRECISION_STEP limiting the depth of recursion (and number of ranges)
-  protected final short DETAIL_LEVEL;
-
-  GeoPointTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                    final double maxLon, final double maxLat) {
-    super(tenum);
-    final long rectMinHash = GeoUtils.mortonHash(minLon, minLat);
-    final long rectMaxHash = GeoUtils.mortonHash(maxLon, maxLat);
-    this.minLon = GeoUtils.mortonUnhashLon(rectMinHash);
-    this.minLat = GeoUtils.mortonUnhashLat(rectMinHash);
-    this.maxLon = GeoUtils.mortonUnhashLon(rectMaxHash);
-    this.maxLat = GeoUtils.mortonUnhashLat(rectMaxHash);
-    DETAIL_LEVEL = (short)(((GeoUtils.BITS<<1)-computeMaxShift())/2);
-
-    computeRange(0L, (short) ((GeoUtils.BITS << 1) - 1));
-    assert rangeBounds.isEmpty() == false;
-    Collections.sort(rangeBounds);
-  }
-
-  /**
-   * entry point for recursively computing ranges
-   */
-  private final void computeRange(long term, final short shift) {
-    final long split = term | (0x1L<<shift);
-    assert shift < 64;
-    final long upperMax;
-    if (shift < 63) {
-      upperMax = term | ((1L << (shift+1))-1);
-    } else {
-      upperMax = 0xffffffffffffffffL;
-    }
-    final long lowerMax = split-1;
-
-    relateAndRecurse(term, lowerMax, shift);
-    relateAndRecurse(split, upperMax, shift);
-  }
-
-  /**
-   * recurse to higher level precision cells to find ranges along the space-filling curve that fall within the
-   * query box
-   *
-   * @param start starting value on the space-filling curve for a cell at a given res
-   * @param end ending value on the space-filling curve for a cell at a given res
-   * @param res spatial res represented as a bit shift (MSB is lower res)
-   */
-  private void relateAndRecurse(final long start, final long end, final short res) {
-    final double minLon = GeoUtils.mortonUnhashLon(start);
-    final double minLat = GeoUtils.mortonUnhashLat(start);
-    final double maxLon = GeoUtils.mortonUnhashLon(end);
-    final double maxLat = GeoUtils.mortonUnhashLat(end);
-
-    final short level = (short)((GeoUtils.BITS<<1)-res>>>1);
-
-    // if cell is within and a factor of the precision step, or it crosses the edge of the shape add the range
-    final boolean within = res % GeoPointField.PRECISION_STEP == 0 && cellWithin(minLon, minLat, maxLon, maxLat);
-    if (within || (level == DETAIL_LEVEL && cellIntersectsShape(minLon, minLat, maxLon, maxLat))) {
-      final short nextRes = (short)(res-1);
-      if (nextRes % GeoPointField.PRECISION_STEP == 0) {
-        rangeBounds.add(new Range(start, nextRes, !within));
-        rangeBounds.add(new Range(start|(1L<<nextRes), nextRes, !within));
-      } else {
-        rangeBounds.add(new Range(start, res, !within));
-      }
-    } else if (level < DETAIL_LEVEL && cellIntersectsMBR(minLon, minLat, maxLon, maxLat)) {
-      computeRange(start, (short) (res - 1));
-    }
-  }
-
-  protected short computeMaxShift() {
-    // in this case a factor of 4 brings the detail level to ~0.002/0.001 degrees lon/lat respectively (or ~222m/111m)
-    return GeoPointField.PRECISION_STEP * 4;
-  }
-
-  /**
-   * Determine whether the quad-cell crosses the shape
-   */
-  protected abstract boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
-  /**
-   * Determine whether quad-cell is within the shape
-   */
-  protected abstract boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
-  /**
-   * Default shape is a rectangle, so this returns the same as {@code cellIntersectsMBR}
-   */
-  protected abstract boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
-  /**
-   * Primary driver for cells intersecting shape boundaries
-   */
-  protected boolean cellIntersectsMBR(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    return GeoRelationUtils.rectIntersects(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
-  }
-
-  /**
-   * Return whether quad-cell contains the bounding box of this shape
-   */
-  protected boolean cellContains(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    return GeoRelationUtils.rectWithin(this.minLon, this.minLat, this.maxLon, this.maxLat, minLon, minLat, maxLon, maxLat);
-  }
-
-  public boolean boundaryTerm() {
-    if (currentRange == null) {
-      throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
-    }
-    return currentRange.boundary;
-  }
-
-  private void nextRange() {
-    currentRange = rangeBounds.remove(0);
-    currentRange.fillBytesRef(currentCell);
-  }
-
-  @Override
-  protected final BytesRef nextSeekTerm(BytesRef term) {
-    while (!rangeBounds.isEmpty()) {
-      if (currentRange == null) {
-        nextRange();
-      }
-
-      // if the new upper bound is before the term parameter, the sub-range is never a hit
-      if (term != null && term.compareTo(currentCell.get()) > 0) {
-        nextRange();
-        if (!rangeBounds.isEmpty()) {
-          continue;
-        }
-      }
-      // never seek backwards, so use current term if lower bound is smaller
-      return (term != null && term.compareTo(currentCell.get()) > 0) ?
-          term : currentCell.get();
-    }
-
-    // no more sub-range enums available
-    assert rangeBounds.isEmpty();
-    return null;
-  }
-
-  /**
-   * The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
-   * range of the bounding box. Those terms that pass the initial range filter are then compared against the
-   * decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
-   * (e.g., a range that straddles the boundary of the bbox).
-   * @param term term for candidate document
-   * @return match status
-   */
-  @Override
-  protected AcceptStatus accept(BytesRef term) {
-    // validate value is in range
-    while (currentCell == null || term.compareTo(currentCell.get()) > 0) {
-      if (rangeBounds.isEmpty()) {
-        return AcceptStatus.END;
-      }
-      // peek next sub-range, only seek if the current term is smaller than next lower bound
-      rangeBounds.get(0).fillBytesRef(this.nextSubRange);
-      if (term.compareTo(this.nextSubRange.get()) < 0) {
-        return AcceptStatus.NO_AND_SEEK;
-      }
-      // step forward to next range without seeking, as next range is less or equal current term
-      nextRange();
-    }
-
-    return AcceptStatus.YES;
-  }
-
-  protected abstract boolean postFilter(final double lon, final double lat);
-
-  /**
-   * Internal class to represent a range along the space filling curve
-   */
-  protected final class Range implements Comparable<Range> {
-    final short shift;
-    final long start;
-    final boolean boundary;
-
-    Range(final long lower, final short shift, boolean boundary) {
-      this.boundary = boundary;
-      this.start = lower;
-      this.shift = shift;
-    }
-
-    /**
-     * Encode as a BytesRef using a reusable object. This allows us to lazily create the BytesRef (which is
-     * quite expensive), only when we need it.
-     */
-    private void fillBytesRef(BytesRefBuilder result) {
-      assert result != null;
-      LegacyNumericUtils.longToPrefixCoded(start, shift, result);
-    }
-
-    @Override
-    public int compareTo(Range other) {
-      final int result = Short.compare(this.shift, other.shift);
-      if (result == 0) {
-        return Long.compare(this.start, other.start);
-      }
-      return result;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
index 861a109..8e099c4 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
@@ -26,9 +26,9 @@ import org.apache.lucene.index.PointValues;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
 
 /** Finds all previously indexed points that fall within the specified polygon.
  *

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
index 6ef1703..1d95399 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
@@ -26,8 +26,8 @@ import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoUtils;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
 
 /** Finds all previously indexed points that fall within the specified boundings box.
  *

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
deleted file mode 100644
index 9134709..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
+++ /dev/null
@@ -1,217 +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.util;
-
-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;
-
-  /**
-   * Compute the great-circle distance using original haversine implementation published by Sinnot in:
-   * R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159
-   *
-   * NOTE: this differs from {@link org.apache.lucene.util.SloppyMath#haversin} in that it uses the semi-major axis
-   * of the earth instead of an approximation based on the average latitude of the two points (which can introduce an
-   * additional error up to .337%, or ~67.6 km at the equator)
-   */
-  public static double haversin(double lat1, double lon1, double lat2, double lon2) {
-    double dLat = TO_RADIANS * (lat2 - lat1);
-    double dLon = TO_RADIANS * (lon2 - lon1);
-    lat1 = TO_RADIANS * (lat1);
-    lat2 = TO_RADIANS * (lat2);
-
-    final double sinDLatO2 = SloppyMath.sin(dLat / 2);
-    final double sinDLonO2 = SloppyMath.sin(dLon / 2);
-
-    double a = sinDLatO2*sinDLatO2 + sinDLonO2 * sinDLonO2 * SloppyMath.cos(lat1) * SloppyMath.cos(lat2);
-    double c = 2 * SloppyMath.asin(Math.sqrt(a));
-    return (GeoProjectionUtils.SEMIMAJOR_AXIS * c);
-  }
-
-  /**
-   * Compute the distance between two geo-points using vincenty distance formula
-   * Vincenty uses the oblate spheroid whereas haversine uses unit sphere, this will give roughly
-   * 22m better accuracy (in worst case) than haversine
-   *
-   * @param lonA longitudinal coordinate of point A (in degrees)
-   * @param latA latitudinal coordinate of point A (in degrees)
-   * @param lonB longitudinal coordinate of point B (in degrees)
-   * @param latB latitudinal coordinate of point B (in degrees)
-   * @return distance (in meters) between point A and point B
-   */
-  public static final double vincentyDistance(final double lonA, final double latA, final double lonB, final double latB) {
-    final double L = StrictMath.toRadians(lonB - lonA);
-    final double oF = 1 - GeoProjectionUtils.FLATTENING;
-    final double U1 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latA)));
-    final double U2 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latB)));
-    final double sU1 = StrictMath.sin(U1);
-    final double cU1 = StrictMath.cos(U1);
-    final double sU2 = StrictMath.sin(U2);
-    final double cU2 = StrictMath.cos(U2);
-
-    double sigma, sinSigma, cosSigma;
-    double sinAlpha, cos2Alpha, cos2SigmaM;
-    double lambda = L;
-    double lambdaP;
-    double iters = 100;
-    double sinLambda, cosLambda, c;
-
-    do {
-      sinLambda = StrictMath.sin(lambda);
-      cosLambda = Math.cos(lambda);
-      sinSigma = Math.sqrt((cU2 * sinLambda) * (cU2 * sinLambda) + (cU1 * sU2 - sU1 * cU2 * cosLambda)
-          * (cU1 * sU2 - sU1 * cU2 * cosLambda));
-      if (sinSigma == 0) {
-        return 0;
-      }
-
-      cosSigma = sU1 * sU2 + cU1 * cU2 * cosLambda;
-      sigma = Math.atan2(sinSigma, cosSigma);
-      sinAlpha = cU1 * cU2 * sinLambda / sinSigma;
-      cos2Alpha = 1 - sinAlpha * sinAlpha;
-      cos2SigmaM = cosSigma - 2 * sU1 * sU2 / cos2Alpha;
-
-      c = GeoProjectionUtils.FLATTENING/16 * cos2Alpha * (4 + GeoProjectionUtils.FLATTENING * (4 - 3 * cos2Alpha));
-      lambdaP = lambda;
-      lambda = L + (1 - c) * GeoProjectionUtils.FLATTENING * sinAlpha * (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma *
-          (-1 + 2 * cos2SigmaM * cos2SigmaM)));
-    } while (StrictMath.abs(lambda - lambdaP) > 1E-12 && --iters > 0);
-
-    if (iters == 0) {
-      return 0;
-    }
-
-    final double uSq = cos2Alpha * (GeoProjectionUtils.SEMIMAJOR_AXIS2 - GeoProjectionUtils.SEMIMINOR_AXIS2) / (GeoProjectionUtils.SEMIMINOR_AXIS2);
-    final double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
-    final double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
-    final double deltaSigma = B * sinSigma * (cos2SigmaM + B/4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B/6 * cos2SigmaM
-        * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
-
-    return (GeoProjectionUtils.SEMIMINOR_AXIS * A * (sigma - deltaSigma));
-  }
-
-  /**
-   * Computes distance between two points in a cartesian (x, y, {z - optional}) coordinate system
-   */
-  public static double linearDistance(double[] pt1, double[] pt2) {
-    assert pt1 != null && pt2 != null && pt1.length == pt2.length && pt1.length > 1;
-    final double d0 = pt1[0] - pt2[0];
-    final double d1 = pt1[1] - pt2[1];
-    if (pt1.length == 3) {
-      final double d2 = pt1[2] - pt2[2];
-      return Math.sqrt(d0*d0 + d1*d1 + d2*d2);
-    }
-    return Math.sqrt(d0*d0 + d1*d1);
-  }
-
-  /**
-   * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
-   * @param lat latitude to compute delta degrees lon
-   * @param distance distance in meters to convert to degrees lon
-   * @return Sloppy distance in degrees longitude for provided distance in meters
-   */
-  public static double distanceToDegreesLon(double lat, double distance) {
-    distance /= 1000.0;
-    // convert latitude to radians
-    lat = StrictMath.toRadians(lat);
-
-    // get the diameter at the latitude
-    final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
-
-    // compute inverse haversine
-    double a = StrictMath.sin(distance/diameter);
-    double h = StrictMath.min(1, a);
-    h *= h;
-    double cLat = StrictMath.cos(lat);
-
-    return StrictMath.toDegrees(StrictMath.acos(1-((2d*h)/(cLat*cLat))));
-  }
-
-  /**
-   *  Finds the closest point within a rectangle (defined by rMinX, rMinY, rMaxX, rMaxY) to the given (lon, lat) point
-   *  the result is provided in closestPt.  When the point is outside the rectangle, the closest point is on an edge
-   *  or corner of the rectangle; else, the closest point is the point itself.
-   */
-  public static void closestPointOnBBox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                        final double lon, final double lat, double[] closestPt) {
-    assert closestPt != null && closestPt.length == 2;
-
-    closestPt[0] = 0;
-    closestPt[1] = 0;
-
-    boolean xSet = true;
-    boolean ySet = true;
-
-    if (lon > rMaxX) {
-      closestPt[0] = rMaxX;
-    } else if (lon < rMinX) {
-      closestPt[0] = rMinX;
-    } else {
-      xSet = false;
-    }
-
-    if (lat > rMaxY) {
-      closestPt[1] = rMaxY;
-    } else if (lat < rMinY) {
-      closestPt[1] = rMinY;
-    } else {
-      ySet = false;
-    }
-
-    if (closestPt[0] == 0 && xSet == false) {
-      closestPt[0] = lon;
-    }
-
-    if (closestPt[1] == 0 && ySet == false) {
-      closestPt[1] = lat;
-    }
-  }
-
-  /** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
-  public static double maxRadialDistanceMeters(final double centerLon, final double centerLat) {
-    if (Math.abs(centerLat) == GeoUtils.MAX_LAT_INCL) {
-      return GeoDistanceUtils.haversin(centerLat, centerLon, 0, centerLon);
-    }
-    return GeoDistanceUtils.haversin(centerLat, centerLon, centerLat, (GeoUtils.MAX_LON_INCL + centerLon) % 360);
-  }
-
-  /**
-   * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
-   * @param lat latitude to compute delta degrees lon
-   * @param distance distance in meters to convert to degrees lon
-   * @return Sloppy distance in degrees longitude for provided distance in meters
-   */
-  public static double distanceToDegreesLat(double lat, double distance) {
-    // get the diameter at the latitude
-    final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
-    distance /= 1000.0;
-
-    // compute inverse haversine
-    double a = StrictMath.sin(distance/diameter);
-    double h = StrictMath.min(1, a);
-    h *= h;
-
-    return StrictMath.toDegrees(StrictMath.acos(1-(2d*h)));
-  }
-}


[3/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
new file mode 100644
index 0000000..bd44b1e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+
+/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
+ *
+ *    @lucene.experimental
+ */
+class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
+  /**
+   * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
+   * of the bounding box defined by the input parameters
+   * @param field the field name
+   * @param minLon lower longitude (x) value of the bounding box
+   * @param minLat lower latitude (y) value of the bounding box
+   * @param maxLon upper longitude (x) value of the bounding box
+   * @param maxLat upper latitude (y) value of the bounding box
+   */
+  GeoPointInBBoxQueryImpl(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    super(field, minLon, minLat, maxLon, maxLat);
+  }
+
+  @Override @SuppressWarnings("unchecked")
+  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+    return new GeoPointInBBoxTermsEnum(terms.iterator(), minLon, minLat, maxLon, maxLat);
+  }
+
+  @Override
+  public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
+    throw new UnsupportedOperationException("cannot change rewrite method");
+  }
+
+  protected class GeoPointInBBoxTermsEnum extends GeoPointTermsEnum {
+    protected GeoPointInBBoxTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+                            final double maxLon, final double maxLat) {
+      super(tenum, minLon, minLat, maxLon, maxLat);
+    }
+
+    @Override
+    protected short computeMaxShift() {
+      final short shiftFactor;
+
+      // compute diagonal radius
+      double midLon = (minLon + maxLon) * 0.5;
+      double midLat = (minLat + maxLat) * 0.5;
+
+      if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
+        shiftFactor = 5;
+      } else {
+        shiftFactor = 4;
+      }
+
+      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+    }
+
+    /**
+     * Determine whether the quad-cell crosses the shape
+     */
+    @Override
+    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+    }
+
+    /**
+     * Determine whether quad-cell is within the shape
+     */
+    @Override
+    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+    }
+
+    @Override
+    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return cellIntersectsMBR(minLon, minLat, maxLon, maxLat);
+    }
+
+    @Override
+    protected boolean postFilter(final double lon, final double lat) {
+      return GeoRelationUtils.pointInRectPrecise(lon, lat, minLon, minLat, maxLon, maxLat);
+    }
+  }
+
+  @Override
+  @SuppressWarnings({"unchecked","rawtypes"})
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
+
+    if (Double.compare(that.maxLat, maxLat) != 0) return false;
+    if (Double.compare(that.maxLon, maxLon) != 0) return false;
+    if (Double.compare(that.minLat, minLat) != 0) return false;
+    if (Double.compare(that.minLon, minLon) != 0) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp;
+    temp = Double.doubleToLongBits(minLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(minLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public String toString(String field) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (!getField().equals(field)) {
+      sb.append(" field=");
+      sb.append(getField());
+      sb.append(':');
+    }
+    return sb.append(" Lower Left: [")
+        .append(minLon)
+        .append(',')
+        .append(minLat)
+        .append(']')
+        .append(" Upper Right: [")
+        .append(maxLon)
+        .append(',')
+        .append(maxLat)
+        .append("]")
+        .toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
new file mode 100644
index 0000000..b1e864b
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
@@ -0,0 +1,196 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple point in polygon query on a GeoPoint field. This is based on
+ * {@code GeoPointInBBoxQueryImpl} and is implemented using a
+ * three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
+ * candidate terms are queried using a numeric range based on the morton codes
+ * of the min and max lat/lon pairs. Terms passing this initial filter are passed
+ * to a secondary filter that verifies whether the decoded lat/lon point falls within
+ * (or on the boundary) of the bounding box query. Finally, the remaining candidate
+ * term is passed to the final point in polygon check. All value comparisons are subject
+ * to the same precision tolerance defined in {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE}
+ *
+ * <p>NOTES:
+ *    1.  The polygon coordinates need to be in either clockwise or counter-clockwise order.
+ *    2.  The polygon must not be self-crossing, otherwise the query may result in unexpected behavior
+ *    3.  All latitude/longitude values must be in decimal degrees.
+ *    4.  Complex computational geometry (e.g., dateline wrapping, polygon with holes) is not supported
+ *    5.  For more advanced GeoSpatial indexing and query operations see spatial module
+ *
+ * @lucene.experimental
+ */
+public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
+  // polygon position arrays - this avoids the use of any objects or
+  // or geo library dependencies
+  private final double[] x;
+  private final double[] y;
+
+  /**
+   * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.spatial.document.GeoPointField} terms
+   * that fall within or on the boundary of the polygon defined by the input parameters.
+   */
+  public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
+    this(field, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
+  }
+
+  /** Common constructor, used only internally. */
+  private GeoPointInPolygonQuery(final String field, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
+    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+    if (polyLats.length != polyLons.length) {
+      throw new IllegalArgumentException("polyLats and polyLons must be equal length");
+    }
+    if (polyLats.length < 4) {
+      throw new IllegalArgumentException("at least 4 polygon points required");
+    }
+    if (polyLats[0] != polyLats[polyLats.length-1]) {
+      throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLats[0]=" + polyLats[0] + " polyLats[" + (polyLats.length-1) + "]=" + polyLats[polyLats.length-1]);
+    }
+    if (polyLons[0] != polyLons[polyLons.length-1]) {
+      throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLons[0]=" + polyLons[0] + " polyLons[" + (polyLons.length-1) + "]=" + polyLons[polyLons.length-1]);
+    }
+
+    this.x = polyLons;
+    this.y = polyLats;
+  }
+
+  @Override @SuppressWarnings("unchecked")
+  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+    return new GeoPolygonTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
+  }
+
+  /** throw exception if trying to change rewrite method */
+  @Override
+  public void setRewriteMethod(RewriteMethod method) {
+    throw new UnsupportedOperationException("cannot change rewrite method");
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointInPolygonQuery that = (GeoPointInPolygonQuery) o;
+
+    if (!Arrays.equals(x, that.x)) return false;
+    if (!Arrays.equals(y, that.y)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + (x != null ? Arrays.hashCode(x) : 0);
+    result = 31 * result + (y != null ? Arrays.hashCode(y) : 0);
+    return result;
+  }
+
+  /** print out this polygon query */
+  @Override
+  public String toString(String field) {
+    assert x.length == y.length;
+
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (!getField().equals(field)) {
+      sb.append(" field=");
+      sb.append(getField());
+      sb.append(':');
+    }
+    sb.append(" Points: ");
+    for (int i=0; i<x.length; ++i) {
+      sb.append("[")
+        .append(x[i])
+        .append(", ")
+        .append(y[i])
+        .append("] ");
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
+   * the provided polygon.
+   */
+  private final class GeoPolygonTermsEnum extends GeoPointTermsEnum {
+    GeoPolygonTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+                        final double maxLon, final double maxLat) {
+      super(tenum, minLon, minLat, maxLon, maxLat);
+    }
+
+    @Override
+    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+    }
+
+    @Override
+    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+    }
+
+    @Override
+    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
+          || cellCrosses(minLon, minLat, maxLon, maxLat);
+    }
+
+    /**
+     * The two-phase query approach. The parent
+     * {@link GeoPointTermsEnum#accept} method is called to match
+     * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
+     * bounding box filter are then compared to the provided polygon using the
+     * {@link org.apache.lucene.spatial.util.GeoRelationUtils#pointInPolygon} method.
+     */
+    @Override
+    protected boolean postFilter(final double lon, final double lat) {
+      return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
+    }
+  }
+
+  /**
+   * API utility method for returning the array of longitudinal values for this GeoPolygon
+   * The returned array is not a copy so do not change it!
+   */
+  public double[] getLons() {
+    return this.x;
+  }
+
+  /**
+   * API utility method for returning the array of latitudinal values for this GeoPolygon
+   * The returned array is not a copy so do not change it!
+   */
+  public double[] getLats() {
+    return this.y;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
new file mode 100644
index 0000000..894a1e9
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
@@ -0,0 +1,114 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.AttributeSource;
+
+/**
+ * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
+ * Geospatial operations
+ *
+ * @lucene.experimental
+ */
+abstract class GeoPointTermQuery extends MultiTermQuery {
+  // simple bounding box optimization - no objects used to avoid dependencies
+  /** minimum longitude value (in degrees) */
+  protected final double minLon;
+  /** minimum latitude value (in degrees) */
+  protected final double minLat;
+  /** maximum longitude value (in degrees) */
+  protected final double maxLon;
+  /** maximum latitude value (in degrees) */
+  protected final double maxLat;
+
+  /**
+   * Constructs a query matching terms that cannot be represented with a single
+   * Term.
+   */
+  public GeoPointTermQuery(String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    super(field);
+
+    if (GeoUtils.isValidLon(minLon) == false) {
+      throw new IllegalArgumentException("invalid minLon " + minLon);
+    }
+    if (GeoUtils.isValidLon(maxLon) == false) {
+      throw new IllegalArgumentException("invalid maxLon " + maxLon);
+    }
+    if (GeoUtils.isValidLat(minLat) == false) {
+      throw new IllegalArgumentException("invalid minLat " + minLat);
+    }
+    if (GeoUtils.isValidLat(maxLat) == false) {
+      throw new IllegalArgumentException("invalid maxLat " + maxLat);
+    }
+    this.minLon = minLon;
+    this.minLat = minLat;
+    this.maxLon = maxLon;
+    this.maxLat = maxLat;
+
+    this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
+  }
+
+  private static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
+    @Override
+    public Query rewrite(IndexReader reader, MultiTermQuery query) {
+      return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointTermQuery)query);
+    }
+  };
+
+  /** override package protected method */
+  @Override
+  protected abstract TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException;
+
+  /** check if this instance equals another instance */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointTermQuery that = (GeoPointTermQuery) o;
+
+    if (Double.compare(that.minLon, minLon) != 0) return false;
+    if (Double.compare(that.minLat, minLat) != 0) return false;
+    if (Double.compare(that.maxLon, maxLon) != 0) return false;
+    return Double.compare(that.maxLat, maxLat) == 0;
+  }
+
+  /** compute hashcode */
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp;
+    temp = Double.doubleToLongBits(minLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(minLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
new file mode 100644
index 0000000..8176aec
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
@@ -0,0 +1,138 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.search.BulkScorer;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.DocIdSetBuilder;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/**
+ * Custom ConstantScoreWrapper for {@code GeoPointTermQuery} that cuts over to DocValues
+ * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
+ *
+ * @lucene.experimental
+ */
+final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery> extends Query {
+  protected final Q query;
+
+  protected GeoPointTermQueryConstantScoreWrapper(Q query) {
+    this.query = query;
+  }
+
+  @Override
+  public String toString(String field) {
+    return query.toString();
+  }
+
+  @Override
+  public final boolean equals(final Object o) {
+    if (super.equals(o) == false) {
+      return false;
+    }
+    final GeoPointTermQueryConstantScoreWrapper<?> that = (GeoPointTermQueryConstantScoreWrapper<?>) o;
+    return this.query.equals(that.query);
+  }
+
+  @Override
+  public final int hashCode() {
+    return 31 * super.hashCode() + query.hashCode();
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
+    return new ConstantScoreWeight(this) {
+
+      private DocIdSet getDocIDs(LeafReaderContext context) throws IOException {
+        final Terms terms = context.reader().terms(query.getField());
+        if (terms == null) {
+          return DocIdSet.EMPTY;
+        }
+
+        final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms, null));
+        assert termsEnum != null;
+
+        LeafReader reader = context.reader();
+        DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc());
+        PostingsEnum docs = null;
+        SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
+
+        while (termsEnum.next() != null) {
+          docs = termsEnum.postings(docs, PostingsEnum.NONE);
+          // boundary terms need post filtering by
+          if (termsEnum.boundaryTerm()) {
+            int docId = docs.nextDoc();
+            long hash;
+            do {
+              sdv.setDocument(docId);
+              for (int i=0; i<sdv.count(); ++i) {
+                hash = sdv.valueAt(i);
+                if (termsEnum.postFilter(GeoUtils.mortonUnhashLon(hash), GeoUtils.mortonUnhashLat(hash))) {
+                  builder.add(docId);
+                  break;
+                }
+              }
+            } while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS);
+          } else {
+            builder.add(docs);
+          }
+        }
+
+        return builder.build();
+      }
+
+      private Scorer scorer(DocIdSet set) throws IOException {
+        if (set == null) {
+          return null;
+        }
+        final DocIdSetIterator disi = set.iterator();
+        if (disi == null) {
+          return null;
+        }
+        return new ConstantScoreScorer(this, score(), disi);
+      }
+
+      @Override
+      public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
+        final Scorer scorer = scorer(getDocIDs(context));
+        if (scorer == null) {
+          return null;
+        }
+        return new DefaultBulkScorer(scorer);
+      }
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        return scorer(getDocIDs(context));
+      }
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
new file mode 100644
index 0000000..71eb26e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
@@ -0,0 +1,249 @@
+/*
+ * 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.search;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.index.FilteredTermsEnum;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.LegacyNumericUtils;
+
+/**
+ * computes all ranges along a space-filling curve that represents
+ * the given bounding box and enumerates all terms contained within those ranges
+ *
+ *  @lucene.experimental
+ */
+abstract class GeoPointTermsEnum extends FilteredTermsEnum {
+  protected final double minLon;
+  protected final double minLat;
+  protected final double maxLon;
+  protected final double maxLat;
+
+  protected Range currentRange;
+  private final BytesRefBuilder currentCell = new BytesRefBuilder();
+  private final BytesRefBuilder nextSubRange = new BytesRefBuilder();
+
+  private final List<Range> rangeBounds = new LinkedList<>();
+
+  // detail level should be a factor of PRECISION_STEP limiting the depth of recursion (and number of ranges)
+  protected final short DETAIL_LEVEL;
+
+  GeoPointTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+                    final double maxLon, final double maxLat) {
+    super(tenum);
+    final long rectMinHash = GeoUtils.mortonHash(minLon, minLat);
+    final long rectMaxHash = GeoUtils.mortonHash(maxLon, maxLat);
+    this.minLon = GeoUtils.mortonUnhashLon(rectMinHash);
+    this.minLat = GeoUtils.mortonUnhashLat(rectMinHash);
+    this.maxLon = GeoUtils.mortonUnhashLon(rectMaxHash);
+    this.maxLat = GeoUtils.mortonUnhashLat(rectMaxHash);
+    DETAIL_LEVEL = (short)(((GeoUtils.BITS<<1)-computeMaxShift())/2);
+
+    computeRange(0L, (short) ((GeoUtils.BITS << 1) - 1));
+    assert rangeBounds.isEmpty() == false;
+    Collections.sort(rangeBounds);
+  }
+
+  /**
+   * entry point for recursively computing ranges
+   */
+  private final void computeRange(long term, final short shift) {
+    final long split = term | (0x1L<<shift);
+    assert shift < 64;
+    final long upperMax;
+    if (shift < 63) {
+      upperMax = term | ((1L << (shift+1))-1);
+    } else {
+      upperMax = 0xffffffffffffffffL;
+    }
+    final long lowerMax = split-1;
+
+    relateAndRecurse(term, lowerMax, shift);
+    relateAndRecurse(split, upperMax, shift);
+  }
+
+  /**
+   * recurse to higher level precision cells to find ranges along the space-filling curve that fall within the
+   * query box
+   *
+   * @param start starting value on the space-filling curve for a cell at a given res
+   * @param end ending value on the space-filling curve for a cell at a given res
+   * @param res spatial res represented as a bit shift (MSB is lower res)
+   */
+  private void relateAndRecurse(final long start, final long end, final short res) {
+    final double minLon = GeoUtils.mortonUnhashLon(start);
+    final double minLat = GeoUtils.mortonUnhashLat(start);
+    final double maxLon = GeoUtils.mortonUnhashLon(end);
+    final double maxLat = GeoUtils.mortonUnhashLat(end);
+
+    final short level = (short)((GeoUtils.BITS<<1)-res>>>1);
+
+    // if cell is within and a factor of the precision step, or it crosses the edge of the shape add the range
+    final boolean within = res % GeoPointField.PRECISION_STEP == 0 && cellWithin(minLon, minLat, maxLon, maxLat);
+    if (within || (level == DETAIL_LEVEL && cellIntersectsShape(minLon, minLat, maxLon, maxLat))) {
+      final short nextRes = (short)(res-1);
+      if (nextRes % GeoPointField.PRECISION_STEP == 0) {
+        rangeBounds.add(new Range(start, nextRes, !within));
+        rangeBounds.add(new Range(start|(1L<<nextRes), nextRes, !within));
+      } else {
+        rangeBounds.add(new Range(start, res, !within));
+      }
+    } else if (level < DETAIL_LEVEL && cellIntersectsMBR(minLon, minLat, maxLon, maxLat)) {
+      computeRange(start, (short) (res - 1));
+    }
+  }
+
+  protected short computeMaxShift() {
+    // in this case a factor of 4 brings the detail level to ~0.002/0.001 degrees lon/lat respectively (or ~222m/111m)
+    return GeoPointField.PRECISION_STEP * 4;
+  }
+
+  /**
+   * Determine whether the quad-cell crosses the shape
+   */
+  protected abstract boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+  /**
+   * Determine whether quad-cell is within the shape
+   */
+  protected abstract boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+  /**
+   * Default shape is a rectangle, so this returns the same as {@code cellIntersectsMBR}
+   */
+  protected abstract boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+  /**
+   * Primary driver for cells intersecting shape boundaries
+   */
+  protected boolean cellIntersectsMBR(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    return GeoRelationUtils.rectIntersects(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+  }
+
+  /**
+   * Return whether quad-cell contains the bounding box of this shape
+   */
+  protected boolean cellContains(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    return GeoRelationUtils.rectWithin(this.minLon, this.minLat, this.maxLon, this.maxLat, minLon, minLat, maxLon, maxLat);
+  }
+
+  public boolean boundaryTerm() {
+    if (currentRange == null) {
+      throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
+    }
+    return currentRange.boundary;
+  }
+
+  private void nextRange() {
+    currentRange = rangeBounds.remove(0);
+    currentRange.fillBytesRef(currentCell);
+  }
+
+  @Override
+  protected final BytesRef nextSeekTerm(BytesRef term) {
+    while (!rangeBounds.isEmpty()) {
+      if (currentRange == null) {
+        nextRange();
+      }
+
+      // if the new upper bound is before the term parameter, the sub-range is never a hit
+      if (term != null && term.compareTo(currentCell.get()) > 0) {
+        nextRange();
+        if (!rangeBounds.isEmpty()) {
+          continue;
+        }
+      }
+      // never seek backwards, so use current term if lower bound is smaller
+      return (term != null && term.compareTo(currentCell.get()) > 0) ?
+          term : currentCell.get();
+    }
+
+    // no more sub-range enums available
+    assert rangeBounds.isEmpty();
+    return null;
+  }
+
+  /**
+   * The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
+   * range of the bounding box. Those terms that pass the initial range filter are then compared against the
+   * decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
+   * (e.g., a range that straddles the boundary of the bbox).
+   * @param term term for candidate document
+   * @return match status
+   */
+  @Override
+  protected AcceptStatus accept(BytesRef term) {
+    // validate value is in range
+    while (currentCell == null || term.compareTo(currentCell.get()) > 0) {
+      if (rangeBounds.isEmpty()) {
+        return AcceptStatus.END;
+      }
+      // peek next sub-range, only seek if the current term is smaller than next lower bound
+      rangeBounds.get(0).fillBytesRef(this.nextSubRange);
+      if (term.compareTo(this.nextSubRange.get()) < 0) {
+        return AcceptStatus.NO_AND_SEEK;
+      }
+      // step forward to next range without seeking, as next range is less or equal current term
+      nextRange();
+    }
+
+    return AcceptStatus.YES;
+  }
+
+  protected abstract boolean postFilter(final double lon, final double lat);
+
+  /**
+   * Internal class to represent a range along the space filling curve
+   */
+  protected final class Range implements Comparable<Range> {
+    final short shift;
+    final long start;
+    final boolean boundary;
+
+    Range(final long lower, final short shift, boolean boundary) {
+      this.boundary = boundary;
+      this.start = lower;
+      this.shift = shift;
+    }
+
+    /**
+     * Encode as a BytesRef using a reusable object. This allows us to lazily create the BytesRef (which is
+     * quite expensive), only when we need it.
+     */
+    private void fillBytesRef(BytesRefBuilder result) {
+      assert result != null;
+      LegacyNumericUtils.longToPrefixCoded(start, shift, result);
+    }
+
+    @Override
+    public int compareTo(Range other) {
+      final int result = Short.compare(this.shift, other.shift);
+      if (result == 0) {
+        return Long.compare(this.start, other.start);
+      }
+      return result;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
new file mode 100644
index 0000000..8e8265c
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Geospatial Query Implementations for Core Lucene
+ */
+package org.apache.lucene.spatial.search;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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
new file mode 100644
index 0000000..e845c9e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
@@ -0,0 +1,223 @@
+/*
+ * 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 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() {
+  }
+
+  /**
+   * Compute the great-circle distance using original haversine implementation published by Sinnot in:
+   * R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159
+   *
+   * NOTE: this differs from {@link org.apache.lucene.util.SloppyMath#haversin} in that it uses the semi-major axis
+   * of the earth instead of an approximation based on the average latitude of the two points (which can introduce an
+   * additional error up to .337%, or ~67.6 km at the equator)
+   */
+  public static double haversin(double lat1, double lon1, double lat2, double lon2) {
+    double dLat = TO_RADIANS * (lat2 - lat1);
+    double dLon = TO_RADIANS * (lon2 - lon1);
+    lat1 = TO_RADIANS * (lat1);
+    lat2 = TO_RADIANS * (lat2);
+
+    final double sinDLatO2 = SloppyMath.sin(dLat / 2);
+    final double sinDLonO2 = SloppyMath.sin(dLon / 2);
+
+    double a = sinDLatO2*sinDLatO2 + sinDLonO2 * sinDLonO2 * SloppyMath.cos(lat1) * SloppyMath.cos(lat2);
+    double c = 2 * SloppyMath.asin(Math.sqrt(a));
+    return (GeoProjectionUtils.SEMIMAJOR_AXIS * c);
+  }
+
+  /**
+   * Compute the distance between two geo-points using vincenty distance formula
+   * Vincenty uses the oblate spheroid whereas haversine uses unit sphere, this will give roughly
+   * 22m better accuracy (in worst case) than haversine
+   *
+   * @param lonA longitudinal coordinate of point A (in degrees)
+   * @param latA latitudinal coordinate of point A (in degrees)
+   * @param lonB longitudinal coordinate of point B (in degrees)
+   * @param latB latitudinal coordinate of point B (in degrees)
+   * @return distance (in meters) between point A and point B
+   */
+  public static final double vincentyDistance(final double lonA, final double latA, final double lonB, final double latB) {
+    final double L = StrictMath.toRadians(lonB - lonA);
+    final double oF = 1 - GeoProjectionUtils.FLATTENING;
+    final double U1 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latA)));
+    final double U2 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latB)));
+    final double sU1 = StrictMath.sin(U1);
+    final double cU1 = StrictMath.cos(U1);
+    final double sU2 = StrictMath.sin(U2);
+    final double cU2 = StrictMath.cos(U2);
+
+    double sigma, sinSigma, cosSigma;
+    double sinAlpha, cos2Alpha, cos2SigmaM;
+    double lambda = L;
+    double lambdaP;
+    double iters = 100;
+    double sinLambda, cosLambda, c;
+
+    do {
+      sinLambda = StrictMath.sin(lambda);
+      cosLambda = Math.cos(lambda);
+      sinSigma = Math.sqrt((cU2 * sinLambda) * (cU2 * sinLambda) + (cU1 * sU2 - sU1 * cU2 * cosLambda)
+          * (cU1 * sU2 - sU1 * cU2 * cosLambda));
+      if (sinSigma == 0) {
+        return 0;
+      }
+
+      cosSigma = sU1 * sU2 + cU1 * cU2 * cosLambda;
+      sigma = Math.atan2(sinSigma, cosSigma);
+      sinAlpha = cU1 * cU2 * sinLambda / sinSigma;
+      cos2Alpha = 1 - sinAlpha * sinAlpha;
+      cos2SigmaM = cosSigma - 2 * sU1 * sU2 / cos2Alpha;
+
+      c = GeoProjectionUtils.FLATTENING/16 * cos2Alpha * (4 + GeoProjectionUtils.FLATTENING * (4 - 3 * cos2Alpha));
+      lambdaP = lambda;
+      lambda = L + (1 - c) * GeoProjectionUtils.FLATTENING * sinAlpha * (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma *
+          (-1 + 2 * cos2SigmaM * cos2SigmaM)));
+    } while (StrictMath.abs(lambda - lambdaP) > 1E-12 && --iters > 0);
+
+    if (iters == 0) {
+      return 0;
+    }
+
+    final double uSq = cos2Alpha * (GeoProjectionUtils.SEMIMAJOR_AXIS2 - GeoProjectionUtils.SEMIMINOR_AXIS2) / (GeoProjectionUtils.SEMIMINOR_AXIS2);
+    final double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
+    final double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
+    final double deltaSigma = B * sinSigma * (cos2SigmaM + B/4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B/6 * cos2SigmaM
+        * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
+
+    return (GeoProjectionUtils.SEMIMINOR_AXIS * A * (sigma - deltaSigma));
+  }
+
+  /**
+   * Computes distance between two points in a cartesian (x, y, {z - optional}) coordinate system
+   */
+  public static double linearDistance(double[] pt1, double[] pt2) {
+    assert pt1 != null && pt2 != null && pt1.length == pt2.length && pt1.length > 1;
+    final double d0 = pt1[0] - pt2[0];
+    final double d1 = pt1[1] - pt2[1];
+    if (pt1.length == 3) {
+      final double d2 = pt1[2] - pt2[2];
+      return Math.sqrt(d0*d0 + d1*d1 + d2*d2);
+    }
+    return Math.sqrt(d0*d0 + d1*d1);
+  }
+
+  /**
+   * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
+   * @param lat latitude to compute delta degrees lon
+   * @param distance distance in meters to convert to degrees lon
+   * @return Sloppy distance in degrees longitude for provided distance in meters
+   */
+  public static double distanceToDegreesLon(double lat, double distance) {
+    distance /= 1000.0;
+    // convert latitude to radians
+    lat = StrictMath.toRadians(lat);
+
+    // get the diameter at the latitude
+    final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
+
+    // compute inverse haversine
+    double a = StrictMath.sin(distance/diameter);
+    double h = StrictMath.min(1, a);
+    h *= h;
+    double cLat = StrictMath.cos(lat);
+
+    return StrictMath.toDegrees(StrictMath.acos(1-((2d*h)/(cLat*cLat))));
+  }
+
+  /**
+   *  Finds the closest point within a rectangle (defined by rMinX, rMinY, rMaxX, rMaxY) to the given (lon, lat) point
+   *  the result is provided in closestPt.  When the point is outside the rectangle, the closest point is on an edge
+   *  or corner of the rectangle; else, the closest point is the point itself.
+   */
+  public static void closestPointOnBBox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+                                        final double lon, final double lat, double[] closestPt) {
+    assert closestPt != null && closestPt.length == 2;
+
+    closestPt[0] = 0;
+    closestPt[1] = 0;
+
+    boolean xSet = true;
+    boolean ySet = true;
+
+    if (lon > rMaxX) {
+      closestPt[0] = rMaxX;
+    } else if (lon < rMinX) {
+      closestPt[0] = rMinX;
+    } else {
+      xSet = false;
+    }
+
+    if (lat > rMaxY) {
+      closestPt[1] = rMaxY;
+    } else if (lat < rMinY) {
+      closestPt[1] = rMinY;
+    } else {
+      ySet = false;
+    }
+
+    if (closestPt[0] == 0 && xSet == false) {
+      closestPt[0] = lon;
+    }
+
+    if (closestPt[1] == 0 && ySet == false) {
+      closestPt[1] = lat;
+    }
+  }
+
+  /** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
+  public static double maxRadialDistanceMeters(final double centerLon, final double centerLat) {
+    if (Math.abs(centerLat) == GeoUtils.MAX_LAT_INCL) {
+      return GeoDistanceUtils.haversin(centerLat, centerLon, 0, centerLon);
+    }
+    return GeoDistanceUtils.haversin(centerLat, centerLon, centerLat, (GeoUtils.MAX_LON_INCL + centerLon) % 360);
+  }
+
+  /**
+   * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
+   * @param lat latitude to compute delta degrees lon
+   * @param distance distance in meters to convert to degrees lon
+   * @return Sloppy distance in degrees longitude for provided distance in meters
+   */
+  public static double distanceToDegreesLat(double lat, double distance) {
+    // get the diameter at the latitude
+    final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
+    distance /= 1000.0;
+
+    // compute inverse haversine
+    double a = StrictMath.sin(distance/diameter);
+    double h = StrictMath.min(1, a);
+    h *= h;
+
+    return StrictMath.toDegrees(StrictMath.acos(1-(2d*h)));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
new file mode 100644
index 0000000..9450c1e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
@@ -0,0 +1,283 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.lucene.util.BitUtil;
+
+/**
+ * Utilities for converting to/from the GeoHash standard
+ *
+ * The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits
+ * representing the level (1-12) [xyxy...xyxyllll]
+ *
+ * This differs from a morton encoded value which interleaves lat/lon (y/x).
+ *
+ * @lucene.experimental
+ */
+public class GeoHashUtils {
+  private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
+      '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
+      'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
+
+  private static final String BASE_32_STRING = new String(BASE_32);
+
+  /** maximum precision for geohash strings */
+  public static final int PRECISION = 12;
+  private static final short MORTON_OFFSET = (GeoUtils.BITS<<1) - (PRECISION*5);
+
+  // No instance:
+  private GeoHashUtils() {
+  }
+
+  /**
+   * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
+   */
+  public static final long longEncode(final double lon, final double lat, final int level) {
+    // shift to appropriate level
+    final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET);
+    return ((BitUtil.flipFlop(GeoUtils.mortonHash(lon, lat)) >>> msf) << 4) | level;
+  }
+
+  /**
+   * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
+   */
+  public static final long longEncode(final String hash) {
+    int level = hash.length()-1;
+    long b;
+    long l = 0L;
+    for(char c : hash.toCharArray()) {
+      b = (long)(BASE_32_STRING.indexOf(c));
+      l |= (b<<(level--*5));
+    }
+    return (l<<4)|hash.length();
+  }
+
+  /**
+   * Encode an existing geohash long to the provided precision
+   */
+  public static long longEncode(long geohash, int level) {
+    final short precision = (short)(geohash & 15);
+    if (precision == level) {
+      return geohash;
+    } else if (precision > level) {
+      return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level;
+    }
+    return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level);
+  }
+
+  /**
+   * Convert from a morton encoded long from a geohash encoded long
+   */
+  public static long fromMorton(long morton, int level) {
+    long mFlipped = BitUtil.flipFlop(morton);
+    mFlipped >>>= (((GeoHashUtils.PRECISION - level) * 5) + MORTON_OFFSET);
+    return (mFlipped << 4) | level;
+  }
+
+  /**
+   * Encode to a geohash string from the geohash based long format
+   */
+  public static final String stringEncode(long geoHashLong) {
+    int level = (int)geoHashLong&15;
+    geoHashLong >>>= 4;
+    char[] chars = new char[level];
+    do {
+      chars[--level] = BASE_32[(int) (geoHashLong&31L)];
+      geoHashLong>>>=5;
+    } while(level > 0);
+
+    return new String(chars);
+  }
+
+  /**
+   * Encode to a geohash string from full resolution longitude, latitude)
+   */
+  public static final String stringEncode(final double lon, final double lat) {
+    return stringEncode(lon, lat, 12);
+  }
+
+  /**
+   * Encode to a level specific geohash string from full resolution longitude, latitude
+   */
+  public static final String stringEncode(final double lon, final double lat, final int level) {
+    // convert to geohashlong
+    final long ghLong = fromMorton(GeoUtils.mortonHash(lon, lat), level);
+    return stringEncode(ghLong);
+
+  }
+
+  /**
+   * Encode to a full precision geohash string from a given morton encoded long value
+   */
+  public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception {
+    return stringEncode(hashedVal, PRECISION);
+  }
+
+  /**
+   * Encode to a geohash string at a given level from a morton long
+   */
+  public static final String stringEncodeFromMortonLong(long hashedVal, final int level) {
+    // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding)
+    hashedVal = BitUtil.flipFlop(hashedVal);
+
+    StringBuilder geoHash = new StringBuilder();
+    short precision = 0;
+    final short msf = (GeoUtils.BITS<<1)-5;
+    long mask = 31L<<msf;
+    do {
+      geoHash.append(BASE_32[(int)((mask & hashedVal)>>>(msf-(precision*5)))]);
+      // next 5 bits
+      mask >>>= 5;
+    } while (++precision < level);
+    return geoHash.toString();
+  }
+
+  /**
+   * Encode to a morton long value from a given geohash string
+   */
+  public static final long mortonEncode(final String hash) {
+    int level = 11;
+    long b;
+    long l = 0L;
+    for(char c : hash.toCharArray()) {
+      b = (long)(BASE_32_STRING.indexOf(c));
+      l |= (b<<((level--*5) + MORTON_OFFSET));
+    }
+    return BitUtil.flipFlop(l);
+  }
+
+  /**
+   * Encode to a morton long value from a given geohash long value
+   */
+  public static final long mortonEncode(final long geoHashLong) {
+    final int level = (int)(geoHashLong&15);
+    final short odd = (short)(level & 1);
+
+    return BitUtil.flipFlop(((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)));
+  }
+
+  private static final char encode(int x, int y) {
+    return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
+  }
+
+  /**
+   * Calculate all neighbors of a given geohash cell.
+   *
+   * @param geohash Geohash of the defined cell
+   * @return geohashes of all neighbor cells
+   */
+  public static Collection<? extends CharSequence> neighbors(String geohash) {
+    return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
+  }
+
+  /**
+   * Calculate the geohash of a neighbor of a geohash
+   *
+   * @param geohash the geohash of a cell
+   * @param level   level of the geohash
+   * @param dx      delta of the first grid coordinate (must be -1, 0 or +1)
+   * @param dy      delta of the second grid coordinate (must be -1, 0 or +1)
+   * @return geohash of the defined cell
+   */
+  public final static String neighbor(String geohash, int level, int dx, int dy) {
+    int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1));
+
+    // Decoding the Geohash bit pattern to determine grid coordinates
+    int x0 = cell & 1;  // first bit of x
+    int y0 = cell & 2;  // first bit of y
+    int x1 = cell & 4;  // second bit of x
+    int y1 = cell & 8;  // second bit of y
+    int x2 = cell & 16; // third bit of x
+
+    // combine the bitpattern to grid coordinates.
+    // note that the semantics of x and y are swapping
+    // on each level
+    int x = x0 + (x1 / 2) + (x2 / 4);
+    int y = (y0 / 2) + (y1 / 4);
+
+    if (level == 1) {
+      // Root cells at north (namely "bcfguvyz") or at
+      // south (namely "0145hjnp") do not have neighbors
+      // in north/south direction
+      if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
+        return null;
+      } else {
+        return Character.toString(encode(x + dx, y + dy));
+      }
+    } else {
+      // define grid coordinates for next level
+      final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy);
+      final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx);
+
+      // if the defined neighbor has the same parent a the current cell
+      // encode the cell directly. Otherwise find the cell next to this
+      // cell recursively. Since encoding wraps around within a cell
+      // it can be encoded here.
+      // xLimit and YLimit must always be respectively 7 and 3
+      // since x and y semantics are swapping on each level.
+      if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) {
+        return geohash.substring(0, level - 1) + encode(nx, ny);
+      } else {
+        String neighbor = neighbor(geohash, level - 1, dx, dy);
+        return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor;
+      }
+    }
+  }
+
+  /**
+   * Add all geohashes of the cells next to a given geohash to a list.
+   *
+   * @param geohash   Geohash of a specified cell
+   * @param neighbors list to add the neighbors to
+   * @return the given list
+   */
+  public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
+    return addNeighbors(geohash, geohash.length(), neighbors);
+  }
+
+  /**
+   * Add all geohashes of the cells next to a given geohash to a list.
+   *
+   * @param geohash   Geohash of a specified cell
+   * @param length    level of the given geohash
+   * @param neighbors list to add the neighbors to
+   * @return the given list
+   */
+  public static final <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
+    String south = neighbor(geohash, length, 0, -1);
+    String north = neighbor(geohash, length, 0, +1);
+    if (north != null) {
+      neighbors.add(neighbor(north, length, -1, 0));
+      neighbors.add(north);
+      neighbors.add(neighbor(north, length, +1, 0));
+    }
+
+    neighbors.add(neighbor(geohash, length, -1, 0));
+    neighbors.add(neighbor(geohash, length, +1, 0));
+
+    if (south != null) {
+      neighbors.add(neighbor(south, length, -1, 0));
+      neighbors.add(south);
+      neighbors.add(neighbor(south, length, +1, 0));
+    }
+
+    return neighbors;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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
new file mode 100644
index 0000000..5a81adc
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
@@ -0,0 +1,465 @@
+/*
+ * 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.sin;
+import static org.apache.lucene.util.SloppyMath.tan;
+import static org.apache.lucene.util.SloppyMath.PIO2;
+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.normalizeLat;
+import static org.apache.lucene.spatial.util.GeoUtils.normalizeLon;
+
+/**
+ * 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 geocentric earth-centered earth-fixed to geodesic lat/lon/alt
+   * @param x Cartesian x coordinate
+   * @param y Cartesian y coordinate
+   * @param z Cartesian z coordinate
+   * @param lla 0: longitude 1: latitude: 2: altitude
+   * @return double array as 0: longitude 1: latitude 2: altitude
+   */
+  public static final double[] ecfToLLA(final double x, final double y, final double z, double[] lla) {
+    boolean atPole = false;
+    final double ad_c = 1.0026000D;
+    final double cos67P5 = 0.38268343236508977D;
+
+    if (lla == null) {
+      lla = new double[3];
+    }
+
+    if (x != 0.0) {
+      lla[0] = StrictMath.atan2(y,x);
+    } else {
+      if (y > 0) {
+        lla[0] = PIO2;
+      } else if (y < 0) {
+        lla[0] = -PIO2;
+      } else {
+        atPole = true;
+        lla[0] = 0.0D;
+        if (z > 0.0) {
+          lla[1] = PIO2;
+        } else if (z < 0.0) {
+          lla[1] = -PIO2;
+        } else {
+          lla[1] = PIO2;
+          lla[2] = -SEMIMINOR_AXIS;
+          return lla;
+        }
+      }
+    }
+
+    final double w2 = x*x + y*y;
+    final double w = StrictMath.sqrt(w2);
+    final double t0 = z * ad_c;
+    final double s0 = StrictMath.sqrt(t0 * t0 + w2);
+    final double sinB0 = t0 / s0;
+    final double cosB0 = w / s0;
+    final double sin3B0 = sinB0 * sinB0 * sinB0;
+    final double t1 = z + SEMIMINOR_AXIS * EP2 * sin3B0;
+    final double sum = w - SEMIMAJOR_AXIS * E2 * cosB0 * cosB0 * cosB0;
+    final double s1 = StrictMath.sqrt(t1 * t1 + sum * sum);
+    final double sinP1 = t1 / s1;
+    final double cosP1 = sum / s1;
+    final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * sinP1 * sinP1);
+
+    if (cosP1 >= cos67P5) {
+      lla[2] = w / cosP1 - rn;
+    } else if (cosP1 <= -cos67P5) {
+      lla[2] = w / -cosP1 - rn;
+    } else {
+      lla[2] = z / sinP1 + rn * (E2 - 1.0);
+    }
+    if (!atPole) {
+      lla[1] = StrictMath.atan(sinP1/cosP1);
+    }
+    lla[0] = TO_DEGREES * lla[0];
+    lla[1] = TO_DEGREES * lla[1];
+
+    return lla;
+  }
+
+  /**
+   * Converts from geodesic lon lat alt to geocentric earth-centered earth-fixed
+   * @param lon geodesic longitude
+   * @param lat geodesic latitude
+   * @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 lon, double lat, double alt, double[] ecf) {
+    lon = TO_RADIANS * lon;
+    lat = TO_RADIANS * lat;
+
+    final double sl = sin(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 * sin(lon);
+    ecf[2] = ((rn*(1.0-E2))+alt)*sl;
+
+    return ecf;
+  }
+
+  /**
+   * Converts from lat lon alt (in degrees) to East North Up right-hand coordinate system
+   * @param lon longitude in degrees
+   * @param lat latitude in degrees
+   * @param alt altitude in meters
+   * @param centerLon reference point longitude in degrees
+   * @param centerLat reference point latitude in degrees
+   * @param centerAlt reference point altitude in meters
+   * @param enu result east, north, up coordinate
+   * @return east, north, up coordinate
+   */
+  public static double[] llaToENU(final double lon, final double lat, final double alt, double centerLon,
+                                  double centerLat, final double centerAlt, double[] enu) {
+    if (enu == null) {
+      enu = new double[3];
+    }
+
+    // convert point to ecf coordinates
+    final double[] ecf = llaToECF(lon, lat, alt, null);
+
+    // convert from ecf to enu
+    return ecfToENU(ecf[0], ecf[1], ecf[2], centerLon, centerLat, centerAlt, enu);
+  }
+
+  /**
+   * Converts from East North Up right-hand rule to lat lon alt in degrees
+   * @param x easting (in meters)
+   * @param y northing (in meters)
+   * @param z up (in meters)
+   * @param centerLon reference point longitude (in degrees)
+   * @param centerLat reference point latitude (in degrees)
+   * @param centerAlt reference point altitude (in meters)
+   * @param lla resulting lat, lon, alt point (in degrees)
+   * @return lat, lon, alt point (in degrees)
+   */
+  public static double[] enuToLLA(final double x, final double y, final double z, final double centerLon,
+                                  final double centerLat, final double centerAlt, double[] lla) {
+    // convert enuToECF
+    if (lla == null) {
+      lla = new double[3];
+    }
+
+    // convert enuToECF, storing intermediate result in lla
+    lla = enuToECF(x, y, z, centerLon, centerLat, centerAlt, lla);
+
+    // convert ecf to LLA
+    return ecfToLLA(lla[0], lla[1], lla[2], lla);
+  }
+
+  /**
+   * Convert from Earth-Centered-Fixed to Easting, Northing, Up Right Hand System
+   * @param x ECF X coordinate (in meters)
+   * @param y ECF Y coordinate (in meters)
+   * @param z ECF Z coordinate (in meters)
+   * @param centerLon ENU origin longitude (in degrees)
+   * @param centerLat ENU origin latitude (in degrees)
+   * @param centerAlt ENU altitude (in meters)
+   * @param enu reusable enu result
+   * @return Easting, Northing, Up coordinate
+   */
+  public static double[] ecfToENU(double x, double y, double z, final double centerLon,
+                                  final double centerLat, final double centerAlt, double[] enu) {
+    if (enu == null) {
+      enu = new double[3];
+    }
+
+    // create rotation matrix and rotate to enu orientation
+    final double[][] phi = createPhiTransform(centerLon, centerLat, null);
+
+    // convert origin to ENU
+    final double[] originECF = llaToECF(centerLon, centerLat, centerAlt, null);
+    final double[] originENU = new double[3];
+    originENU[0] = ((phi[0][0] * originECF[0]) + (phi[0][1] * originECF[1]) + (phi[0][2] * originECF[2]));
+    originENU[1] = ((phi[1][0] * originECF[0]) + (phi[1][1] * originECF[1]) + (phi[1][2] * originECF[2]));
+    originENU[2] = ((phi[2][0] * originECF[0]) + (phi[2][1] * originECF[1]) + (phi[2][2] * originECF[2]));
+
+    // rotate then translate
+    enu[0] = ((phi[0][0] * x) + (phi[0][1] * y) + (phi[0][2] * z)) - originENU[0];
+    enu[1] = ((phi[1][0] * x) + (phi[1][1] * y) + (phi[1][2] * z)) - originENU[1];
+    enu[2] = ((phi[2][0] * x) + (phi[2][1] * y) + (phi[2][2] * z)) - originENU[2];
+
+    return enu;
+  }
+
+  /**
+   * Convert from Easting, Northing, Up Right-Handed system to Earth Centered Fixed system
+   * @param x ENU x coordinate (in meters)
+   * @param y ENU y coordinate (in meters)
+   * @param z ENU z coordinate (in meters)
+   * @param centerLon ENU origin longitude (in degrees)
+   * @param centerLat ENU origin latitude (in degrees)
+   * @param centerAlt ENU origin altitude (in meters)
+   * @param ecf reusable ecf result
+   * @return ecf result coordinate
+   */
+  public static double[] enuToECF(final double x, final double y, final double z, double centerLon,
+                                  double centerLat, final double centerAlt, double[] ecf) {
+    if (ecf == null) {
+      ecf = new double[3];
+    }
+
+    double[][] phi = createTransposedPhiTransform(centerLon, centerLat, null);
+    double[] ecfOrigin = llaToECF(centerLon, centerLat, centerAlt, null);
+
+    // rotate and translate
+    ecf[0] = (phi[0][0]*x + phi[0][1]*y + phi[0][2]*z) + ecfOrigin[0];
+    ecf[1] = (phi[1][0]*x + phi[1][1]*y + phi[1][2]*z) + ecfOrigin[1];
+    ecf[2] = (phi[2][0]*x + phi[2][1]*y + phi[2][2]*z) + ecfOrigin[2];
+
+    return ecf;
+  }
+
+  /**
+   * Create the rotation matrix for converting Earth Centered Fixed to Easting Northing Up
+   * @param originLon ENU origin longitude (in degrees)
+   * @param originLat ENU origin latitude (in degrees)
+   * @param phiMatrix reusable phi matrix result
+   * @return phi rotation matrix
+   */
+  private static double[][] createPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
+
+    if (phiMatrix == null) {
+      phiMatrix = new double[3][3];
+    }
+
+    originLon = TO_RADIANS * originLon;
+    originLat = TO_RADIANS * originLat;
+
+    final double sLon = sin(originLon);
+    final double cLon = cos(originLon);
+    final double sLat = sin(originLat);
+    final double cLat = cos(originLat);
+
+    phiMatrix[0][0] = -sLon;
+    phiMatrix[0][1] = cLon;
+    phiMatrix[0][2] = 0.0D;
+    phiMatrix[1][0] = -sLat * cLon;
+    phiMatrix[1][1] = -sLat * sLon;
+    phiMatrix[1][2] = cLat;
+    phiMatrix[2][0] = cLat * cLon;
+    phiMatrix[2][1] = cLat * sLon;
+    phiMatrix[2][2] = sLat;
+
+    return phiMatrix;
+  }
+
+  /**
+   * Create the transposed rotation matrix for converting Easting Northing Up coordinates to Earth Centered Fixed
+   * @param originLon ENU origin longitude (in degrees)
+   * @param originLat ENU origin latitude (in degrees)
+   * @param phiMatrix reusable phi rotation matrix result
+   * @return transposed phi rotation matrix
+   */
+  private static double[][] createTransposedPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
+
+    if (phiMatrix == null) {
+      phiMatrix = new double[3][3];
+    }
+
+    originLon = TO_RADIANS * originLon;
+    originLat = TO_RADIANS * originLat;
+
+    final double sLat = sin(originLat);
+    final double cLat = cos(originLat);
+    final double sLon = sin(originLon);
+    final double cLon = cos(originLon);
+
+    phiMatrix[0][0] = -sLon;
+    phiMatrix[1][0] = cLon;
+    phiMatrix[2][0] = 0.0D;
+    phiMatrix[0][1] = -sLat * cLon;
+    phiMatrix[1][1] = -sLat * sLon;
+    phiMatrix[2][1] = cLat;
+    phiMatrix[0][2] = cLat * cLon;
+    phiMatrix[1][2] = cLat * sLon;
+    phiMatrix[2][2] = sLat;
+
+    return phiMatrix;
+  }
+
+  /**
+   * Finds a point along a bearing from a given lon,lat geolocation using vincenty's distance formula
+   *
+   * @param lon origin longitude in degrees
+   * @param lat origin latitude 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[] pointFromLonLatBearingVincenty(double lon, double lat, double bearing, double dist, double[] pt) {
+
+    if (pt == null) {
+      pt = new double[2];
+    }
+
+    final double alpha1 = TO_RADIANS * bearing;
+    final double cosA1 = cos(alpha1);
+    final double sinA1 = sin(alpha1);
+    final double tanU1 = (1-FLATTENING) * tan(TO_RADIANS * lat);
+    final double cosU1 = 1 / StrictMath.sqrt((1+tanU1*tanU1));
+    final double sinU1 = tanU1*cosU1;
+    final double sig1 = StrictMath.atan2(tanU1, cosA1);
+    final double sinAlpha = cosU1 * sinA1;
+    final double cosSqAlpha = 1 - sinAlpha*sinAlpha;
+    final double uSq = cosSqAlpha * EP2;
+    final double A = 1 + uSq/16384D*(4096D + uSq * (-768D + uSq * (320D - 175D*uSq)));
+    final double B = uSq/1024D * (256D + uSq * (-128D + uSq * (74D - 47D * uSq)));
+
+    double sigma = dist / (SEMIMINOR_AXIS*A);
+    double sigmaP;
+    double sinSigma, cosSigma, cos2SigmaM, deltaSigma;
+
+    do {
+      cos2SigmaM = cos(2*sig1 + sigma);
+      sinSigma = sin(sigma);
+      cosSigma = cos(sigma);
+
+      deltaSigma = B * sinSigma * (cos2SigmaM + (B/4D) * (cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+          (B/6) * cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+      sigmaP = sigma;
+      sigma = dist / (SEMIMINOR_AXIS*A) + deltaSigma;
+    } while (StrictMath.abs(sigma-sigmaP) > 1E-12);
+
+    final double tmp = sinU1*sinSigma - cosU1*cosSigma*cosA1;
+    final double lat2 = StrictMath.atan2(sinU1*cosSigma + cosU1*sinSigma*cosA1,
+        (1-FLATTENING) * StrictMath.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+    final double lambda = StrictMath.atan2(sinSigma*sinA1, cosU1*cosSigma - sinU1*sinSigma*cosA1);
+    final double c = FLATTENING/16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha));
+
+    final double lam = lambda - (1-c) * FLATTENING * sinAlpha *
+        (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2* cos2SigmaM*cos2SigmaM)));
+    pt[0] = normalizeLon(lon + TO_DEGREES * lam);
+    pt[1] = normalizeLat(TO_DEGREES * lat2);
+
+    return pt;
+  }
+
+  /**
+   * Finds a point along a bearing from a given lon,lat geolocation using great circle arc
+   *
+   * @param lon origin longitude in degrees
+   * @param lat origin latitude 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 lon, double lat, double bearing, double dist, double[] pt) {
+
+    if (pt == null) {
+      pt = new double[2];
+    }
+
+    lon *= TO_RADIANS;
+    lat *= TO_RADIANS;
+    bearing *= TO_RADIANS;
+
+    final double cLat = cos(lat);
+    final double sLat = sin(lat);
+    final double sinDoR = sin(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(sin(bearing) * sinDoR * cLat, cosDoR - sLat * sin(pt[1])));
+    pt[1] *= TO_DEGREES;
+
+    return pt;
+  }
+
+  /**
+   * Finds the bearing (in degrees) between 2 geo points (lon, lat) using great circle arc
+   * @param lon1 first point longitude in degrees
+   * @param lat1 first point latitude in degrees
+   * @param lon2 second point longitude in degrees
+   * @param lat2 second point latitude in degrees
+   * @return the bearing (in degrees) between the two provided points
+   */
+  public static double bearingGreatCircle(double lon1, double lat1, double lon2, double lat2) {
+    double dLon = (lon2 - lon1) * TO_RADIANS;
+    lat2 *= TO_RADIANS;
+    lat1 *= TO_RADIANS;
+    double y = sin(dLon) * cos(lat2);
+    double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
+    return Math.atan2(y, x) * TO_DEGREES;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
new file mode 100644
index 0000000..fa93e61
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+/** Represents a lat/lon rectangle. */
+public class GeoRect {
+  public final double minLon;
+  public final double maxLon;
+  public final double minLat;
+  public final double maxLat;
+
+  public GeoRect(double minLon, double maxLon, double minLat, double maxLat) {
+    if (GeoUtils.isValidLon(minLon) == false) {
+      throw new IllegalArgumentException("invalid minLon " + minLon);
+    }
+    if (GeoUtils.isValidLon(maxLon) == false) {
+      throw new IllegalArgumentException("invalid maxLon " + maxLon);
+    }
+    if (GeoUtils.isValidLat(minLat) == false) {
+      throw new IllegalArgumentException("invalid minLat " + minLat);
+    }
+    if (GeoUtils.isValidLat(maxLat) == false) {
+      throw new IllegalArgumentException("invalid maxLat " + maxLat);
+    }
+    this.minLon = minLon;
+    this.maxLon = maxLon;
+    this.minLat = minLat;
+    this.maxLat = maxLat;
+    assert maxLat >= minLat;
+
+    // NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append("GeoRect(lon=");
+    b.append(minLon);
+    b.append(" TO ");
+    b.append(maxLon);
+    if (maxLon < minLon) {
+      b.append(" (crosses dateline!)");
+    }
+    b.append(" lat=");
+    b.append(minLat);
+    b.append(" TO ");
+    b.append(maxLat);
+    b.append(")");
+
+    return b.toString();
+  }
+}


[4/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
deleted file mode 100644
index 5a469db..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
+++ /dev/null
@@ -1,764 +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.util;
-
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.NumericDocValuesField;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-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.Term;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SimpleCollector;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.MockDirectoryWrapper;
-import org.junit.BeforeClass;
-
-// TODO: cutover TestGeoUtils too?
-
-public abstract class BaseGeoPointTestCase extends LuceneTestCase {
-
-  protected static final String FIELD_NAME = "point";
-
-  private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
-  private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
-
-  private static double originLat;
-  private static double originLon;
-  private static double lonRange;
-  private static double latRange;
-
-  @BeforeClass
-  public static void beforeClassBase() throws Exception {
-    // Between 1.0 and 3.0:
-    lonRange = 2 * (random().nextDouble() + 0.5);
-    latRange = 2 * (random().nextDouble() + 0.5);
-
-    originLon = GeoUtils.normalizeLon(GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble());
-    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();
-
-    // Every doc has 2 points:
-    double theLat = randomLat(small);
-    double theLon = randomLon(small);
-
-    double[] lats = new double[numPoints];
-    Arrays.fill(lats, theLat);
-
-    double[] lons = new double[numPoints];
-    Arrays.fill(lons, theLon);
-
-    verify(small, lats, lons);
-  }
-
-  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();
-    double lat = randomLat(small);
-    double[] lats = new double[numPoints];
-    double[] lons = new double[numPoints];
-
-    boolean haveRealDoc = false;
-
-    for(int docID=0;docID<numPoints;docID++) {
-      int x = random().nextInt(20);
-      if (x == 17) {
-        // Some docs don't have a point:
-        lats[docID] = Double.NaN;
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " is missing");
-        }
-        continue;
-      }
-
-      if (docID > 0 && x == 14 && haveRealDoc) {
-        int oldDocID;
-        while (true) {
-          oldDocID = random().nextInt(docID);
-          if (Double.isNaN(lats[oldDocID]) == false) {
-            break;
-          }
-        }
-            
-        // Fully identical point:
-        lons[docID] = lons[oldDocID];
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " lat=" + lat + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
-        }
-      } else {
-        lons[docID] = randomLon(small);
-        haveRealDoc = true;
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " lat=" + lat + " lon=" + lons[docID]);
-        }
-      }
-      lats[docID] = lat;
-    }
-
-    verify(small, lats, lons);
-  }
-
-  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();
-    double theLon = randomLon(small);
-    double[] lats = new double[numPoints];
-    double[] lons = new double[numPoints];
-
-    boolean haveRealDoc = false;
-
-    //System.out.println("theLon=" + theLon);
-
-    for(int docID=0;docID<numPoints;docID++) {
-      int x = random().nextInt(20);
-      if (x == 17) {
-        // Some docs don't have a point:
-        lats[docID] = Double.NaN;
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " is missing");
-        }
-        continue;
-      }
-
-      if (docID > 0 && x == 14 && haveRealDoc) {
-        int oldDocID;
-        while (true) {
-          oldDocID = random().nextInt(docID);
-          if (Double.isNaN(lats[oldDocID]) == false) {
-            break;
-          }
-        }
-            
-        // Fully identical point:
-        lats[docID] = lats[oldDocID];
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon + " (same lat/lon as doc=" + oldDocID + ")");
-        }
-      } else {
-        lats[docID] = randomLat(small);
-        haveRealDoc = true;
-        if (VERBOSE) {
-          System.out.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon);
-        }
-      }
-      lons[docID] = theLon;
-    }
-
-    verify(small, lats, lons);
-  }
-
-  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];
-    double[] lons = new double[2*numPoints];
-    Directory dir = newDirectory();
-    noVirusChecker(dir);
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    initIndexWriterConfig(FIELD_NAME, iwc);
-
-    // We rely on docID order:
-    iwc.setMergePolicy(newLogMergePolicy());
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
-
-    boolean small = random().nextBoolean();
-
-    for (int id=0;id<numPoints;id++) {
-      Document doc = new Document();
-      lats[2*id] = randomLat(small);
-      lons[2*id] = randomLon(small);
-      doc.add(newStringField("id", ""+id, Field.Store.YES));
-      addPointToDoc(FIELD_NAME, doc, lats[2*id], lons[2*id]);
-      lats[2*id+1] = randomLat(small);
-      lons[2*id+1] = randomLon(small);
-      addPointToDoc(FIELD_NAME, doc, lats[2*id+1], lons[2*id+1]);
-
-      if (VERBOSE) {
-        System.out.println("id=" + id);
-        System.out.println("  lat=" + lats[2*id] + " lon=" + lons[2*id]);
-        System.out.println("  lat=" + lats[2*id+1] + " lon=" + lons[2*id+1]);
-      }
-      w.addDocument(doc);
-    }
-
-    // TODO: share w/ verify; just need parallel array of the expected ids
-    if (random().nextBoolean()) {
-      w.forceMerge(1);
-    }
-    IndexReader r = w.getReader();
-    w.close();
-
-    // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
-    IndexSearcher s = newSearcher(r, false);
-
-    int iters = atLeast(75);
-    for (int iter=0;iter<iters;iter++) {
-      GeoRect rect = randomRect(small, small == false);
-
-      if (VERBOSE) {
-        System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
-      }
-
-      Query query = newRectQuery(FIELD_NAME, rect);
-
-      final FixedBitSet hits = new FixedBitSet(r.maxDoc());
-      s.search(query, new SimpleCollector() {
-
-          private int docBase;
-
-          @Override
-          public boolean needsScores() {
-            return false;
-          }
-
-          @Override
-          protected void doSetNextReader(LeafReaderContext context) throws IOException {
-            docBase = context.docBase;
-          }
-
-          @Override
-          public void collect(int doc) {
-            hits.set(docBase+doc);
-          }
-        });
-
-      boolean fail = false;
-
-      for(int docID=0;docID<lats.length/2;docID++) {
-        double latDoc1 = lats[2*docID];
-        double lonDoc1 = lons[2*docID];
-        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 expected = result1 == Boolean.TRUE || result2 == Boolean.TRUE;
-
-        if (hits.get(docID) != expected) {
-          String id = s.doc(docID).get("id");
-          if (expected) {
-            System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should match but did not");
-          } else {
-            System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should not match but did");
-          }
-          System.out.println("  rect=" + rect);
-          System.out.println("  lat=" + latDoc1 + " lon=" + lonDoc1 + "\n  lat=" + latDoc2 + " lon=" + lonDoc2);
-          System.out.println("  result1=" + result1 + " result2=" + result2);
-          fail = true;
-        }
-      }
-
-      if (fail) {
-        fail("some hits were wrong");
-      }
-    }
-    r.close();
-    dir.close();
-  }
-
-  public void testRandomTiny() throws Exception {
-    // Make sure single-leaf-node case is OK:
-    doTestRandom(10);
-  }
-
-  public void testRandomMedium() throws Exception {
-    doTestRandom(10000);
-  }
-
-  @Nightly
-  public void testRandomBig() throws Exception {
-    assumeFalse("Direct codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Direct"));
-    assumeFalse("Memory codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Memory"));
-    doTestRandom(200000);
-  }
-
-  private void doTestRandom(int count) throws Exception {
-
-    int numPoints = atLeast(count);
-
-    if (VERBOSE) {
-      System.out.println("TEST: numPoints=" + numPoints);
-    }
-
-    double[] lats = new double[numPoints];
-    double[] lons = new double[numPoints];
-
-    boolean small = random().nextBoolean();
-
-    boolean haveRealDoc = false;
-
-    for (int id=0;id<numPoints;id++) {
-      int x = random().nextInt(20);
-      if (x == 17) {
-        // Some docs don't have a point:
-        lats[id] = Double.NaN;
-        if (VERBOSE) {
-          System.out.println("  id=" + id + " is missing");
-        }
-        continue;
-      }
-
-      if (id > 0 && x < 3 && haveRealDoc) {
-        int oldID;
-        while (true) {
-          oldID = random().nextInt(id);
-          if (Double.isNaN(lats[oldID]) == false) {
-            break;
-          }
-        }
-            
-        if (x == 0) {
-          // Identical lat to old point
-          lats[id] = lats[oldID];
-          lons[id] = randomLon(small);
-          if (VERBOSE) {
-            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat as doc=" + oldID + ")");
-          }
-        } else if (x == 1) {
-          // Identical lon to old point
-          lats[id] = randomLat(small);
-          lons[id] = lons[oldID];
-          if (VERBOSE) {
-            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lon as doc=" + oldID + ")");
-          }
-        } else {
-          assert x == 2;
-          // Fully identical point:
-          lats[id] = lats[oldID];
-          lons[id] = lons[oldID];
-          if (VERBOSE) {
-            System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat/lon as doc=" + oldID + ")");
-          }
-        }
-      } else {
-        lats[id] = randomLat(small);
-        lons[id] = randomLon(small);
-        haveRealDoc = true;
-        if (VERBOSE) {
-          System.out.println("  id=" + id + " lat=" + lats[id] + " lon=" + lons[id]);
-        }
-      }
-    }
-
-    verify(small, lats, lons);
-  }
-
-  public double randomLat(boolean small) {
-    double result;
-    if (small) {
-      result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
-    } else {
-      result = -90 + 180.0 * random().nextDouble();
-    }
-    return result;
-  }
-
-  public double randomLon(boolean small) {
-    double result;
-    if (small) {
-      result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
-    } else {
-      result = -180 + 360.0 * random().nextDouble();
-    }
-    return result;
-  }
-
-  protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
-    double lat0 = randomLat(small);
-    double lat1 = randomLat(small);
-    double lon0 = randomLon(small);
-    double lon1 = randomLon(small);
-
-    if (lat1 < lat0) {
-      double x = lat0;
-      lat0 = lat1;
-      lat1 = x;
-    }
-
-    if (canCrossDateLine == false && lon1 < lon0) {
-      double x = lon0;
-      lon0 = lon1;
-      lon1 = x;
-    }
-
-    return new GeoRect(lon0, lon1, lat0, lat1);
-  }
-
-  protected void initIndexWriterConfig(String field, IndexWriterConfig iwc) {
-  }
-
-  protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
-
-  protected abstract Query newRectQuery(String field, GeoRect bbox);
-
-  protected abstract Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters);
-
-  protected abstract Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters);
-
-  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);
-
-  /** Returns null if it's borderline case */
-  protected abstract Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon);
-
-  /** Returns null if it's borderline case */
-  protected abstract Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon);
-
-  protected abstract Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon);
-
-  private static abstract class VerifyHits {
-
-    public void test(AtomicBoolean failed, boolean small, IndexSearcher s, NumericDocValues docIDToID, Set<Integer> deleted, Query query, double[] lats, double[] lons) throws Exception {
-      int maxDoc = s.getIndexReader().maxDoc();
-      final FixedBitSet hits = new FixedBitSet(maxDoc);
-      s.search(query, new SimpleCollector() {
-
-          private int docBase;
-
-          @Override
-          public boolean needsScores() {
-            return false;
-          }
-
-          @Override
-          protected void doSetNextReader(LeafReaderContext context) throws IOException {
-            docBase = context.docBase;
-          }
-
-          @Override
-          public void collect(int doc) {
-            hits.set(docBase+doc);
-          }
-        });
-
-      boolean fail = false;
-
-      for(int docID=0;docID<maxDoc;docID++) {
-        int id = (int) docIDToID.get(docID);
-        Boolean expected;
-        if (deleted.contains(id)) {
-          expected = false;
-        } else if (Double.isNaN(lats[id])) {
-          expected = false;
-        } else {
-          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 (expected) {
-            System.out.println(Thread.currentThread().getName() + ": id=" + id + " should match but did not");
-          } else {
-            System.out.println(Thread.currentThread().getName() + ": id=" + id + " should not match but did");
-          }
-          System.out.println("  small=" + small + " query=" + query +
-                             " docID=" + docID + "\n  lat=" + lats[id] + " lon=" + lons[id] +
-                             "\n  deleted?=" + deleted.contains(id));
-          if (Double.isNaN(lats[id]) == false) {
-            describe(docID, lats[id], lons[id]);
-          }
-          fail = true;
-        }
-      }
-
-      if (fail) {
-        failed.set(true);
-        fail("some hits were wrong");
-      }
-    }
-
-    /** 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 void describe(int docID, double lat, double lon);
-  }
-
-  protected void verify(boolean small, double[] lats, double[] lons) throws Exception {
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    // Else we can get O(N^2) merging:
-    int mbd = iwc.getMaxBufferedDocs();
-    if (mbd != -1 && mbd < lats.length/100) {
-      iwc.setMaxBufferedDocs(lats.length/100);
-    }
-    Directory dir;
-    if (lats.length > 100000) {
-      dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
-    } else {
-      dir = newDirectory();
-    }
-    noVirusChecker(dir);
-
-    Set<Integer> deleted = new HashSet<>();
-    // RandomIndexWriter is too slow here:
-    IndexWriter w = new IndexWriter(dir, iwc);
-    for(int id=0;id<lats.length;id++) {
-      Document doc = new Document();
-      doc.add(newStringField("id", ""+id, Field.Store.NO));
-      doc.add(new NumericDocValuesField("id", id));
-      if (Double.isNaN(lats[id]) == false) {
-        addPointToDoc(FIELD_NAME, doc, lats[id], lons[id]);
-      }
-      w.addDocument(doc);
-      if (id > 0 && random().nextInt(100) == 42) {
-        int idToDelete = random().nextInt(id);
-        w.deleteDocuments(new Term("id", ""+idToDelete));
-        deleted.add(idToDelete);
-        if (VERBOSE) {
-          System.out.println("  delete id=" + idToDelete);
-        }
-      }
-    }
-
-    if (random().nextBoolean()) {
-      w.forceMerge(1);
-    }
-    final IndexReader r = DirectoryReader.open(w);
-    w.close();
-
-    // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
-    IndexSearcher s = newSearcher(r, false);
-
-    // Make sure queries are thread safe:
-    int numThreads = TestUtil.nextInt(random(), 2, 5);
-
-    List<Thread> threads = new ArrayList<>();
-    final int iters = atLeast(75);
-
-    final CountDownLatch startingGun = new CountDownLatch(1);
-    final AtomicBoolean failed = new AtomicBoolean();
-
-    for(int i=0;i<numThreads;i++) {
-      Thread thread = new Thread() {
-          @Override
-          public void run() {
-            try {
-              _run();
-            } catch (Exception e) {
-              failed.set(true);
-              throw new RuntimeException(e);
-            }
-          }
-
-          private void _run() throws Exception {
-            startingGun.await();
-
-            NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
-
-            for (int iter=0;iter<iters && failed.get() == false;iter++) {
-
-              if (VERBOSE) {
-                System.out.println("\nTEST: iter=" + iter + " s=" + s);
-              }
-              Query query;
-              VerifyHits verifyHits;
-
-              if (random().nextBoolean()) {
-                // Rect: don't allow dateline crossing when testing small:
-                final GeoRect rect = randomRect(small, small == false);
-
-                query = newRectQuery(FIELD_NAME, rect);
-
-                verifyHits = new VerifyHits() {
-                    @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
-                      return rectContainsPoint(rect, pointLat, pointLon);
-                    }
-                    @Override
-                    protected void describe(int docID, double lat, double lon) {
-                    }
-                  };
-
-              } else if (random().nextBoolean()) {
-                // Distance
-                final boolean rangeQuery = random().nextBoolean();
-                final double centerLat = randomLat(small);
-                final double centerLon = randomLon(small);
-
-                double radiusMeters;
-                double minRadiusMeters;
-
-                if (small) {
-                  // Approx 3 degrees lon at the equator:
-                  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;
-                }
-
-                // generate a random minimum radius between 1% and 95% the max radius
-                minRadiusMeters = (0.01 + 0.94 * random().nextDouble()) * radiusMeters;
-
-                if (VERBOSE) {
-                  final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
-                  System.out.println("  radiusMeters = " + df.format(radiusMeters)
-                      + ((rangeQuery == true) ? " minRadiusMeters = " + df.format(minRadiusMeters) : ""));
-                }
-
-                try {
-                  if (rangeQuery == true) {
-                    query = newDistanceRangeQuery(FIELD_NAME, centerLat, centerLon, minRadiusMeters, radiusMeters);
-                  } else {
-                    query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
-                  }
-                } catch (IllegalArgumentException e) {
-                  if (e.getMessage().contains("exceeds maxRadius")) {
-                    continue;
-                  }
-                  throw e;
-                }
-
-                verifyHits = new VerifyHits() {
-                    @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
-                      if (rangeQuery == false) {
-                        return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
-                      } else {
-                        return distanceRangeContainsPoint(centerLat, centerLon, minRadiusMeters, radiusMeters, pointLat, pointLon);
-                      }
-                    }
-
-                    @Override
-                    protected void describe(int docID, double pointLat, double pointLon) {
-                      double distanceKM = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon);
-                      System.out.println("  docID=" + docID + " centerLon=" + centerLon + " centerLat=" + centerLat
-                          + " pointLon=" + pointLon + " pointLat=" + pointLat + " distanceMeters=" + (distanceKM * 1000)
-                          + " vs" + ((rangeQuery == true) ? " minRadiusMeters=" + minRadiusMeters : "") + " radiusMeters=" + radiusMeters);
-                    }
-                   };
-
-              // TODO: get poly query working with dateline crossing too (how?)!
-              } else {
-
-                // TODO: poly query can't handle dateline crossing yet:
-                final GeoRect bbox = randomRect(small, false);
-
-                // Polygon
-                double[] lats = new double[5];
-                double[] lons = new double[5];
-                lats[0] = bbox.minLat;
-                lons[0] = bbox.minLon;
-                lats[1] = bbox.maxLat;
-                lons[1] = bbox.minLon;
-                lats[2] = bbox.maxLat;
-                lons[2] = bbox.maxLon;
-                lats[3] = bbox.minLat;
-                lons[3] = bbox.maxLon;
-                lats[4] = bbox.minLat;
-                lons[4] = bbox.minLon;
-                query = newPolygonQuery(FIELD_NAME, lats, lons);
-
-                verifyHits = new VerifyHits() {
-                    @Override
-                    protected Boolean shouldMatch(double pointLat, double pointLon) {
-                      return polyRectContainsPoint(bbox, pointLat, pointLon);
-                    }
-
-                    @Override
-                    protected void describe(int docID, double lat, double lon) {
-                    }
-                  };
-              }
-
-              if (query != null) {
-
-                if (VERBOSE) {
-                  System.out.println("  query=" + query);
-                }
-
-                verifyHits.test(failed, small, s, docIDToID, deleted, query, lats, lons);
-              }
-            }
-          }
-      };
-      thread.setName("T" + i);
-      thread.start();
-      threads.add(thread);
-    }
-    startingGun.countDown();
-    for(Thread thread : threads) {
-      thread.join();
-    }
-    IOUtils.close(r, dir);
-    assertFalse(failed.get());
-  }
-
-  protected Directory noVirusChecker(Directory dir) {
-    if (dir instanceof MockDirectoryWrapper) {
-      ((MockDirectoryWrapper) dir).setEnableVirusScanner(false);
-    }
-    return dir;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java b/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
deleted file mode 100644
index 5650614..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
+++ /dev/null
@@ -1,545 +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.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.junit.BeforeClass;
-
-import com.carrotsearch.randomizedtesting.generators.RandomInts;
-
-import static org.apache.lucene.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
-
-/**
- * Tests class for methods in GeoUtils
- *
- * @lucene.experimental
- */
-public class TestGeoUtils extends LuceneTestCase {
-
-  private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
-  private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
-
-  // Global bounding box we will "cover" in the random test; we have to make this "smallish" else the queries take very long:
-  private static double originLat;
-  private static double originLon;
-  //  private static double range;
-  private static double lonRange;
-  private static double latRange;
-
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    // Between 1.0 and 3.0:
-    lonRange = 2 * (random().nextDouble() + 0.5);
-    latRange = 2 * (random().nextDouble() + 0.5);
-
-    originLon = GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble();
-    originLon = GeoUtils.normalizeLon(originLon);
-    originLat = GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble();
-    originLat = GeoUtils.normalizeLat(originLat);
-
-    if (VERBOSE) {
-      System.out.println("TEST: originLon=" + originLon + " lonRange= " + lonRange + " originLat=" + originLat + " latRange=" + latRange);
-    }
-  }
-
-  public void testGeoHash() {
-    int numPoints = atLeast(100);
-    String randomGeoHashString;
-    String mortonGeoHash;
-    long mortonLongFromGHLong, geoHashLong, mortonLongFromGHString;
-    int randomLevel;
-    for (int i = 0; i < numPoints; ++i) {
-      // random point
-      double lat = randomLat(false);
-      double lon = randomLon(false);
-
-      // compute geohash straight from lat/lon and from morton encoded value to ensure they're the same
-      randomGeoHashString = GeoHashUtils.stringEncode(lon, lat, randomLevel = random().nextInt(12 - 1) + 1);
-      mortonGeoHash = GeoHashUtils.stringEncodeFromMortonLong(GeoUtils.mortonHash(lon, lat), randomLevel);
-      assertEquals(randomGeoHashString, mortonGeoHash);
-
-      // v&v conversion from lat/lon or geohashstring to geohash long and back to geohash string
-      geoHashLong = (random().nextBoolean()) ? GeoHashUtils.longEncode(lon, lat, randomLevel) : GeoHashUtils.longEncode(randomGeoHashString);
-      assertEquals(randomGeoHashString, GeoHashUtils.stringEncode(geoHashLong));
-
-      // v&v conversion from geohash long to morton long
-      mortonLongFromGHString = GeoHashUtils.mortonEncode(randomGeoHashString);
-      mortonLongFromGHLong = GeoHashUtils.mortonEncode(geoHashLong);
-      assertEquals(mortonLongFromGHLong, mortonLongFromGHString);
-
-      // v&v lat/lon from geohash string and geohash long
-      assertEquals(GeoUtils.mortonUnhashLat(mortonLongFromGHString), GeoUtils.mortonUnhashLat(mortonLongFromGHLong), 0);
-      assertEquals(GeoUtils.mortonUnhashLon(mortonLongFromGHString), GeoUtils.mortonUnhashLon(mortonLongFromGHLong), 0);
-    }
-  }
-
-  /**
-   * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
-   * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
-   */
-  public void testEncode() {
-    String hash = GeoHashUtils.stringEncode(-5.6, 42.6, 12);
-    assertEquals("ezs42e44yx96", hash);
-
-    hash = GeoHashUtils.stringEncode(10.40744, 57.64911, 12);
-    assertEquals("u4pruydqqvj8", hash);
-  }
-
-  /**
-   * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then
-   * decoded within 0.00001 of the original value
-   */
-  public void testDecodePreciseLongitudeLatitude() {
-    final String geohash = GeoHashUtils.stringEncode(4.8909347, 52.3738007);
-    final long hash = GeoHashUtils.mortonEncode(geohash);
-
-    assertEquals(52.3738007, GeoUtils.mortonUnhashLat(hash), 0.00001D);
-    assertEquals(4.8909347, GeoUtils.mortonUnhashLon(hash), 0.00001D);
-  }
-
-  /**
-   * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded
-   * within 0.00001 of the original value
-   */
-  public void testDecodeImpreciseLongitudeLatitude() {
-    final String geohash = GeoHashUtils.stringEncode(10.5, 84.6);
-
-    final long hash = GeoHashUtils.mortonEncode(geohash);
-
-    assertEquals(84.6, GeoUtils.mortonUnhashLat(hash), 0.00001D);
-    assertEquals(10.5, GeoUtils.mortonUnhashLon(hash), 0.00001D);
-  }
-
-  public void testDecodeEncode() {
-    final String geoHash = "u173zq37x014";
-    assertEquals(geoHash, GeoHashUtils.stringEncode(4.8909347, 52.3738007));
-    final long mortonHash = GeoHashUtils.mortonEncode(geoHash);
-    final double lon = GeoUtils.mortonUnhashLon(mortonHash);
-    final double lat = GeoUtils.mortonUnhashLat(mortonHash);
-    assertEquals(52.37380061d, GeoUtils.mortonUnhashLat(mortonHash), 0.000001d);
-    assertEquals(4.8909343d, GeoUtils.mortonUnhashLon(mortonHash), 0.000001d);
-
-    assertEquals(geoHash, GeoHashUtils.stringEncode(lon, lat));
-  }
-
-  public void testNeighbors() {
-    String geohash = "gcpv";
-    List<String> expectedNeighbors = new ArrayList<>();
-    expectedNeighbors.add("gcpw");
-    expectedNeighbors.add("gcpy");
-    expectedNeighbors.add("u10n");
-    expectedNeighbors.add("gcpt");
-    expectedNeighbors.add("u10j");
-    expectedNeighbors.add("gcps");
-    expectedNeighbors.add("gcpu");
-    expectedNeighbors.add("u10h");
-    Collection<? super String> neighbors = new ArrayList<>();
-    GeoHashUtils.addNeighbors(geohash, neighbors );
-    assertEquals(expectedNeighbors, neighbors);
-
-    // Border odd geohash
-    geohash = "u09x";
-    expectedNeighbors = new ArrayList<>();
-    expectedNeighbors.add("u0c2");
-    expectedNeighbors.add("u0c8");
-    expectedNeighbors.add("u0cb");
-    expectedNeighbors.add("u09r");
-    expectedNeighbors.add("u09z");
-    expectedNeighbors.add("u09q");
-    expectedNeighbors.add("u09w");
-    expectedNeighbors.add("u09y");
-    neighbors = new ArrayList<>();
-    GeoHashUtils.addNeighbors(geohash, neighbors);
-    assertEquals(expectedNeighbors, neighbors);
-
-    // Border even geohash
-    geohash = "u09tv";
-    expectedNeighbors = new ArrayList<>();
-    expectedNeighbors.add("u09wh");
-    expectedNeighbors.add("u09wj");
-    expectedNeighbors.add("u09wn");
-    expectedNeighbors.add("u09tu");
-    expectedNeighbors.add("u09ty");
-    expectedNeighbors.add("u09ts");
-    expectedNeighbors.add("u09tt");
-    expectedNeighbors.add("u09tw");
-    neighbors = new ArrayList<>();
-    GeoHashUtils.addNeighbors(geohash, neighbors );
-    assertEquals(expectedNeighbors, neighbors);
-
-    // Border even and odd geohash
-    geohash = "ezzzz";
-    expectedNeighbors = new ArrayList<>();
-    expectedNeighbors.add("gbpbn");
-    expectedNeighbors.add("gbpbp");
-    expectedNeighbors.add("u0000");
-    expectedNeighbors.add("ezzzy");
-    expectedNeighbors.add("spbpb");
-    expectedNeighbors.add("ezzzw");
-    expectedNeighbors.add("ezzzx");
-    expectedNeighbors.add("spbp8");
-    neighbors = new ArrayList<>();
-    GeoHashUtils.addNeighbors(geohash, neighbors );
-    assertEquals(expectedNeighbors, neighbors);
-  }
-
-  public void testClosestPointOnBBox() {
-    double[] result = new double[2];
-    GeoDistanceUtils.closestPointOnBBox(20, 30, 40, 50, 70, 70, result);
-    assertEquals(40.0, result[0], 0.0);
-    assertEquals(50.0, result[1], 0.0);
-
-    GeoDistanceUtils.closestPointOnBBox(-20, -20, 0, 0, 70, 70, result);
-    assertEquals(0.0, result[0], 0.0);
-    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);
-  }
-
-  public long scaleLat(final double val) {
-    return (long) ((val-GeoUtils.MIN_LAT_INCL) * LAT_SCALE);
-  }
-
-  public double unscaleLon(final long val) {
-    return (val / LON_SCALE) + GeoUtils.MIN_LON_INCL;
-  }
-
-  public double unscaleLat(final long val) {
-    return (val / LAT_SCALE) + GeoUtils.MIN_LAT_INCL;
-  }
-
-  public double randomLat(boolean small) {
-    double result;
-    if (small) {
-      result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
-    } else {
-      result = -90 + 180.0 * random().nextDouble();
-    }
-    return result;
-  }
-
-  public double randomLon(boolean small) {
-    double result;
-    if (small) {
-      result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
-    } else {
-      result = -180 + 360.0 * random().nextDouble();
-    }
-    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 = GeoDistanceUtils.haversin(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.minLon, cell.minLat, cell.maxLon, cell.maxLat, centerLon, centerLat, radiusMeters)) {
-          // 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.minLon, root.minLat, root.maxLon, root.maxLat,
-                                       cell.minLon, cell.minLat, cell.maxLon, cell.maxLat)) {
-          // Fall through below to "recurse"
-          if (VERBOSE) {
-            log.println("    cell fully contains circle: keep splitting");
-          }
-        } else if (GeoRelationUtils.rectCrossesCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat,
-                                              centerLon, centerLat, radiusMeters)) {
-          // 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 GeoUtils.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(centerLon, centerLat, 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 = GeoDistanceUtils.haversin(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)");
-      }
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
new file mode 100644
index 0000000..6386ee6
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
@@ -0,0 +1,136 @@
+/*
+ * 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.document;
+
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/**
+ * <p>
+ * Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
+ * for efficient encoding, sorting, and querying. This Geo capability is intended
+ * to provide a basic and efficient out of the box field type for indexing and
+ * querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
+ *
+ * <pre class="prettyprint">
+ *  document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
+ * </pre>
+ *
+ * <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
+ * see {@link org.apache.lucene.spatial.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.spatial.search.GeoPointInPolygonQuery},
+ * or {@link org.apache.lucene.spatial.search.GeoPointDistanceQuery}
+ *
+ * NOTE: This indexes only high precision encoded terms which may result in visiting a high number
+ * of terms for large queries. See LUCENE-6481 for a future improvement.
+ *
+ * @lucene.experimental
+ */
+public final class GeoPointField extends Field {
+  /** encoding step value for GeoPoint prefix terms */
+  public static final int PRECISION_STEP = 9;
+
+  /**
+   * Type for an GeoPointField that is not stored:
+   * normalization factors, frequencies, and positions are omitted.
+   */
+  public static final FieldType TYPE_NOT_STORED = new FieldType();
+  static {
+    TYPE_NOT_STORED.setTokenized(false);
+    TYPE_NOT_STORED.setOmitNorms(true);
+    TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
+    TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    TYPE_NOT_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
+    TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
+    TYPE_NOT_STORED.freeze();
+  }
+
+  /**
+   * Type for a stored GeoPointField:
+   * normalization factors, frequencies, and positions are omitted.
+   */
+  public static final FieldType TYPE_STORED = new FieldType();
+  static {
+    TYPE_STORED.setTokenized(false);
+    TYPE_STORED.setOmitNorms(true);
+    TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
+    TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    TYPE_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
+    TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
+    TYPE_STORED.setStored(true);
+    TYPE_STORED.freeze();
+  }
+
+  /** Creates a stored or un-stored GeoPointField with the provided value
+   *  and default <code>precisionStep</code> set to 64 to avoid wasteful
+   *  indexing of lower precision terms.
+   *  @param name field name
+   *  @param lon longitude double value [-180.0 : 180.0]
+   *  @param lat latitude double value [-90.0 : 90.0]
+   *  @param stored Store.YES if the content should also be stored
+   *  @throws IllegalArgumentException if the field name is null.
+   */
+  public GeoPointField(String name, double lon, double lat, Store stored) {
+    super(name, stored == Store.YES ? TYPE_STORED : TYPE_NOT_STORED);
+    fieldsData = GeoUtils.mortonHash(lon, lat);
+  }
+
+  /** Expert: allows you to customize the {@link
+   *  FieldType}.
+   *  @param name field name
+   *  @param lon longitude double value [-180.0 : 180.0]
+   *  @param lat latitude double value [-90.0 : 90.0]
+   *  @param type customized field type: must have {@link FieldType#numericType()}
+   *         of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
+   *  @throws IllegalArgumentException if the field name or type is null, or
+   *          if the field type does not have a LONG numericType()
+   */
+  public GeoPointField(String name, double lon, double lat, FieldType type) {
+    super(name, type);
+    if (type.numericType() != FieldType.LegacyNumericType.LONG) {
+      throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
+    }
+    if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
+      throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
+    }
+    fieldsData = GeoUtils.mortonHash(lon, lat);
+  }
+
+  /** access longitude value */
+  public double getLon() {
+    return GeoUtils.mortonUnhashLon((long) fieldsData);
+  }
+
+  /** access latitude value */
+  public double getLat() {
+    return GeoUtils.mortonUnhashLat((long) fieldsData);
+  }
+
+  @Override
+  public String toString() {
+    if (fieldsData == null) {
+      return null;
+    }
+    StringBuilder sb = new StringBuilder();
+    sb.append(GeoUtils.mortonUnhashLon((long) fieldsData));
+    sb.append(',');
+    sb.append(GeoUtils.mortonUnhashLat((long) fieldsData));
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
new file mode 100644
index 0000000..2550fa1
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Geospatial Field Implementations for Core Lucene
+ */
+package org.apache.lucene.spatial.document;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
new file mode 100644
index 0000000..f26f8b1
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
@@ -0,0 +1,53 @@
+/*
+ * 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.search;
+
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** NOTE: package private; just used so {@link GeoPointInPolygonQuery} can communicate its bounding box to {@link GeoPointInBBoxQuery}. */
+class GeoBoundingBox {
+  /** minimum longitude value (in degrees) */
+  public final double minLon;
+  /** minimum latitude value (in degrees) */
+  public final double maxLon;
+  /** maximum longitude value (in degrees) */
+  public final double minLat;
+  /** maximum latitude value (in degrees) */
+  public final double maxLat;
+
+  /**
+   * Constructs a bounding box by first validating the provided latitude and longitude coordinates
+   */
+  public GeoBoundingBox(double minLon, double maxLon, double minLat, double maxLat) {
+    if (GeoUtils.isValidLon(minLon) == false) {
+      throw new IllegalArgumentException("invalid minLon " + minLon);
+    }
+    if (GeoUtils.isValidLon(maxLon) == false) {
+      throw new IllegalArgumentException("invalid maxLon " + minLon);
+    }
+    if (GeoUtils.isValidLat(minLat) == false) {
+      throw new IllegalArgumentException("invalid minLat " + minLat);
+    }
+    if (GeoUtils.isValidLat(maxLat) == false) {
+      throw new IllegalArgumentException("invalid maxLat " + minLat);
+    }
+    this.minLon = minLon;
+    this.maxLon = maxLon;
+    this.minLat = minLat;
+    this.maxLat = maxLat;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
new file mode 100644
index 0000000..27f78e8
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
@@ -0,0 +1,182 @@
+/*
+ * 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.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple point distance query on a GeoPoint field. This is based on
+ * {@link GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
+ * like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
+ * the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
+ * circle. Terms
+ * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
+ * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversin}.
+ * All morton value comparisons are subject to the same precision tolerance defined in
+ * {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
+ * haversine formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
+ *
+ * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
+ * queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
+ * using the spheroid
+ *
+ * @lucene.experimental */
+public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
+  /** longitude value (in degrees) for query location */
+  protected final double centerLon;
+  /** latitude value (in degrees) for query location */
+  protected final double centerLat;
+  /** distance (in meters) from lon, lat center location */
+  protected final double radiusMeters;
+
+  /**
+   * Constructs a Query for all {@link org.apache.lucene.spatial.document.GeoPointField} types within a
+   * distance (in meters) from a given point
+   **/
+  public GeoPointDistanceQuery(final String field, final double centerLon, final double centerLat, final double radiusMeters) {
+    this(field, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
+  }
+
+  private GeoPointDistanceQuery(final String field, GeoRect bbox, final double centerLon,
+                                final double centerLat, final double radiusMeters) {
+    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+    {
+      // check longitudinal overlap (limits radius)
+      final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLon, centerLat);
+      if (radiusMeters > maxRadius) {
+        throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
+            + "] at location [" + centerLon + " " + centerLat + "]");
+      }
+    }
+
+    if (GeoUtils.isValidLon(centerLon) == false) {
+      throw new IllegalArgumentException("invalid centerLon " + centerLon);
+    }
+
+    if (GeoUtils.isValidLat(centerLat) == false) {
+      throw new IllegalArgumentException("invalid centerLat " + centerLat);
+    }
+
+    if (radiusMeters <= 0.0) {
+      throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
+    }
+
+    this.centerLon = centerLon;
+    this.centerLat = centerLat;
+    this.radiusMeters = radiusMeters;
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) {
+    // query crosses dateline; split into left and right queries
+    if (maxLon < minLon) {
+      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+      // unwrap the longitude iff outside the specified min/max lon range
+      double unwrappedLon = centerLon;
+      if (unwrappedLon > maxLon) {
+        // unwrap left
+        unwrappedLon += -360.0D;
+      }
+      GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+          new GeoRect(GeoUtils.MIN_LON_INCL, maxLon, minLat, maxLat));
+      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+
+      if (unwrappedLon < maxLon) {
+        // unwrap right
+        unwrappedLon += 360.0D;
+      }
+      GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+          new GeoRect(minLon, GeoUtils.MAX_LON_INCL, minLat, maxLat));
+      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+
+      return bqb.build();
+    }
+    return new GeoPointDistanceQueryImpl(field, this, centerLon,
+        new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof GeoPointDistanceQuery)) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
+
+    if (Double.compare(that.centerLat, centerLat) != 0) return false;
+    if (Double.compare(that.centerLon, centerLon) != 0) return false;
+    if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp;
+    temp = Double.doubleToLongBits(centerLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(centerLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(radiusMeters);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public String toString(String field) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (!this.field.equals(field)) {
+      sb.append(" field=");
+      sb.append(this.field);
+      sb.append(':');
+    }
+    return sb.append( " Center: [")
+        .append(centerLon)
+        .append(',')
+        .append(centerLat)
+        .append(']')
+        .append(" Distance: ")
+        .append(radiusMeters)
+        .append(" meters")
+        .append("]")
+        .toString();
+  }
+
+  /** getter method for center longitude value */
+  public double getCenterLon() {
+    return this.centerLon;
+  }
+
+  /** getter method for center latitude value */
+  public double getCenterLat() {
+    return this.centerLat;
+  }
+
+  /** getter method for distance value (in meters) */
+  public double getRadiusMeters() {
+    return this.radiusMeters;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
new file mode 100644
index 0000000..0777799
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
@@ -0,0 +1,131 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.document.GeoPointField;
+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.
+ *
+ *    @lucene.experimental
+ */
+final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
+  private final GeoPointDistanceQuery query;
+  private final double centerLon;
+
+  GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q, final double centerLonUnwrapped,
+                            final GeoRect bbox) {
+    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+    query = q;
+    centerLon = centerLonUnwrapped;
+  }
+
+  @Override @SuppressWarnings("unchecked")
+  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+    return new GeoPointRadiusTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
+  }
+
+  @Override
+  public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
+    throw new UnsupportedOperationException("cannot change rewrite method");
+  }
+
+  private final class GeoPointRadiusTermsEnum extends GeoPointTermsEnum {
+    GeoPointRadiusTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+                            final double maxLon, final double maxLat) {
+      super(tenum, minLon, minLat, maxLon, maxLat);
+    }
+
+    /**
+     * Computes the maximum shift for the given pointDistanceQuery. This prevents unnecessary depth traversal
+     * given the size of the distance query.
+     */
+    @Override
+    protected short computeMaxShift() {
+      final short shiftFactor;
+
+      if (query.radiusMeters > 1000000) {
+        shiftFactor = 5;
+      } else {
+        shiftFactor = 4;
+      }
+
+      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+    }
+
+    @Override
+    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectCrossesCircle(minLon, minLat, maxLon, maxLat,
+          centerLon, query.centerLat, query.radiusMeters, true);
+    }
+
+    @Override
+    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectWithinCircle(minLon, minLat, maxLon, maxLat,
+          centerLon, query.centerLat, query.radiusMeters, true);
+    }
+
+    @Override
+    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return (cellContains(minLon, minLat, maxLon, maxLat)
+          || cellWithin(minLon, minLat, maxLon, maxLat) || cellCrosses(minLon, minLat, maxLon, maxLat));
+    }
+
+    /**
+     * The two-phase query approach. The parent {@link GeoPointTermsEnum} class matches
+     * encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
+     * the initial bounding box filter are then post filter compared to the provided distance using the
+     * {@link org.apache.lucene.util.SloppyMath#haversin} method.
+     */
+    @Override
+    protected boolean postFilter(final double lon, final double lat) {
+      return (SloppyMath.haversin(query.centerLat, centerLon, lat, lon) * 1000.0 <= query.radiusMeters);
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
+
+    if (!query.equals(that.query)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + query.hashCode();
+    return result;
+  }
+
+  public double getRadiusMeters() {
+    return query.getRadiusMeters();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
new file mode 100644
index 0000000..df76c0c
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
@@ -0,0 +1,110 @@
+/*
+ * 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.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+
+/** Implements a point distance range query on a GeoPoint field. This is based on
+ * {@code org.apache.lucene.spatial.search.GeoPointDistanceQuery} and is implemented using a
+ * {@code org.apache.lucene.search.BooleanClause.MUST_NOT} clause to exclude any points that fall within
+ * minRadiusMeters from the provided point.
+ *
+ *    @lucene.experimental
+ */
+public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
+  protected final double minRadiusMeters;
+
+  /**
+   * Constructs a query for all {@link org.apache.lucene.spatial.document.GeoPointField} types within a minimum / maximum
+   * distance (in meters) range from a given point
+   */
+  public GeoPointDistanceRangeQuery(final String field, final double centerLon, final double centerLat,
+                                    final double minRadiusMeters, final double maxRadius) {
+    super(field, centerLon, centerLat, maxRadius);
+    this.minRadiusMeters = minRadiusMeters;
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) {
+    Query q = super.rewrite(reader);
+    if (minRadiusMeters == 0.0) {
+      return q;
+    }
+
+    // add an exclusion query
+    BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+    // create a new exclusion query
+    GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, centerLon, centerLat, minRadiusMeters);
+    // full map search
+//    if (radiusMeters >= GeoProjectionUtils.SEMIMINOR_AXIS) {
+//      bqb.add(new BooleanClause(new GeoPointInBBoxQuery(this.field, -180.0, -90.0, 180.0, 90.0), BooleanClause.Occur.MUST));
+//    } else {
+      bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
+//    }
+    bqb.add(new BooleanClause(exclude, BooleanClause.Occur.MUST_NOT));
+
+    return bqb.build();
+  }
+
+  @Override
+  public String toString(String field) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (!this.field.equals(field)) {
+      sb.append(" field=");
+      sb.append(this.field);
+      sb.append(':');
+    }
+    return sb.append( " Center: [")
+        .append(centerLon)
+        .append(',')
+        .append(centerLat)
+        .append(']')
+        .append(" From Distance: ")
+        .append(minRadiusMeters)
+        .append(" m")
+        .append(" To Distance: ")
+        .append(radiusMeters)
+        .append(" m")
+        .append(" Lower Left: [")
+        .append(minLon)
+        .append(',')
+        .append(minLat)
+        .append(']')
+        .append(" Upper Right: [")
+        .append(maxLon)
+        .append(',')
+        .append(maxLat)
+        .append("]")
+        .toString();
+  }
+
+  /** getter method for minimum distance */
+  public double getMinRadiusMeters() {
+    return this.minRadiusMeters;
+  }
+
+  /** getter method for maximum distance */
+  public double getMaxRadiusMeters() {
+    return this.radiusMeters;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
new file mode 100644
index 0000000..75f38a9
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
@@ -0,0 +1,166 @@
+/*
+ * 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.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FieldValueQuery;
+import org.apache.lucene.search.LegacyNumericRangeQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple bounding box query on a GeoPoint field. This is inspired by
+ * {@link LegacyNumericRangeQuery} and is implemented using a
+ * two phase approach. First, candidate terms are queried using a numeric
+ * range based on the morton codes of the min and max lat/lon pairs. Terms
+ * passing this initial filter are passed to a final check that verifies whether
+ * the decoded lat/lon falls within (or on the boundary) of the query bounding box.
+ * The value comparisons are subject to a precision tolerance defined in
+ * {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE}
+ *
+ * NOTES:
+ *    1.  All latitude/longitude values must be in decimal degrees.
+ *    2.  Complex computational geometry (e.g., dateline wrapping) is not supported
+ *    3.  For more advanced GeoSpatial indexing and query operations see spatial module
+ *    4.  This is well suited for small rectangles, large bounding boxes may result
+ *        in many terms, depending whether the bounding box falls on the boundary of
+ *        many cells (degenerate case)
+ *
+ * @lucene.experimental
+ */
+public class GeoPointInBBoxQuery extends Query {
+  protected final String field;
+  protected final double minLon;
+  protected final double minLat;
+  protected final double maxLon;
+  protected final double maxLat;
+
+  /**
+   * Constructs a query for all {@link org.apache.lucene.spatial.document.GeoPointField} types that fall within a
+   * defined bounding box
+   */
+  public GeoPointInBBoxQuery(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    this.field = field;
+    this.minLon = minLon;
+    this.minLat = minLat;
+    this.maxLon = maxLon;
+    this.maxLat = maxLat;
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) {
+    // short-circuit to match all if specifying the whole map
+    if (minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL
+        && minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL) {
+      // FieldValueQuery is valid since DocValues are *required* for GeoPointField
+      return new FieldValueQuery(field);
+    }
+
+    if (maxLon < minLon) {
+      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
+      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
+      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+      return bqb.build();
+    }
+    return new GeoPointInBBoxQueryImpl(field, minLon, minLat, maxLon, maxLat);
+  }
+
+  @Override
+  public String toString(String field) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (!this.field.equals(field)) {
+      sb.append(" field=");
+      sb.append(this.field);
+      sb.append(':');
+    }
+    return sb.append(" Lower Left: [")
+        .append(minLon)
+        .append(',')
+        .append(minLat)
+        .append(']')
+        .append(" Upper Right: [")
+        .append(maxLon)
+        .append(',')
+        .append(maxLat)
+        .append("]")
+        .toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof GeoPointInBBoxQuery)) return false;
+    if (!super.equals(o)) return false;
+
+    GeoPointInBBoxQuery that = (GeoPointInBBoxQuery) o;
+
+    if (Double.compare(that.maxLat, maxLat) != 0) return false;
+    if (Double.compare(that.maxLon, maxLon) != 0) return false;
+    if (Double.compare(that.minLat, minLat) != 0) return false;
+    if (Double.compare(that.minLon, minLon) != 0) return false;
+    if (!field.equals(that.field)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp;
+    result = 31 * result + field.hashCode();
+    temp = Double.doubleToLongBits(minLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(minLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLon);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(maxLat);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  /** getter method for retrieving the field name */
+  public final String getField() {
+    return this.field;
+  }
+
+  /** getter method for retrieving the minimum longitude (in degrees) */
+  public final double getMinLon() {
+    return this.minLon;
+  }
+
+  /** getter method for retrieving the minimum latitude (in degrees) */
+  public final double getMinLat() {
+    return this.minLat;
+  }
+
+  /** getter method for retrieving the maximum longitude (in degrees) */
+  public final double getMaxLon() {
+    return this.maxLon;
+  }
+
+  /** getter method for retrieving the maximum latitude (in degrees) */
+  public final double getMaxLat() {
+    return this.maxLat;
+  }
+}


[5/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
deleted file mode 100644
index d421cc9..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
+++ /dev/null
@@ -1,273 +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.util;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Utilities for converting to/from the GeoHash standard
- *
- * The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits
- * representing the level (1-12) [xyxy...xyxyllll]
- *
- * This differs from a morton encoded value which interleaves lat/lon (y/x).
- *
- * @lucene.experimental
- */
-public class GeoHashUtils {
-  public static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
-      '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
-      'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
-
-  public static final String BASE_32_STRING = new String(BASE_32);
-
-  public static final int PRECISION = 12;
-  private static final short MORTON_OFFSET = (GeoUtils.BITS<<1) - (PRECISION*5);
-
-  /**
-   * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
-   */
-  public static final long longEncode(final double lon, final double lat, final int level) {
-    // shift to appropriate level
-    final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET);
-    return ((BitUtil.flipFlop(GeoUtils.mortonHash(lon, lat)) >>> msf) << 4) | level;
-  }
-
-  /**
-   * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
-   */
-  public static final long longEncode(final String hash) {
-    int level = hash.length()-1;
-    long b;
-    long l = 0L;
-    for(char c : hash.toCharArray()) {
-      b = (long)(BASE_32_STRING.indexOf(c));
-      l |= (b<<(level--*5));
-    }
-    return (l<<4)|hash.length();
-  }
-
-  /**
-   * Encode an existing geohash long to the provided precision
-   */
-  public static long longEncode(long geohash, int level) {
-    final short precision = (short)(geohash & 15);
-    if (precision == level) {
-      return geohash;
-    } else if (precision > level) {
-      return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level;
-    }
-    return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level);
-  }
-
-  public static long fromMorton(long morton, int level) {
-    long mFlipped = BitUtil.flipFlop(morton);
-    mFlipped >>>= (((GeoHashUtils.PRECISION - level) * 5) + MORTON_OFFSET);
-    return (mFlipped << 4) | level;
-  }
-
-  /**
-   * Encode to a geohash string from the geohash based long format
-   */
-  public static final String stringEncode(long geoHashLong) {
-    int level = (int)geoHashLong&15;
-    geoHashLong >>>= 4;
-    char[] chars = new char[level];
-    do {
-      chars[--level] = BASE_32[(int) (geoHashLong&31L)];
-      geoHashLong>>>=5;
-    } while(level > 0);
-
-    return new String(chars);
-  }
-
-  /**
-   * Encode to a geohash string from full resolution longitude, latitude)
-   */
-  public static final String stringEncode(final double lon, final double lat) {
-    return stringEncode(lon, lat, 12);
-  }
-
-  /**
-   * Encode to a level specific geohash string from full resolution longitude, latitude
-   */
-  public static final String stringEncode(final double lon, final double lat, final int level) {
-    // convert to geohashlong
-    final long ghLong = fromMorton(GeoUtils.mortonHash(lon, lat), level);
-    return stringEncode(ghLong);
-
-  }
-
-  /**
-   * Encode to a full precision geohash string from a given morton encoded long value
-   */
-  public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception {
-    return stringEncode(hashedVal, PRECISION);
-  }
-
-  /**
-   * Encode to a geohash string at a given level from a morton long
-   */
-  public static final String stringEncodeFromMortonLong(long hashedVal, final int level) {
-    // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding)
-    hashedVal = BitUtil.flipFlop(hashedVal);
-
-    StringBuilder geoHash = new StringBuilder();
-    short precision = 0;
-    final short msf = (GeoUtils.BITS<<1)-5;
-    long mask = 31L<<msf;
-    do {
-      geoHash.append(BASE_32[(int)((mask & hashedVal)>>>(msf-(precision*5)))]);
-      // next 5 bits
-      mask >>>= 5;
-    } while (++precision < level);
-    return geoHash.toString();
-  }
-
-  /**
-   * Encode to a morton long value from a given geohash string
-   */
-  public static final long mortonEncode(final String hash) {
-    int level = 11;
-    long b;
-    long l = 0L;
-    for(char c : hash.toCharArray()) {
-      b = (long)(BASE_32_STRING.indexOf(c));
-      l |= (b<<((level--*5) + MORTON_OFFSET));
-    }
-    return BitUtil.flipFlop(l);
-  }
-
-  /**
-   * Encode to a morton long value from a given geohash long value
-   */
-  public static final long mortonEncode(final long geoHashLong) {
-    final int level = (int)(geoHashLong&15);
-    final short odd = (short)(level & 1);
-
-    return BitUtil.flipFlop(((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)));
-  }
-
-  private static final char encode(int x, int y) {
-    return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
-  }
-
-  /**
-   * Calculate all neighbors of a given geohash cell.
-   *
-   * @param geohash Geohash of the defined cell
-   * @return geohashes of all neighbor cells
-   */
-  public static Collection<? extends CharSequence> neighbors(String geohash) {
-    return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
-  }
-
-  /**
-   * Calculate the geohash of a neighbor of a geohash
-   *
-   * @param geohash the geohash of a cell
-   * @param level   level of the geohash
-   * @param dx      delta of the first grid coordinate (must be -1, 0 or +1)
-   * @param dy      delta of the second grid coordinate (must be -1, 0 or +1)
-   * @return geohash of the defined cell
-   */
-  public final static String neighbor(String geohash, int level, int dx, int dy) {
-    int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1));
-
-    // Decoding the Geohash bit pattern to determine grid coordinates
-    int x0 = cell & 1;  // first bit of x
-    int y0 = cell & 2;  // first bit of y
-    int x1 = cell & 4;  // second bit of x
-    int y1 = cell & 8;  // second bit of y
-    int x2 = cell & 16; // third bit of x
-
-    // combine the bitpattern to grid coordinates.
-    // note that the semantics of x and y are swapping
-    // on each level
-    int x = x0 + (x1 / 2) + (x2 / 4);
-    int y = (y0 / 2) + (y1 / 4);
-
-    if (level == 1) {
-      // Root cells at north (namely "bcfguvyz") or at
-      // south (namely "0145hjnp") do not have neighbors
-      // in north/south direction
-      if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
-        return null;
-      } else {
-        return Character.toString(encode(x + dx, y + dy));
-      }
-    } else {
-      // define grid coordinates for next level
-      final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy);
-      final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx);
-
-      // if the defined neighbor has the same parent a the current cell
-      // encode the cell directly. Otherwise find the cell next to this
-      // cell recursively. Since encoding wraps around within a cell
-      // it can be encoded here.
-      // xLimit and YLimit must always be respectively 7 and 3
-      // since x and y semantics are swapping on each level.
-      if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) {
-        return geohash.substring(0, level - 1) + encode(nx, ny);
-      } else {
-        String neighbor = neighbor(geohash, level - 1, dx, dy);
-        return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor;
-      }
-    }
-  }
-
-  /**
-   * Add all geohashes of the cells next to a given geohash to a list.
-   *
-   * @param geohash   Geohash of a specified cell
-   * @param neighbors list to add the neighbors to
-   * @return the given list
-   */
-  public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
-    return addNeighbors(geohash, geohash.length(), neighbors);
-  }
-
-  /**
-   * Add all geohashes of the cells next to a given geohash to a list.
-   *
-   * @param geohash   Geohash of a specified cell
-   * @param length    level of the given geohash
-   * @param neighbors list to add the neighbors to
-   * @return the given list
-   */
-  public static final <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
-    String south = neighbor(geohash, length, 0, -1);
-    String north = neighbor(geohash, length, 0, +1);
-    if (north != null) {
-      neighbors.add(neighbor(north, length, -1, 0));
-      neighbors.add(north);
-      neighbors.add(neighbor(north, length, +1, 0));
-    }
-
-    neighbors.add(neighbor(geohash, length, -1, 0));
-    neighbors.add(neighbor(geohash, length, +1, 0));
-
-    if (south != null) {
-      neighbors.add(neighbor(south, length, -1, 0));
-      neighbors.add(south);
-      neighbors.add(neighbor(south, length, +1, 0));
-    }
-
-    return neighbors;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
deleted file mode 100644
index 7e285da..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
+++ /dev/null
@@ -1,437 +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.util;
-
-import static org.apache.lucene.util.SloppyMath.PIO2;
-import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-/**
- * Reusable geo-spatial projection utility methods.
- *
- * @lucene.experimental
- */
-public class GeoProjectionUtils {
-  // WGS84 earth-ellipsoid major (a) minor (b) radius, (f) flattening and eccentricity (e)
-  public static final double SEMIMAJOR_AXIS = 6_378_137; // [m]
-  public static final double FLATTENING = 1.0/298.257223563;
-  public static final double SEMIMINOR_AXIS = SEMIMAJOR_AXIS * (1.0 - FLATTENING); //6_356_752.31420; // [m]
-  public static final double ECCENTRICITY = StrictMath.sqrt((2.0 - FLATTENING) * FLATTENING);
-  static final double SEMIMAJOR_AXIS2 = SEMIMAJOR_AXIS * SEMIMAJOR_AXIS;
-  static final double SEMIMINOR_AXIS2 = SEMIMINOR_AXIS * SEMIMINOR_AXIS;
-  public static final double MIN_LON_RADIANS = TO_RADIANS * GeoUtils.MIN_LON_INCL;
-  public static final double MIN_LAT_RADIANS = TO_RADIANS * GeoUtils.MIN_LAT_INCL;
-  public static final double MAX_LON_RADIANS = TO_RADIANS * GeoUtils.MAX_LON_INCL;
-  public static final double MAX_LAT_RADIANS = TO_RADIANS * GeoUtils.MAX_LAT_INCL;
-  private static final double E2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2);
-  private static final double EP2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMINOR_AXIS2);
-
-  /**
-   * Converts from geocentric earth-centered earth-fixed to geodesic lat/lon/alt
-   * @param x Cartesian x coordinate
-   * @param y Cartesian y coordinate
-   * @param z Cartesian z coordinate
-   * @param lla 0: longitude 1: latitude: 2: altitude
-   * @return double array as 0: longitude 1: latitude 2: altitude
-   */
-  public static final double[] ecfToLLA(final double x, final double y, final double z, double[] lla) {
-    boolean atPole = false;
-    final double ad_c = 1.0026000D;
-    final double cos67P5 = 0.38268343236508977D;
-
-    if (lla == null) {
-      lla = new double[3];
-    }
-
-    if (x != 0.0) {
-      lla[0] = StrictMath.atan2(y,x);
-    } else {
-      if (y > 0) {
-        lla[0] = PIO2;
-      } else if (y < 0) {
-        lla[0] = -PIO2;
-      } else {
-        atPole = true;
-        lla[0] = 0.0D;
-        if (z > 0.0) {
-          lla[1] = PIO2;
-        } else if (z < 0.0) {
-          lla[1] = -PIO2;
-        } else {
-          lla[1] = PIO2;
-          lla[2] = -SEMIMINOR_AXIS;
-          return lla;
-        }
-      }
-    }
-
-    final double w2 = x*x + y*y;
-    final double w = StrictMath.sqrt(w2);
-    final double t0 = z * ad_c;
-    final double s0 = StrictMath.sqrt(t0 * t0 + w2);
-    final double sinB0 = t0 / s0;
-    final double cosB0 = w / s0;
-    final double sin3B0 = sinB0 * sinB0 * sinB0;
-    final double t1 = z + SEMIMINOR_AXIS * EP2 * sin3B0;
-    final double sum = w - SEMIMAJOR_AXIS * E2 * cosB0 * cosB0 * cosB0;
-    final double s1 = StrictMath.sqrt(t1 * t1 + sum * sum);
-    final double sinP1 = t1 / s1;
-    final double cosP1 = sum / s1;
-    final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * sinP1 * sinP1);
-
-    if (cosP1 >= cos67P5) {
-      lla[2] = w / cosP1 - rn;
-    } else if (cosP1 <= -cos67P5) {
-      lla[2] = w / -cosP1 - rn;
-    } else {
-      lla[2] = z / sinP1 + rn * (E2 - 1.0);
-    }
-    if (!atPole) {
-      lla[1] = StrictMath.atan(sinP1/cosP1);
-    }
-    lla[0] = TO_DEGREES * lla[0];
-    lla[1] = TO_DEGREES * lla[1];
-
-    return lla;
-  }
-
-  /**
-   * Converts from geodesic lon lat alt to geocentric earth-centered earth-fixed
-   * @param lon geodesic longitude
-   * @param lat geodesic latitude
-   * @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 lon, double lat, double alt, double[] ecf) {
-    lon = TO_RADIANS * lon;
-    lat = TO_RADIANS * lat;
-
-    final double sl = SloppyMath.sin(lat);
-    final double s2 = sl*sl;
-    final double cl = SloppyMath.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 * SloppyMath.cos(lon);
-    ecf[1] = (rn+alt) * cl * SloppyMath.sin(lon);
-    ecf[2] = ((rn*(1.0-E2))+alt)*sl;
-
-    return ecf;
-  }
-
-  /**
-   * Converts from lat lon alt (in degrees) to East North Up right-hand coordinate system
-   * @param lon longitude in degrees
-   * @param lat latitude in degrees
-   * @param alt altitude in meters
-   * @param centerLon reference point longitude in degrees
-   * @param centerLat reference point latitude in degrees
-   * @param centerAlt reference point altitude in meters
-   * @param enu result east, north, up coordinate
-   * @return east, north, up coordinate
-   */
-  public static double[] llaToENU(final double lon, final double lat, final double alt, double centerLon,
-                                  double centerLat, final double centerAlt, double[] enu) {
-    if (enu == null) {
-      enu = new double[3];
-    }
-
-    // convert point to ecf coordinates
-    final double[] ecf = llaToECF(lon, lat, alt, null);
-
-    // convert from ecf to enu
-    return ecfToENU(ecf[0], ecf[1], ecf[2], centerLon, centerLat, centerAlt, enu);
-  }
-
-  /**
-   * Converts from East North Up right-hand rule to lat lon alt in degrees
-   * @param x easting (in meters)
-   * @param y northing (in meters)
-   * @param z up (in meters)
-   * @param centerLon reference point longitude (in degrees)
-   * @param centerLat reference point latitude (in degrees)
-   * @param centerAlt reference point altitude (in meters)
-   * @param lla resulting lat, lon, alt point (in degrees)
-   * @return lat, lon, alt point (in degrees)
-   */
-  public static double[] enuToLLA(final double x, final double y, final double z, final double centerLon,
-                                  final double centerLat, final double centerAlt, double[] lla) {
-    // convert enuToECF
-    if (lla == null) {
-      lla = new double[3];
-    }
-
-    // convert enuToECF, storing intermediate result in lla
-    lla = enuToECF(x, y, z, centerLon, centerLat, centerAlt, lla);
-
-    // convert ecf to LLA
-    return ecfToLLA(lla[0], lla[1], lla[2], lla);
-  }
-
-  /**
-   * Convert from Earth-Centered-Fixed to Easting, Northing, Up Right Hand System
-   * @param x ECF X coordinate (in meters)
-   * @param y ECF Y coordinate (in meters)
-   * @param z ECF Z coordinate (in meters)
-   * @param centerLon ENU origin longitude (in degrees)
-   * @param centerLat ENU origin latitude (in degrees)
-   * @param centerAlt ENU altitude (in meters)
-   * @param enu reusable enu result
-   * @return Easting, Northing, Up coordinate
-   */
-  public static double[] ecfToENU(double x, double y, double z, final double centerLon,
-                                  final double centerLat, final double centerAlt, double[] enu) {
-    if (enu == null) {
-      enu = new double[3];
-    }
-
-    // create rotation matrix and rotate to enu orientation
-    final double[][] phi = createPhiTransform(centerLon, centerLat, null);
-
-    // convert origin to ENU
-    final double[] originECF = llaToECF(centerLon, centerLat, centerAlt, null);
-    final double[] originENU = new double[3];
-    originENU[0] = ((phi[0][0] * originECF[0]) + (phi[0][1] * originECF[1]) + (phi[0][2] * originECF[2]));
-    originENU[1] = ((phi[1][0] * originECF[0]) + (phi[1][1] * originECF[1]) + (phi[1][2] * originECF[2]));
-    originENU[2] = ((phi[2][0] * originECF[0]) + (phi[2][1] * originECF[1]) + (phi[2][2] * originECF[2]));
-
-    // rotate then translate
-    enu[0] = ((phi[0][0] * x) + (phi[0][1] * y) + (phi[0][2] * z)) - originENU[0];
-    enu[1] = ((phi[1][0] * x) + (phi[1][1] * y) + (phi[1][2] * z)) - originENU[1];
-    enu[2] = ((phi[2][0] * x) + (phi[2][1] * y) + (phi[2][2] * z)) - originENU[2];
-
-    return enu;
-  }
-
-  /**
-   * Convert from Easting, Northing, Up Right-Handed system to Earth Centered Fixed system
-   * @param x ENU x coordinate (in meters)
-   * @param y ENU y coordinate (in meters)
-   * @param z ENU z coordinate (in meters)
-   * @param centerLon ENU origin longitude (in degrees)
-   * @param centerLat ENU origin latitude (in degrees)
-   * @param centerAlt ENU origin altitude (in meters)
-   * @param ecf reusable ecf result
-   * @return ecf result coordinate
-   */
-  public static double[] enuToECF(final double x, final double y, final double z, double centerLon,
-                                  double centerLat, final double centerAlt, double[] ecf) {
-    if (ecf == null) {
-      ecf = new double[3];
-    }
-
-    double[][] phi = createTransposedPhiTransform(centerLon, centerLat, null);
-    double[] ecfOrigin = llaToECF(centerLon, centerLat, centerAlt, null);
-
-    // rotate and translate
-    ecf[0] = (phi[0][0]*x + phi[0][1]*y + phi[0][2]*z) + ecfOrigin[0];
-    ecf[1] = (phi[1][0]*x + phi[1][1]*y + phi[1][2]*z) + ecfOrigin[1];
-    ecf[2] = (phi[2][0]*x + phi[2][1]*y + phi[2][2]*z) + ecfOrigin[2];
-
-    return ecf;
-  }
-
-  /**
-   * Create the rotation matrix for converting Earth Centered Fixed to Easting Northing Up
-   * @param originLon ENU origin longitude (in degrees)
-   * @param originLat ENU origin latitude (in degrees)
-   * @param phiMatrix reusable phi matrix result
-   * @return phi rotation matrix
-   */
-  private static double[][] createPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
-
-    if (phiMatrix == null) {
-      phiMatrix = new double[3][3];
-    }
-
-    originLon = TO_RADIANS * originLon;
-    originLat = TO_RADIANS * originLat;
-
-    final double sLon = SloppyMath.sin(originLon);
-    final double cLon = SloppyMath.cos(originLon);
-    final double sLat = SloppyMath.sin(originLat);
-    final double cLat = SloppyMath.cos(originLat);
-
-    phiMatrix[0][0] = -sLon;
-    phiMatrix[0][1] = cLon;
-    phiMatrix[0][2] = 0.0D;
-    phiMatrix[1][0] = -sLat * cLon;
-    phiMatrix[1][1] = -sLat * sLon;
-    phiMatrix[1][2] = cLat;
-    phiMatrix[2][0] = cLat * cLon;
-    phiMatrix[2][1] = cLat * sLon;
-    phiMatrix[2][2] = sLat;
-
-    return phiMatrix;
-  }
-
-  /**
-   * Create the transposed rotation matrix for converting Easting Northing Up coordinates to Earth Centered Fixed
-   * @param originLon ENU origin longitude (in degrees)
-   * @param originLat ENU origin latitude (in degrees)
-   * @param phiMatrix reusable phi rotation matrix result
-   * @return transposed phi rotation matrix
-   */
-  private static double[][] createTransposedPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
-
-    if (phiMatrix == null) {
-      phiMatrix = new double[3][3];
-    }
-
-    originLon = TO_RADIANS * originLon;
-    originLat = TO_RADIANS * originLat;
-
-    final double sLat = SloppyMath.sin(originLat);
-    final double cLat = SloppyMath.cos(originLat);
-    final double sLon = SloppyMath.sin(originLon);
-    final double cLon = SloppyMath.cos(originLon);
-
-    phiMatrix[0][0] = -sLon;
-    phiMatrix[1][0] = cLon;
-    phiMatrix[2][0] = 0.0D;
-    phiMatrix[0][1] = -sLat * cLon;
-    phiMatrix[1][1] = -sLat * sLon;
-    phiMatrix[2][1] = cLat;
-    phiMatrix[0][2] = cLat * cLon;
-    phiMatrix[1][2] = cLat * sLon;
-    phiMatrix[2][2] = sLat;
-
-    return phiMatrix;
-  }
-
-  /**
-   * Finds a point along a bearing from a given lon,lat geolocation using vincenty's distance formula
-   *
-   * @param lon origin longitude in degrees
-   * @param lat origin latitude 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[] pointFromLonLatBearingVincenty(double lon, double lat, double bearing, double dist, double[] pt) {
-
-    if (pt == null) {
-      pt = new double[2];
-    }
-
-    final double alpha1 = TO_RADIANS * bearing;
-    final double cosA1 = SloppyMath.cos(alpha1);
-    final double sinA1 = SloppyMath.sin(alpha1);
-    final double tanU1 = (1-FLATTENING) * SloppyMath.tan(TO_RADIANS * lat);
-    final double cosU1 = 1 / StrictMath.sqrt((1+tanU1*tanU1));
-    final double sinU1 = tanU1*cosU1;
-    final double sig1 = StrictMath.atan2(tanU1, cosA1);
-    final double sinAlpha = cosU1 * sinA1;
-    final double cosSqAlpha = 1 - sinAlpha*sinAlpha;
-    final double uSq = cosSqAlpha * EP2;
-    final double A = 1 + uSq/16384D*(4096D + uSq * (-768D + uSq * (320D - 175D*uSq)));
-    final double B = uSq/1024D * (256D + uSq * (-128D + uSq * (74D - 47D * uSq)));
-
-    double sigma = dist / (SEMIMINOR_AXIS*A);
-    double sigmaP;
-    double sinSigma, cosSigma, cos2SigmaM, deltaSigma;
-
-    do {
-      cos2SigmaM = SloppyMath.cos(2*sig1 + sigma);
-      sinSigma = SloppyMath.sin(sigma);
-      cosSigma = SloppyMath.cos(sigma);
-
-      deltaSigma = B * sinSigma * (cos2SigmaM + (B/4D) * (cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
-          (B/6) * cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
-      sigmaP = sigma;
-      sigma = dist / (SEMIMINOR_AXIS*A) + deltaSigma;
-    } while (StrictMath.abs(sigma-sigmaP) > 1E-12);
-
-    final double tmp = sinU1*sinSigma - cosU1*cosSigma*cosA1;
-    final double lat2 = StrictMath.atan2(sinU1*cosSigma + cosU1*sinSigma*cosA1,
-        (1-FLATTENING) * StrictMath.sqrt(sinAlpha*sinAlpha + tmp*tmp));
-    final double lambda = StrictMath.atan2(sinSigma*sinA1, cosU1*cosSigma - sinU1*sinSigma*cosA1);
-    final double c = FLATTENING/16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha));
-
-    final double lam = lambda - (1-c) * FLATTENING * sinAlpha *
-        (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2* cos2SigmaM*cos2SigmaM)));
-    pt[0] = GeoUtils.normalizeLon(lon + TO_DEGREES * lam);
-    pt[1] = GeoUtils.normalizeLat(TO_DEGREES * lat2);
-
-    return pt;
-  }
-
-  /**
-   * Finds a point along a bearing from a given lon,lat geolocation using great circle arc
-   *
-   * @param lon origin longitude in degrees
-   * @param lat origin latitude 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 lon, double lat, double bearing, double dist, double[] pt) {
-
-    if (pt == null) {
-      pt = new double[2];
-    }
-
-    lon *= TO_RADIANS;
-    lat *= TO_RADIANS;
-    bearing *= TO_RADIANS;
-
-    final double cLat = SloppyMath.cos(lat);
-    final double sLat = SloppyMath.sin(lat);
-    final double sinDoR = SloppyMath.sin(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
-    final double cosDoR = SloppyMath.cos(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
-
-    pt[1] = SloppyMath.asin(sLat*cosDoR + cLat * sinDoR * SloppyMath.cos(bearing));
-    pt[0] = TO_DEGREES * (lon + Math.atan2(SloppyMath.sin(bearing) * sinDoR * cLat, cosDoR - sLat * SloppyMath.sin(pt[1])));
-    pt[1] *= TO_DEGREES;
-
-    return pt;
-  }
-
-  /**
-   * Finds the bearing (in degrees) between 2 geo points (lon, lat) using great circle arc
-   * @param lon1 first point longitude in degrees
-   * @param lat1 first point latitude in degrees
-   * @param lon2 second point longitude in degrees
-   * @param lat2 second point latitude in degrees
-   * @return the bearing (in degrees) between the two provided points
-   */
-  public static double bearingGreatCircle(double lon1, double lat1, double lon2, double lat2) {
-    double dLon = (lon2 - lon1) * TO_RADIANS;
-    lat2 *= TO_RADIANS;
-    lat1 *= TO_RADIANS;
-    double y = SloppyMath.sin(dLon) * SloppyMath.cos(lat2);
-    double x = SloppyMath.cos(lat1) * SloppyMath.sin(lat2) - SloppyMath.sin(lat1) * SloppyMath.cos(lat2) * SloppyMath.cos(dLon);
-    return Math.atan2(y, x) * TO_DEGREES;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
deleted file mode 100644
index f8cf1da..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
+++ /dev/null
@@ -1,66 +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.util;
-
-/** Represents a lat/lon rectangle. */
-public class GeoRect {
-  public final double minLon;
-  public final double maxLon;
-  public final double minLat;
-  public final double maxLat;
-
-  public GeoRect(double minLon, double maxLon, double minLat, double maxLat) {
-    if (GeoUtils.isValidLon(minLon) == false) {
-      throw new IllegalArgumentException("invalid minLon " + minLon);
-    }
-    if (GeoUtils.isValidLon(maxLon) == false) {
-      throw new IllegalArgumentException("invalid maxLon " + maxLon);
-    }
-    if (GeoUtils.isValidLat(minLat) == false) {
-      throw new IllegalArgumentException("invalid minLat " + minLat);
-    }
-    if (GeoUtils.isValidLat(maxLat) == false) {
-      throw new IllegalArgumentException("invalid maxLat " + maxLat);
-    }
-    this.minLon = minLon;
-    this.maxLon = maxLon;
-    this.minLat = minLat;
-    this.maxLat = maxLat;
-    assert maxLat >= minLat;
-
-    // NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder b = new StringBuilder();
-    b.append("GeoRect(lon=");
-    b.append(minLon);
-    b.append(" TO ");
-    b.append(maxLon);
-    if (maxLon < minLon) {
-      b.append(" (crosses dateline!)");
-    }
-    b.append(" lat=");
-    b.append(minLat);
-    b.append(" TO ");
-    b.append(maxLat);
-    b.append(")");
-
-    return b.toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
deleted file mode 100644
index 092b949..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
+++ /dev/null
@@ -1,497 +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.util;
-
-/**
- * Reusable geo-relation utility methods
- */
-public class GeoRelationUtils {
-
-  /**
-   * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
-   * NOTE: this is a basic method that does not handle dateline or pole crossing. Unwrapping must be done before
-   * calling this method.
-   */
-  public static boolean pointInRectPrecise(final double lon, final double lat, final double minLon,
-                                           final double minLat, final double maxLon, final double maxLat) {
-    return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
-  }
-
-  /**
-   * simple even-odd point in polygon computation
-   *    1.  Determine if point is contained in the longitudinal range
-   *    2.  Determine whether point crosses the edge by computing the latitudinal delta
-   *        between the end-point of a parallel vector (originating at the point) and the
-   *        y-component of the edge sink
-   *
-   * NOTE: Requires polygon point (x,y) order either clockwise or counter-clockwise
-   */
-  public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) {
-    assert x.length == y.length;
-    boolean inPoly = false;
-    /**
-     * Note: This is using a euclidean coordinate system which could result in
-     * upwards of 110KM error at the equator.
-     * TODO convert coordinates to cylindrical projection (e.g. mercator)
-     */
-    for (int i = 1; i < x.length; i++) {
-      if (x[i] <= lon && x[i-1] >= lon || x[i-1] <= lon && x[i] >= lon) {
-        if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) <= lat) {
-          inPoly = !inPoly;
-        }
-      }
-    }
-    return inPoly;
-  }
-
-  /////////////////////////
-  // Rectangle relations
-  /////////////////////////
-
-  public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
-                                     final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
-    return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
-  }
-
-  /**
-   * Computes whether the first (a) rectangle is wholly within another (b) rectangle (shared boundaries allowed)
-   */
-  public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
-                                   final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
-    return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY);
-  }
-
-  public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
-                                    final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
-    return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) ||
-        rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY));
-  }
-
-  /**
-   * Computes whether rectangle a contains rectangle b (touching allowed)
-   */
-  public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
-                                     final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
-    return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY);
-  }
-
-  /**
-   * Computes whether a rectangle intersects another rectangle (crosses, within, touching, etc)
-   */
-  public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
-                                       final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
-    return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) );
-  }
-
-  /////////////////////////
-  // Polygon relations
-  /////////////////////////
-
-  /**
-   * Convenience method for accurately computing whether a rectangle crosses a poly
-   */
-  public static boolean rectCrossesPolyPrecise(final double rMinX, final double rMinY, final double rMaxX,
-                                        final double rMaxY, final double[] shapeX, final double[] shapeY,
-                                        final double sMinX, final double sMinY, final double sMaxX,
-                                        final double sMaxY) {
-    // short-circuit: if the bounding boxes are disjoint then the shape does not cross
-    if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
-      return false;
-    }
-    return rectCrossesPoly(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY);
-  }
-
-  /**
-   * Compute whether a rectangle crosses a shape. (touching not allowed) Includes a flag for approximating the
-   * relation.
-   */
-  public static boolean rectCrossesPolyApprox(final double rMinX, final double rMinY, final double rMaxX,
-                                              final double rMaxY, final double[] shapeX, final double[] shapeY,
-                                              final double sMinX, final double sMinY, final double sMaxX,
-                                              final double sMaxY) {
-    // short-circuit: if the bounding boxes are disjoint then the shape does not cross
-    if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
-      return false;
-    }
-
-    final int polyLength = shapeX.length-1;
-    for (short p=0; p<polyLength; ++p) {
-      if (lineCrossesRect(shapeX[p], shapeY[p], shapeX[p+1], shapeY[p+1], rMinX, rMinY, rMaxX, rMaxY) == true) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Accurately compute (within restrictions of cartesian decimal degrees) whether a rectangle crosses a polygon
-   */
-  private static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX,
-                                         final double rMaxY, final double[] shapeX, final double[] shapeY) {
-    final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} };
-    final int polyLength = shapeX.length-1;
-    double d, s, t, a1, b1, c1, a2, b2, c2;
-    double x00, y00, x01, y01, x10, y10, x11, y11;
-
-    // computes the intersection point between each bbox edge and the polygon edge
-    for (short b=0; b<4; ++b) {
-      a1 = bbox[b+1][1]-bbox[b][1];
-      b1 = bbox[b][0]-bbox[b+1][0];
-      c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1];
-      for (int p=0; p<polyLength; ++p) {
-        a2 = shapeY[p+1]-shapeY[p];
-        b2 = shapeX[p]-shapeX[p+1];
-        // compute determinant
-        d = a1*b2 - a2*b1;
-        if (d != 0) {
-          // lines are not parallel, check intersecting points
-          c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
-          s = (1/d)*(b2*c1 - b1*c2);
-          t = (1/d)*(a1*c2 - a2*c1);
-          x00 = StrictMath.min(bbox[b][0], bbox[b+1][0]) - GeoUtils.TOLERANCE;
-          x01 = StrictMath.max(bbox[b][0], bbox[b+1][0]) + GeoUtils.TOLERANCE;
-          y00 = StrictMath.min(bbox[b][1], bbox[b+1][1]) - GeoUtils.TOLERANCE;
-          y01 = StrictMath.max(bbox[b][1], bbox[b+1][1]) + GeoUtils.TOLERANCE;
-          x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - GeoUtils.TOLERANCE;
-          x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + GeoUtils.TOLERANCE;
-          y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - GeoUtils.TOLERANCE;
-          y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + GeoUtils.TOLERANCE;
-          // check whether the intersection point is touching one of the line segments
-          boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
-              || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
-          // if line segments are not touching and the intersection point is within the range of either segment
-          if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
-            return true;
-          }
-        }
-      } // for each poly edge
-    } // for each bbox edge
-    return false;
-  }
-
-  private static boolean lineCrossesRect(double aX1, double aY1, double aX2, double aY2,
-                                         final double rMinX, final double rMinY, final double rMaxX, final double rMaxY) {
-    // short-circuit: if one point inside rect, other outside
-    if (pointInRectPrecise(aX1, aY1, rMinX, rMinY, rMaxX, rMaxY) ?
-        !pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY) : pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)) {
-      return true;
-    }
-
-    return lineCrossesLine(aX1, aY1, aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)
-        || lineCrossesLine(aX1, aY1, aX2, aY2, rMaxX, rMinY, rMinX, rMaxY);
-  }
-
-  private static boolean lineCrossesLine(final double aX1, final double aY1, final double aX2, final double aY2,
-                                         final double bX1, final double bY1, final double bX2, final double bY2) {
-    // determine if three points are ccw (right-hand rule) by computing the determinate
-    final double aX2X1d = aX2 - aX1;
-    final double aY2Y1d = aY2 - aY1;
-    final double bX2X1d = bX2 - bX1;
-    final double bY2Y1d = bY2 - bY1;
-
-    final double t1B = aX2X1d * (bY2 - aY1) - aY2Y1d * (bX2 - aX1);
-    final double test1 = (aX2X1d * (bY1 - aY1) - aY2Y1d * (bX1 - aX1)) * t1B;
-    final double t2B = bX2X1d * (aY2 - bY1) - bY2Y1d * (aX2 - bX1);
-    final double test2 = (bX2X1d * (aY1 - bY1) - bY2Y1d * (aX1 - bX1)) * t2B;
-
-    if (test1 < 0 && test2 < 0) {
-      return true;
-    }
-
-    if (test1 == 0 || test2 == 0) {
-      // vertically collinear
-      if (aX1 == aX2 || bX1 == bX2) {
-        final double minAy = Math.min(aY1, aY2);
-        final double maxAy = Math.max(aY1, aY2);
-        final double minBy = Math.min(bY1, bY2);
-        final double maxBy = Math.max(bY1, bY2);
-
-        return !(minBy >= maxAy || maxBy <= minAy);
-      }
-      // horizontally collinear
-      final double minAx = Math.min(aX1, aX2);
-      final double maxAx = Math.max(aX1, aX2);
-      final double minBx = Math.min(bX1, bX2);
-      final double maxBx = Math.max(bX1, bX2);
-
-      return !(minBx >= maxAx || maxBx <= minAx);
-    }
-    return false;
-  }
-
-  public static boolean rectWithinPolyPrecise(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                       final double[] shapeX, final double[] shapeY, final double sMinX,
-                                       final double sMinY, final double sMaxX, final double sMaxY) {
-    // check if rectangle crosses poly (to handle concave/pacman polys), then check that all 4 corners
-    // are contained
-    return !(rectCrossesPolyPrecise(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY) ||
-        !pointInPolygon(shapeX, shapeY, rMinY, rMinX) || !pointInPolygon(shapeX, shapeY, rMinY, rMaxX) ||
-        !pointInPolygon(shapeX, shapeY, rMaxY, rMaxX) || !pointInPolygon(shapeX, shapeY, rMaxY, rMinX));
-  }
-
-  /**
-   * Computes whether a rectangle is within a given polygon (shared boundaries allowed)
-   */
-  public static boolean rectWithinPolyApprox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                       final double[] shapeX, final double[] shapeY, final double sMinX,
-                                       final double sMinY, final double sMaxX, final double sMaxY) {
-    // approximation: check if rectangle crosses poly (to handle concave/pacman polys), then check one of the corners
-    // are contained
-
-    // short-cut: if bounding boxes cross, rect is not within
-     if (rectCrosses(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY) == true) {
-       return false;
-     }
-
-     return !(rectCrossesPolyApprox(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY)
-         || !pointInPolygon(shapeX, shapeY, rMinY, rMinX));
-  }
-
-  /////////////////////////
-  // Circle relations
-  /////////////////////////
-
-  private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX,
-                                                final double rMaxY, final double centerLon, final double centerLat,
-                                                final double radiusMeters, final boolean approx) {
-    if (approx == true) {
-      return rectAnyCornersInCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
-    }
-    double w = Math.abs(rMaxX - rMinX);
-    if (w <= 90.0) {
-      return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
-    }
-    // partition
-    w /= 4;
-    final double p1 = rMinX + w;
-    final double p2 = p1 + w;
-    final double p3 = p2 + w;
-
-    return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p1) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p1) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p2) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p2) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p3) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p3) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
-        || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
-  }
-
-  private static boolean rectAnyCornersInCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                                      final double centerLon, final double centerLat, final double radiusMeters) {
-    return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= 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 lon/lat 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 rMinX, final double rMinY, final double rMaxX,
-                                                     final double rMaxY, final double centerLon, final double centerLat,
-                                                     final double radiusMeters, final boolean approx) {
-    if (approx == true) {
-      return rectAnyCornersOutsideCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
-    }
-    // if span is less than 70 degrees we can approximate using distance alone
-    if (Math.abs(rMaxX - rMinX) <= 70.0) {
-      return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) > radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) > radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) > radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) > radiusMeters;
-    }
-    return rectCrossesOblateCircle(centerLon, centerLat, radiusMeters, rMinX, rMinY, rMaxX, rMaxY);
-  }
-
-  /**
-   * Compute whether the rectangle (defined by min/max Lon/Lat) crosses a potentially oblate circle
-   *
-   * TODO benchmark for replacing existing rectCrossesCircle.
-   */
-  public static boolean rectCrossesOblateCircle(double centerLon, double centerLat, double radiusMeters, double rMinLon, double rMinLat, double  rMaxLon, double rMaxLat) {
-    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 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, p1)) > radiusMeters
-          || (d2 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, maxLon)) > radiusMeters
-          || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, p1) > radiusMeters
-          || GeoDistanceUtils.haversin(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(p1, rMinLat,
-          GeoProjectionUtils.bearingGreatCircle(p1, rMinLat, p1, rMaxLat), radiusMeters - d1, pt))[1] < rMinLat || pt[1] < rMaxLat
-          || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
-          GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, maxLon, rMaxLat), radiusMeters - d2, pt))[1] < rMinLat || pt[1] < rMaxLat
-          || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
-          GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, (midLon = p1 + 0.5*(maxLon - p1)), rMaxLat),
-          radiusMeters - GeoDistanceUtils.haversin(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 rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                                           final double centerLon, final double centerLat, final double radiusMeters) {
-    return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 > radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radiusMeters
-        || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radiusMeters;
-  }
-
-  public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                         final double centerLon, final double centerLat, final double radiusMeters) {
-    return rectWithinCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
-  }
-
-  public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                         final double centerLon, final double centerLat, final double radiusMeters,
-                                         final boolean approx) {
-    return rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) == false;
-  }
-
-  /**
-   * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
-   * 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 rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                          final double centerLon, final double centerLat, final double radiusMeters) {
-    return rectCrossesCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
-  }
-
-  public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                          final double centerLon, final double centerLat, final double radiusMeters,
-                                          final boolean approx) {
-    if (approx == true) {
-      return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx)
-          || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
-    }
-
-    return (rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) &&
-        rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx))
-        || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
-  }
-
-  private static boolean isClosestPointOnRectWithinRange(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
-                                                         final double centerLon, final double centerLat, final double radiusMeters,
-                                                         final boolean approx) {
-    double[] closestPt = {0, 0};
-    GeoDistanceUtils.closestPointOnBBox(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, closestPt);
-    boolean haverShortCut = GeoDistanceUtils.haversin(centerLat, centerLon, closestPt[1], closestPt[0]) <= radiusMeters;
-    if (approx == true || haverShortCut == true) {
-      return haverShortCut;
-    }
-    double lon1 = rMinX;
-    double lon2 = rMaxX;
-    double lat1 = rMinY;
-    double lat2 = rMaxY;
-    if (closestPt[0] == rMinX || closestPt[0] == rMaxX) {
-      lon1 = closestPt[0];
-      lon2 = lon1;
-    } else if (closestPt[1] == rMinY || closestPt[1] == rMaxY) {
-      lat1 = closestPt[1];
-      lat2 = lat1;
-    }
-
-    return lineCrossesSphere(lon1, lat1, 0, lon2, lat2, 0, centerLon, centerLat, 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 lon1, double lat1, double alt1, double lon2,
-                                           double lat2, double alt2, double centerLon, double centerLat,
-                                           double centerAlt, double radiusMeters) {
-    // convert to cartesian 3d (in meters)
-    double[] ecf1 = GeoProjectionUtils.llaToECF(lon1, lat1, alt1, null);
-    double[] ecf2 = GeoProjectionUtils.llaToECF(lon2, lat2, alt2, null);
-    double[] cntr = GeoProjectionUtils.llaToECF(centerLon, centerLat, centerAlt, null);
-
-    // convert radius from arc radius to cartesian radius
-    double[] oneEighty = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(centerLon, centerLat, 180.0d, radiusMeters, new double[3]);
-    GeoProjectionUtils.llaToECF(oneEighty[0], oneEighty[1], 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/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
deleted file mode 100644
index a2d0fd1..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
+++ /dev/null
@@ -1,237 +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.util;
-
-import java.util.ArrayList;
-
-import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-/**
- * Basic reusable geo-spatial utility methods
- *
- * @lucene.experimental
- */
-public final class GeoUtils {
-  public static final short BITS = 31;
-  private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
-  private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
-  public static final double TOLERANCE = 1E-6;
-
-  /** Minimum longitude value. */
-  public static final double MIN_LON_INCL = -180.0D;
-
-  /** Maximum longitude value. */
-  public static final double MAX_LON_INCL = 180.0D;
-
-  /** Minimum latitude value. */
-  public static final double MIN_LAT_INCL = -90.0D;
-
-  /** Maximum latitude value. */
-  public static final double MAX_LAT_INCL = 90.0D;
-
-  // No instance:
-  private GeoUtils() {
-  }
-
-  public static final Long mortonHash(final double lon, final double lat) {
-    return BitUtil.interleave(scaleLon(lon), scaleLat(lat));
-  }
-
-  public static final double mortonUnhashLon(final long hash) {
-    return unscaleLon(BitUtil.deinterleave(hash));
-  }
-
-  public static final double mortonUnhashLat(final long hash) {
-    return unscaleLat(BitUtil.deinterleave(hash >>> 1));
-  }
-
-  private static final long scaleLon(final double val) {
-    return (long) ((val-MIN_LON_INCL) * LON_SCALE);
-  }
-
-  private static final long scaleLat(final double val) {
-    return (long) ((val-MIN_LAT_INCL) * LAT_SCALE);
-  }
-
-  private static final double unscaleLon(final long val) {
-    return (val / LON_SCALE) + MIN_LON_INCL;
-  }
-
-  private static final double unscaleLat(final long val) {
-    return (val / LAT_SCALE) + MIN_LAT_INCL;
-  }
-
-  /**
-   * Compare two position values within a {@link org.apache.lucene.util.GeoUtils#TOLERANCE} factor
-   */
-  public static double compare(final double v1, final double v2) {
-    final double delta = v1-v2;
-    return Math.abs(delta) <= TOLERANCE ? 0 : delta;
-  }
-
-  /**
-   * Puts longitude in range of -180 to +180.
-   */
-  public static double normalizeLon(double lon_deg) {
-    if (lon_deg >= -180 && lon_deg <= 180) {
-      return lon_deg; //common case, and avoids slight double precision shifting
-    }
-    double off = (lon_deg + 180) % 360;
-    if (off < 0) {
-      return 180 + off;
-    } else if (off == 0 && lon_deg > 0) {
-      return 180;
-    } else {
-      return -180 + off;
-    }
-  }
-
-  /**
-   * Puts latitude in range of -90 to 90.
-   */
-  public static double normalizeLat(double lat_deg) {
-    if (lat_deg >= -90 && lat_deg <= 90) {
-      return lat_deg; //common case, and avoids slight double precision shifting
-    }
-    double off = Math.abs((lat_deg + 90) % 360);
-    return (off <= 180 ? off : 360-off) - 90;
-  }
-
-  public static String geoTermToString(long term) {
-    StringBuilder s = new StringBuilder(64);
-    final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term);
-    for (int i = 0; i < numberOfLeadingZeros; i++) {
-      s.append('0');
-    }
-    if (term != 0) {
-      s.append(Long.toBinaryString(term));
-    }
-    return s.toString();
-  }
-
-  /**
-   * Converts a given circle (defined as a point/radius) to an approximated line-segment polygon
-   *
-   * @param lon longitudinal center of circle (in degrees)
-   * @param lat latitudinal center of circle (in degrees)
-   * @param radiusMeters distance radius of circle (in meters)
-   * @return a list of lon/lat points representing the circle
-   */
-  @SuppressWarnings({"unchecked","rawtypes"})
-  public static ArrayList<double[]> circleToPoly(final double lon, final double lat, final double radiusMeters) {
-    double angle;
-    // a little under-sampling (to limit the number of polygonal points): using archimedes estimation of pi
-    final int sides = 25;
-    ArrayList<double[]> geometry = new ArrayList();
-    double[] lons = new double[sides];
-    double[] lats = new double[sides];
-
-    double[] pt = new double[2];
-    final int sidesLen = sides-1;
-    for (int i=0; i<sidesLen; ++i) {
-      angle = (i*360/sides);
-      pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(lon, lat, angle, radiusMeters, pt);
-      lons[i] = pt[0];
-      lats[i] = pt[1];
-    }
-    // close the poly
-    lons[sidesLen] = lons[0];
-    lats[sidesLen] = lats[0];
-    geometry.add(lons);
-    geometry.add(lats);
-
-    return geometry;
-  }
-
-  /**
-   * Compute Bounding Box for a circle using WGS-84 parameters
-   */
-  public static GeoRect circleToBBox(final double centerLon, final double centerLat, final double radiusMeters) {
-    final double radLat = TO_RADIANS * centerLat;
-    final double radLon = TO_RADIANS * centerLon;
-    double radDistance = radiusMeters / GeoProjectionUtils.SEMIMAJOR_AXIS;
-    double minLat = radLat - radDistance;
-    double maxLat = radLat + radDistance;
-    double minLon;
-    double maxLon;
-
-    if (minLat > GeoProjectionUtils.MIN_LAT_RADIANS && maxLat < GeoProjectionUtils.MAX_LAT_RADIANS) {
-      double deltaLon = SloppyMath.asin(SloppyMath.sin(radDistance) / SloppyMath.cos(radLat));
-      minLon = radLon - deltaLon;
-      if (minLon < GeoProjectionUtils.MIN_LON_RADIANS) {
-        minLon += 2d * StrictMath.PI;
-      }
-      maxLon = radLon + deltaLon;
-      if (maxLon > GeoProjectionUtils.MAX_LON_RADIANS) {
-        maxLon -= 2d * StrictMath.PI;
-      }
-    } else {
-      // a pole is within the distance
-      minLat = StrictMath.max(minLat, GeoProjectionUtils.MIN_LAT_RADIANS);
-      maxLat = StrictMath.min(maxLat, GeoProjectionUtils.MAX_LAT_RADIANS);
-      minLon = GeoProjectionUtils.MIN_LON_RADIANS;
-      maxLon = GeoProjectionUtils.MAX_LON_RADIANS;
-    }
-
-    return new GeoRect(TO_DEGREES * minLon, TO_DEGREES * maxLon, TO_DEGREES * minLat, TO_DEGREES * maxLat);
-  }
-
-  /**
-   * Compute Bounding Box for a polygon using WGS-84 parameters
-   */
-  public static GeoRect polyToBBox(double[] polyLons, double[] polyLats) {
-    if (polyLons.length != polyLats.length) {
-      throw new IllegalArgumentException("polyLons and polyLats must be equal length");
-    }
-
-    double minLon = Double.POSITIVE_INFINITY;
-    double maxLon = Double.NEGATIVE_INFINITY;
-    double minLat = Double.POSITIVE_INFINITY;
-    double maxLat = Double.NEGATIVE_INFINITY;
-
-    for (int i=0;i<polyLats.length;i++) {
-      if (GeoUtils.isValidLon(polyLons[i]) == false) {
-        throw new IllegalArgumentException("invalid polyLons[" + i + "]=" + polyLons[i]);
-      }
-      if (GeoUtils.isValidLat(polyLats[i]) == false) {
-        throw new IllegalArgumentException("invalid polyLats[" + i + "]=" + polyLats[i]);
-      }
-      minLon = Math.min(polyLons[i], minLon);
-      maxLon = Math.max(polyLons[i], maxLon);
-      minLat = Math.min(polyLats[i], minLat);
-      maxLat = Math.max(polyLats[i], maxLat);
-    }
-    // expand bounding box by TOLERANCE factor to handle round-off error
-    return new GeoRect(Math.max(minLon - TOLERANCE, MIN_LON_INCL), Math.min(maxLon + TOLERANCE, MAX_LON_INCL),
-        Math.max(minLat - TOLERANCE, MIN_LAT_INCL), Math.min(maxLat + TOLERANCE, MAX_LAT_INCL));
-  }
-
-  /**
-   * validates latitude value is within standard +/-90 coordinate bounds
-   */
-  public static boolean isValidLat(double lat) {
-    return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL;
-  }
-
-  /**
-   * validates longitude value is within standard +/-180 coordinate bounds
-   */
-  public static boolean isValidLon(double lon) {
-    return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/package.html
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/package.html b/lucene/sandbox/src/java/org/apache/lucene/util/package.html
deleted file mode 100644
index 9a8856c..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/package.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-
-<!-- not a package-info.java, because we already defined this package in core/ -->
-
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-This package contains utility APIs, currently for geospatial searching.
-</body>
-</html>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
deleted file mode 100644
index a91cbd6..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
+++ /dev/null
@@ -1,386 +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.search;
-
-import org.apache.lucene.analysis.MockAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.document.StringField;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.BaseGeoPointTestCase;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.TestUtil;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import static org.apache.lucene.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
-
-/**
- * Unit testing for basic GeoPoint query logic
- *
- * @lucene.experimental
- */
-
-public class TestGeoPointQuery extends BaseGeoPointTestCase {
-
-  private static Directory directory = null;
-  private static IndexReader reader = null;
-  private static IndexSearcher searcher = null;
-
-  @Override
-  protected boolean forceSmall() {
-    return false;
-  }
-
-  @Override
-  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
-    doc.add(new GeoPointField(field, lon, lat, Field.Store.NO));
-  }
-
-  @Override
-  protected Query newRectQuery(String field, GeoRect rect) {
-    return new GeoPointInBBoxQuery(field, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
-  }
-
-  @Override
-  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
-    return new GeoPointDistanceQuery(field, centerLon, centerLat, radiusMeters);
-  }
-
-  @Override
-  protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
-    return new GeoPointDistanceRangeQuery(field, centerLon, centerLat, minRadiusMeters, radiusMeters);
-  }
-
-  @Override
-  protected Query newPolygonQuery(String field, double[] lats, double[] lons) {
-    return new GeoPointInPolygonQuery(field, lons, lats);
-  }
-
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    directory = newDirectory();
-
-    RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
-            newIndexWriterConfig(new MockAnalyzer(random()))
-                    .setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
-                    .setMergePolicy(newLogMergePolicy()));
-
-    // create some simple geo points
-    final FieldType storedPoint = new FieldType(GeoPointField.TYPE_STORED);
-    // this is a simple systematic test
-    GeoPointField[] pts = new GeoPointField[] {
-         new GeoPointField(FIELD_NAME, -96.774, 32.763420, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.7759895324707, 32.7559529921407, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.77701950073242, 32.77866942010977, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.7706036567688, 32.7756745755423, storedPoint),
-         new GeoPointField(FIELD_NAME, -139.73458170890808, 27.703618681345585, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.4538113027811, 32.94823588839368, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.65084838867188, 33.06047141970814, storedPoint),
-         new GeoPointField(FIELD_NAME, -96.7772, 32.778650, storedPoint),
-         new GeoPointField(FIELD_NAME, -177.23537676036358, -88.56029371730983, storedPoint),
-         new GeoPointField(FIELD_NAME, -26.779373834241003, 33.541429799076354, storedPoint),
-         new GeoPointField(FIELD_NAME, -77.35379276106497, 26.774024500421728, storedPoint),
-         new GeoPointField(FIELD_NAME, -14.796283808944777, -90.0, storedPoint),
-         new GeoPointField(FIELD_NAME, -178.8538113027811, 32.94823588839368, storedPoint),
-         new GeoPointField(FIELD_NAME, 178.8538113027811, 32.94823588839368, storedPoint),
-         new GeoPointField(FIELD_NAME, -73.998776, 40.720611, storedPoint),
-         new GeoPointField(FIELD_NAME, -179.5, -44.5, storedPoint)};
-
-    for (GeoPointField p : pts) {
-        Document doc = new Document();
-        doc.add(p);
-        writer.addDocument(doc);
-    }
-
-    // add explicit multi-valued docs
-    for (int i=0; i<pts.length; i+=2) {
-      Document doc = new Document();
-      doc.add(pts[i]);
-      doc.add(pts[i+1]);
-      writer.addDocument(doc);
-    }
-
-    // index random string documents
-    for (int i=0; i<random().nextInt(10); ++i) {
-      Document doc = new Document();
-      doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
-      writer.addDocument(doc);
-    }
-
-    reader = writer.getReader();
-    searcher = newSearcher(reader);
-    writer.close();
-  }
-
-  @AfterClass
-  public static void afterClass() throws Exception {
-    searcher = null;
-    reader.close();
-    reader = null;
-    directory.close();
-    directory = null;
-  }
-
-  private TopDocs bboxQuery(double minLon, double minLat, double maxLon, double maxLat, int limit) throws Exception {
-    GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, minLon, minLat, maxLon, maxLat);
-    return searcher.search(q, limit);
-  }
-
-  private TopDocs polygonQuery(double[] lon, double[] lat, int limit) throws Exception {
-    GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, lon, lat);
-    return searcher.search(q, limit);
-  }
-
-  private TopDocs geoDistanceQuery(double lon, double lat, double radius, int limit) throws Exception {
-    GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, lon, lat, radius);
-    return searcher.search(q, limit);
-  }
-
-  @Override
-  protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
-    if (GeoUtils.compare(pointLon, rect.minLon) == 0.0 ||
-        GeoUtils.compare(pointLon, rect.maxLon) == 0.0 ||
-        GeoUtils.compare(pointLat, rect.minLat) == 0.0 ||
-        GeoUtils.compare(pointLat, rect.maxLat) == 0.0) {
-      // Point is very close to rect boundary
-      return null;
-    }
-
-    if (rect.minLon < rect.maxLon) {
-      return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
-    } else {
-      // Rect crosses dateline:
-      return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, -180.0, rect.minLat, rect.maxLon, rect.maxLat)
-          || GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, 180.0, rect.maxLat);
-    }
-  }
-
-  @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.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0 <= 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.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0;
-      return d >= minRadiusMeters && d <= radiusMeters;
-    }
-  }
-
-  private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
-                                               final double radius) {
-    final long hashedCntr = GeoUtils.mortonHash(centerLon, centerLat);
-    centerLon = GeoUtils.mortonUnhashLon(hashedCntr);
-    centerLat = GeoUtils.mortonUnhashLat(hashedCntr);
-    final long hashedPt = GeoUtils.mortonHash(ptLon, ptLat);
-    ptLon = GeoUtils.mortonUnhashLon(hashedPt);
-    ptLat = GeoUtils.mortonUnhashLat(hashedPt);
-
-    double ptDistance = SloppyMath.haversin(centerLat, centerLon, ptLat, ptLon)*1000.0;
-    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 testRectCrossesCircle() throws Exception {
-    assertTrue(GeoRelationUtils.rectCrossesCircle(-180, -90, 180, 0.0, 0.667, 0.0, 88000.0));
-  }
-
-  private TopDocs geoDistanceRangeQuery(double lon, double lat, double minRadius, double maxRadius, int limit)
-      throws Exception {
-    GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, lon, lat, minRadius, maxRadius);
-    return searcher.search(q, limit);
-  }
-
-  public void testBBoxQuery() throws Exception {
-    TopDocs td = bboxQuery(-96.7772, 32.778650, -96.77690000, 32.778950, 5);
-    assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
-  }
-
-  public void testPolyQuery() throws Exception {
-    TopDocs td = polygonQuery(new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
-            -96.6041564, -96.7449188, -96.76826477, -96.7682647},
-        new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
-            33.1369762, 33.1162747, 33.073130, 33.073130}, 5);
-    assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
-  }
-
-  public void testPacManPolyQuery() throws Exception {
-    // pacman
-    double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
-    double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
-
-    // shape bbox
-    double xMinA = -10;
-    double xMaxA = 10;
-    double yMinA = -10;
-    double yMaxA = 10;
-
-    // candidate crosses cell
-    double xMin = 2;//-5;
-    double xMax = 11;//0.000001;
-    double yMin = -1;//0;
-    double yMax = 1;//5;
-
-    // test cell crossing poly
-    assertTrue(GeoRelationUtils.rectCrossesPolyApprox(xMin, yMin, xMax, yMax, px, py, xMinA, yMinA, xMaxA, yMaxA));
-    assertFalse(GeoRelationUtils.rectCrossesPolyApprox(-5, 0,  0.000001, 5, px, py, xMin, yMin, xMax, yMax));
-    assertTrue(GeoRelationUtils.rectWithinPolyApprox(-5, 0, -2, 5, px, py, xMin, yMin, xMax, yMax));
-  }
-
-  public void testBBoxCrossDateline() throws Exception {
-    TopDocs td = bboxQuery(179.0, -45.0, -179.0, -44.0, 20);
-    assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
-  }
-
-  public void testWholeMap() throws Exception {
-    TopDocs td = bboxQuery(GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LAT_INCL, 20);
-    assertEquals("testWholeMap failed", 24, td.totalHits);
-    td = polygonQuery(new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
-        new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL}, 20);
-    assertEquals("testWholeMap failed", 24, td.totalHits);
-  }
-
-  public void smallTest() throws Exception {
-    TopDocs td = geoDistanceQuery(-73.998776, 40.720611, 1, 20);
-    assertEquals("smallTest failed", 2, td.totalHits);
-  }
-
-  public void testInvalidBBox() throws Exception {
-    try {
-      bboxQuery(179.0, -92.0, 181.0, -91.0, 20);
-    } catch(Exception e) {
-      return;
-    }
-    throw new Exception("GeoBoundingBox should not accept invalid lat/lon");
-  }
-
-  public void testGeoDistanceQuery() throws Exception {
-    TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000, 20);
-    assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
-  }
-
-  /** see https://issues.apache.org/jira/browse/LUCENE-6905 */
-  public void testNonEmptyTermsEnum() throws Exception {
-    TopDocs td = geoDistanceQuery(-177.23537676036358, -88.56029371730983, 7757.999232959935, 20);
-    assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
-  }
-
-  public void testMultiValuedQuery() throws Exception {
-    TopDocs td = bboxQuery(-96.4538113027811, 32.7559529921407, -96.7706036567688, 32.7756745755423, 20);
-    // 3 single valued docs + 2 multi-valued docs
-    assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
-  }
-
-  public void testTooBigRadius() throws Exception {
-    try {
-      geoDistanceQuery(0.0, 85.0, 4000000, 20);
-    } catch (IllegalArgumentException e) {
-      e.getMessage().contains("exceeds maxRadius");
-    }
-  }
-
-  /**
-   * Explicitly large
-   */
-  public void testGeoDistanceQueryHuge() throws Exception {
-    TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000000, 20);
-    assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
-  }
-
-  public void testGeoDistanceQueryCrossDateline() throws Exception {
-    TopDocs td = geoDistanceQuery(-179.9538113027811, 32.94823588839368, 120000, 20);
-    assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
-  }
-
-  public void testInvalidGeoDistanceQuery() throws Exception {
-    try {
-      geoDistanceQuery(181.0, 92.0, 120000, 20);
-    } catch (Exception e) {
-      return;
-    }
-    throw new Exception("GeoDistanceQuery should not accept invalid lat/lon as origin");
-  }
-
-  public void testMaxDistanceRangeQuery() throws Exception {
-    TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
-    assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
-  }
-
-  public void testMortonEncoding() throws Exception {
-    long hash = GeoUtils.mortonHash(180, 90);
-    assertEquals(180.0, GeoUtils.mortonUnhashLon(hash), 0);
-    assertEquals(90.0, GeoUtils.mortonUnhashLat(hash), 0);
-  }
-
-  public void testEncodeDecode() throws Exception {
-    int iters = atLeast(10000);
-    boolean small = random().nextBoolean();
-    for(int iter=0;iter<iters;iter++) {
-      double lat = randomLat(small);
-      double lon = randomLon(small);
-
-      long enc = GeoUtils.mortonHash(lon, lat);
-      double latEnc = GeoUtils.mortonUnhashLat(enc);
-      double lonEnc = GeoUtils.mortonUnhashLon(enc);
-
-      assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoUtils.TOLERANCE);
-      assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoUtils.TOLERANCE);
-    }
-  }
-
-  public void testScaleUnscaleIsStable() throws Exception {
-    int iters = atLeast(1000);
-    boolean small = random().nextBoolean();
-    for(int iter=0;iter<iters;iter++) {
-      double lat = randomLat(small);
-      double lon = randomLon(small);
-
-      long enc = GeoUtils.mortonHash(lon, lat);
-      double latEnc = GeoUtils.mortonUnhashLat(enc);
-      double lonEnc = GeoUtils.mortonUnhashLon(enc);
-
-      long enc2 = GeoUtils.mortonHash(lon, lat);
-      double latEnc2 = GeoUtils.mortonUnhashLat(enc2);
-      double lonEnc2 = GeoUtils.mortonUnhashLon(enc2);
-      assertEquals(latEnc, latEnc2, 0.0);
-      assertEquals(lonEnc, lonEnc2, 0.0);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/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 e898f1c..5ae9503 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -18,9 +18,9 @@ package org.apache.lucene.search;
 
 import org.apache.lucene.document.LatLonPoint;
 import org.apache.lucene.document.Document;
-import org.apache.lucene.util.BaseGeoPointTestCase;
-import org.apache.lucene.util.GeoDistanceUtils;
-import org.apache.lucene.util.GeoRect;
+import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
+import org.apache.lucene.spatial.util.GeoRect;
 
 public class TestLatLonPointQueries extends BaseGeoPointTestCase {
   // todo deconflict GeoPoint and BKD encoding methods and error tolerance