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 2018/07/25 23:53:21 UTC

[1/4] lucene-solr:branch_7x: LUCENE-7314: Graduate LatLonPoint and query classes to core

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x cf9c3c11a -> 950b7b6b1


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/search/TestNearest.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestNearest.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestNearest.java
new file mode 100644
index 0000000..cb42f22
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestNearest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.Arrays;
+import java.util.Comparator;
+
+import org.apache.lucene.codecs.Codec;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.LatLonDocValuesField;
+import org.apache.lucene.document.LatLonPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.search.NearestNeighbor.NearestHit;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+ 
+public class TestNearest extends LuceneTestCase {
+
+  public void testNearestNeighborWithDeletedDocs() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
+    Document doc = new Document();
+    doc.add(new LatLonPoint("point", 40.0, 50.0));
+    doc.add(new StringField("id", "0", Field.Store.YES));
+    w.addDocument(doc);
+
+    doc = new Document();
+    doc.add(new LatLonPoint("point", 45.0, 55.0));
+    doc.add(new StringField("id", "1", Field.Store.YES));
+    w.addDocument(doc);
+
+    DirectoryReader r = w.getReader();
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    IndexSearcher s = newSearcher(r, false);
+    FieldDoc hit = (FieldDoc) LatLonPointPrototypeQueries.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
+    assertEquals("0", r.document(hit.doc).getField("id").stringValue());
+    r.close();
+
+    w.deleteDocuments(new Term("id", "0"));
+    r = w.getReader();
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    s = newSearcher(r, false);
+    hit = (FieldDoc) LatLonPointPrototypeQueries.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
+    assertEquals("1", r.document(hit.doc).getField("id").stringValue());
+    r.close();
+    w.close();
+    dir.close();
+  }
+
+  public void testNearestNeighborWithAllDeletedDocs() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
+    Document doc = new Document();
+    doc.add(new LatLonPoint("point", 40.0, 50.0));
+    doc.add(new StringField("id", "0", Field.Store.YES));
+    w.addDocument(doc);
+    doc = new Document();
+    doc.add(new LatLonPoint("point", 45.0, 55.0));
+    doc.add(new StringField("id", "1", Field.Store.YES));
+    w.addDocument(doc);
+
+    DirectoryReader r = w.getReader();
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    IndexSearcher s = newSearcher(r, false);
+    FieldDoc hit = (FieldDoc) LatLonPointPrototypeQueries.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
+    assertEquals("0", r.document(hit.doc).getField("id").stringValue());
+    r.close();
+
+    w.deleteDocuments(new Term("id", "0"));
+    w.deleteDocuments(new Term("id", "1"));
+    r = w.getReader();
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    s = newSearcher(r, false);
+    assertEquals(0, LatLonPointPrototypeQueries.nearest(s, "point", 40.0, 50.0, 1).scoreDocs.length);
+    r.close();
+    w.close();
+    dir.close();
+  }
+
+  public void testTieBreakByDocID() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriter w = new IndexWriter(dir, getIndexWriterConfig());
+    Document doc = new Document();
+    doc.add(new LatLonPoint("point", 40.0, 50.0));
+    doc.add(new StringField("id", "0", Field.Store.YES));
+    w.addDocument(doc);
+    doc = new Document();
+    doc.add(new LatLonPoint("point", 40.0, 50.0));
+    doc.add(new StringField("id", "1", Field.Store.YES));
+    w.addDocument(doc);
+
+    DirectoryReader r = DirectoryReader.open(w);
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    ScoreDoc[] hits = LatLonPointPrototypeQueries.nearest(newSearcher(r, false), "point", 45.0, 50.0, 2).scoreDocs;
+    assertEquals("0", r.document(hits[0].doc).getField("id").stringValue());
+    assertEquals("1", r.document(hits[1].doc).getField("id").stringValue());
+
+    r.close();
+    w.close();
+    dir.close();
+  }
+
+  public void testNearestNeighborWithNoDocs() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
+    DirectoryReader r = w.getReader();
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    assertEquals(0, LatLonPointPrototypeQueries.nearest(newSearcher(r, false), "point", 40.0, 50.0, 1).scoreDocs.length);
+    r.close();
+    w.close();
+    dir.close();
+  }
+
+  private double quantizeLat(double latRaw) {
+    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
+  }
+
+  private double quantizeLon(double lonRaw) {
+    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
+  }
+
+  public void testNearestNeighborRandom() throws Exception {
+    
+    int numPoints = atLeast(5000);
+    Directory dir;
+    if (numPoints > 100000) {
+      dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
+    } else {
+      dir = newDirectory();
+    }
+    double[] lats = new double[numPoints];
+    double[] lons = new double[numPoints];
+
+    IndexWriterConfig iwc = getIndexWriterConfig();
+    iwc.setMergePolicy(newLogMergePolicy());
+    iwc.setMergeScheduler(new SerialMergeScheduler());
+    RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
+    for(int id=0;id<numPoints;id++) {
+      lats[id] = quantizeLat(GeoTestUtil.nextLatitude());
+      lons[id] = quantizeLon(GeoTestUtil.nextLongitude());
+      Document doc = new Document();
+      doc.add(new LatLonPoint("point", lats[id], lons[id]));
+      doc.add(new LatLonDocValuesField("point", lats[id], lons[id]));
+      doc.add(new StoredField("id", id));
+      w.addDocument(doc);
+    }
+
+    if (random().nextBoolean()) {
+      w.forceMerge(1);
+    }
+
+    DirectoryReader r = w.getReader();
+    if (VERBOSE) {      
+      System.out.println("TEST: reader=" + r);
+    }
+    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
+    IndexSearcher s = newSearcher(r, false);
+    int iters = atLeast(100);
+    for(int iter=0;iter<iters;iter++) {
+      if (VERBOSE) {      
+        System.out.println("\nTEST: iter=" + iter);
+      }
+      double pointLat = GeoTestUtil.nextLatitude();
+      double pointLon = GeoTestUtil.nextLongitude();
+
+      // dumb brute force search to get the expected result:
+      NearestHit[] expectedHits = new NearestHit[lats.length];
+      for(int id=0;id<lats.length;id++) {
+        NearestHit hit = new NearestHit();
+        hit.distanceMeters = SloppyMath.haversinMeters(pointLat, pointLon, lats[id], lons[id]);
+        hit.docID = id;
+        expectedHits[id] = hit;
+      }
+
+      Arrays.sort(expectedHits, new Comparator<NearestHit>() {
+          @Override
+          public int compare(NearestHit a, NearestHit b) {
+            int cmp = Double.compare(a.distanceMeters, b.distanceMeters);
+            if (cmp != 0) {
+              return cmp;
+            }
+            // tie break by smaller docID:
+            return a.docID - b.docID;
+          }
+        });
+
+      int topN = TestUtil.nextInt(random(), 1, lats.length);
+
+      if (VERBOSE) {
+        System.out.println("\nhits for pointLat=" + pointLat + " pointLon=" + pointLon);
+      }
+
+      // Also test with MatchAllDocsQuery, sorting by distance:
+      TopFieldDocs fieldDocs = s.search(new MatchAllDocsQuery(), topN, new Sort(LatLonDocValuesField.newDistanceSort("point", pointLat, pointLon)));
+
+      ScoreDoc[] hits = LatLonPointPrototypeQueries.nearest(s, "point", pointLat, pointLon, topN).scoreDocs;
+      for(int i=0;i<topN;i++) {
+        NearestHit expected = expectedHits[i];
+        FieldDoc expected2 = (FieldDoc) fieldDocs.scoreDocs[i];
+        FieldDoc actual = (FieldDoc) hits[i];
+        Document actualDoc = r.document(actual.doc);
+
+        if (VERBOSE) {
+          System.out.println("hit " + i);
+          System.out.println("  expected id=" + expected.docID + " lat=" + lats[expected.docID] + " lon=" + lons[expected.docID] + " distance=" + expected.distanceMeters + " meters");
+          System.out.println("  actual id=" + actualDoc.getField("id") + " distance=" + actual.fields[0] + " meters");
+        }
+
+        assertEquals(expected.docID, actual.doc);
+        assertEquals(expected.distanceMeters, ((Double) actual.fields[0]).doubleValue(), 0.0);
+
+        assertEquals(expected.docID, expected.docID);
+        assertEquals(((Double) expected2.fields[0]).doubleValue(), expected.distanceMeters, 0.0);
+      }
+    }
+
+    r.close();
+    w.close();
+    dir.close();
+  }
+
+  private IndexWriterConfig getIndexWriterConfig() {
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    iwc.setCodec(Codec.forName("Lucene70"));
+    return iwc;
+  }
+}


[4/4] lucene-solr:branch_7x: LUCENE-7314: Graduate LatLonPoint and query classes to core

Posted by nk...@apache.org.
LUCENE-7314: Graduate LatLonPoint and query classes to core


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

Branch: refs/heads/branch_7x
Commit: 950b7b6b1b92849721eaed50ecad9711199180e8
Parents: cf9c3c1
Author: Nicholas Knize <nk...@gmail.com>
Authored: Wed Jul 25 18:03:46 2018 -0500
Committer: Nicholas Knize <nk...@gmail.com>
Committed: Wed Jul 25 18:30:15 2018 -0500

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   2 +
 .../document/LatLonDocValuesBoxQuery.java       | 152 ++++++++
 .../document/LatLonDocValuesDistanceQuery.java  | 139 +++++++
 .../lucene/document/LatLonDocValuesField.java   | 181 +++++++++
 .../org/apache/lucene/document/LatLonPoint.java | 254 ++++++++++++
 .../document/LatLonPointDistanceComparator.java | 229 +++++++++++
 .../document/LatLonPointDistanceQuery.java      | 389 +++++++++++++++++++
 .../document/LatLonPointInPolygonQuery.java     | 213 ++++++++++
 .../lucene/document/LatLonPointSortField.java   | 100 +++++
 .../org/apache/lucene/index/PointValues.java    |   3 +-
 .../document/TestLatLonDocValuesField.java      |  30 ++
 .../apache/lucene/document/TestLatLonPoint.java |  37 ++
 .../document/TestLatLonPointDistanceSort.java   | 264 +++++++++++++
 .../search/TestLatLonDocValuesQueries.java      |  62 +++
 .../lucene/search/TestLatLonPointQueries.java   |  91 +++++
 .../document/LatLonDocValuesBoxQuery.java       | 152 --------
 .../document/LatLonDocValuesDistanceQuery.java  | 139 -------
 .../lucene/document/LatLonDocValuesField.java   | 181 ---------
 .../org/apache/lucene/document/LatLonPoint.java | 335 ----------------
 .../document/LatLonPointDistanceComparator.java | 229 -----------
 .../document/LatLonPointDistanceQuery.java      | 389 -------------------
 .../document/LatLonPointInPolygonQuery.java     | 213 ----------
 .../lucene/document/LatLonPointSortField.java   | 100 -----
 .../apache/lucene/document/NearestNeighbor.java | 328 ----------------
 .../search/LatLonPointPrototypeQueries.java     | 111 ++++++
 .../apache/lucene/search/NearestNeighbor.java   | 329 ++++++++++++++++
 .../document/TestFloatPointNearestNeighbor.java |   3 +-
 .../document/TestLatLonDocValuesField.java      |  30 --
 .../apache/lucene/document/TestLatLonPoint.java |  37 --
 .../document/TestLatLonPointDistanceSort.java   | 264 -------------
 .../org/apache/lucene/document/TestNearest.java | 253 ------------
 .../search/TestLatLonDocValuesQueries.java      |  62 ---
 .../lucene/search/TestLatLonPointQueries.java   |  91 -----
 .../org/apache/lucene/search/TestNearest.java   | 253 ++++++++++++
 34 files changed, 2840 insertions(+), 2805 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 3968d46..be45dcd 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -36,6 +36,8 @@ API Changes:
   new deletesPctAllowed setting to control how aggressively deletes should be
   reclaimed. (Erick Erickson, Adrien Grand)
 
+* LUCENE-7314: Graduate LatLonPoint and query classes to core (Nick Knize)
+
 Bug Fixes:
 
 * LUCENE-8380: UTF8TaxonomyWriterCache inconsistency. (Ruslan Torobaev, Dawid Weiss)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
