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 21:41:26 UTC

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

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/187fe876/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 4b4f8d7..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
+++ /dev/null
@@ -1,755 +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.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());
-  }
-
-  // A particularly tricky adversary for BKD tree:
-  @Nightly
-  public void testSamePointManyTimes() throws Exception {
-    int numPoints = atLeast(1000);
-    // TODO: GeoUtils are potentially slow if we use small=false with heavy testing
-    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);
-  }
-
-  @Nightly
-  public void testAllLatEqual() throws Exception {
-    int numPoints = atLeast(10000);
-    // TODO: GeoUtils are potentially slow if we use small=false with heavy testing
-    // boolean small = random().nextBoolean();
-    boolean small = true;
-    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);
-  }
-
-  @Nightly
-  public void testAllLonEqual() throws Exception {
-    int numPoints = atLeast(10000);
-    // TODO: GeoUtils are potentially slow if we use small=false with heavy testing
-    // boolean small = random().nextBoolean();
-    boolean small = true;
-    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);
-  }
-
-  @Nightly
-  public void testMultiValued() throws Exception {
-    int numPoints = atLeast(10000);
-    // Every doc has 2 points:
-    double[] lats = new double[2*numPoints];
-    double[] lons = new double[2*numPoints];
-    Directory dir = newDirectory();
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    initIndexWriterConfig(FIELD_NAME, iwc);
-
-    // We rely on docID order:
-    iwc.setMergePolicy(newLogMergePolicy());
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
-
-    // TODO: GeoUtils are potentially slow if we use small=false with heavy testing
-    boolean small = random().nextBoolean();
-    //boolean small = true;
-
-    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);
-    }
-
-    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 + " bbox=" + rect);
-      }
-
-      Query query = newBBoxQuery(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];
-
-    // TODO: GeoUtils are potentially slow if we use small=false with heavy testing
-    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 (lat0 == lat1) {
-      lat1 = randomLat(small);
-    }
-
-    if (lon0 == lon1) {
-      lon1 = randomLon(small);
-    }
-
-    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 newBBoxQuery(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(final boolean small, final double[] lats, final 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);
-    }
-    initIndexWriterConfig(FIELD_NAME, iwc);
-    Directory dir;
-    if (lats.length > 100000) {
-      dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
-    } else {
-      dir = newDirectory();
-    }
-
-    final 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:
-    final 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()) {
-              // BBox: don't allow dateline crossing when testing small:
-              final GeoRect bbox = randomRect(small, small == false);
-
-              query = newBBoxQuery(FIELD_NAME, bbox);
-
-              verifyHits = new VerifyHits() {
-                @Override
-                protected Boolean shouldMatch(double pointLat, double pointLon) {
-                  return rectContainsPoint(bbox, 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);
-
-              final double radiusMeters;
-              final 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) {
-                  final double radius = radiusMeters;
-                  final double minRadius = minRadiusMeters;
-                  if (rangeQuery == false) {
-                    return circleContainsPoint(centerLat, centerLon, radius, pointLat, pointLon);
-                  } else {
-                    return distanceRangeContainsPoint(centerLat, centerLon, minRadius, radius, 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());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/187fe876/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 fa662f9..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/187fe876/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..cf8d223
--- /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.NumericType.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.NumericType.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 FieldType.NumericType#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.NumericType.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/187fe876/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/187fe876/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..26b0a24
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
@@ -0,0 +1,186 @@
+/*
+ * 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.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) throws IOException {
+    if (getBoost() != 1f) {
+      return super.rewrite(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/187fe876/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/187fe876/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..2173054
--- /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 java.io.IOException;
+
+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) throws IOException {
+    if (getBoost() != 1f) {
+      return super.rewrite(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);
+    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/187fe876/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..a216aa0
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
@@ -0,0 +1,173 @@
+/*
+ * 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.util.ToStringUtils;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FieldValueQuery;
+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 org.apache.lucene.search.NumericRangeQuery} 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) throws IOException {
+    if (getBoost() != 1f) {
+      return super.rewrite(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 bq = new BooleanQuery.Builder();
+
+      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
+      bq.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
+      bq.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+      return bq.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("]")
+        .append(ToStringUtils.boost(getBoost()))
+        .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;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/187fe876/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();
+  }
+}