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();
- }
-}