new file mode 100644
index 0000000..6411fcc
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
@@ -0,0 +1,152 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+
+/** Distance query for {@link LatLonDocValuesField}. */
+final class LatLonDocValuesBoxQuery extends Query {
+
+  private final String field;
+  private final int minLatitude, maxLatitude, minLongitude, maxLongitude;
+  private final boolean crossesDateline;
+
+  LatLonDocValuesBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
+    GeoUtils.checkLatitude(minLatitude);
+    GeoUtils.checkLatitude(maxLatitude);
+    GeoUtils.checkLongitude(minLongitude);
+    GeoUtils.checkLongitude(maxLongitude);
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    this.field = field;
+    this.crossesDateline = minLongitude > maxLongitude; // make sure to compute this before rounding
+    this.minLatitude = GeoEncodingUtils.encodeLatitudeCeil(minLatitude);
+    this.maxLatitude = GeoEncodingUtils.encodeLatitude(maxLatitude);
+    this.minLongitude = GeoEncodingUtils.encodeLongitudeCeil(minLongitude);
+    this.maxLongitude = GeoEncodingUtils.encodeLongitude(maxLongitude);
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder();
+    if (!this.field.equals(field)) {
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append("box(minLat=").append(GeoEncodingUtils.decodeLatitude(minLatitude));
+    sb.append(", maxLat=").append(GeoEncodingUtils.decodeLatitude(maxLatitude));
+    sb.append(", minLon=").append(GeoEncodingUtils.decodeLongitude(minLongitude));
+    sb.append(", maxLon=").append(GeoEncodingUtils.decodeLongitude(maxLongitude));
+    return sb.append(")").toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    LatLonDocValuesBoxQuery other = (LatLonDocValuesBoxQuery) obj;
+    return field.equals(other.field) &&
+        crossesDateline == other.crossesDateline &&
+        minLatitude == other.minLatitude &&
+        maxLatitude == other.maxLatitude &&
+        minLongitude == other.minLongitude &&
+        maxLongitude == other.maxLongitude;
+  }
+
+  @Override
+  public int hashCode() {
+    int h = classHash();
+    h = 31 * h + field.hashCode();
+    h = 31 * h + Boolean.hashCode(crossesDateline);
+    h = 31 * h + Integer.hashCode(minLatitude);
+    h = 31 * h + Integer.hashCode(maxLatitude);
+    h = 31 * h + Integer.hashCode(minLongitude);
+    h = 31 * h + Integer.hashCode(maxLongitude);
+    return h;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    return new ConstantScoreWeight(this, boost) {
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
+        if (values == null) {
+          return null;
+        }
+
+        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
+          @Override
+          public boolean matches() throws IOException {
+            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
+              final long value = values.nextValue();
+              final int lat = (int) (value >>> 32);
+              if (lat < minLatitude || lat > maxLatitude) {
+                // not within latitude range
+                continue;
+              }
+
+              final int lon = (int) (value & 0xFFFFFFFF);
+              if (crossesDateline) {
+                if (lon > maxLongitude && lon < minLongitude) {
+                  // not within longitude range
+                  continue;
+                }
+              } else {
+                if (lon < minLongitude || lon > maxLongitude) {
+                  // not within longitude range
+                  continue;
+                }
+              }
+
+              return true;
+            }
+            return false;
+          }
+
+          @Override
+          public float matchCost() {
+            return 5; // 5 comparisons
+          }
+        };
+        return new ConstantScoreScorer(this, boost, iterator);
+      }
+
+      @Override
+      public boolean isCacheable(LeafReaderContext ctx) {
+        return DocValues.isCacheable(ctx, field);
+      }
+
+    };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
new file mode 100644
index 0000000..d52cd31
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+
+/** Distance query for {@link LatLonDocValuesField}. */
+final class LatLonDocValuesDistanceQuery extends Query {
+
+  private final String field;
+  private final double latitude, longitude;
+  private final double radiusMeters;
+
+  LatLonDocValuesDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
+      throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
+    }
+    GeoUtils.checkLatitude(latitude);
+    GeoUtils.checkLongitude(longitude);
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    this.field = field;
+    this.latitude = latitude;
+    this.longitude = longitude;
+    this.radiusMeters = radiusMeters;
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder();
+    if (!this.field.equals(field)) {
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append(latitude);
+    sb.append(",");
+    sb.append(longitude);
+    sb.append(" +/- ");
+    sb.append(radiusMeters);
+    sb.append(" meters");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (sameClassAs(obj) == false) {
+      return false;
+    }
+    LatLonDocValuesDistanceQuery other = (LatLonDocValuesDistanceQuery) obj;
+    return field.equals(other.field) &&
+        Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
+        Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
+        Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
+  }
+
+  @Override
+  public int hashCode() {
+    int h = classHash();
+    h = 31 * h + field.hashCode();
+    h = 31 * h + Double.hashCode(latitude);
+    h = 31 * h + Double.hashCode(longitude);
+    h = 31 * h + Double.hashCode(radiusMeters);
+    return h;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    return new ConstantScoreWeight(this, boost) {
+
+      private final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
+        if (values == null) {
+          return null;
+        }
+
+        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
+
+          @Override
+          public boolean matches() throws IOException {
+            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
+              final long value = values.nextValue();
+              final int lat = (int) (value >>> 32);
+              final int lon = (int) (value & 0xFFFFFFFF);
+              if (distancePredicate.test(lat, lon)) {
+                return true;
+              }
+            }
+            return false;
+          }
+
+          @Override
+          public float matchCost() {
+            return 100f; // TODO: what should it be?
+          }
+
+        };
+        return new ConstantScoreScorer(this, boost, iterator);
+      }
+
+      @Override
+      public boolean isCacheable(LeafReaderContext ctx) {
+        return DocValues.isCacheable(ctx, field);
+      }
+
+    };
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
new file mode 100644
index 0000000..6bcc394
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
@@ -0,0 +1,181 @@
+/*
+ * 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 static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.IndexOrDocValuesQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+
+/** 
+ * An per-document location field.
+ * <p>
+ * Sorting by distance is efficient. Multiple values for the same field in one document
+ * is allowed. 
+ * <p>
+ * This field defines static factory methods for common operations:
+ * <ul>
+ *   <li>{@link #newDistanceSort newDistanceSort()} for ordering documents by distance from a specified location. 
+ * </ul>
+ * <p>
+ * If you also need query operations, you should add a separate {@link LatLonPoint} instance.
+ * If you also need to store the value, you should add a separate {@link StoredField} instance.
+ * <p>
+ * <b>WARNING</b>: Values are indexed with some loss of precision from the
+ * original {@code double} values (4.190951585769653E-8 for the latitude component
+ * and 8.381903171539307E-8 for longitude).
+ * @see LatLonPoint
+ */
+public class LatLonDocValuesField extends Field {
+
+  /**
+   * Type for a LatLonDocValuesField
+   * <p>
+   * Each value stores a 64-bit long where the upper 32 bits are the encoded latitude,
+   * and the lower 32 bits are the encoded longitude.
+   * @see org.apache.lucene.geo.GeoEncodingUtils#decodeLatitude(int)
+   * @see org.apache.lucene.geo.GeoEncodingUtils#decodeLongitude(int)
+   */
+  public static final FieldType TYPE = new FieldType();
+  static {
+    TYPE.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    TYPE.freeze();
+  }
+  
+  /** 
+   * Creates a new LatLonDocValuesField with the specified latitude and longitude
+   * @param name field name
+   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
+   * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
+   */
+  public LatLonDocValuesField(String name, double latitude, double longitude) {
+    super(name, TYPE);
+    setLocationValue(latitude, longitude);
+  }
+  
+  /**
+   * Change the values of this field
+   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
+   * @throws IllegalArgumentException if latitude or longitude are out of bounds
+   */
+  public void setLocationValue(double latitude, double longitude) {
+    int latitudeEncoded = encodeLatitude(latitude);
+    int longitudeEncoded = encodeLongitude(longitude);
+    fieldsData = Long.valueOf((((long)latitudeEncoded) << 32) | (longitudeEncoded & 0xFFFFFFFFL));
+  }
+
+  /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */
+  static void checkCompatible(FieldInfo fieldInfo) {
+    // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment.
+    if (fieldInfo.getDocValuesType() != DocValuesType.NONE && fieldInfo.getDocValuesType() != TYPE.docValuesType()) {
+      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with docValuesType=" + fieldInfo.getDocValuesType() + 
+                                         " but this type has docValuesType=" + TYPE.docValuesType() + 
+                                         ", is the field really a LatLonDocValuesField?");
+    }
+  }
+  
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(getClass().getSimpleName());
+    result.append(" <");
+    result.append(name);
+    result.append(':');
+
+    long currentValue = (Long)fieldsData;
+    result.append(decodeLatitude((int)(currentValue >> 32)));
+    result.append(',');
+    result.append(decodeLongitude((int)(currentValue & 0xFFFFFFFF)));
+
+    result.append('>');
+    return result.toString();
+  }
+
+  /**
+   * Creates a SortField for sorting by distance from a location.
+   * <p>
+   * This sort orders documents by ascending distance from the location. The value returned in {@link FieldDoc} for
+   * the hits contains a Double instance with the distance in meters.
+   * <p>
+   * If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
+   * (missing values sort last).
+   * <p>
+   * If a document contains multiple values for the field, the <i>closest</i> distance to the location is used.
+   * 
+   * @param field field name. must not be null.
+   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
+   * @return SortField ordering documents by distance
+   * @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates.
+   */
+  public static SortField newDistanceSort(String field, double latitude, double longitude) {
+    return new LatLonPointSortField(field, latitude, longitude);
+  }
+
+  /**
+   * Create a query for matching a bounding box using doc values.
+   * This query is usually slow as it does not use an index structure and needs
+   * to verify documents one-by-one in order to know whether they match. It is
+   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
+   * {@link LatLonPoint#newBoxQuery}.
+   */
+  public static Query newSlowBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
+    // exact double values of lat=90.0D and lon=180.0D must be treated special as they are not represented in the encoding
+    // and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
+    if (minLatitude == 90.0) {
+      // range cannot match as 90.0 can never exist
+      return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLatitude=90.0");
+    }
+    if (minLongitude == 180.0) {
+      if (maxLongitude == 180.0) {
+        // range cannot match as 180.0 can never exist
+        return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLongitude=maxLongitude=180.0");
+      } else if (maxLongitude < minLongitude) {
+        // encodeCeil() with dateline wrapping!
+        minLongitude = -180.0;
+      }
+    }
+    return new LatLonDocValuesBoxQuery(field, minLatitude, maxLatitude, minLongitude, maxLongitude);
+  }
+
+  /**
+   * Create a query for matching points within the specified distance of the supplied location.
+   * This query is usually slow as it does not use an index structure and needs
+   * to verify documents one-by-one in order to know whether they match. It is
+   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
+   * {@link LatLonPoint#newDistanceQuery}.
+   * @param field field name. must not be null.
+   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
+   * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
+   * @return query matching points within this distance
+   * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
+   */
+  public static Query newSlowDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    return new LatLonDocValuesDistanceQuery(field, latitude, longitude, radiusMeters);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
new file mode 100644
index 0000000..a44795d
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -0,0 +1,254 @@
+/*
+ * 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.geo.Polygon;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
+import org.apache.lucene.search.PointRangeQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+
+/** 
+ * An indexed location field.
+ * <p>
+ * Finding all documents within a range at search time is
+ * efficient.  Multiple values for the same field in one document
+ * is allowed. 
+ * <p>
+ * This field defines static factory methods for common operations:
+ * <ul>
+ *   <li>{@link #newBoxQuery newBoxQuery()} for matching points within a bounding box.
+ *   <li>{@link #newDistanceQuery newDistanceQuery()} for matching points within a specified distance.
+ *   <li>{@link #newPolygonQuery newPolygonQuery()} for matching points within an arbitrary polygon.
+ * </ul>
+ * <p>
+ * If you also need per-document operations such as sort by distance, add a separate {@link LatLonDocValuesField} instance.
+ * If you also need to store the value, you should add a separate {@link StoredField} instance.
+ * <p>
+ * <b>WARNING</b>: Values are indexed with some loss of precision from the
+ * original {@code double} values (4.190951585769653E-8 for the latitude component
+ * and 8.381903171539307E-8 for longitude).
+ * @see PointValues
+ * @see LatLonDocValuesField
+ */
+// TODO ^^^ that is very sandy and hurts the API, usage, and tests tremendously, because what the user passes
+// to the field is not actually what gets indexed. Float would be 1E-5 error vs 1E-7, but it might be
+// a better tradeoff? then it would be completely transparent to the user and lucene would be "lossless".
+public class LatLonPoint extends Field {
+  /** LatLonPoint is encoded as integer values so number of bytes is 4 */
+  public static final int BYTES = Integer.BYTES;
+  /**
+   * Type for an indexed LatLonPoint
+   * <p>
+   * Each point stores two dimensions with 4 bytes per dimension.
+   */
+  public static final FieldType TYPE = new FieldType();
+  static {
+    TYPE.setDimensions(2, Integer.BYTES);
+    TYPE.freeze();
+  }
+  
+  /**
+   * Change the values of this field
+   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
+   * @throws IllegalArgumentException if latitude or longitude are out of bounds
+   */
+  public void setLocationValue(double latitude, double longitude) {
+    final byte[] bytes;
+
+    if (fieldsData == null) {
+      bytes = new byte[8];
+      fieldsData = new BytesRef(bytes);
+    } else {
+      bytes = ((BytesRef) fieldsData).bytes;
+    }
+
+    int latitudeEncoded = encodeLatitude(latitude);
+    int longitudeEncoded = encodeLongitude(longitude);
+    NumericUtils.intToSortableBytes(latitudeEncoded, bytes, 0);
+    NumericUtils.intToSortableBytes(longitudeEncoded, bytes, Integer.BYTES);
+  }
+
+  /** 
+   * Creates a new LatLonPoint with the specified latitude and longitude
+   * @param name field name
+   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
+   * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
+   */
+  public LatLonPoint(String name, double latitude, double longitude) {
+    super(name, TYPE);
+    setLocationValue(latitude, longitude);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(getClass().getSimpleName());
+    result.append(" <");
+    result.append(name);
+    result.append(':');
+
+    byte bytes[] = ((BytesRef) fieldsData).bytes;
+    result.append(decodeLatitude(bytes, 0));
+    result.append(',');
+    result.append(decodeLongitude(bytes, Integer.BYTES));
+
+    result.append('>');
+    return result.toString();
+  }
+  
+  /** sugar encodes a single point as a byte array */
+  private static byte[] encode(double latitude, double longitude) {
+    byte[] bytes = new byte[2 * Integer.BYTES];
+    NumericUtils.intToSortableBytes(encodeLatitude(latitude), bytes, 0);
+    NumericUtils.intToSortableBytes(encodeLongitude(longitude), bytes, Integer.BYTES);
+    return bytes;
+  }
+  
+  /** sugar encodes a single point as a byte array, rounding values up */
+  private static byte[] encodeCeil(double latitude, double longitude) {
+    byte[] bytes = new byte[2 * Integer.BYTES];
+    NumericUtils.intToSortableBytes(encodeLatitudeCeil(latitude), bytes, 0);
+    NumericUtils.intToSortableBytes(encodeLongitudeCeil(longitude), bytes, Integer.BYTES);
+    return bytes;
+  }
+
+  /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonPoint */
+  static void checkCompatible(FieldInfo fieldInfo) {
+    // point/dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment.
+    if (fieldInfo.getPointDimensionCount() != 0 && fieldInfo.getPointDimensionCount() != TYPE.pointDimensionCount()) {
+      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with numDims=" + fieldInfo.getPointDimensionCount() + 
+                                         " but this point type has numDims=" + TYPE.pointDimensionCount() + 
+                                         ", is the field really a LatLonPoint?");
+    }
+    if (fieldInfo.getPointNumBytes() != 0 && fieldInfo.getPointNumBytes() != TYPE.pointNumBytes()) {
+      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with bytesPerDim=" + fieldInfo.getPointNumBytes() + 
+                                         " but this point type has bytesPerDim=" + TYPE.pointNumBytes() + 
+                                         ", is the field really a LatLonPoint?");
+    }
+  }
+
+  // static methods for generating queries
+
+  /**
+   * Create a query for matching a bounding box.
+   * <p>
+   * The box may cross over the dateline.
+   * @param field field name. must not be null.
+   * @param minLatitude latitude lower bound: must be within standard +/-90 coordinate bounds.
+   * @param maxLatitude latitude upper bound: must be within standard +/-90 coordinate bounds.
+   * @param minLongitude longitude lower bound: must be within standard +/-180 coordinate bounds.
+   * @param maxLongitude longitude upper bound: must be within standard +/-180 coordinate bounds.
+   * @return query matching points within this box
+   * @throws IllegalArgumentException if {@code field} is null, or the box has invalid coordinates.
+   */
+  public static Query newBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
+    // exact double values of lat=90.0D and lon=180.0D must be treated special as they are not represented in the encoding
+    // and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
+    if (minLatitude == 90.0) {
+      // range cannot match as 90.0 can never exist
+      return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLatitude=90.0");
+    }
+    if (minLongitude == 180.0) {
+      if (maxLongitude == 180.0) {
+        // range cannot match as 180.0 can never exist
+        return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLongitude=maxLongitude=180.0");
+      } else if (maxLongitude < minLongitude) {
+        // encodeCeil() with dateline wrapping!
+        minLongitude = -180.0;
+      }
+    }
+    byte[] lower = encodeCeil(minLatitude, minLongitude);
+    byte[] upper = encode(maxLatitude, maxLongitude);
+    // Crosses date line: we just rewrite into OR of two bboxes, with longitude as an open range:
+    if (maxLongitude < minLongitude) {
+      // Disable coord here because a multi-valued doc could match both rects and get unfairly boosted:
+      BooleanQuery.Builder q = new BooleanQuery.Builder();
+
+      // E.g.: maxLon = -179, minLon = 179
+      byte[] leftOpen = lower.clone();
+      // leave longitude open
+      NumericUtils.intToSortableBytes(Integer.MIN_VALUE, leftOpen, Integer.BYTES);
+      Query left = newBoxInternal(field, leftOpen, upper);
+      q.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+
+      byte[] rightOpen = upper.clone();
+      // leave longitude open
+      NumericUtils.intToSortableBytes(Integer.MAX_VALUE, rightOpen, Integer.BYTES);
+      Query right = newBoxInternal(field, lower, rightOpen);
+      q.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+      return new ConstantScoreQuery(q.build());
+    } else {
+      return newBoxInternal(field, lower, upper);
+    }
+  }
+  
+  private static Query newBoxInternal(String field, byte[] min, byte[] max) {
+    return new PointRangeQuery(field, min, max, 2) {
+      @Override
+      protected String toString(int dimension, byte[] value) {
+        if (dimension == 0) {
+          return Double.toString(decodeLatitude(value, 0));
+        } else if (dimension == 1) {
+          return Double.toString(decodeLongitude(value, 0));
+        } else {
+          throw new AssertionError();
+        }
+      }
+    };
+  }
+  
+  /**
+   * Create a query for matching points within the specified distance of the supplied location.
+   * @param field field name. must not be null.
+   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
+   * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
+   * @return query matching points within this distance
+   * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
+   */
+  public static Query newDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    return new LatLonPointDistanceQuery(field, latitude, longitude, radiusMeters);
+  }
+  
+  /** 
+   * Create a query for matching one or more polygons.
+   * @param field field name. must not be null.
+   * @param polygons array of polygons. must not be null or empty
+   * @return query matching points within this polygon
+   * @throws IllegalArgumentException if {@code field} is null, {@code polygons} is null or empty
+   * @see Polygon
+   */
+  public static Query newPolygonQuery(String field, Polygon... polygons) {
+    return new LatLonPointInPolygonQuery(field, polygons);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
new file mode 100644
index 0000000..40e8dee
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
@@ -0,0 +1,229 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.LeafFieldComparator;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.lucene.util.SloppyMath;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/**
+ * Compares documents by distance from an origin point
+ * <p>
+ * When the least competitive item on the priority queue changes (setBottom), we recompute
+ * a bounding box representing competitive distance to the top-N. Then in compareBottom, we can
+ * quickly reject hits based on bounding box alone without computing distance for every element.
+ */
+class LatLonPointDistanceComparator extends FieldComparator<Double> implements LeafFieldComparator {
+  final String field;
+  final double latitude;
+  final double longitude;
+
+  final double[] values;
+  double bottom;
+  double topValue;
+  SortedNumericDocValues currentDocs;
+  
+  // current bounding box(es) for the bottom distance on the PQ.
+  // these are pre-encoded with LatLonPoint's encoding and 
+  // used to exclude uncompetitive hits faster.
+  int minLon = Integer.MIN_VALUE;
+  int maxLon = Integer.MAX_VALUE;
+  int minLat = Integer.MIN_VALUE;
+  int maxLat = Integer.MAX_VALUE;
+
+  // second set of longitude ranges to check (for cross-dateline case)
+  int minLon2 = Integer.MAX_VALUE;
+
+  // the number of times setBottom has been called (adversary protection)
+  int setBottomCounter = 0;
+
+  private long[] currentValues = new long[4];
+  private int valuesDocID = -1;
+
+  public LatLonPointDistanceComparator(String field, double latitude, double longitude, int numHits) {
+    this.field = field;
+    this.latitude = latitude;
+    this.longitude = longitude;
+    this.values = new double[numHits];
+  }
+  
+  @Override
+  public void setScorer(Scorer scorer) {}
+
+  @Override
+  public int compare(int slot1, int slot2) {
+    return Double.compare(values[slot1], values[slot2]);
+  }
+  
+  @Override
+  public void setBottom(int slot) {
+    bottom = values[slot];
+    // make bounding box(es) to exclude non-competitive hits, but start
+    // sampling if we get called way too much: don't make gobs of bounding
+    // boxes if comparator hits a worst case order (e.g. backwards distance order)
+    if (setBottomCounter < 1024 || (setBottomCounter & 0x3F) == 0x3F) {
+      Rectangle box = Rectangle.fromPointDistance(latitude, longitude, haversin2(bottom));
+      // pre-encode our box to our integer encoding, so we don't have to decode 
+      // to double values for uncompetitive hits. This has some cost!
+      minLat = encodeLatitude(box.minLat);
+      maxLat = encodeLatitude(box.maxLat);
+      if (box.crossesDateline()) {
+        // box1
+        minLon = Integer.MIN_VALUE;
+        maxLon = encodeLongitude(box.maxLon);
+        // box2
+        minLon2 = encodeLongitude(box.minLon);
+      } else {
+        minLon = encodeLongitude(box.minLon);
+        maxLon = encodeLongitude(box.maxLon);
+        // disable box2
+        minLon2 = Integer.MAX_VALUE;
+      }
+    }
+    setBottomCounter++;
+  }
+  
+  @Override
+  public void setTopValue(Double value) {
+    topValue = value.doubleValue();
+  }
+
+  private void setValues() throws IOException {
+    if (valuesDocID != currentDocs.docID()) {
+      assert valuesDocID < currentDocs.docID(): " valuesDocID=" + valuesDocID + " vs " + currentDocs.docID();
+      valuesDocID = currentDocs.docID();
+      int count = currentDocs.docValueCount();
+      if (count > currentValues.length) {
+        currentValues = new long[ArrayUtil.oversize(count, RamUsageEstimator.NUM_BYTES_LONG)];
+      }
+      for(int i=0;i<count;i++) {
+        currentValues[i] = currentDocs.nextValue();
+      }
+    }
+  }
+  
+  @Override
+  public int compareBottom(int doc) throws IOException {
+    if (doc > currentDocs.docID()) {
+      currentDocs.advance(doc);
+    }
+    if (doc < currentDocs.docID()) {
+      return Double.compare(bottom, Double.POSITIVE_INFINITY);
+    }
+
+    setValues();
+
+    int numValues = currentDocs.docValueCount();
+
+    int cmp = -1;
+    for (int i = 0; i < numValues; i++) {
+      long encoded = currentValues[i];
+
+      // test bounding box
+      int latitudeBits = (int)(encoded >> 32);
+      if (latitudeBits < minLat || latitudeBits > maxLat) {
+        continue;
+      }
+      int longitudeBits = (int)(encoded & 0xFFFFFFFF);
+      if ((longitudeBits < minLon || longitudeBits > maxLon) && (longitudeBits < minLon2)) {
+        continue;
+      }
+
+      // only compute actual distance if its inside "competitive bounding box"
+      double docLatitude = decodeLatitude(latitudeBits);
+      double docLongitude = decodeLongitude(longitudeBits);
+      cmp = Math.max(cmp, Double.compare(bottom, SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude)));
+      // once we compete in the PQ, no need to continue.
+      if (cmp > 0) {
+        return cmp;
+      }
+    }
+    return cmp;
+  }
+  
+  @Override
+  public void copy(int slot, int doc) throws IOException {
+    values[slot] = sortKey(doc);
+  }
+  
+  @Override
+  public LeafFieldComparator getLeafComparator(LeafReaderContext context) throws IOException {
+    LeafReader reader = context.reader();
+    FieldInfo info = reader.getFieldInfos().fieldInfo(field);
+    if (info != null) {
+      LatLonDocValuesField.checkCompatible(info);
+    }
+    currentDocs = DocValues.getSortedNumeric(reader, field);
+    valuesDocID = -1;
+    return this;
+  }
+  
+  @Override
+  public Double value(int slot) {
+    return Double.valueOf(haversin2(values[slot]));
+  }
+  
+  @Override
+  public int compareTop(int doc) throws IOException {
+    return Double.compare(topValue, haversin2(sortKey(doc)));
+  }
+  
+  // TODO: optimize for single-valued case?
+  // TODO: do all kinds of other optimizations!
+  double sortKey(int doc) throws IOException {
+    if (doc > currentDocs.docID()) {
+      currentDocs.advance(doc);
+    }
+    double minValue = Double.POSITIVE_INFINITY;
+    if (doc == currentDocs.docID()) {
+      setValues();
+      int numValues = currentDocs.docValueCount();
+      for (int i = 0; i < numValues; i++) {
+        long encoded = currentValues[i];
+        double docLatitude = decodeLatitude((int)(encoded >> 32));
+        double docLongitude = decodeLongitude((int)(encoded & 0xFFFFFFFF));
+        minValue = Math.min(minValue, SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude));
+      }
+    }
+    return minValue;
+  }
+
+  // second half of the haversin calculation, used to convert results from haversin1 (used internally
+  // for sorting) for display purposes.
+  static double haversin2(double partial) {
+    if (Double.isInfinite(partial)) {
+      return partial;
+    }
+    return SloppyMath.haversinMeters(partial);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
new file mode 100644
index 0000000..90e63c6
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.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.document;
+
+import java.io.IOException;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+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.ScorerSupplier;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.BitSetIterator;
+import org.apache.lucene.util.DocIdSetBuilder;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.util.StringHelper;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/**
+ * Distance query for {@link LatLonPoint}.
+ */
+final class LatLonPointDistanceQuery extends Query {
+  final String field;
+  final double latitude;
+  final double longitude;
+  final double radiusMeters;
+
+  public LatLonPointDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
+      throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
+    }
+    GeoUtils.checkLatitude(latitude);
+    GeoUtils.checkLongitude(longitude);
+    this.field = field;
+    this.latitude = latitude;
+    this.longitude = longitude;
+    this.radiusMeters = radiusMeters;
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+    Rectangle box = Rectangle.fromPointDistance(latitude, longitude, radiusMeters);
+    // create bounding box(es) for the distance range
+    // these are pre-encoded with LatLonPoint's encoding
+    final byte minLat[] = new byte[Integer.BYTES];
+    final byte maxLat[] = new byte[Integer.BYTES];
+    final byte minLon[] = new byte[Integer.BYTES];
+    final byte maxLon[] = new byte[Integer.BYTES];
+    // second set of longitude ranges to check (for cross-dateline case)
+    final byte minLon2[] = new byte[Integer.BYTES];
+
+    NumericUtils.intToSortableBytes(encodeLatitude(box.minLat), minLat, 0);
+    NumericUtils.intToSortableBytes(encodeLatitude(box.maxLat), maxLat, 0);
+
+    // crosses dateline: split
+    if (box.crossesDateline()) {
+      // box1
+      NumericUtils.intToSortableBytes(Integer.MIN_VALUE, minLon, 0);
+      NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
+      // box2
+      NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon2, 0);
+    } else {
+      NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0);
+      NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
+      // disable box2
+      NumericUtils.intToSortableBytes(Integer.MAX_VALUE, minLon2, 0);
+    }
+
+    // compute exact sort key: avoid any asin() computations
+    final double sortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
+
+    final double axisLat = Rectangle.axisLat(latitude, radiusMeters);
+
+    return new ConstantScoreWeight(this, boost) {
+
+      final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        ScorerSupplier scorerSupplier = scorerSupplier(context);
+        if (scorerSupplier == null) {
+          return null;
+        }
+        return scorerSupplier.get(Long.MAX_VALUE);
+      }
+
+      @Override
+      public boolean isCacheable(LeafReaderContext ctx) {
+        return true;
+      }
+
+      @Override
+      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+        LeafReader reader = context.reader();
+        PointValues values = reader.getPointValues(field);
+        if (values == null) {
+          // No docs in this segment had any points fields
+          return null;
+        }
+        FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
+        if (fieldInfo == null) {
+          // No docs in this segment indexed this field at all
+          return null;
+        }
+        LatLonPoint.checkCompatible(fieldInfo);
+
+        // matching docids
+        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
+        final IntersectVisitor visitor = getIntersectVisitor(result);
+
+        final Weight weight = this;
+        return new ScorerSupplier() {
+
+          long cost = -1;
+
+          @Override
+          public Scorer get(long leadCost) throws IOException {
+            if (values.getDocCount() == reader.maxDoc()
+                && values.getDocCount() == values.size()
+                && cost() > reader.maxDoc() / 2) {
+              // If all docs have exactly one value and the cost is greater
+              // than half the leaf size then maybe we can make things faster
+              // by computing the set of documents that do NOT match the range
+              final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+              result.set(0, reader.maxDoc());
+              int[] cost = new int[]{reader.maxDoc()};
+              values.intersect(getInverseIntersectVisitor(result, cost));
+              final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
+              return new ConstantScoreScorer(weight, score(), iterator);
+            }
+            values.intersect(visitor);
+            return new ConstantScoreScorer(weight, score(), result.build().iterator());
+          }
+
+          @Override
+          public long cost() {
+            if (cost == -1) {
+              cost = values.estimatePointCount(visitor);
+            }
+            assert cost >= 0;
+            return cost;
+          }
+        };
+
+      }
+
+      /**
+       * Create a visitor that collects documents matching the range.
+       */
+      private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result) {
+        return new IntersectVisitor() {
+
+          DocIdSetBuilder.BulkAdder adder;
+
+          @Override
+          public void grow(int count) {
+            adder = result.grow(count);
+          }
+
+          @Override
+          public void visit(int docID) {
+            adder.add(docID);
+          }
+
+          @Override
+          public void visit(int docID, byte[] packedValue) {
+            // bounding box check
+            if (StringHelper.compare(Integer.BYTES, packedValue, 0, maxLat, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, packedValue, 0, minLat, 0) < 0) {
+              // latitude out of bounding box range
+              return;
+            }
+
+            if ((StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, maxLon, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon, 0) < 0)
+                && StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon2, 0) < 0) {
+              // longitude out of bounding box range
+              return;
+            }
+
+            int docLatitude = NumericUtils.sortableBytesToInt(packedValue, 0);
+            int docLongitude = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
+            if (distancePredicate.test(docLatitude, docLongitude)) {
+              adder.add(docID);
+            }
+          }
+
+          // algorithm: we create a bounding box (two bounding boxes if we cross the dateline).
+          // 1. check our bounding box(es) first. if the subtree is entirely outside of those, bail.
+          // 2. check if the subtree is disjoint. it may cross the bounding box but not intersect with circle
+          // 3. see if the subtree is fully contained. if the subtree is enormous along the x axis, wrapping half way around the world, etc: then this can't work, just go to step 4.
+          // 4. recurse naively (subtrees crossing over circle edge)
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0) {
+              // latitude out of bounding box range
+              return Relation.CELL_OUTSIDE_QUERY;
+            }
+
+            if ((StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0)
+                && StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon2, 0) < 0) {
+              // longitude out of bounding box range
+              return Relation.CELL_OUTSIDE_QUERY;
+            }
+
+            double latMin = decodeLatitude(minPackedValue, 0);
+            double lonMin = decodeLongitude(minPackedValue, Integer.BYTES);
+            double latMax = decodeLatitude(maxPackedValue, 0);
+            double lonMax = decodeLongitude(maxPackedValue, Integer.BYTES);
+
+            return GeoUtils.relate(latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
+          }
+        };
+      }
+
+      /**
+       * Create a visitor that clears documents that do NOT match the range.
+       */
+      private IntersectVisitor getInverseIntersectVisitor(FixedBitSet result, int[] cost) {
+        return new IntersectVisitor() {
+
+          @Override
+          public void visit(int docID) {
+            result.clear(docID);
+            cost[0]--;
+          }
+
+          @Override
+          public void visit(int docID, byte[] packedValue) {
+            // bounding box check
+            if (StringHelper.compare(Integer.BYTES, packedValue, 0, maxLat, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, packedValue, 0, minLat, 0) < 0) {
+              // latitude out of bounding box range
+              result.clear(docID);
+              cost[0]--;
+              return;
+            }
+
+            if ((StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, maxLon, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon, 0) < 0)
+                && StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon2, 0) < 0) {
+              // longitude out of bounding box range
+              result.clear(docID);
+              cost[0]--;
+              return;
+            }
+
+            int docLatitude = NumericUtils.sortableBytesToInt(packedValue, 0);
+            int docLongitude = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
+            if (!distancePredicate.test(docLatitude, docLongitude)) {
+              result.clear(docID);
+              cost[0]--;
+            }
+          }
+
+          @Override
+          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+            if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0) {
+              // latitude out of bounding box range
+              return Relation.CELL_INSIDE_QUERY;
+            }
+
+            if ((StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
+                StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0)
+                && StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon2, 0) < 0) {
+              // latitude out of bounding box range
+              return Relation.CELL_INSIDE_QUERY;
+            }
+
+            double latMin = decodeLatitude(minPackedValue, 0);
+            double lonMin = decodeLongitude(minPackedValue, Integer.BYTES);
+            double latMax = decodeLatitude(maxPackedValue, 0);
+            double lonMax = decodeLongitude(maxPackedValue, Integer.BYTES);
+
+            Relation relation = GeoUtils.relate(latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
+            switch (relation) {
+              case CELL_INSIDE_QUERY:
+                // all points match, skip this subtree
+                return Relation.CELL_OUTSIDE_QUERY;
+              case CELL_OUTSIDE_QUERY:
+                // none of the points match, clear all documents
+                return Relation.CELL_INSIDE_QUERY;
+              default:
+                return relation;
+            }
+          }
+
+        };
+      }
+
+    };
+  }
+
+  public String getField() {
+    return field;
+  }
+
+  public double getLatitude() {
+    return latitude;
+  }
+
+  public double getLongitude() {
+    return longitude;
+  }
+
+  public double getRadiusMeters() {
+    return radiusMeters;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = classHash();
+    result = prime * result + field.hashCode();
+    long temp;
+    temp = Double.doubleToLongBits(latitude);
+    result = prime * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(longitude);
+    result = prime * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(radiusMeters);
+    result = prime * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return sameClassAs(other) &&
+           equalsTo(getClass().cast(other));
+  }
+
+  private boolean equalsTo(LatLonPointDistanceQuery other) {
+    return field.equals(other.field) &&
+           Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
+           Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
+           Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder();
+    if (!this.field.equals(field)) {
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append(latitude);
+    sb.append(",");
+    sb.append(longitude);
+    sb.append(" +/- ");
+    sb.append(radiusMeters);
+    sb.append(" meters");
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
new file mode 100644
index 0000000..3fdc80b
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
@@ -0,0 +1,213 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Polygon2D;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+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.util.NumericUtils;
+import org.apache.lucene.util.StringHelper;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/** Finds all previously indexed points that fall within the specified polygons.
+ *
+ *  <p>The field must be indexed with using {@link org.apache.lucene.document.LatLonPoint} added per document.
+ *
+ *  @lucene.experimental */
+
+final class LatLonPointInPolygonQuery extends Query {
+  final String field;
+  final Polygon[] polygons;
+
+  LatLonPointInPolygonQuery(String field, Polygon[] polygons) {
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    if (polygons == null) {
+      throw new IllegalArgumentException("polygons must not be null");
+    }
+    if (polygons.length == 0) {
+      throw new IllegalArgumentException("polygons must not be empty");
+    }
+    for (int i = 0; i < polygons.length; i++) {
+      if (polygons[i] == null) {
+        throw new IllegalArgumentException("polygon[" + i + "] must not be null");
+      }
+    }
+    this.field = field;
+    this.polygons = polygons.clone();
+    // TODO: we could also compute the maximal inner bounding box, to make relations faster to compute?
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+
+    // I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be
+    // used in the first pass:
+    
+    // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons
+    // these are pre-encoded with LatLonPoint's encoding
+    final Rectangle box = Rectangle.fromPolygon(polygons);
+    final byte minLat[] = new byte[Integer.BYTES];
+    final byte maxLat[] = new byte[Integer.BYTES];
+    final byte minLon[] = new byte[Integer.BYTES];
+    final byte maxLon[] = new byte[Integer.BYTES];
+    NumericUtils.intToSortableBytes(encodeLatitude(box.minLat), minLat, 0);
+    NumericUtils.intToSortableBytes(encodeLatitude(box.maxLat), maxLat, 0);
+    NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0);
+    NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
+
+    final Polygon2D tree = Polygon2D.create(polygons);
+    final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createPolygonPredicate(polygons, tree);
+
+    return new ConstantScoreWeight(this, boost) {
+
+      @Override
+      public Scorer scorer(LeafReaderContext context) throws IOException {
+        LeafReader reader = context.reader();
+        PointValues values = reader.getPointValues(field);
+        if (values == null) {
+          // No docs in this segment had any points fields
+          return null;
+        }
+        FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
+        if (fieldInfo == null) {
+          // No docs in this segment indexed this field at all
+          return null;
+        }
+        LatLonPoint.checkCompatible(fieldInfo);
+
+        // matching docids
+        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
+
+        values.intersect( 
+                         new IntersectVisitor() {
+
+                           DocIdSetBuilder.BulkAdder adder;
+
+                           @Override
+                           public void grow(int count) {
+                             adder = result.grow(count);
+                           }
+
+                           @Override
+                           public void visit(int docID) {
+                             adder.add(docID);
+                           }
+
+                           @Override
+                           public void visit(int docID, byte[] packedValue) {
+                             if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
+                                                       NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
+                               adder.add(docID);
+                             }
+                           }
+
+                           @Override
+                           public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+                             if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
+                                 StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0 ||
+                                 StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
+                                 StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0) {
+                               // outside of global bounding box range
+                               return Relation.CELL_OUTSIDE_QUERY;
+                             }
+                             
+                             double cellMinLat = decodeLatitude(minPackedValue, 0);
+                             double cellMinLon = decodeLongitude(minPackedValue, Integer.BYTES);
+                             double cellMaxLat = decodeLatitude(maxPackedValue, 0);
+                             double cellMaxLon = decodeLongitude(maxPackedValue, Integer.BYTES);
+
+                             return tree.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon);
+                           }
+                         });
+
+        return new ConstantScoreScorer(this, score(), result.build().iterator());
+      }
+
+      @Override
+      public boolean isCacheable(LeafReaderContext ctx) {
+        return true;
+      }
+    };
+  }
+
+  /** Returns the query field */
+  public String getField() {
+    return field;
+  }
+
+  /** Returns a copy of the internal polygon array */
+  public Polygon[] getPolygons() {
+    return polygons.clone();
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = classHash();
+    result = prime * result + field.hashCode();
+    result = prime * result + Arrays.hashCode(polygons);
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return sameClassAs(other) &&
+           equalsTo(getClass().cast(other));
+  }
+
+  private boolean equalsTo(LatLonPointInPolygonQuery other) {
+    return field.equals(other.field) &&
+           Arrays.equals(polygons, other.polygons);
+  }
+
+  @Override
+  public String toString(String field) {
+    final StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(':');
+    if (this.field.equals(field) == false) {
+      sb.append(" field=");
+      sb.append(this.field);
+      sb.append(':');
+    }
+    sb.append(Arrays.toString(polygons));
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
new file mode 100644
index 0000000..10e72cc
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
@@ -0,0 +1,100 @@
+/*
+ * 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.geo.GeoUtils;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.SortField;
+
+/**
+ * Sorts by distance from an origin location.
+ */
+final class LatLonPointSortField extends SortField {
+  final double latitude;
+  final double longitude;
+
+  LatLonPointSortField(String field, double latitude, double longitude) {
+    super(field, SortField.Type.CUSTOM);
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    GeoUtils.checkLatitude(latitude);
+    GeoUtils.checkLongitude(longitude);
+    this.latitude = latitude;
+    this.longitude = longitude;
+    setMissingValue(Double.POSITIVE_INFINITY);
+  }
+  
+  @Override
+  public FieldComparator<?> getComparator(int numHits, int sortPos) {
+    return new LatLonPointDistanceComparator(getField(), latitude, longitude, numHits);
+  }
+
+  @Override
+  public Double getMissingValue() {
+    return (Double) super.getMissingValue();
+  }
+
+  @Override
+  public void setMissingValue(Object missingValue) {
+    if (Double.valueOf(Double.POSITIVE_INFINITY).equals(missingValue) == false) {
+      throw new IllegalArgumentException("Missing value can only be Double.POSITIVE_INFINITY (missing values last), but got " + missingValue);
+    }
+    this.missingValue = missingValue;
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = super.hashCode();
+    long temp;
+    temp = Double.doubleToLongBits(latitude);
+    result = prime * result + (int) (temp ^ (temp >>> 32));
+    temp = Double.doubleToLongBits(longitude);
+    result = prime * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (!super.equals(obj)) return false;
+    if (getClass() != obj.getClass()) return false;
+    LatLonPointSortField other = (LatLonPointSortField) obj;
+    if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) return false;
+    if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) return false;
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("<distance:");
+    builder.append('"');
+    builder.append(getField());
+    builder.append('"');
+    builder.append(" latitude=");
+    builder.append(latitude);
+    builder.append(" longitude=");
+    builder.append(longitude);
+    if (Double.POSITIVE_INFINITY != getMissingValue()) {
+      builder.append(" missingValue=" + getMissingValue());
+    }
+    builder.append('>');
+    return builder.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/PointValues.java b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
index 186dbd5..c338383 100644
--- a/lucene/core/src/java/org/apache/lucene/index/PointValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
@@ -25,6 +25,7 @@ import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FloatPoint;
 import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LatLonPoint;
 import org.apache.lucene.document.LongPoint;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.util.StringHelper;
@@ -70,7 +71,7 @@ import org.apache.lucene.util.bkd.BKDWriter;
  * support special operations such as <i>distance</i> and <i>polygon</i> queries. There are currently two implementations:
  * <br>
  * <ol>
- *   <li><a href="{@docRoot}/../sandbox/org/apache/lucene/document/LatLonPoint.html">LatLonPoint</a> in <i>lucene-sandbox</i>: indexes {@code (latitude,longitude)} as {@code (x,y)} in two-dimensional space.
+ *   <li>{@link LatLonPoint}: indexes {@code (latitude,longitude)} as {@code (x,y)} in two-dimensional space.
  *   <li><a href="{@docRoot}/../spatial3d/org/apache/lucene/spatial3d/Geo3DPoint.html">Geo3DPoint</a>* in <i>lucene-spatial3d</i>: indexes {@code (latitude,longitude)} as {@code (x,y,z)} in three-dimensional space.
  * </ol>
  * * does <b>not</b> support altitude, 3D here means "uses three dimensions under-the-hood"<br>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
new file mode 100644
index 0000000..df934d1
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
@@ -0,0 +1,30 @@
+/*
+ * 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.util.LuceneTestCase;
+
+/** Simple tests for LatLonDocValuesField */
+public class TestLatLonDocValuesField extends LuceneTestCase {
+  public void testToString() throws Exception {
+    // looks crazy due to lossiness
+    assertEquals("LatLonDocValuesField <field:18.313693958334625,-65.22744401358068>",(new LatLonDocValuesField("field", 18.313694, -65.227444)).toString());
+    
+    // sort field
+    assertEquals("<distance:\"field\" latitude=18.0 longitude=19.0>", LatLonDocValuesField.newDistanceSort("field", 18.0, 19.0).toString());
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPoint.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPoint.java
new file mode 100644
index 0000000..700eb56
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPoint.java
@@ -0,0 +1,37 @@
+/*
+ * 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.util.LuceneTestCase;
+
+/**
+ * Simple tests for {@link LatLonPoint}
+ * TODO: move this lone test and remove class?
+ * */
+public class TestLatLonPoint extends LuceneTestCase {
+
+  public void testToString() throws Exception {
+    // looks crazy due to lossiness
+    assertEquals("LatLonPoint <field:18.313693958334625,-65.22744401358068>",(new LatLonPoint("field", 18.313694, -65.227444)).toString());
+    
+    // looks crazy due to lossiness
+    assertEquals("field:[18.000000016763806 TO 18.999999999068677],[-65.9999999217689 TO -65.00000006519258]", LatLonPoint.newBoxQuery("field", 18, 19, -66, -65).toString());
+    
+    // distance query does not quantize inputs
+    assertEquals("field:18.0,19.0 +/- 25.0 meters", LatLonPoint.newDistanceQuery("field", 18, 19, 25).toString());
+  }
+}


[2/4] lucene-solr:branch_7x: LUCENE-7314: Graduate LatLonPoint and query classes to core

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
deleted file mode 100644
index 3fdc80b..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
+++ /dev/null
@@ -1,213 +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 java.io.IOException;
-import java.util.Arrays;
-
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.Polygon;
-import org.apache.lucene.geo.Polygon2D;
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PointValues;
-import org.apache.lucene.index.PointValues.IntersectVisitor;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-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.util.NumericUtils;
-import org.apache.lucene.util.StringHelper;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-/** Finds all previously indexed points that fall within the specified polygons.
- *
- *  <p>The field must be indexed with using {@link org.apache.lucene.document.LatLonPoint} added per document.
- *
- *  @lucene.experimental */
-
-final class LatLonPointInPolygonQuery extends Query {
-  final String field;
-  final Polygon[] polygons;
-
-  LatLonPointInPolygonQuery(String field, Polygon[] polygons) {
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    if (polygons == null) {
-      throw new IllegalArgumentException("polygons must not be null");
-    }
-    if (polygons.length == 0) {
-      throw new IllegalArgumentException("polygons must not be empty");
-    }
-    for (int i = 0; i < polygons.length; i++) {
-      if (polygons[i] == null) {
-        throw new IllegalArgumentException("polygon[" + i + "] must not be null");
-      }
-    }
-    this.field = field;
-    this.polygons = polygons.clone();
-    // TODO: we could also compute the maximal inner bounding box, to make relations faster to compute?
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-
-    // I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be
-    // used in the first pass:
-    
-    // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons
-    // these are pre-encoded with LatLonPoint's encoding
-    final Rectangle box = Rectangle.fromPolygon(polygons);
-    final byte minLat[] = new byte[Integer.BYTES];
-    final byte maxLat[] = new byte[Integer.BYTES];
-    final byte minLon[] = new byte[Integer.BYTES];
-    final byte maxLon[] = new byte[Integer.BYTES];
-    NumericUtils.intToSortableBytes(encodeLatitude(box.minLat), minLat, 0);
-    NumericUtils.intToSortableBytes(encodeLatitude(box.maxLat), maxLat, 0);
-    NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0);
-    NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
-
-    final Polygon2D tree = Polygon2D.create(polygons);
-    final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createPolygonPredicate(polygons, tree);
-
-    return new ConstantScoreWeight(this, boost) {
-
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        LeafReader reader = context.reader();
-        PointValues values = reader.getPointValues(field);
-        if (values == null) {
-          // No docs in this segment had any points fields
-          return null;
-        }
-        FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
-        if (fieldInfo == null) {
-          // No docs in this segment indexed this field at all
-          return null;
-        }
-        LatLonPoint.checkCompatible(fieldInfo);
-
-        // matching docids
-        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
-
-        values.intersect( 
-                         new IntersectVisitor() {
-
-                           DocIdSetBuilder.BulkAdder adder;
-
-                           @Override
-                           public void grow(int count) {
-                             adder = result.grow(count);
-                           }
-
-                           @Override
-                           public void visit(int docID) {
-                             adder.add(docID);
-                           }
-
-                           @Override
-                           public void visit(int docID, byte[] packedValue) {
-                             if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
-                                                       NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
-                               adder.add(docID);
-                             }
-                           }
-
-                           @Override
-                           public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
-                             if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
-                                 StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0 ||
-                                 StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
-                                 StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0) {
-                               // outside of global bounding box range
-                               return Relation.CELL_OUTSIDE_QUERY;
-                             }
-                             
-                             double cellMinLat = decodeLatitude(minPackedValue, 0);
-                             double cellMinLon = decodeLongitude(minPackedValue, Integer.BYTES);
-                             double cellMaxLat = decodeLatitude(maxPackedValue, 0);
-                             double cellMaxLon = decodeLongitude(maxPackedValue, Integer.BYTES);
-
-                             return tree.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon);
-                           }
-                         });
-
-        return new ConstantScoreScorer(this, score(), result.build().iterator());
-      }
-
-      @Override
-      public boolean isCacheable(LeafReaderContext ctx) {
-        return true;
-      }
-    };
-  }
-
-  /** Returns the query field */
-  public String getField() {
-    return field;
-  }
-
-  /** Returns a copy of the internal polygon array */
-  public Polygon[] getPolygons() {
-    return polygons.clone();
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = classHash();
-    result = prime * result + field.hashCode();
-    result = prime * result + Arrays.hashCode(polygons);
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    return sameClassAs(other) &&
-           equalsTo(getClass().cast(other));
-  }
-
-  private boolean equalsTo(LatLonPointInPolygonQuery other) {
-    return field.equals(other.field) &&
-           Arrays.equals(polygons, other.polygons);
-  }
-
-  @Override
-  public String toString(String field) {
-    final StringBuilder sb = new StringBuilder();
-    sb.append(getClass().getSimpleName());
-    sb.append(':');
-    if (this.field.equals(field) == false) {
-      sb.append(" field=");
-      sb.append(this.field);
-      sb.append(':');
-    }
-    sb.append(Arrays.toString(polygons));
-    return sb.toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
deleted file mode 100644
index 10e72cc..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointSortField.java
+++ /dev/null
@@ -1,100 +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.geo.GeoUtils;
-import org.apache.lucene.search.FieldComparator;
-import org.apache.lucene.search.SortField;
-
-/**
- * Sorts by distance from an origin location.
- */
-final class LatLonPointSortField extends SortField {
-  final double latitude;
-  final double longitude;
-
-  LatLonPointSortField(String field, double latitude, double longitude) {
-    super(field, SortField.Type.CUSTOM);
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    GeoUtils.checkLatitude(latitude);
-    GeoUtils.checkLongitude(longitude);
-    this.latitude = latitude;
-    this.longitude = longitude;
-    setMissingValue(Double.POSITIVE_INFINITY);
-  }
-  
-  @Override
-  public FieldComparator<?> getComparator(int numHits, int sortPos) {
-    return new LatLonPointDistanceComparator(getField(), latitude, longitude, numHits);
-  }
-
-  @Override
-  public Double getMissingValue() {
-    return (Double) super.getMissingValue();
-  }
-
-  @Override
-  public void setMissingValue(Object missingValue) {
-    if (Double.valueOf(Double.POSITIVE_INFINITY).equals(missingValue) == false) {
-      throw new IllegalArgumentException("Missing value can only be Double.POSITIVE_INFINITY (missing values last), but got " + missingValue);
-    }
-    this.missingValue = missingValue;
-  }
-  
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(latitude);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(longitude);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (this == obj) return true;
-    if (!super.equals(obj)) return false;
-    if (getClass() != obj.getClass()) return false;
-    LatLonPointSortField other = (LatLonPointSortField) obj;
-    if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude)) return false;
-    if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude)) return false;
-    return true;
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder();
-    builder.append("<distance:");
-    builder.append('"');
-    builder.append(getField());
-    builder.append('"');
-    builder.append(" latitude=");
-    builder.append(latitude);
-    builder.append(" longitude=");
-    builder.append(longitude);
-    if (Double.POSITIVE_INFINITY != getMissingValue()) {
-      builder.append(" missingValue=" + getMissingValue());
-    }
-    builder.append('>');
-    return builder.toString();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/NearestNeighbor.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/NearestNeighbor.java b/lucene/sandbox/src/java/org/apache/lucene/document/NearestNeighbor.java
deleted file mode 100644
index 587c63f..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/NearestNeighbor.java
+++ /dev/null
@@ -1,328 +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 java.io.IOException;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.PriorityQueue;
-
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.PointValues.IntersectVisitor;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.util.Bits;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.bkd.BKDReader.IndexTree;
-import org.apache.lucene.util.bkd.BKDReader.IntersectState;
-import org.apache.lucene.util.bkd.BKDReader;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-
-/**
- * KNN search on top of 2D lat/lon indexed points.
- *
- * @lucene.experimental
- */
-class NearestNeighbor {
-
-  static class Cell implements Comparable<Cell> {
-    final int readerIndex;
-    final byte[] minPacked;
-    final byte[] maxPacked;
-    final IndexTree index;
-
-    /** The closest possible distance of all points in this cell */
-    final double distanceMeters;
-
-    public Cell(IndexTree index, int readerIndex, byte[] minPacked, byte[] maxPacked, double distanceMeters) {
-      this.index = index;
-      this.readerIndex = readerIndex;
-      this.minPacked = minPacked.clone();
-      this.maxPacked = maxPacked.clone();
-      this.distanceMeters = distanceMeters;
-    }
-
-    public int compareTo(Cell other) {
-      return Double.compare(distanceMeters, other.distanceMeters);
-    }
-
-    @Override
-    public String toString() {
-      double minLat = decodeLatitude(minPacked, 0);
-      double minLon = decodeLongitude(minPacked, Integer.BYTES);
-      double maxLat = decodeLatitude(maxPacked, 0);
-      double maxLon = decodeLongitude(maxPacked, Integer.BYTES);
-      return "Cell(readerIndex=" + readerIndex + " nodeID=" + index.getNodeID() + " isLeaf=" + index.isLeafNode() + " lat=" + minLat + " TO " + maxLat + ", lon=" + minLon + " TO " + maxLon + "; distanceMeters=" + distanceMeters + ")";
-    }
-  }
-
-  private static class NearestVisitor implements IntersectVisitor {
-
-    public int curDocBase;
-    public Bits curLiveDocs;
-    final int topN;
-    final PriorityQueue<NearestHit> hitQueue;
-    final double pointLat;
-    final double pointLon;
-    private int setBottomCounter;
-
-    private double minLon = Double.NEGATIVE_INFINITY;
-    private double maxLon = Double.POSITIVE_INFINITY;
-    private double minLat = Double.NEGATIVE_INFINITY;
-    private double maxLat = Double.POSITIVE_INFINITY;
-
-    // second set of longitude ranges to check (for cross-dateline case)
-    private double minLon2 = Double.POSITIVE_INFINITY;
-
-    public NearestVisitor(PriorityQueue<NearestHit> hitQueue, int topN, double pointLat, double pointLon) {
-      this.hitQueue = hitQueue;
-      this.topN = topN;
-      this.pointLat = pointLat;
-      this.pointLon = pointLon;
-    }
-
-    @Override
-    public void visit(int docID) {
-      throw new AssertionError();
-    }
-
-    private void maybeUpdateBBox() {
-      if (setBottomCounter < 1024 || (setBottomCounter & 0x3F) == 0x3F) {
-        NearestHit hit = hitQueue.peek();
-        Rectangle box = Rectangle.fromPointDistance(pointLat, pointLon, hit.distanceMeters);
-        //System.out.println("    update bbox to " + box);
-        minLat = box.minLat;
-        maxLat = box.maxLat;
-        if (box.crossesDateline()) {
-          // box1
-          minLon = Double.NEGATIVE_INFINITY;
-          maxLon = box.maxLon;
-          // box2
-          minLon2 = box.minLon;
-        } else {
-          minLon = box.minLon;
-          maxLon = box.maxLon;
-          // disable box2
-          minLon2 = Double.POSITIVE_INFINITY;
-        }
-      }
-      setBottomCounter++;
-    }
-
-    @Override
-    public void visit(int docID, byte[] packedValue) {
-      //System.out.println("visit docID=" + docID + " liveDocs=" + curLiveDocs);
-
-      if (curLiveDocs != null && curLiveDocs.get(docID) == false) {
-        return;
-      }
-
-      // TODO: work in int space, use haversinSortKey
-
-      double docLatitude = decodeLatitude(packedValue, 0);
-      double docLongitude = decodeLongitude(packedValue, Integer.BYTES);
-
-      // test bounding box
-      if (docLatitude < minLat || docLatitude > maxLat) {
-        return;
-      }
-      if ((docLongitude < minLon || docLongitude > maxLon) && (docLongitude < minLon2)) {
-        return;
-      }
-
-      double distanceMeters = SloppyMath.haversinMeters(pointLat, pointLon, docLatitude, docLongitude);
-
-      //System.out.println("    visit docID=" + docID + " distanceMeters=" + distanceMeters + " docLat=" + docLatitude + " docLon=" + docLongitude);
-
-      int fullDocID = curDocBase + docID;
-
-      if (hitQueue.size() == topN) {
-        // queue already full
-        NearestHit hit = hitQueue.peek();
-        //System.out.println("      bottom distanceMeters=" + hit.distanceMeters);
-        // we don't collect docs in order here, so we must also test the tie-break case ourselves:
-        if (distanceMeters < hit.distanceMeters || (distanceMeters == hit.distanceMeters && fullDocID < hit.docID)) {
-          hitQueue.poll();
-          hit.docID = fullDocID;
-          hit.distanceMeters = distanceMeters;
-          hitQueue.offer(hit);
-          //System.out.println("      ** keep2, now bottom=" + hit);
-          maybeUpdateBBox();
-        }
-        
-      } else {
-        NearestHit hit = new NearestHit();
-        hit.docID = fullDocID;
-        hit.distanceMeters = distanceMeters;
-        hitQueue.offer(hit);
-        //System.out.println("      ** keep1, now bottom=" + hit);
-      }
-    }
-
-    @Override
-    public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
-      throw new AssertionError();
-    }
-  }
-
-  /** Holds one hit from {@link LatLonPoint#nearest} */
-  static class NearestHit {
-    public int docID;
-    public double distanceMeters;
-
-    @Override
-    public String toString() {
-      return "NearestHit(docID=" + docID + " distanceMeters=" + distanceMeters + ")";
-    }
-  }
-
-  // TODO: can we somehow share more with, or simply directly use, the LatLonPointDistanceComparator?  It's really doing the same thing as
-  // our hitQueue...
-
-  public static NearestHit[] nearest(double pointLat, double pointLon, List<BKDReader> readers, List<Bits> liveDocs, List<Integer> docBases, final int n) throws IOException {
-
-    //System.out.println("NEAREST: readers=" + readers + " liveDocs=" + liveDocs + " pointLat=" + pointLat + " pointLon=" + pointLon);
-    // Holds closest collected points seen so far:
-    // TODO: if we used lucene's PQ we could just updateTop instead of poll/offer:
-    final PriorityQueue<NearestHit> hitQueue = new PriorityQueue<>(n, new Comparator<NearestHit>() {
-        @Override
-        public int compare(NearestHit a, NearestHit b) {
-          // sort by opposite distanceMeters natural order
-          int cmp = Double.compare(a.distanceMeters, b.distanceMeters);
-          if (cmp != 0) {
-            return -cmp;
-          }
-
-          // tie-break by higher docID:
-          return b.docID - a.docID;
-        }
-      });
-
-    // Holds all cells, sorted by closest to the point:
-    PriorityQueue<Cell> cellQueue = new PriorityQueue<>();
-
-    NearestVisitor visitor = new NearestVisitor(hitQueue, n, pointLat, pointLon);
-    List<BKDReader.IntersectState> states = new ArrayList<>();
-
-    // Add root cell for each reader into the queue:
-    int bytesPerDim = -1;
-    
-    for(int i=0;i<readers.size();i++) {
-      BKDReader reader = readers.get(i);
-      if (bytesPerDim == -1) {
-        bytesPerDim = reader.getBytesPerDimension();
-      } else if (bytesPerDim != reader.getBytesPerDimension()) {
-        throw new IllegalStateException("bytesPerDim changed from " + bytesPerDim + " to " + reader.getBytesPerDimension() + " across readers");
-      }
-      byte[] minPackedValue = reader.getMinPackedValue();
-      byte[] maxPackedValue = reader.getMaxPackedValue();
-      IntersectState state = reader.getIntersectState(visitor);
-      states.add(state);
-
-      cellQueue.offer(new Cell(state.index, i, reader.getMinPackedValue(), reader.getMaxPackedValue(),
-                               approxBestDistance(minPackedValue, maxPackedValue, pointLat, pointLon)));
-    }
-
-    while (cellQueue.size() > 0) {
-      Cell cell = cellQueue.poll();
-      //System.out.println("  visit " + cell);
-
-      // TODO: if we replace approxBestDistance with actualBestDistance, we can put an opto here to break once this "best" cell is fully outside of the hitQueue bottom's radius:
-      BKDReader reader = readers.get(cell.readerIndex);
-
-      if (cell.index.isLeafNode()) {
-        //System.out.println("    leaf");
-        // Leaf block: visit all points and possibly collect them:
-        visitor.curDocBase = docBases.get(cell.readerIndex);
-        visitor.curLiveDocs = liveDocs.get(cell.readerIndex);
-        reader.visitLeafBlockValues(cell.index, states.get(cell.readerIndex));
-        //System.out.println("    now " + hitQueue.size() + " hits");
-      } else {
-        //System.out.println("    non-leaf");
-        // Non-leaf block: split into two cells and put them back into the queue:
-
-        double cellMinLat = decodeLatitude(cell.minPacked, 0);
-        double cellMinLon = decodeLongitude(cell.minPacked, Integer.BYTES);
-        double cellMaxLat = decodeLatitude(cell.maxPacked, 0);
-        double cellMaxLon = decodeLongitude(cell.maxPacked, Integer.BYTES);
-
-        if (cellMaxLat < visitor.minLat || visitor.maxLat < cellMinLat || ((cellMaxLon < visitor.minLon || visitor.maxLon < cellMinLon) && cellMaxLon < visitor.minLon2)) {
-          // this cell is outside our search bbox; don't bother exploring any more
-          continue;
-        }
-        
-        BytesRef splitValue = BytesRef.deepCopyOf(cell.index.getSplitDimValue());
-        int splitDim = cell.index.getSplitDim();
-        
-        // we must clone the index so that we we can recurse left and right "concurrently":
-        IndexTree newIndex = cell.index.clone();
-        byte[] splitPackedValue = cell.maxPacked.clone();
-        System.arraycopy(splitValue.bytes, splitValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
-
-        cell.index.pushLeft();
-        cellQueue.offer(new Cell(cell.index, cell.readerIndex, cell.minPacked, splitPackedValue,
-                                 approxBestDistance(cell.minPacked, splitPackedValue, pointLat, pointLon)));
-
-        splitPackedValue = cell.minPacked.clone();
-        System.arraycopy(splitValue.bytes, splitValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
-
-        newIndex.pushRight();
-        cellQueue.offer(new Cell(newIndex, cell.readerIndex, splitPackedValue, cell.maxPacked,
-                                 approxBestDistance(splitPackedValue, cell.maxPacked, pointLat, pointLon)));
-      }
-    }
-
-    NearestHit[] hits = new NearestHit[hitQueue.size()];
-    int downTo = hitQueue.size()-1;
-    while (hitQueue.size() != 0) {
-      hits[downTo] = hitQueue.poll();
-      downTo--;
-    }
-
-    return hits;
-  }
-
-  // NOTE: incoming args never cross the dateline, since they are a BKD cell
-  private static double approxBestDistance(byte[] minPackedValue, byte[] maxPackedValue, double pointLat, double pointLon) {
-    double minLat = decodeLatitude(minPackedValue, 0);
-    double minLon = decodeLongitude(minPackedValue, Integer.BYTES);
-    double maxLat = decodeLatitude(maxPackedValue, 0);
-    double maxLon = decodeLongitude(maxPackedValue, Integer.BYTES);
-    return approxBestDistance(minLat, maxLat, minLon, maxLon, pointLat, pointLon);
-  }
-
-  // NOTE: incoming args never cross the dateline, since they are a BKD cell
-  private static double approxBestDistance(double minLat, double maxLat, double minLon, double maxLon, double pointLat, double pointLon) {
-    
-    // TODO: can we make this the trueBestDistance?  I.e., minimum distance between the point and ANY point on the box?  we can speed things
-    // up if so, but not enrolling any BKD cell whose true best distance is > bottom of the current hit queue
-
-    if (pointLat >= minLat && pointLat <= maxLat && pointLon >= minLon && pointLon <= maxLon) {
-      // point is inside the cell!
-      return 0.0;
-    }
-
-    double d1 = SloppyMath.haversinMeters(pointLat, pointLon, minLat, minLon);
-    double d2 = SloppyMath.haversinMeters(pointLat, pointLon, minLat, maxLon);
-    double d3 = SloppyMath.haversinMeters(pointLat, pointLon, maxLat, maxLon);
-    double d4 = SloppyMath.haversinMeters(pointLat, pointLon, maxLat, minLon);
-    return Math.min(Math.min(d1, d2), Math.min(d3, d4));
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/search/LatLonPointPrototypeQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/LatLonPointPrototypeQueries.java b/lucene/sandbox/src/java/org/apache/lucene/search/LatLonPointPrototypeQueries.java
new file mode 100644
index 0000000..bd8c5b7
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/LatLonPointPrototypeQueries.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.codecs.lucene60.Lucene60PointsFormat;
+import org.apache.lucene.document.LatLonDocValuesField;
+import org.apache.lucene.document.LatLonPoint;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.bkd.BKDReader;
+
+/**
+ * Holder class for prototype sandboxed queries
+ *
+ * When the query graduates from sandbox, these static calls should be
+ * placed in {@link LatLonPoint}
+ *
+ * @lucene.experimental
+ */
+public class LatLonPointPrototypeQueries {
+
+    // no instance
+    private LatLonPointPrototypeQueries() {
+    }
+
+  /**
+   * Finds the {@code n} nearest indexed points to the provided point, according to Haversine distance.
+   * <p>
+   * This is functionally equivalent to running {@link MatchAllDocsQuery} with a {@link LatLonDocValuesField#newDistanceSort},
+   * but is far more efficient since it takes advantage of properties the indexed BKD tree.  Currently this
+   * only works with {@link Lucene60PointsFormat} (used by the default codec).  Multi-valued fields are
+   * currently not de-duplicated, so if a document had multiple instances of the specified field that
+   * make it into the top n, that document will appear more than once.
+   * <p>
+   * Documents are ordered by ascending distance from the location. The value returned in {@link FieldDoc} for
+   * the hits contains a Double instance with the distance in meters.
+   *
+   * @param searcher IndexSearcher to find nearest points from.
+   * @param field field name. must not be null.
+   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
+   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
+   * @param n the number of nearest neighbors to retrieve.
+   * @return TopFieldDocs containing documents ordered by distance, where the field value for each {@link FieldDoc} is the distance in meters
+   * @throws IllegalArgumentException if the underlying PointValues is not a {@code Lucene60PointsReader} (this is a current limitation), or
+   *         if {@code field} or {@code searcher} is null, or if {@code latitude}, {@code longitude} or {@code n} are out-of-bounds
+   * @throws IOException if an IOException occurs while finding the points.
+   */
+  // TODO: what about multi-valued documents? what happens?
+  public static TopFieldDocs nearest(IndexSearcher searcher, String field, double latitude, double longitude, int n) throws IOException {
+    GeoUtils.checkLatitude(latitude);
+    GeoUtils.checkLongitude(longitude);
+    if (n < 1) {
+      throw new IllegalArgumentException("n must be at least 1; got " + n);
+    }
+    if (field == null) {
+      throw new IllegalArgumentException("field must not be null");
+    }
+    if (searcher == null) {
+      throw new IllegalArgumentException("searcher must not be null");
+    }
+    List<BKDReader> readers = new ArrayList<>();
+    List<Integer> docBases = new ArrayList<>();
+    List<Bits> liveDocs = new ArrayList<>();
+    int totalHits = 0;
+    for(LeafReaderContext leaf : searcher.getIndexReader().leaves()) {
+      PointValues points = leaf.reader().getPointValues(field);
+      if (points != null) {
+        if (points instanceof BKDReader == false) {
+          throw new IllegalArgumentException("can only run on Lucene60PointsReader points implementation, but got " + points);
+        }
+        totalHits += points.getDocCount();
+        BKDReader reader = (BKDReader) points;
+        if (reader != null) {
+          readers.add(reader);
+          docBases.add(leaf.docBase);
+          liveDocs.add(leaf.reader().getLiveDocs());
+        }
+      }
+    }
+
+    NearestNeighbor.NearestHit[] hits = NearestNeighbor.nearest(latitude, longitude, readers, liveDocs, docBases, n);
+
+    // Convert to TopFieldDocs:
+    ScoreDoc[] scoreDocs = new ScoreDoc[hits.length];
+    for(int i=0;i<hits.length;i++) {
+      NearestNeighbor.NearestHit hit = hits[i];
+      scoreDocs[i] = new FieldDoc(hit.docID, 0.0f, new Object[] {Double.valueOf(hit.distanceMeters)});
+    }
+    return new TopFieldDocs(totalHits, scoreDocs, null, 0.0f);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/search/NearestNeighbor.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/NearestNeighbor.java b/lucene/sandbox/src/java/org/apache/lucene/search/NearestNeighbor.java
new file mode 100644
index 0000000..8502c45
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/NearestNeighbor.java
@@ -0,0 +1,329 @@
+/*
+ * 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.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.bkd.BKDReader.IndexTree;
+import org.apache.lucene.util.bkd.BKDReader.IntersectState;
+import org.apache.lucene.util.bkd.BKDReader;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+
+/**
+ * KNN search on top of 2D lat/lon indexed points.
+ *
+ * @lucene.experimental
+ */
+class NearestNeighbor {
+
+  static class Cell implements Comparable<Cell> {
+    final int readerIndex;
+    final byte[] minPacked;
+    final byte[] maxPacked;
+    final IndexTree index;
+
+    /** The closest possible distance of all points in this cell */
+    final double distanceMeters;
+
+    public Cell(IndexTree index, int readerIndex, byte[] minPacked, byte[] maxPacked, double distanceMeters) {
+      this.index = index;
+      this.readerIndex = readerIndex;
+      this.minPacked = minPacked.clone();
+      this.maxPacked = maxPacked.clone();
+      this.distanceMeters = distanceMeters;
+    }
+
+    public int compareTo(Cell other) {
+      return Double.compare(distanceMeters, other.distanceMeters);
+    }
+
+    @Override
+    public String toString() {
+      double minLat = decodeLatitude(minPacked, 0);
+      double minLon = decodeLongitude(minPacked, Integer.BYTES);
+      double maxLat = decodeLatitude(maxPacked, 0);
+      double maxLon = decodeLongitude(maxPacked, Integer.BYTES);
+      return "Cell(readerIndex=" + readerIndex + " nodeID=" + index.getNodeID() + " isLeaf=" + index.isLeafNode() + " lat=" + minLat + " TO " + maxLat + ", lon=" + minLon + " TO " + maxLon + "; distanceMeters=" + distanceMeters + ")";
+    }
+  }
+
+  private static class NearestVisitor implements IntersectVisitor {
+
+    public int curDocBase;
+    public Bits curLiveDocs;
+    final int topN;
+    final PriorityQueue<NearestHit> hitQueue;
+    final double pointLat;
+    final double pointLon;
+    private int setBottomCounter;
+
+    private double minLon = Double.NEGATIVE_INFINITY;
+    private double maxLon = Double.POSITIVE_INFINITY;
+    private double minLat = Double.NEGATIVE_INFINITY;
+    private double maxLat = Double.POSITIVE_INFINITY;
+
+    // second set of longitude ranges to check (for cross-dateline case)
+    private double minLon2 = Double.POSITIVE_INFINITY;
+
+    public NearestVisitor(PriorityQueue<NearestHit> hitQueue, int topN, double pointLat, double pointLon) {
+      this.hitQueue = hitQueue;
+      this.topN = topN;
+      this.pointLat = pointLat;
+      this.pointLon = pointLon;
+    }
+
+    @Override
+    public void visit(int docID) {
+      throw new AssertionError();
+    }
+
+    private void maybeUpdateBBox() {
+      if (setBottomCounter < 1024 || (setBottomCounter & 0x3F) == 0x3F) {
+        NearestHit hit = hitQueue.peek();
+        Rectangle box = Rectangle.fromPointDistance(pointLat, pointLon, hit.distanceMeters);
+        //System.out.println("    update bbox to " + box);
+        minLat = box.minLat;
+        maxLat = box.maxLat;
+        if (box.crossesDateline()) {
+          // box1
+          minLon = Double.NEGATIVE_INFINITY;
+          maxLon = box.maxLon;
+          // box2
+          minLon2 = box.minLon;
+        } else {
+          minLon = box.minLon;
+          maxLon = box.maxLon;
+          // disable box2
+          minLon2 = Double.POSITIVE_INFINITY;
+        }
+      }
+      setBottomCounter++;
+    }
+
+    @Override
+    public void visit(int docID, byte[] packedValue) {
+      //System.out.println("visit docID=" + docID + " liveDocs=" + curLiveDocs);
+
+      if (curLiveDocs != null && curLiveDocs.get(docID) == false) {
+        return;
+      }
+
+      // TODO: work in int space, use haversinSortKey
+
+      double docLatitude = decodeLatitude(packedValue, 0);
+      double docLongitude = decodeLongitude(packedValue, Integer.BYTES);
+
+      // test bounding box
+      if (docLatitude < minLat || docLatitude > maxLat) {
+        return;
+      }
+      if ((docLongitude < minLon || docLongitude > maxLon) && (docLongitude < minLon2)) {
+        return;
+      }
+
+      double distanceMeters = SloppyMath.haversinMeters(pointLat, pointLon, docLatitude, docLongitude);
+
+      //System.out.println("    visit docID=" + docID + " distanceMeters=" + distanceMeters + " docLat=" + docLatitude + " docLon=" + docLongitude);
+
+      int fullDocID = curDocBase + docID;
+
+      if (hitQueue.size() == topN) {
+        // queue already full
+        NearestHit hit = hitQueue.peek();
+        //System.out.println("      bottom distanceMeters=" + hit.distanceMeters);
+        // we don't collect docs in order here, so we must also test the tie-break case ourselves:
+        if (distanceMeters < hit.distanceMeters || (distanceMeters == hit.distanceMeters && fullDocID < hit.docID)) {
+          hitQueue.poll();
+          hit.docID = fullDocID;
+          hit.distanceMeters = distanceMeters;
+          hitQueue.offer(hit);
+          //System.out.println("      ** keep2, now bottom=" + hit);
+          maybeUpdateBBox();
+        }
+        
+      } else {
+        NearestHit hit = new NearestHit();
+        hit.docID = fullDocID;
+        hit.distanceMeters = distanceMeters;
+        hitQueue.offer(hit);
+        //System.out.println("      ** keep1, now bottom=" + hit);
+      }
+    }
+
+    @Override
+    public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+      throw new AssertionError();
+    }
+  }
+
+  /** Holds one hit from {@link LatLonPointPrototypeQueries#nearest} */
+  static class NearestHit {
+    public int docID;
+    public double distanceMeters;
+
+    @Override
+    public String toString() {
+      return "NearestHit(docID=" + docID + " distanceMeters=" + distanceMeters + ")";
+    }
+  }
+
+  // TODO: can we somehow share more with, or simply directly use, the LatLonPointDistanceComparator?  It's really doing the same thing as
+  // our hitQueue...
+
+  public static NearestHit[] nearest(double pointLat, double pointLon, List<BKDReader> readers, List<Bits> liveDocs, List<Integer> docBases, final int n) throws IOException {
+
+    //System.out.println("NEAREST: readers=" + readers + " liveDocs=" + liveDocs + " pointLat=" + pointLat + " pointLon=" + pointLon);
+    // Holds closest collected points seen so far:
+    // TODO: if we used lucene's PQ we could just updateTop instead of poll/offer:
+    final PriorityQueue<NearestHit> hitQueue = new PriorityQueue<>(n, new Comparator<NearestHit>() {
+        @Override
+        public int compare(NearestHit a, NearestHit b) {
+          // sort by opposite distanceMeters natural order
+          int cmp = Double.compare(a.distanceMeters, b.distanceMeters);
+          if (cmp != 0) {
+            return -cmp;
+          }
+
+          // tie-break by higher docID:
+          return b.docID - a.docID;
+        }
+      });
+
+    // Holds all cells, sorted by closest to the point:
+    PriorityQueue<Cell> cellQueue = new PriorityQueue<>();
+
+    NearestVisitor visitor = new NearestVisitor(hitQueue, n, pointLat, pointLon);
+    List<BKDReader.IntersectState> states = new ArrayList<>();
+
+    // Add root cell for each reader into the queue:
+    int bytesPerDim = -1;
+    
+    for(int i=0;i<readers.size();i++) {
+      BKDReader reader = readers.get(i);
+      if (bytesPerDim == -1) {
+        bytesPerDim = reader.getBytesPerDimension();
+      } else if (bytesPerDim != reader.getBytesPerDimension()) {
+        throw new IllegalStateException("bytesPerDim changed from " + bytesPerDim + " to " + reader.getBytesPerDimension() + " across readers");
+      }
+      byte[] minPackedValue = reader.getMinPackedValue();
+      byte[] maxPackedValue = reader.getMaxPackedValue();
+      IntersectState state = reader.getIntersectState(visitor);
+      states.add(state);
+
+      cellQueue.offer(new Cell(state.index, i, reader.getMinPackedValue(), reader.getMaxPackedValue(),
+                               approxBestDistance(minPackedValue, maxPackedValue, pointLat, pointLon)));
+    }
+
+    while (cellQueue.size() > 0) {
+      Cell cell = cellQueue.poll();
+      //System.out.println("  visit " + cell);
+
+      // TODO: if we replace approxBestDistance with actualBestDistance, we can put an opto here to break once this "best" cell is fully outside of the hitQueue bottom's radius:
+      BKDReader reader = readers.get(cell.readerIndex);
+
+      if (cell.index.isLeafNode()) {
+        //System.out.println("    leaf");
+        // Leaf block: visit all points and possibly collect them:
+        visitor.curDocBase = docBases.get(cell.readerIndex);
+        visitor.curLiveDocs = liveDocs.get(cell.readerIndex);
+        reader.visitLeafBlockValues(cell.index, states.get(cell.readerIndex));
+        //System.out.println("    now " + hitQueue.size() + " hits");
+      } else {
+        //System.out.println("    non-leaf");
+        // Non-leaf block: split into two cells and put them back into the queue:
+
+        double cellMinLat = decodeLatitude(cell.minPacked, 0);
+        double cellMinLon = decodeLongitude(cell.minPacked, Integer.BYTES);
+        double cellMaxLat = decodeLatitude(cell.maxPacked, 0);
+        double cellMaxLon = decodeLongitude(cell.maxPacked, Integer.BYTES);
+
+        if (cellMaxLat < visitor.minLat || visitor.maxLat < cellMinLat || ((cellMaxLon < visitor.minLon || visitor.maxLon < cellMinLon) && cellMaxLon < visitor.minLon2)) {
+          // this cell is outside our search bbox; don't bother exploring any more
+          continue;
+        }
+        
+        BytesRef splitValue = BytesRef.deepCopyOf(cell.index.getSplitDimValue());
+        int splitDim = cell.index.getSplitDim();
+        
+        // we must clone the index so that we we can recurse left and right "concurrently":
+        IndexTree newIndex = cell.index.clone();
+        byte[] splitPackedValue = cell.maxPacked.clone();
+        System.arraycopy(splitValue.bytes, splitValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+
+        cell.index.pushLeft();
+        cellQueue.offer(new Cell(cell.index, cell.readerIndex, cell.minPacked, splitPackedValue,
+                                 approxBestDistance(cell.minPacked, splitPackedValue, pointLat, pointLon)));
+
+        splitPackedValue = cell.minPacked.clone();
+        System.arraycopy(splitValue.bytes, splitValue.offset, splitPackedValue, splitDim*bytesPerDim, bytesPerDim);
+
+        newIndex.pushRight();
+        cellQueue.offer(new Cell(newIndex, cell.readerIndex, splitPackedValue, cell.maxPacked,
+                                 approxBestDistance(splitPackedValue, cell.maxPacked, pointLat, pointLon)));
+      }
+    }
+
+    NearestHit[] hits = new NearestHit[hitQueue.size()];
+    int downTo = hitQueue.size()-1;
+    while (hitQueue.size() != 0) {
+      hits[downTo] = hitQueue.poll();
+      downTo--;
+    }
+
+    return hits;
+  }
+
+  // NOTE: incoming args never cross the dateline, since they are a BKD cell
+  private static double approxBestDistance(byte[] minPackedValue, byte[] maxPackedValue, double pointLat, double pointLon) {
+    double minLat = decodeLatitude(minPackedValue, 0);
+    double minLon = decodeLongitude(minPackedValue, Integer.BYTES);
+    double maxLat = decodeLatitude(maxPackedValue, 0);
+    double maxLon = decodeLongitude(maxPackedValue, Integer.BYTES);
+    return approxBestDistance(minLat, maxLat, minLon, maxLon, pointLat, pointLon);
+  }
+
+  // NOTE: incoming args never cross the dateline, since they are a BKD cell
+  private static double approxBestDistance(double minLat, double maxLat, double minLon, double maxLon, double pointLat, double pointLon) {
+    
+    // TODO: can we make this the trueBestDistance?  I.e., minimum distance between the point and ANY point on the box?  we can speed things
+    // up if so, but not enrolling any BKD cell whose true best distance is > bottom of the current hit queue
+
+    if (pointLat >= minLat && pointLat <= maxLat && pointLon >= minLon && pointLon <= maxLon) {
+      // point is inside the cell!
+      return 0.0;
+    }
+
+    double d1 = SloppyMath.haversinMeters(pointLat, pointLon, minLat, minLon);
+    double d2 = SloppyMath.haversinMeters(pointLat, pointLon, minLat, maxLon);
+    double d3 = SloppyMath.haversinMeters(pointLat, pointLon, maxLat, maxLon);
+    double d4 = SloppyMath.haversinMeters(pointLat, pointLon, maxLat, minLon);
+    return Math.min(Math.min(d1, d2), Math.min(d3, d4));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/document/TestFloatPointNearestNeighbor.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestFloatPointNearestNeighbor.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestFloatPointNearestNeighbor.java
index 9e3c3c1..72e7900 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestFloatPointNearestNeighbor.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestFloatPointNearestNeighbor.java
@@ -28,6 +28,7 @@ import org.apache.lucene.index.SerialMergeScheduler;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.FieldDoc;
 import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.LatLonPointPrototypeQueries;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.LuceneTestCase;
@@ -59,7 +60,7 @@ public class TestFloatPointNearestNeighbor extends LuceneTestCase {
     r = w.getReader();
     // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
     s = newSearcher(r, false);
-    hit = (FieldDoc)LatLonPoint.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
+    hit = (FieldDoc) LatLonPointPrototypeQueries.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
     assertEquals("1", r.document(hit.doc).getField("id").stringValue());
     r.close();
     w.close();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
deleted file mode 100644
index df934d1..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonDocValuesField.java
+++ /dev/null
@@ -1,30 +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.util.LuceneTestCase;
-
-/** Simple tests for LatLonDocValuesField */
-public class TestLatLonDocValuesField extends LuceneTestCase {
-  public void testToString() throws Exception {
-    // looks crazy due to lossiness
-    assertEquals("LatLonDocValuesField <field:18.313693958334625,-65.22744401358068>",(new LatLonDocValuesField("field", 18.313694, -65.227444)).toString());
-    
-    // sort field
-    assertEquals("<distance:\"field\" latitude=18.0 longitude=19.0>", LatLonDocValuesField.newDistanceSort("field", 18.0, 19.0).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
deleted file mode 100644
index 700eb56..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
+++ /dev/null
@@ -1,37 +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.util.LuceneTestCase;
-
-/**
- * Simple tests for {@link LatLonPoint}
- * TODO: move this lone test and remove class?
- * */
-public class TestLatLonPoint extends LuceneTestCase {
-
-  public void testToString() throws Exception {
-    // looks crazy due to lossiness
-    assertEquals("LatLonPoint <field:18.313693958334625,-65.22744401358068>",(new LatLonPoint("field", 18.313694, -65.227444)).toString());
-    
-    // looks crazy due to lossiness
-    assertEquals("field:[18.000000016763806 TO 18.999999999068677],[-65.9999999217689 TO -65.00000006519258]", LatLonPoint.newBoxQuery("field", 18, 19, -66, -65).toString());
-    
-    // distance query does not quantize inputs
-    assertEquals("field:18.0,19.0 +/- 25.0 meters", LatLonPoint.newDistanceQuery("field", 18, 19, 25).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
deleted file mode 100644
index 6d825d2..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.document;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import org.apache.lucene.geo.GeoTestUtil;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.index.SerialMergeScheduler;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.TestUtil;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-/** Simple tests for {@link LatLonDocValuesField#newDistanceSort} */
-public class TestLatLonPointDistanceSort extends LuceneTestCase {
-
-  /** Add three points and sort by distance */
-  public void testDistanceSort() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    
-    // add some docs
-    Document doc = new Document();
-    doc.add(new LatLonDocValuesField("location", 40.759011, -73.9844722));
-    iw.addDocument(doc);
-    
-    doc = new Document();
-    doc.add(new LatLonDocValuesField("location", 40.718266, -74.007819));
-    iw.addDocument(doc);
-    
-    doc = new Document();
-    doc.add(new LatLonDocValuesField("location", 40.7051157, -74.0088305));
-    iw.addDocument(doc);
-    
-    IndexReader reader = iw.getReader();
-    IndexSearcher searcher = newSearcher(reader);
-    iw.close();
-
-    Sort sort = new Sort(LatLonDocValuesField.newDistanceSort("location", 40.7143528, -74.0059731));
-    TopDocs td = searcher.search(new MatchAllDocsQuery(), 3, sort);
-    
-    FieldDoc d = (FieldDoc) td.scoreDocs[0];
-    assertEquals(462.1028401330431, (Double)d.fields[0], 0.0D);
-    
-    d = (FieldDoc) td.scoreDocs[1];
-    assertEquals(1054.9842850974826, (Double)d.fields[0], 0.0D);
-    
-    d = (FieldDoc) td.scoreDocs[2];
-    assertEquals(5285.881528419706, (Double)d.fields[0], 0.0D);
-    
-    reader.close();
-    dir.close();
-  }
-  
-  /** Add two points (one doc missing) and sort by distance */
-  public void testMissingLast() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    
-    // missing
-    Document doc = new Document();
-    iw.addDocument(doc);
-    
-    doc = new Document();
-    doc.add(new LatLonDocValuesField("location", 40.718266, -74.007819));
-    iw.addDocument(doc);
-    
-    doc = new Document();
-    doc.add(new LatLonDocValuesField("location", 40.7051157, -74.0088305));
-    iw.addDocument(doc);
-    
-    IndexReader reader = iw.getReader();
-    IndexSearcher searcher = newSearcher(reader);
-    iw.close();
-
-    Sort sort = new Sort(LatLonDocValuesField.newDistanceSort("location", 40.7143528, -74.0059731));
-    TopDocs td = searcher.search(new MatchAllDocsQuery(), 3, sort);
-    
-    FieldDoc d = (FieldDoc) td.scoreDocs[0];
-    assertEquals(462.1028401330431D, (Double)d.fields[0], 0.0D);
-    
-    d = (FieldDoc) td.scoreDocs[1];
-    assertEquals(1054.9842850974826, (Double)d.fields[0], 0.0D);
-    
-    d = (FieldDoc) td.scoreDocs[2];
-    assertEquals(Double.POSITIVE_INFINITY, (Double)d.fields[0], 0.0D);
-    
-    reader.close();
-    dir.close();
-  }
-  
-  /** Run a few iterations with just 10 docs, hopefully easy to debug */
-  public void testRandom() throws Exception {
-    for (int iters = 0; iters < 100; iters++) {
-      doRandomTest(10, 100);
-    }
-  }
-  
-  /** Runs with thousands of docs */
-  @Nightly
-  public void testRandomHuge() throws Exception {
-    for (int iters = 0; iters < 10; iters++) {
-      doRandomTest(2000, 100);
-    }
-  }
-  
-  // result class used for testing. holds an id+distance.
-  // we sort these with Arrays.sort and compare with lucene's results
-  static class Result implements Comparable<Result> {
-    int id;
-    double distance;
-    
-    Result(int id, double distance) {
-      this.id = id;
-      this.distance = distance;
-    }
-
-    @Override
-    public int compareTo(Result o) {
-      int cmp = Double.compare(distance, o.distance);
-      if (cmp == 0) {
-        return Integer.compare(id, o.id);
-      }
-      return cmp;
-    }
-
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      long temp;
-      temp = Double.doubleToLongBits(distance);
-      result = prime * result + (int) (temp ^ (temp >>> 32));
-      result = prime * result + id;
-      return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) return true;
-      if (obj == null) return false;
-      if (getClass() != obj.getClass()) return false;
-      Result other = (Result) obj;
-      if (Double.doubleToLongBits(distance) != Double.doubleToLongBits(other.distance)) return false;
-      if (id != other.id) return false;
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      return "Result [id=" + id + ", distance=" + distance + "]";
-    }
-  }
-  
-  private void doRandomTest(int numDocs, int numQueries) throws IOException {
-    Directory dir = newDirectory();    
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    // else seeds may not to reproduce:
-    iwc.setMergeScheduler(new SerialMergeScheduler());
-    RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
-
-    for (int i = 0; i < numDocs; i++) {
-      Document doc = new Document();
-      doc.add(new StoredField("id", i));
-      doc.add(new NumericDocValuesField("id", i));
-      if (random().nextInt(10) > 7) {
-        double latRaw = GeoTestUtil.nextLatitude();
-        double lonRaw = GeoTestUtil.nextLongitude();
-        // pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
-        double lat = decodeLatitude(encodeLatitude(latRaw));
-        double lon = decodeLongitude(encodeLongitude(lonRaw));
-
-        doc.add(new LatLonDocValuesField("field", lat, lon));
-        doc.add(new StoredField("lat", lat));
-        doc.add(new StoredField("lon", lon));
-      } // otherwise "missing"
-      writer.addDocument(doc);
-    }
-    IndexReader reader = writer.getReader();
-    IndexSearcher searcher = newSearcher(reader);
-
-    for (int i = 0; i < numQueries; i++) {
-      double lat = GeoTestUtil.nextLatitude();
-      double lon = GeoTestUtil.nextLongitude();
-      double missingValue = Double.POSITIVE_INFINITY;
-
-      Result expected[] = new Result[reader.maxDoc()];
-      
-      for (int doc = 0; doc < reader.maxDoc(); doc++) {
-        Document targetDoc = reader.document(doc);
-        final double distance;
-        if (targetDoc.getField("lat") == null) {
-          distance = missingValue; // missing
-        } else {
-          double docLatitude = targetDoc.getField("lat").numericValue().doubleValue();
-          double docLongitude = targetDoc.getField("lon").numericValue().doubleValue();
-          distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
-        }
-        int id = targetDoc.getField("id").numericValue().intValue();
-        expected[doc] = new Result(id, distance);
-      }
-      
-      Arrays.sort(expected);
-      
-      // randomize the topN a bit
-      int topN = TestUtil.nextInt(random(), 1, reader.maxDoc());
-      // sort by distance, then ID
-      SortField distanceSort = LatLonDocValuesField.newDistanceSort("field", lat, lon);
-      distanceSort.setMissingValue(missingValue);
-      Sort sort = new Sort(distanceSort, 
-                           new SortField("id", SortField.Type.INT));
-
-      TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), topN, sort);
-      for (int resultNumber = 0; resultNumber < topN; resultNumber++) {
-        FieldDoc fieldDoc = (FieldDoc) topDocs.scoreDocs[resultNumber];
-        Result actual = new Result((Integer) fieldDoc.fields[1], (Double) fieldDoc.fields[0]);
-        assertEquals(expected[resultNumber], actual);
-      }
-
-      // get page2 with searchAfter()
-      if (topN < reader.maxDoc()) {
-        int page2 = TestUtil.nextInt(random(), 1, reader.maxDoc() - topN);
-        TopDocs topDocs2 = searcher.searchAfter(topDocs.scoreDocs[topN - 1], new MatchAllDocsQuery(), page2, sort);
-        for (int resultNumber = 0; resultNumber < page2; resultNumber++) {
-          FieldDoc fieldDoc = (FieldDoc) topDocs2.scoreDocs[resultNumber];
-          Result actual = new Result((Integer) fieldDoc.fields[1], (Double) fieldDoc.fields[0]);
-          assertEquals(expected[topN + resultNumber], actual);
-        }
-      }
-    }
-    reader.close();
-    writer.close();
-    dir.close();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/document/TestNearest.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestNearest.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestNearest.java
deleted file mode 100644
index 2fb2077..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestNearest.java
+++ /dev/null
@@ -1,253 +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 java.util.Arrays;
-import java.util.Comparator;
-
-import org.apache.lucene.codecs.Codec;
-import org.apache.lucene.document.NearestNeighbor.NearestHit;
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.GeoTestUtil;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.index.SerialMergeScheduler;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.TopFieldDocs;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.TestUtil;
- 
-public class TestNearest extends LuceneTestCase {
-
-  public void testNearestNeighborWithDeletedDocs() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
-    Document doc = new Document();
-    doc.add(new LatLonPoint("point", 40.0, 50.0));
-    doc.add(new StringField("id", "0", Field.Store.YES));
-    w.addDocument(doc);
-
-    doc = new Document();
-    doc.add(new LatLonPoint("point", 45.0, 55.0));
-    doc.add(new StringField("id", "1", Field.Store.YES));
-    w.addDocument(doc);
-
-    DirectoryReader r = w.getReader();
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    IndexSearcher s = newSearcher(r, false);
-    FieldDoc hit = (FieldDoc) LatLonPoint.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
-    assertEquals("0", r.document(hit.doc).getField("id").stringValue());
-    r.close();
-
-    w.deleteDocuments(new Term("id", "0"));
-    r = w.getReader();
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    s = newSearcher(r, false);
-    hit = (FieldDoc) LatLonPoint.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
-    assertEquals("1", r.document(hit.doc).getField("id").stringValue());
-    r.close();
-    w.close();
-    dir.close();
-  }
-
-  public void testNearestNeighborWithAllDeletedDocs() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
-    Document doc = new Document();
-    doc.add(new LatLonPoint("point", 40.0, 50.0));
-    doc.add(new StringField("id", "0", Field.Store.YES));
-    w.addDocument(doc);
-    doc = new Document();
-    doc.add(new LatLonPoint("point", 45.0, 55.0));
-    doc.add(new StringField("id", "1", Field.Store.YES));
-    w.addDocument(doc);
-
-    DirectoryReader r = w.getReader();
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    IndexSearcher s = newSearcher(r, false);
-    FieldDoc hit = (FieldDoc) LatLonPoint.nearest(s, "point", 40.0, 50.0, 1).scoreDocs[0];
-    assertEquals("0", r.document(hit.doc).getField("id").stringValue());
-    r.close();
-
-    w.deleteDocuments(new Term("id", "0"));
-    w.deleteDocuments(new Term("id", "1"));
-    r = w.getReader();
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    s = newSearcher(r, false);
-    assertEquals(0, LatLonPoint.nearest(s, "point", 40.0, 50.0, 1).scoreDocs.length);
-    r.close();
-    w.close();
-    dir.close();
-  }
-
-  public void testTieBreakByDocID() throws Exception {
-    Directory dir = newDirectory();
-    IndexWriter w = new IndexWriter(dir, getIndexWriterConfig());
-    Document doc = new Document();
-    doc.add(new LatLonPoint("point", 40.0, 50.0));
-    doc.add(new StringField("id", "0", Field.Store.YES));
-    w.addDocument(doc);
-    doc = new Document();
-    doc.add(new LatLonPoint("point", 40.0, 50.0));
-    doc.add(new StringField("id", "1", Field.Store.YES));
-    w.addDocument(doc);
-
-    DirectoryReader r = DirectoryReader.open(w);
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    ScoreDoc[] hits = LatLonPoint.nearest(newSearcher(r, false), "point", 45.0, 50.0, 2).scoreDocs;
-    assertEquals("0", r.document(hits[0].doc).getField("id").stringValue());
-    assertEquals("1", r.document(hits[1].doc).getField("id").stringValue());
-
-    r.close();
-    w.close();
-    dir.close();
-  }
-
-  public void testNearestNeighborWithNoDocs() throws Exception {
-    Directory dir = newDirectory();
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, getIndexWriterConfig());
-    DirectoryReader r = w.getReader();
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    assertEquals(0, LatLonPoint.nearest(newSearcher(r, false), "point", 40.0, 50.0, 1).scoreDocs.length);
-    r.close();
-    w.close();
-    dir.close();
-  }
-
-  private double quantizeLat(double latRaw) {
-    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
-  }
-
-  private double quantizeLon(double lonRaw) {
-    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
-  }
-
-  public void testNearestNeighborRandom() throws Exception {
-    
-    int numPoints = atLeast(5000);
-    Directory dir;
-    if (numPoints > 100000) {
-      dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
-    } else {
-      dir = newDirectory();
-    }
-    double[] lats = new double[numPoints];
-    double[] lons = new double[numPoints];
-
-    IndexWriterConfig iwc = getIndexWriterConfig();
-    iwc.setMergePolicy(newLogMergePolicy());
-    iwc.setMergeScheduler(new SerialMergeScheduler());
-    RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
-    for(int id=0;id<numPoints;id++) {
-      lats[id] = quantizeLat(GeoTestUtil.nextLatitude());
-      lons[id] = quantizeLon(GeoTestUtil.nextLongitude());
-      Document doc = new Document();
-      doc.add(new LatLonPoint("point", lats[id], lons[id]));
-      doc.add(new LatLonDocValuesField("point", lats[id], lons[id]));
-      doc.add(new StoredField("id", id));
-      w.addDocument(doc);
-    }
-
-    if (random().nextBoolean()) {
-      w.forceMerge(1);
-    }
-
-    DirectoryReader r = w.getReader();
-    if (VERBOSE) {      
-      System.out.println("TEST: reader=" + r);
-    }
-    // can't wrap because we require Lucene60PointsFormat directly but e.g. ParallelReader wraps with its own points impl:
-    IndexSearcher s = newSearcher(r, false);
-    int iters = atLeast(100);
-    for(int iter=0;iter<iters;iter++) {
-      if (VERBOSE) {      
-        System.out.println("\nTEST: iter=" + iter);
-      }
-      double pointLat = GeoTestUtil.nextLatitude();
-      double pointLon = GeoTestUtil.nextLongitude();
-
-      // dumb brute force search to get the expected result:
-      NearestHit[] expectedHits = new NearestHit[lats.length];
-      for(int id=0;id<lats.length;id++) {
-        NearestHit hit = new NearestHit();
-        hit.distanceMeters = SloppyMath.haversinMeters(pointLat, pointLon, lats[id], lons[id]);
-        hit.docID = id;
-        expectedHits[id] = hit;
-      }
-
-      Arrays.sort(expectedHits, new Comparator<NearestHit>() {
-          @Override
-          public int compare(NearestHit a, NearestHit b) {
-            int cmp = Double.compare(a.distanceMeters, b.distanceMeters);
-            if (cmp != 0) {
-              return cmp;
-            }
-            // tie break by smaller docID:
-            return a.docID - b.docID;
-          }
-        });
-
-      int topN = TestUtil.nextInt(random(), 1, lats.length);
-
-      if (VERBOSE) {
-        System.out.println("\nhits for pointLat=" + pointLat + " pointLon=" + pointLon);
-      }
-
-      // Also test with MatchAllDocsQuery, sorting by distance:
-      TopFieldDocs fieldDocs = s.search(new MatchAllDocsQuery(), topN, new Sort(LatLonDocValuesField.newDistanceSort("point", pointLat, pointLon)));
-
-      ScoreDoc[] hits = LatLonPoint.nearest(s, "point", pointLat, pointLon, topN).scoreDocs;
-      for(int i=0;i<topN;i++) {
-        NearestHit expected = expectedHits[i];
-        FieldDoc expected2 = (FieldDoc) fieldDocs.scoreDocs[i];
-        FieldDoc actual = (FieldDoc) hits[i];
-        Document actualDoc = r.document(actual.doc);
-
-        if (VERBOSE) {
-          System.out.println("hit " + i);
-          System.out.println("  expected id=" + expected.docID + " lat=" + lats[expected.docID] + " lon=" + lons[expected.docID] + " distance=" + expected.distanceMeters + " meters");
-          System.out.println("  actual id=" + actualDoc.getField("id") + " distance=" + actual.fields[0] + " meters");
-        }
-
-        assertEquals(expected.docID, actual.doc);
-        assertEquals(expected.distanceMeters, ((Double) actual.fields[0]).doubleValue(), 0.0);
-
-        assertEquals(expected.docID, expected.docID);
-        assertEquals(((Double) expected2.fields[0]).doubleValue(), expected.distanceMeters, 0.0);
-      }
-    }
-
-    r.close();
-    w.close();
-    dir.close();
-  }
-
-  private IndexWriterConfig getIndexWriterConfig() {
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    iwc.setCodec(Codec.forName("Lucene70"));
-    return iwc;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
deleted file mode 100644
index bf1b8cb..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
+++ /dev/null
@@ -1,62 +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.document.Document;
-import org.apache.lucene.document.LatLonDocValuesField;
-import org.apache.lucene.geo.BaseGeoPointTestCase;
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.Polygon;
-
-public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
-
-  @Override
-  protected boolean supportsPolygons() {
-    return false;
-  }
-
-  @Override
-  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
-    doc.add(new LatLonDocValuesField(field, lat, lon));
-  }
-
-  @Override
-  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
-    return LatLonDocValuesField.newSlowBoxQuery(field, minLat, maxLat, minLon, maxLon);
-  }
-
-  @Override
-  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
-    return LatLonDocValuesField.newSlowDistanceQuery(field, centerLat, centerLon, radiusMeters);
-  }
-
-  @Override
-  protected Query newPolygonQuery(String field, Polygon... polygons) {
-    fail();
-    return null;
-  }
-
-  @Override
-  protected double quantizeLat(double latRaw) {
-    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
-  }
-
-  @Override
-  protected double quantizeLon(double lonRaw) {
-    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/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
deleted file mode 100644
index f0e9612..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ /dev/null
@@ -1,91 +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.Document;
-import org.apache.lucene.document.LatLonPoint;
-import org.apache.lucene.geo.BaseGeoPointTestCase;
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.Polygon;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.bkd.BKDWriter;
-
-public class TestLatLonPointQueries extends BaseGeoPointTestCase {
-
-  @Override
-  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
-    doc.add(new LatLonPoint(field, lat, lon));
-  }
-
-  @Override
-  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
-    return LatLonPoint.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
-  }
-
-  @Override
-  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
-    return LatLonPoint.newDistanceQuery(field, centerLat, centerLon, radiusMeters);
-  }
-
-  @Override
-  protected Query newPolygonQuery(String field, Polygon... polygons) {
-    return LatLonPoint.newPolygonQuery(field, polygons);
-  }
-
-  @Override
-  protected double quantizeLat(double latRaw) {
-    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
-  }
-
-  @Override
-  protected double quantizeLon(double lonRaw) {
-    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
-  }
-
-  public void testDistanceQueryWithInvertedIntersection() throws IOException {
-    final int numMatchingDocs = atLeast(10 * BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE);
-
-    try (Directory dir = newDirectory()) {
-
-      try (IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
-        for (int i = 0; i < numMatchingDocs; ++i) {
-          Document doc = new Document();
-          addPointToDoc("field", doc, 18.313694, -65.227444);
-          w.addDocument(doc);
-        }
-
-        // Add a handful of docs that don't match
-        for (int i = 0; i < 11; ++i) {
-          Document doc = new Document();
-          addPointToDoc("field", doc, 10, -65.227444);
-          w.addDocument(doc);
-        }
-        w.forceMerge(1);
-      }
-
-      try (IndexReader r = DirectoryReader.open(dir)) {
-        IndexSearcher searcher = newSearcher(r);
-        assertEquals(numMatchingDocs, searcher.count(newDistanceQuery("field", 18, -65, 50_000)));
-      }
-    }
-  }
-}


[3/4] lucene-solr:branch_7x: LUCENE-7314: Graduate LatLonPoint and query classes to core

Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
new file mode 100644
index 0000000..6d825d2
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
@@ -0,0 +1,264 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+
+/** Simple tests for {@link LatLonDocValuesField#newDistanceSort} */
+public class TestLatLonPointDistanceSort extends LuceneTestCase {
+
+  /** Add three points and sort by distance */
+  public void testDistanceSort() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    
+    // add some docs
+    Document doc = new Document();
+    doc.add(new LatLonDocValuesField("location", 40.759011, -73.9844722));
+    iw.addDocument(doc);
+    
+    doc = new Document();
+    doc.add(new LatLonDocValuesField("location", 40.718266, -74.007819));
+    iw.addDocument(doc);
+    
+    doc = new Document();
+    doc.add(new LatLonDocValuesField("location", 40.7051157, -74.0088305));
+    iw.addDocument(doc);
+    
+    IndexReader reader = iw.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+    iw.close();
+
+    Sort sort = new Sort(LatLonDocValuesField.newDistanceSort("location", 40.7143528, -74.0059731));
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 3, sort);
+    
+    FieldDoc d = (FieldDoc) td.scoreDocs[0];
+    assertEquals(462.1028401330431, (Double)d.fields[0], 0.0D);
+    
+    d = (FieldDoc) td.scoreDocs[1];
+    assertEquals(1054.9842850974826, (Double)d.fields[0], 0.0D);
+    
+    d = (FieldDoc) td.scoreDocs[2];
+    assertEquals(5285.881528419706, (Double)d.fields[0], 0.0D);
+    
+    reader.close();
+    dir.close();
+  }
+  
+  /** Add two points (one doc missing) and sort by distance */
+  public void testMissingLast() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    
+    // missing
+    Document doc = new Document();
+    iw.addDocument(doc);
+    
+    doc = new Document();
+    doc.add(new LatLonDocValuesField("location", 40.718266, -74.007819));
+    iw.addDocument(doc);
+    
+    doc = new Document();
+    doc.add(new LatLonDocValuesField("location", 40.7051157, -74.0088305));
+    iw.addDocument(doc);
+    
+    IndexReader reader = iw.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+    iw.close();
+
+    Sort sort = new Sort(LatLonDocValuesField.newDistanceSort("location", 40.7143528, -74.0059731));
+    TopDocs td = searcher.search(new MatchAllDocsQuery(), 3, sort);
+    
+    FieldDoc d = (FieldDoc) td.scoreDocs[0];
+    assertEquals(462.1028401330431D, (Double)d.fields[0], 0.0D);
+    
+    d = (FieldDoc) td.scoreDocs[1];
+    assertEquals(1054.9842850974826, (Double)d.fields[0], 0.0D);
+    
+    d = (FieldDoc) td.scoreDocs[2];
+    assertEquals(Double.POSITIVE_INFINITY, (Double)d.fields[0], 0.0D);
+    
+    reader.close();
+    dir.close();
+  }
+  
+  /** Run a few iterations with just 10 docs, hopefully easy to debug */
+  public void testRandom() throws Exception {
+    for (int iters = 0; iters < 100; iters++) {
+      doRandomTest(10, 100);
+    }
+  }
+  
+  /** Runs with thousands of docs */
+  @Nightly
+  public void testRandomHuge() throws Exception {
+    for (int iters = 0; iters < 10; iters++) {
+      doRandomTest(2000, 100);
+    }
+  }
+  
+  // result class used for testing. holds an id+distance.
+  // we sort these with Arrays.sort and compare with lucene's results
+  static class Result implements Comparable<Result> {
+    int id;
+    double distance;
+    
+    Result(int id, double distance) {
+      this.id = id;
+      this.distance = distance;
+    }
+
+    @Override
+    public int compareTo(Result o) {
+      int cmp = Double.compare(distance, o.distance);
+      if (cmp == 0) {
+        return Integer.compare(id, o.id);
+      }
+      return cmp;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      long temp;
+      temp = Double.doubleToLongBits(distance);
+      result = prime * result + (int) (temp ^ (temp >>> 32));
+      result = prime * result + id;
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      Result other = (Result) obj;
+      if (Double.doubleToLongBits(distance) != Double.doubleToLongBits(other.distance)) return false;
+      if (id != other.id) return false;
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "Result [id=" + id + ", distance=" + distance + "]";
+    }
+  }
+  
+  private void doRandomTest(int numDocs, int numQueries) throws IOException {
+    Directory dir = newDirectory();    
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    // else seeds may not to reproduce:
+    iwc.setMergeScheduler(new SerialMergeScheduler());
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
+
+    for (int i = 0; i < numDocs; i++) {
+      Document doc = new Document();
+      doc.add(new StoredField("id", i));
+      doc.add(new NumericDocValuesField("id", i));
+      if (random().nextInt(10) > 7) {
+        double latRaw = GeoTestUtil.nextLatitude();
+        double lonRaw = GeoTestUtil.nextLongitude();
+        // pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
+        double lat = decodeLatitude(encodeLatitude(latRaw));
+        double lon = decodeLongitude(encodeLongitude(lonRaw));
+
+        doc.add(new LatLonDocValuesField("field", lat, lon));
+        doc.add(new StoredField("lat", lat));
+        doc.add(new StoredField("lon", lon));
+      } // otherwise "missing"
+      writer.addDocument(doc);
+    }
+    IndexReader reader = writer.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+
+    for (int i = 0; i < numQueries; i++) {
+      double lat = GeoTestUtil.nextLatitude();
+      double lon = GeoTestUtil.nextLongitude();
+      double missingValue = Double.POSITIVE_INFINITY;
+
+      Result expected[] = new Result[reader.maxDoc()];
+      
+      for (int doc = 0; doc < reader.maxDoc(); doc++) {
+        Document targetDoc = reader.document(doc);
+        final double distance;
+        if (targetDoc.getField("lat") == null) {
+          distance = missingValue; // missing
+        } else {
+          double docLatitude = targetDoc.getField("lat").numericValue().doubleValue();
+          double docLongitude = targetDoc.getField("lon").numericValue().doubleValue();
+          distance = SloppyMath.haversinMeters(lat, lon, docLatitude, docLongitude);
+        }
+        int id = targetDoc.getField("id").numericValue().intValue();
+        expected[doc] = new Result(id, distance);
+      }
+      
+      Arrays.sort(expected);
+      
+      // randomize the topN a bit
+      int topN = TestUtil.nextInt(random(), 1, reader.maxDoc());
+      // sort by distance, then ID
+      SortField distanceSort = LatLonDocValuesField.newDistanceSort("field", lat, lon);
+      distanceSort.setMissingValue(missingValue);
+      Sort sort = new Sort(distanceSort, 
+                           new SortField("id", SortField.Type.INT));
+
+      TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), topN, sort);
+      for (int resultNumber = 0; resultNumber < topN; resultNumber++) {
+        FieldDoc fieldDoc = (FieldDoc) topDocs.scoreDocs[resultNumber];
+        Result actual = new Result((Integer) fieldDoc.fields[1], (Double) fieldDoc.fields[0]);
+        assertEquals(expected[resultNumber], actual);
+      }
+
+      // get page2 with searchAfter()
+      if (topN < reader.maxDoc()) {
+        int page2 = TestUtil.nextInt(random(), 1, reader.maxDoc() - topN);
+        TopDocs topDocs2 = searcher.searchAfter(topDocs.scoreDocs[topN - 1], new MatchAllDocsQuery(), page2, sort);
+        for (int resultNumber = 0; resultNumber < page2; resultNumber++) {
+          FieldDoc fieldDoc = (FieldDoc) topDocs2.scoreDocs[resultNumber];
+          Result actual = new Result((Integer) fieldDoc.fields[1], (Double) fieldDoc.fields[0]);
+          assertEquals(expected[topN + resultNumber], actual);
+        }
+      }
+    }
+    reader.close();
+    writer.close();
+    dir.close();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
new file mode 100644
index 0000000..bf1b8cb
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
@@ -0,0 +1,62 @@
+/*
+ * 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.document.Document;
+import org.apache.lucene.document.LatLonDocValuesField;
+import org.apache.lucene.geo.BaseGeoPointTestCase;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.Polygon;
+
+public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
+
+  @Override
+  protected boolean supportsPolygons() {
+    return false;
+  }
+
+  @Override
+  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
+    doc.add(new LatLonDocValuesField(field, lat, lon));
+  }
+
+  @Override
+  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
+    return LatLonDocValuesField.newSlowBoxQuery(field, minLat, maxLat, minLon, maxLon);
+  }
+
+  @Override
+  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
+    return LatLonDocValuesField.newSlowDistanceQuery(field, centerLat, centerLon, radiusMeters);
+  }
+
+  @Override
+  protected Query newPolygonQuery(String field, Polygon... polygons) {
+    fail();
+    return null;
+  }
+
+  @Override
+  protected double quantizeLat(double latRaw) {
+    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
+  }
+
+  @Override
+  protected double quantizeLon(double lonRaw) {
+    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
new file mode 100644
index 0000000..f0e9612
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -0,0 +1,91 @@
+/*
+ * 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.Document;
+import org.apache.lucene.document.LatLonPoint;
+import org.apache.lucene.geo.BaseGeoPointTestCase;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.bkd.BKDWriter;
+
+public class TestLatLonPointQueries extends BaseGeoPointTestCase {
+
+  @Override
+  protected void addPointToDoc(String field, Document doc, double lat, double lon) {
+    doc.add(new LatLonPoint(field, lat, lon));
+  }
+
+  @Override
+  protected Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon) {
+    return LatLonPoint.newBoxQuery(field, minLat, maxLat, minLon, maxLon);
+  }
+
+  @Override
+  protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
+    return LatLonPoint.newDistanceQuery(field, centerLat, centerLon, radiusMeters);
+  }
+
+  @Override
+  protected Query newPolygonQuery(String field, Polygon... polygons) {
+    return LatLonPoint.newPolygonQuery(field, polygons);
+  }
+
+  @Override
+  protected double quantizeLat(double latRaw) {
+    return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
+  }
+
+  @Override
+  protected double quantizeLon(double lonRaw) {
+    return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lonRaw));
+  }
+
+  public void testDistanceQueryWithInvertedIntersection() throws IOException {
+    final int numMatchingDocs = atLeast(10 * BKDWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE);
+
+    try (Directory dir = newDirectory()) {
+
+      try (IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
+        for (int i = 0; i < numMatchingDocs; ++i) {
+          Document doc = new Document();
+          addPointToDoc("field", doc, 18.313694, -65.227444);
+          w.addDocument(doc);
+        }
+
+        // Add a handful of docs that don't match
+        for (int i = 0; i < 11; ++i) {
+          Document doc = new Document();
+          addPointToDoc("field", doc, 10, -65.227444);
+          w.addDocument(doc);
+        }
+        w.forceMerge(1);
+      }
+
+      try (IndexReader r = DirectoryReader.open(dir)) {
+        IndexSearcher searcher = newSearcher(r);
+        assertEquals(numMatchingDocs, searcher.count(newDistanceQuery("field", 18, -65, 50_000)));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.java
deleted file mode 100644
index 6411fcc..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesBoxQuery.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.document;
-
-import java.io.IOException;
-
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
-
-/** Distance query for {@link LatLonDocValuesField}. */
-final class LatLonDocValuesBoxQuery extends Query {
-
-  private final String field;
-  private final int minLatitude, maxLatitude, minLongitude, maxLongitude;
-  private final boolean crossesDateline;
-
-  LatLonDocValuesBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
-    GeoUtils.checkLatitude(minLatitude);
-    GeoUtils.checkLatitude(maxLatitude);
-    GeoUtils.checkLongitude(minLongitude);
-    GeoUtils.checkLongitude(maxLongitude);
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    this.field = field;
-    this.crossesDateline = minLongitude > maxLongitude; // make sure to compute this before rounding
-    this.minLatitude = GeoEncodingUtils.encodeLatitudeCeil(minLatitude);
-    this.maxLatitude = GeoEncodingUtils.encodeLatitude(maxLatitude);
-    this.minLongitude = GeoEncodingUtils.encodeLongitudeCeil(minLongitude);
-    this.maxLongitude = GeoEncodingUtils.encodeLongitude(maxLongitude);
-  }
-
-  @Override
-  public String toString(String field) {
-    StringBuilder sb = new StringBuilder();
-    if (!this.field.equals(field)) {
-      sb.append(this.field);
-      sb.append(':');
-    }
-    sb.append("box(minLat=").append(GeoEncodingUtils.decodeLatitude(minLatitude));
-    sb.append(", maxLat=").append(GeoEncodingUtils.decodeLatitude(maxLatitude));
-    sb.append(", minLon=").append(GeoEncodingUtils.decodeLongitude(minLongitude));
-    sb.append(", maxLon=").append(GeoEncodingUtils.decodeLongitude(maxLongitude));
-    return sb.append(")").toString();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (sameClassAs(obj) == false) {
-      return false;
-    }
-    LatLonDocValuesBoxQuery other = (LatLonDocValuesBoxQuery) obj;
-    return field.equals(other.field) &&
-        crossesDateline == other.crossesDateline &&
-        minLatitude == other.minLatitude &&
-        maxLatitude == other.maxLatitude &&
-        minLongitude == other.minLongitude &&
-        maxLongitude == other.maxLongitude;
-  }
-
-  @Override
-  public int hashCode() {
-    int h = classHash();
-    h = 31 * h + field.hashCode();
-    h = 31 * h + Boolean.hashCode(crossesDateline);
-    h = 31 * h + Integer.hashCode(minLatitude);
-    h = 31 * h + Integer.hashCode(maxLatitude);
-    h = 31 * h + Integer.hashCode(minLongitude);
-    h = 31 * h + Integer.hashCode(maxLongitude);
-    return h;
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-    return new ConstantScoreWeight(this, boost) {
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
-        if (values == null) {
-          return null;
-        }
-
-        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
-          @Override
-          public boolean matches() throws IOException {
-            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
-              final long value = values.nextValue();
-              final int lat = (int) (value >>> 32);
-              if (lat < minLatitude || lat > maxLatitude) {
-                // not within latitude range
-                continue;
-              }
-
-              final int lon = (int) (value & 0xFFFFFFFF);
-              if (crossesDateline) {
-                if (lon > maxLongitude && lon < minLongitude) {
-                  // not within longitude range
-                  continue;
-                }
-              } else {
-                if (lon < minLongitude || lon > maxLongitude) {
-                  // not within longitude range
-                  continue;
-                }
-              }
-
-              return true;
-            }
-            return false;
-          }
-
-          @Override
-          public float matchCost() {
-            return 5; // 5 comparisons
-          }
-        };
-        return new ConstantScoreScorer(this, boost, iterator);
-      }
-
-      @Override
-      public boolean isCacheable(LeafReaderContext ctx) {
-        return DocValues.isCacheable(ctx, field);
-      }
-
-    };
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
deleted file mode 100644
index d52cd31..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
+++ /dev/null
@@ -1,139 +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 java.io.IOException;
-
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
-
-/** Distance query for {@link LatLonDocValuesField}. */
-final class LatLonDocValuesDistanceQuery extends Query {
-
-  private final String field;
-  private final double latitude, longitude;
-  private final double radiusMeters;
-
-  LatLonDocValuesDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
-    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
-      throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
-    }
-    GeoUtils.checkLatitude(latitude);
-    GeoUtils.checkLongitude(longitude);
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    this.field = field;
-    this.latitude = latitude;
-    this.longitude = longitude;
-    this.radiusMeters = radiusMeters;
-  }
-
-  @Override
-  public String toString(String field) {
-    StringBuilder sb = new StringBuilder();
-    if (!this.field.equals(field)) {
-      sb.append(this.field);
-      sb.append(':');
-    }
-    sb.append(latitude);
-    sb.append(",");
-    sb.append(longitude);
-    sb.append(" +/- ");
-    sb.append(radiusMeters);
-    sb.append(" meters");
-    return sb.toString();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (sameClassAs(obj) == false) {
-      return false;
-    }
-    LatLonDocValuesDistanceQuery other = (LatLonDocValuesDistanceQuery) obj;
-    return field.equals(other.field) &&
-        Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
-        Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
-        Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
-  }
-
-  @Override
-  public int hashCode() {
-    int h = classHash();
-    h = 31 * h + field.hashCode();
-    h = 31 * h + Double.hashCode(latitude);
-    h = 31 * h + Double.hashCode(longitude);
-    h = 31 * h + Double.hashCode(radiusMeters);
-    return h;
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-    return new ConstantScoreWeight(this, boost) {
-
-      private final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
-
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
-        if (values == null) {
-          return null;
-        }
-
-        final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
-
-          @Override
-          public boolean matches() throws IOException {
-            for (int i = 0, count = values.docValueCount(); i < count; ++i) {
-              final long value = values.nextValue();
-              final int lat = (int) (value >>> 32);
-              final int lon = (int) (value & 0xFFFFFFFF);
-              if (distancePredicate.test(lat, lon)) {
-                return true;
-              }
-            }
-            return false;
-          }
-
-          @Override
-          public float matchCost() {
-            return 100f; // TODO: what should it be?
-          }
-
-        };
-        return new ConstantScoreScorer(this, boost, iterator);
-      }
-
-      @Override
-      public boolean isCacheable(LeafReaderContext ctx) {
-        return DocValues.isCacheable(ctx, field);
-      }
-
-    };
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
deleted file mode 100644
index 6bcc394..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonDocValuesField.java
+++ /dev/null
@@ -1,181 +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 static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexOrDocValuesQuery;
-import org.apache.lucene.search.MatchNoDocsQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SortField;
-
-/** 
- * An per-document location field.
- * <p>
- * Sorting by distance is efficient. Multiple values for the same field in one document
- * is allowed. 
- * <p>
- * This field defines static factory methods for common operations:
- * <ul>
- *   <li>{@link #newDistanceSort newDistanceSort()} for ordering documents by distance from a specified location. 
- * </ul>
- * <p>
- * If you also need query operations, you should add a separate {@link LatLonPoint} instance.
- * If you also need to store the value, you should add a separate {@link StoredField} instance.
- * <p>
- * <b>WARNING</b>: Values are indexed with some loss of precision from the
- * original {@code double} values (4.190951585769653E-8 for the latitude component
- * and 8.381903171539307E-8 for longitude).
- * @see LatLonPoint
- */
-public class LatLonDocValuesField extends Field {
-
-  /**
-   * Type for a LatLonDocValuesField
-   * <p>
-   * Each value stores a 64-bit long where the upper 32 bits are the encoded latitude,
-   * and the lower 32 bits are the encoded longitude.
-   * @see org.apache.lucene.geo.GeoEncodingUtils#decodeLatitude(int)
-   * @see org.apache.lucene.geo.GeoEncodingUtils#decodeLongitude(int)
-   */
-  public static final FieldType TYPE = new FieldType();
-  static {
-    TYPE.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE.freeze();
-  }
-  
-  /** 
-   * Creates a new LatLonDocValuesField with the specified latitude and longitude
-   * @param name field name
-   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
-   * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
-   */
-  public LatLonDocValuesField(String name, double latitude, double longitude) {
-    super(name, TYPE);
-    setLocationValue(latitude, longitude);
-  }
-  
-  /**
-   * Change the values of this field
-   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
-   * @throws IllegalArgumentException if latitude or longitude are out of bounds
-   */
-  public void setLocationValue(double latitude, double longitude) {
-    int latitudeEncoded = encodeLatitude(latitude);
-    int longitudeEncoded = encodeLongitude(longitude);
-    fieldsData = Long.valueOf((((long)latitudeEncoded) << 32) | (longitudeEncoded & 0xFFFFFFFFL));
-  }
-
-  /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */
-  static void checkCompatible(FieldInfo fieldInfo) {
-    // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment.
-    if (fieldInfo.getDocValuesType() != DocValuesType.NONE && fieldInfo.getDocValuesType() != TYPE.docValuesType()) {
-      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with docValuesType=" + fieldInfo.getDocValuesType() + 
-                                         " but this type has docValuesType=" + TYPE.docValuesType() + 
-                                         ", is the field really a LatLonDocValuesField?");
-    }
-  }
-  
-  @Override
-  public String toString() {
-    StringBuilder result = new StringBuilder();
-    result.append(getClass().getSimpleName());
-    result.append(" <");
-    result.append(name);
-    result.append(':');
-
-    long currentValue = (Long)fieldsData;
-    result.append(decodeLatitude((int)(currentValue >> 32)));
-    result.append(',');
-    result.append(decodeLongitude((int)(currentValue & 0xFFFFFFFF)));
-
-    result.append('>');
-    return result.toString();
-  }
-
-  /**
-   * Creates a SortField for sorting by distance from a location.
-   * <p>
-   * This sort orders documents by ascending distance from the location. The value returned in {@link FieldDoc} for
-   * the hits contains a Double instance with the distance in meters.
-   * <p>
-   * If a document is missing the field, then by default it is treated as having {@link Double#POSITIVE_INFINITY} distance
-   * (missing values sort last).
-   * <p>
-   * If a document contains multiple values for the field, the <i>closest</i> distance to the location is used.
-   * 
-   * @param field field name. must not be null.
-   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
-   * @return SortField ordering documents by distance
-   * @throws IllegalArgumentException if {@code field} is null or location has invalid coordinates.
-   */
-  public static SortField newDistanceSort(String field, double latitude, double longitude) {
-    return new LatLonPointSortField(field, latitude, longitude);
-  }
-
-  /**
-   * Create a query for matching a bounding box using doc values.
-   * This query is usually slow as it does not use an index structure and needs
-   * to verify documents one-by-one in order to know whether they match. It is
-   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
-   * {@link LatLonPoint#newBoxQuery}.
-   */
-  public static Query newSlowBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
-    // exact double values of lat=90.0D and lon=180.0D must be treated special as they are not represented in the encoding
-    // and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
-    if (minLatitude == 90.0) {
-      // range cannot match as 90.0 can never exist
-      return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLatitude=90.0");
-    }
-    if (minLongitude == 180.0) {
-      if (maxLongitude == 180.0) {
-        // range cannot match as 180.0 can never exist
-        return new MatchNoDocsQuery("LatLonDocValuesField.newBoxQuery with minLongitude=maxLongitude=180.0");
-      } else if (maxLongitude < minLongitude) {
-        // encodeCeil() with dateline wrapping!
-        minLongitude = -180.0;
-      }
-    }
-    return new LatLonDocValuesBoxQuery(field, minLatitude, maxLatitude, minLongitude, maxLongitude);
-  }
-
-  /**
-   * Create a query for matching points within the specified distance of the supplied location.
-   * This query is usually slow as it does not use an index structure and needs
-   * to verify documents one-by-one in order to know whether they match. It is
-   * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
-   * {@link LatLonPoint#newDistanceQuery}.
-   * @param field field name. must not be null.
-   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
-   * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
-   * @return query matching points within this distance
-   * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
-   */
-  public static Query newSlowDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
-    return new LatLonDocValuesDistanceQuery(field, latitude, longitude, radiusMeters);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/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
deleted file mode 100644
index 1e5e4bd..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
+++ /dev/null
@@ -1,335 +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 java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.lucene.codecs.lucene60.Lucene60PointsFormat;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.geo.Polygon;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PointValues;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.ConstantScoreQuery;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.MatchNoDocsQuery;
-import org.apache.lucene.search.PointRangeQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.TopFieldDocs;
-import org.apache.lucene.util.Bits;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.NumericUtils;
-import org.apache.lucene.util.bkd.BKDReader;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
-
-/** 
- * An indexed location field.
- * <p>
- * Finding all documents within a range at search time is
- * efficient.  Multiple values for the same field in one document
- * is allowed. 
- * <p>
- * This field defines static factory methods for common operations:
- * <ul>
- *   <li>{@link #newBoxQuery newBoxQuery()} for matching points within a bounding box.
- *   <li>{@link #newDistanceQuery newDistanceQuery()} for matching points within a specified distance.
- *   <li>{@link #newPolygonQuery newPolygonQuery()} for matching points within an arbitrary polygon.
- *   <li>{@link #nearest nearest()} for finding the k-nearest neighbors by distance.
- * </ul>
- * <p>
- * If you also need per-document operations such as sort by distance, add a separate {@link LatLonDocValuesField} instance.
- * If you also need to store the value, you should add a separate {@link StoredField} instance.
- * <p>
- * <b>WARNING</b>: Values are indexed with some loss of precision from the
- * original {@code double} values (4.190951585769653E-8 for the latitude component
- * and 8.381903171539307E-8 for longitude).
- * @see PointValues
- * @see LatLonDocValuesField
- */
-// TODO ^^^ that is very sandy and hurts the API, usage, and tests tremendously, because what the user passes
-// to the field is not actually what gets indexed. Float would be 1E-5 error vs 1E-7, but it might be
-// a better tradeoff? then it would be completely transparent to the user and lucene would be "lossless".
-public class LatLonPoint extends Field {
-  /** LatLonPoint is encoded as integer values so number of bytes is 4 */
-  public static final int BYTES = Integer.BYTES;
-  /**
-   * Type for an indexed LatLonPoint
-   * <p>
-   * Each point stores two dimensions with 4 bytes per dimension.
-   */
-  public static final FieldType TYPE = new FieldType();
-  static {
-    TYPE.setDimensions(2, Integer.BYTES);
-    TYPE.freeze();
-  }
-  
-  /**
-   * Change the values of this field
-   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
-   * @throws IllegalArgumentException if latitude or longitude are out of bounds
-   */
-  public void setLocationValue(double latitude, double longitude) {
-    final byte[] bytes;
-
-    if (fieldsData == null) {
-      bytes = new byte[8];
-      fieldsData = new BytesRef(bytes);
-    } else {
-      bytes = ((BytesRef) fieldsData).bytes;
-    }
-
-    int latitudeEncoded = encodeLatitude(latitude);
-    int longitudeEncoded = encodeLongitude(longitude);
-    NumericUtils.intToSortableBytes(latitudeEncoded, bytes, 0);
-    NumericUtils.intToSortableBytes(longitudeEncoded, bytes, Integer.BYTES);
-  }
-
-  /** 
-   * Creates a new LatLonPoint with the specified latitude and longitude
-   * @param name field name
-   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
-   * @throws IllegalArgumentException if the field name is null or latitude or longitude are out of bounds
-   */
-  public LatLonPoint(String name, double latitude, double longitude) {
-    super(name, TYPE);
-    setLocationValue(latitude, longitude);
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder result = new StringBuilder();
-    result.append(getClass().getSimpleName());
-    result.append(" <");
-    result.append(name);
-    result.append(':');
-
-    byte bytes[] = ((BytesRef) fieldsData).bytes;
-    result.append(decodeLatitude(bytes, 0));
-    result.append(',');
-    result.append(decodeLongitude(bytes, Integer.BYTES));
-
-    result.append('>');
-    return result.toString();
-  }
-  
-  /** sugar encodes a single point as a byte array */
-  private static byte[] encode(double latitude, double longitude) {
-    byte[] bytes = new byte[2 * Integer.BYTES];
-    NumericUtils.intToSortableBytes(encodeLatitude(latitude), bytes, 0);
-    NumericUtils.intToSortableBytes(encodeLongitude(longitude), bytes, Integer.BYTES);
-    return bytes;
-  }
-  
-  /** sugar encodes a single point as a byte array, rounding values up */
-  private static byte[] encodeCeil(double latitude, double longitude) {
-    byte[] bytes = new byte[2 * Integer.BYTES];
-    NumericUtils.intToSortableBytes(encodeLatitudeCeil(latitude), bytes, 0);
-    NumericUtils.intToSortableBytes(encodeLongitudeCeil(longitude), bytes, Integer.BYTES);
-    return bytes;
-  }
-
-  /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonPoint */
-  static void checkCompatible(FieldInfo fieldInfo) {
-    // point/dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment.
-    if (fieldInfo.getPointDimensionCount() != 0 && fieldInfo.getPointDimensionCount() != TYPE.pointDimensionCount()) {
-      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with numDims=" + fieldInfo.getPointDimensionCount() + 
-                                         " but this point type has numDims=" + TYPE.pointDimensionCount() + 
-                                         ", is the field really a LatLonPoint?");
-    }
-    if (fieldInfo.getPointNumBytes() != 0 && fieldInfo.getPointNumBytes() != TYPE.pointNumBytes()) {
-      throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with bytesPerDim=" + fieldInfo.getPointNumBytes() + 
-                                         " but this point type has bytesPerDim=" + TYPE.pointNumBytes() + 
-                                         ", is the field really a LatLonPoint?");
-    }
-  }
-
-  // static methods for generating queries
-
-  /**
-   * Create a query for matching a bounding box.
-   * <p>
-   * The box may cross over the dateline.
-   * @param field field name. must not be null.
-   * @param minLatitude latitude lower bound: must be within standard +/-90 coordinate bounds.
-   * @param maxLatitude latitude upper bound: must be within standard +/-90 coordinate bounds.
-   * @param minLongitude longitude lower bound: must be within standard +/-180 coordinate bounds.
-   * @param maxLongitude longitude upper bound: must be within standard +/-180 coordinate bounds.
-   * @return query matching points within this box
-   * @throws IllegalArgumentException if {@code field} is null, or the box has invalid coordinates.
-   */
-  public static Query newBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
-    // exact double values of lat=90.0D and lon=180.0D must be treated special as they are not represented in the encoding
-    // and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
-    if (minLatitude == 90.0) {
-      // range cannot match as 90.0 can never exist
-      return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLatitude=90.0");
-    }
-    if (minLongitude == 180.0) {
-      if (maxLongitude == 180.0) {
-        // range cannot match as 180.0 can never exist
-        return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLongitude=maxLongitude=180.0");
-      } else if (maxLongitude < minLongitude) {
-        // encodeCeil() with dateline wrapping!
-        minLongitude = -180.0;
-      }
-    }
-    byte[] lower = encodeCeil(minLatitude, minLongitude);
-    byte[] upper = encode(maxLatitude, maxLongitude);
-    // Crosses date line: we just rewrite into OR of two bboxes, with longitude as an open range:
-    if (maxLongitude < minLongitude) {
-      // Disable coord here because a multi-valued doc could match both rects and get unfairly boosted:
-      BooleanQuery.Builder q = new BooleanQuery.Builder();
-
-      // E.g.: maxLon = -179, minLon = 179
-      byte[] leftOpen = lower.clone();
-      // leave longitude open
-      NumericUtils.intToSortableBytes(Integer.MIN_VALUE, leftOpen, Integer.BYTES);
-      Query left = newBoxInternal(field, leftOpen, upper);
-      q.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-
-      byte[] rightOpen = upper.clone();
-      // leave longitude open
-      NumericUtils.intToSortableBytes(Integer.MAX_VALUE, rightOpen, Integer.BYTES);
-      Query right = newBoxInternal(field, lower, rightOpen);
-      q.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-      return new ConstantScoreQuery(q.build());
-    } else {
-      return newBoxInternal(field, lower, upper);
-    }
-  }
-  
-  private static Query newBoxInternal(String field, byte[] min, byte[] max) {
-    return new PointRangeQuery(field, min, max, 2) {
-      @Override
-      protected String toString(int dimension, byte[] value) {
-        if (dimension == 0) {
-          return Double.toString(decodeLatitude(value, 0));
-        } else if (dimension == 1) {
-          return Double.toString(decodeLongitude(value, 0));
-        } else {
-          throw new AssertionError();
-        }
-      }
-    };
-  }
-  
-  /**
-   * Create a query for matching points within the specified distance of the supplied location.
-   * @param field field name. must not be null.
-   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
-   * @param radiusMeters maximum distance from the center in meters: must be non-negative and finite.
-   * @return query matching points within this distance
-   * @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
-   */
-  public static Query newDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
-    return new LatLonPointDistanceQuery(field, latitude, longitude, radiusMeters);
-  }
-  
-  /** 
-   * Create a query for matching one or more polygons.
-   * @param field field name. must not be null.
-   * @param polygons array of polygons. must not be null or empty
-   * @return query matching points within this polygon
-   * @throws IllegalArgumentException if {@code field} is null, {@code polygons} is null or empty
-   * @see Polygon
-   */
-  public static Query newPolygonQuery(String field, Polygon... polygons) {
-    return new LatLonPointInPolygonQuery(field, polygons);
-  }
-
-  /**
-   * Finds the {@code n} nearest indexed points to the provided point, according to Haversine distance.
-   * <p>
-   * This is functionally equivalent to running {@link MatchAllDocsQuery} with a {@link LatLonDocValuesField#newDistanceSort},
-   * but is far more efficient since it takes advantage of properties the indexed BKD tree.  Currently this
-   * only works with {@link Lucene60PointsFormat} (used by the default codec).  Multi-valued fields are
-   * currently not de-duplicated, so if a document had multiple instances of the specified field that
-   * make it into the top n, that document will appear more than once.
-   * <p>
-   * Documents are ordered by ascending distance from the location. The value returned in {@link FieldDoc} for
-   * the hits contains a Double instance with the distance in meters.
-   * 
-   * @param searcher IndexSearcher to find nearest points from.
-   * @param field field name. must not be null.
-   * @param latitude latitude at the center: must be within standard +/-90 coordinate bounds.
-   * @param longitude longitude at the center: must be within standard +/-180 coordinate bounds.
-   * @param n the number of nearest neighbors to retrieve.
-   * @return TopFieldDocs containing documents ordered by distance, where the field value for each {@link FieldDoc} is the distance in meters
-   * @throws IllegalArgumentException if the underlying PointValues is not a {@code Lucene60PointsReader} (this is a current limitation), or
-   *         if {@code field} or {@code searcher} is null, or if {@code latitude}, {@code longitude} or {@code n} are out-of-bounds
-   * @throws IOException if an IOException occurs while finding the points.
-   */
-  // TODO: what about multi-valued documents? what happens?
-  public static TopFieldDocs nearest(IndexSearcher searcher, String field, double latitude, double longitude, int n) throws IOException {
-    GeoUtils.checkLatitude(latitude);
-    GeoUtils.checkLongitude(longitude);
-    if (n < 1) {
-      throw new IllegalArgumentException("n must be at least 1; got " + n);
-    }
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    if (searcher == null) {
-      throw new IllegalArgumentException("searcher must not be null");
-    }
-    List<BKDReader> readers = new ArrayList<>();
-    List<Integer> docBases = new ArrayList<>();
-    List<Bits> liveDocs = new ArrayList<>();
-    int totalHits = 0;
-    for(LeafReaderContext leaf : searcher.getIndexReader().leaves()) {
-      PointValues points = leaf.reader().getPointValues(field);
-      if (points != null) {
-        if (points instanceof BKDReader == false) {
-          throw new IllegalArgumentException("can only run on Lucene60PointsReader points implementation, but got " + points);
-        }
-        totalHits += points.getDocCount();
-        BKDReader reader = (BKDReader) points;
-        if (reader != null) {
-          readers.add(reader);
-          docBases.add(leaf.docBase);
-          liveDocs.add(leaf.reader().getLiveDocs());
-        }
-      }
-    }
-
-    NearestNeighbor.NearestHit[] hits = NearestNeighbor.nearest(latitude, longitude, readers, liveDocs, docBases, n);
-
-    // Convert to TopFieldDocs:
-    ScoreDoc[] scoreDocs = new ScoreDoc[hits.length];
-    for(int i=0;i<hits.length;i++) {
-      NearestNeighbor.NearestHit hit = hits[i];
-      scoreDocs[i] = new FieldDoc(hit.docID, 0.0f, new Object[] {Double.valueOf(hit.distanceMeters)});
-    }
-    return new TopFieldDocs(totalHits, scoreDocs, null, 0.0f);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
deleted file mode 100644
index 40e8dee..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceComparator.java
+++ /dev/null
@@ -1,229 +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 java.io.IOException;
-
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.search.FieldComparator;
-import org.apache.lucene.search.LeafFieldComparator;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.util.ArrayUtil;
-import org.apache.lucene.util.RamUsageEstimator;
-import org.apache.lucene.util.SloppyMath;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-/**
- * Compares documents by distance from an origin point
- * <p>
- * When the least competitive item on the priority queue changes (setBottom), we recompute
- * a bounding box representing competitive distance to the top-N. Then in compareBottom, we can
- * quickly reject hits based on bounding box alone without computing distance for every element.
- */
-class LatLonPointDistanceComparator extends FieldComparator<Double> implements LeafFieldComparator {
-  final String field;
-  final double latitude;
-  final double longitude;
-
-  final double[] values;
-  double bottom;
-  double topValue;
-  SortedNumericDocValues currentDocs;
-  
-  // current bounding box(es) for the bottom distance on the PQ.
-  // these are pre-encoded with LatLonPoint's encoding and 
-  // used to exclude uncompetitive hits faster.
-  int minLon = Integer.MIN_VALUE;
-  int maxLon = Integer.MAX_VALUE;
-  int minLat = Integer.MIN_VALUE;
-  int maxLat = Integer.MAX_VALUE;
-
-  // second set of longitude ranges to check (for cross-dateline case)
-  int minLon2 = Integer.MAX_VALUE;
-
-  // the number of times setBottom has been called (adversary protection)
-  int setBottomCounter = 0;
-
-  private long[] currentValues = new long[4];
-  private int valuesDocID = -1;
-
-  public LatLonPointDistanceComparator(String field, double latitude, double longitude, int numHits) {
-    this.field = field;
-    this.latitude = latitude;
-    this.longitude = longitude;
-    this.values = new double[numHits];
-  }
-  
-  @Override
-  public void setScorer(Scorer scorer) {}
-
-  @Override
-  public int compare(int slot1, int slot2) {
-    return Double.compare(values[slot1], values[slot2]);
-  }
-  
-  @Override
-  public void setBottom(int slot) {
-    bottom = values[slot];
-    // make bounding box(es) to exclude non-competitive hits, but start
-    // sampling if we get called way too much: don't make gobs of bounding
-    // boxes if comparator hits a worst case order (e.g. backwards distance order)
-    if (setBottomCounter < 1024 || (setBottomCounter & 0x3F) == 0x3F) {
-      Rectangle box = Rectangle.fromPointDistance(latitude, longitude, haversin2(bottom));
-      // pre-encode our box to our integer encoding, so we don't have to decode 
-      // to double values for uncompetitive hits. This has some cost!
-      minLat = encodeLatitude(box.minLat);
-      maxLat = encodeLatitude(box.maxLat);
-      if (box.crossesDateline()) {
-        // box1
-        minLon = Integer.MIN_VALUE;
-        maxLon = encodeLongitude(box.maxLon);
-        // box2
-        minLon2 = encodeLongitude(box.minLon);
-      } else {
-        minLon = encodeLongitude(box.minLon);
-        maxLon = encodeLongitude(box.maxLon);
-        // disable box2
-        minLon2 = Integer.MAX_VALUE;
-      }
-    }
-    setBottomCounter++;
-  }
-  
-  @Override
-  public void setTopValue(Double value) {
-    topValue = value.doubleValue();
-  }
-
-  private void setValues() throws IOException {
-    if (valuesDocID != currentDocs.docID()) {
-      assert valuesDocID < currentDocs.docID(): " valuesDocID=" + valuesDocID + " vs " + currentDocs.docID();
-      valuesDocID = currentDocs.docID();
-      int count = currentDocs.docValueCount();
-      if (count > currentValues.length) {
-        currentValues = new long[ArrayUtil.oversize(count, RamUsageEstimator.NUM_BYTES_LONG)];
-      }
-      for(int i=0;i<count;i++) {
-        currentValues[i] = currentDocs.nextValue();
-      }
-    }
-  }
-  
-  @Override
-  public int compareBottom(int doc) throws IOException {
-    if (doc > currentDocs.docID()) {
-      currentDocs.advance(doc);
-    }
-    if (doc < currentDocs.docID()) {
-      return Double.compare(bottom, Double.POSITIVE_INFINITY);
-    }
-
-    setValues();
-
-    int numValues = currentDocs.docValueCount();
-
-    int cmp = -1;
-    for (int i = 0; i < numValues; i++) {
-      long encoded = currentValues[i];
-
-      // test bounding box
-      int latitudeBits = (int)(encoded >> 32);
-      if (latitudeBits < minLat || latitudeBits > maxLat) {
-        continue;
-      }
-      int longitudeBits = (int)(encoded & 0xFFFFFFFF);
-      if ((longitudeBits < minLon || longitudeBits > maxLon) && (longitudeBits < minLon2)) {
-        continue;
-      }
-
-      // only compute actual distance if its inside "competitive bounding box"
-      double docLatitude = decodeLatitude(latitudeBits);
-      double docLongitude = decodeLongitude(longitudeBits);
-      cmp = Math.max(cmp, Double.compare(bottom, SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude)));
-      // once we compete in the PQ, no need to continue.
-      if (cmp > 0) {
-        return cmp;
-      }
-    }
-    return cmp;
-  }
-  
-  @Override
-  public void copy(int slot, int doc) throws IOException {
-    values[slot] = sortKey(doc);
-  }
-  
-  @Override
-  public LeafFieldComparator getLeafComparator(LeafReaderContext context) throws IOException {
-    LeafReader reader = context.reader();
-    FieldInfo info = reader.getFieldInfos().fieldInfo(field);
-    if (info != null) {
-      LatLonDocValuesField.checkCompatible(info);
-    }
-    currentDocs = DocValues.getSortedNumeric(reader, field);
-    valuesDocID = -1;
-    return this;
-  }
-  
-  @Override
-  public Double value(int slot) {
-    return Double.valueOf(haversin2(values[slot]));
-  }
-  
-  @Override
-  public int compareTop(int doc) throws IOException {
-    return Double.compare(topValue, haversin2(sortKey(doc)));
-  }
-  
-  // TODO: optimize for single-valued case?
-  // TODO: do all kinds of other optimizations!
-  double sortKey(int doc) throws IOException {
-    if (doc > currentDocs.docID()) {
-      currentDocs.advance(doc);
-    }
-    double minValue = Double.POSITIVE_INFINITY;
-    if (doc == currentDocs.docID()) {
-      setValues();
-      int numValues = currentDocs.docValueCount();
-      for (int i = 0; i < numValues; i++) {
-        long encoded = currentValues[i];
-        double docLatitude = decodeLatitude((int)(encoded >> 32));
-        double docLongitude = decodeLongitude((int)(encoded & 0xFFFFFFFF));
-        minValue = Math.min(minValue, SloppyMath.haversinSortKey(latitude, longitude, docLatitude, docLongitude));
-      }
-    }
-    return minValue;
-  }
-
-  // second half of the haversin calculation, used to convert results from haversin1 (used internally
-  // for sorting) for display purposes.
-  static double haversin2(double partial) {
-    if (Double.isInfinite(partial)) {
-      return partial;
-    }
-    return SloppyMath.haversinMeters(partial);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/950b7b6b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
deleted file mode 100644
index 90e63c6..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPointDistanceQuery.java
+++ /dev/null
@@ -1,389 +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 java.io.IOException;
-
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PointValues;
-import org.apache.lucene.index.PointValues.IntersectVisitor;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-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.ScorerSupplier;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.BitSetIterator;
-import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.FixedBitSet;
-import org.apache.lucene.util.NumericUtils;
-import org.apache.lucene.util.StringHelper;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-/**
- * Distance query for {@link LatLonPoint}.
- */
-final class LatLonPointDistanceQuery extends Query {
-  final String field;
-  final double latitude;
-  final double longitude;
-  final double radiusMeters;
-
-  public LatLonPointDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
-    if (field == null) {
-      throw new IllegalArgumentException("field must not be null");
-    }
-    if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
-      throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
-    }
-    GeoUtils.checkLatitude(latitude);
-    GeoUtils.checkLongitude(longitude);
-    this.field = field;
-    this.latitude = latitude;
-    this.longitude = longitude;
-    this.radiusMeters = radiusMeters;
-  }
-
-  @Override
-  public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
-    Rectangle box = Rectangle.fromPointDistance(latitude, longitude, radiusMeters);
-    // create bounding box(es) for the distance range
-    // these are pre-encoded with LatLonPoint's encoding
-    final byte minLat[] = new byte[Integer.BYTES];
-    final byte maxLat[] = new byte[Integer.BYTES];
-    final byte minLon[] = new byte[Integer.BYTES];
-    final byte maxLon[] = new byte[Integer.BYTES];
-    // second set of longitude ranges to check (for cross-dateline case)
-    final byte minLon2[] = new byte[Integer.BYTES];
-
-    NumericUtils.intToSortableBytes(encodeLatitude(box.minLat), minLat, 0);
-    NumericUtils.intToSortableBytes(encodeLatitude(box.maxLat), maxLat, 0);
-
-    // crosses dateline: split
-    if (box.crossesDateline()) {
-      // box1
-      NumericUtils.intToSortableBytes(Integer.MIN_VALUE, minLon, 0);
-      NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
-      // box2
-      NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon2, 0);
-    } else {
-      NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0);
-      NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);
-      // disable box2
-      NumericUtils.intToSortableBytes(Integer.MAX_VALUE, minLon2, 0);
-    }
-
-    // compute exact sort key: avoid any asin() computations
-    final double sortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
-
-    final double axisLat = Rectangle.axisLat(latitude, radiusMeters);
-
-    return new ConstantScoreWeight(this, boost) {
-
-      final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
-
-      @Override
-      public Scorer scorer(LeafReaderContext context) throws IOException {
-        ScorerSupplier scorerSupplier = scorerSupplier(context);
-        if (scorerSupplier == null) {
-          return null;
-        }
-        return scorerSupplier.get(Long.MAX_VALUE);
-      }
-
-      @Override
-      public boolean isCacheable(LeafReaderContext ctx) {
-        return true;
-      }
-
-      @Override
-      public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
-        LeafReader reader = context.reader();
-        PointValues values = reader.getPointValues(field);
-        if (values == null) {
-          // No docs in this segment had any points fields
-          return null;
-        }
-        FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
-        if (fieldInfo == null) {
-          // No docs in this segment indexed this field at all
-          return null;
-        }
-        LatLonPoint.checkCompatible(fieldInfo);
-
-        // matching docids
-        DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
-        final IntersectVisitor visitor = getIntersectVisitor(result);
-
-        final Weight weight = this;
-        return new ScorerSupplier() {
-
-          long cost = -1;
-
-          @Override
-          public Scorer get(long leadCost) throws IOException {
-            if (values.getDocCount() == reader.maxDoc()
-                && values.getDocCount() == values.size()
-                && cost() > reader.maxDoc() / 2) {
-              // If all docs have exactly one value and the cost is greater
-              // than half the leaf size then maybe we can make things faster
-              // by computing the set of documents that do NOT match the range
-              final FixedBitSet result = new FixedBitSet(reader.maxDoc());
-              result.set(0, reader.maxDoc());
-              int[] cost = new int[]{reader.maxDoc()};
-              values.intersect(getInverseIntersectVisitor(result, cost));
-              final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
-              return new ConstantScoreScorer(weight, score(), iterator);
-            }
-            values.intersect(visitor);
-            return new ConstantScoreScorer(weight, score(), result.build().iterator());
-          }
-
-          @Override
-          public long cost() {
-            if (cost == -1) {
-              cost = values.estimatePointCount(visitor);
-            }
-            assert cost >= 0;
-            return cost;
-          }
-        };
-
-      }
-
-      /**
-       * Create a visitor that collects documents matching the range.
-       */
-      private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result) {
-        return new IntersectVisitor() {
-
-          DocIdSetBuilder.BulkAdder adder;
-
-          @Override
-          public void grow(int count) {
-            adder = result.grow(count);
-          }
-
-          @Override
-          public void visit(int docID) {
-            adder.add(docID);
-          }
-
-          @Override
-          public void visit(int docID, byte[] packedValue) {
-            // bounding box check
-            if (StringHelper.compare(Integer.BYTES, packedValue, 0, maxLat, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, packedValue, 0, minLat, 0) < 0) {
-              // latitude out of bounding box range
-              return;
-            }
-
-            if ((StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, maxLon, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon, 0) < 0)
-                && StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon2, 0) < 0) {
-              // longitude out of bounding box range
-              return;
-            }
-
-            int docLatitude = NumericUtils.sortableBytesToInt(packedValue, 0);
-            int docLongitude = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
-            if (distancePredicate.test(docLatitude, docLongitude)) {
-              adder.add(docID);
-            }
-          }
-
-          // algorithm: we create a bounding box (two bounding boxes if we cross the dateline).
-          // 1. check our bounding box(es) first. if the subtree is entirely outside of those, bail.
-          // 2. check if the subtree is disjoint. it may cross the bounding box but not intersect with circle
-          // 3. see if the subtree is fully contained. if the subtree is enormous along the x axis, wrapping half way around the world, etc: then this can't work, just go to step 4.
-          // 4. recurse naively (subtrees crossing over circle edge)
-          @Override
-          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
-            if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0) {
-              // latitude out of bounding box range
-              return Relation.CELL_OUTSIDE_QUERY;
-            }
-
-            if ((StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0)
-                && StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon2, 0) < 0) {
-              // longitude out of bounding box range
-              return Relation.CELL_OUTSIDE_QUERY;
-            }
-
-            double latMin = decodeLatitude(minPackedValue, 0);
-            double lonMin = decodeLongitude(minPackedValue, Integer.BYTES);
-            double latMax = decodeLatitude(maxPackedValue, 0);
-            double lonMax = decodeLongitude(maxPackedValue, Integer.BYTES);
-
-            return GeoUtils.relate(latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
-          }
-        };
-      }
-
-      /**
-       * Create a visitor that clears documents that do NOT match the range.
-       */
-      private IntersectVisitor getInverseIntersectVisitor(FixedBitSet result, int[] cost) {
-        return new IntersectVisitor() {
-
-          @Override
-          public void visit(int docID) {
-            result.clear(docID);
-            cost[0]--;
-          }
-
-          @Override
-          public void visit(int docID, byte[] packedValue) {
-            // bounding box check
-            if (StringHelper.compare(Integer.BYTES, packedValue, 0, maxLat, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, packedValue, 0, minLat, 0) < 0) {
-              // latitude out of bounding box range
-              result.clear(docID);
-              cost[0]--;
-              return;
-            }
-
-            if ((StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, maxLon, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon, 0) < 0)
-                && StringHelper.compare(Integer.BYTES, packedValue, Integer.BYTES, minLon2, 0) < 0) {
-              // longitude out of bounding box range
-              result.clear(docID);
-              cost[0]--;
-              return;
-            }
-
-            int docLatitude = NumericUtils.sortableBytesToInt(packedValue, 0);
-            int docLongitude = NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES);
-            if (!distancePredicate.test(docLatitude, docLongitude)) {
-              result.clear(docID);
-              cost[0]--;
-            }
-          }
-
-          @Override
-          public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
-            if (StringHelper.compare(Integer.BYTES, minPackedValue, 0, maxLat, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, maxPackedValue, 0, minLat, 0) < 0) {
-              // latitude out of bounding box range
-              return Relation.CELL_INSIDE_QUERY;
-            }
-
-            if ((StringHelper.compare(Integer.BYTES, minPackedValue, Integer.BYTES, maxLon, 0) > 0 ||
-                StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon, 0) < 0)
-                && StringHelper.compare(Integer.BYTES, maxPackedValue, Integer.BYTES, minLon2, 0) < 0) {
-              // latitude out of bounding box range
-              return Relation.CELL_INSIDE_QUERY;
-            }
-
-            double latMin = decodeLatitude(minPackedValue, 0);
-            double lonMin = decodeLongitude(minPackedValue, Integer.BYTES);
-            double latMax = decodeLatitude(maxPackedValue, 0);
-            double lonMax = decodeLongitude(maxPackedValue, Integer.BYTES);
-
-            Relation relation = GeoUtils.relate(latMin, latMax, lonMin, lonMax, latitude, longitude, sortKey, axisLat);
-            switch (relation) {
-              case CELL_INSIDE_QUERY:
-                // all points match, skip this subtree
-                return Relation.CELL_OUTSIDE_QUERY;
-              case CELL_OUTSIDE_QUERY:
-                // none of the points match, clear all documents
-                return Relation.CELL_INSIDE_QUERY;
-              default:
-                return relation;
-            }
-          }
-
-        };
-      }
-
-    };
-  }
-
-  public String getField() {
-    return field;
-  }
-
-  public double getLatitude() {
-    return latitude;
-  }
-
-  public double getLongitude() {
-    return longitude;
-  }
-
-  public double getRadiusMeters() {
-    return radiusMeters;
-  }
-
-  @Override
-  public int hashCode() {
-    final int prime = 31;
-    int result = classHash();
-    result = prime * result + field.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(latitude);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(longitude);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(radiusMeters);
-    result = prime * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    return sameClassAs(other) &&
-           equalsTo(getClass().cast(other));
-  }
-
-  private boolean equalsTo(LatLonPointDistanceQuery other) {
-    return field.equals(other.field) &&
-           Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
-           Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
-           Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
-  }
-
-  @Override
-  public String toString(String field) {
-    StringBuilder sb = new StringBuilder();
-    if (!this.field.equals(field)) {
-      sb.append(this.field);
-      sb.append(':');
-    }
-    sb.append(latitude);
-    sb.append(",");
-    sb.append(longitude);
-    sb.append(" +/- ");
-    sb.append(radiusMeters);
-    sb.append(" meters");
-    return sb.toString();
-  }
-}