You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by iv...@apache.org on 2021/01/08 08:21:13 UTC
[lucene-solr] branch branch_8x updated: LUCENE-9641: Support for
spatial relationships in LatLonPoint (#2155) (#2188)
This is an automated email from the ASF dual-hosted git repository.
ivera pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/branch_8x by this push:
new f73f6b1 LUCENE-9641: Support for spatial relationships in LatLonPoint (#2155) (#2188)
f73f6b1 is described below
commit f73f6b11f0ed4bcbfce445152d813adb3327d249
Author: Ignacio Vera <iv...@apache.org>
AuthorDate: Fri Jan 8 09:20:51 2021 +0100
LUCENE-9641: Support for spatial relationships in LatLonPoint (#2155) (#2188)
Equivalent to LatLonShape, LatLonPoint can be queried now using spatial relationships.
---
lucene/CHANGES.txt | 2 +
.../lucene/document/LatLonDocValuesField.java | 192 +++---
.../LatLonDocValuesPointInGeometryQuery.java | 166 -----
.../lucene/document/LatLonDocValuesQuery.java | 272 ++++++++
.../org/apache/lucene/document/LatLonPoint.java | 212 +++---
.../document/LatLonPointInGeometryQuery.java | 275 --------
.../apache/lucene/document/LatLonPointQuery.java | 183 ++++++
.../document/LatLonShapeBoundingBoxQuery.java | 536 ++++++++-------
.../apache/lucene/document/LatLonShapeQuery.java | 258 +++++---
.../org/apache/lucene/document/ShapeQuery.java | 621 ------------------
.../org/apache/lucene/document/SpatialQuery.java | 722 +++++++++++++++++++++
.../org/apache/lucene/document/XYShapeQuery.java | 333 +++++-----
.../org/apache/lucene/geo/GeoEncodingUtils.java | 212 +++---
.../src/java/org/apache/lucene/geo/Point2D.java | 109 +++-
.../document/BaseLatLonDocValueTestCase.java | 72 ++
.../lucene/document/BaseLatLonPointTestCase.java | 139 ++++
.../lucene/document/BaseLatLonShapeTestCase.java | 361 ++++-------
.../lucene/document/BaseLatLonSpatialTestCase.java | 220 +++++++
...ShapeTestCase.java => BaseSpatialTestCase.java} | 199 ++++--
.../lucene/document/BaseXYShapeTestCase.java | 49 +-
.../TestLatLonDocValuesMultiPointPointQueries.java | 99 +++
.../TestLatLonDocValuesPointPointQueries.java | 71 ++
.../document/TestLatLonMultiPointPointQueries.java | 99 +++
.../document/TestLatLonPointPointQueries.java | 69 ++
.../apache/lucene/document/TestLatLonShape.java | 9 +
.../lucene/search/TestLatLonDocValuesQueries.java | 4 +-
.../lucene/search/TestLatLonPointQueries.java | 3 +-
27 files changed, 3362 insertions(+), 2125 deletions(-)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 563d8e2..bfc1add 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -14,6 +14,8 @@ New Features
* LUCENE-9552: New LatLonPoint query that accepts an array of LatLonGeometries. (Ignacio Vera)
+* LUCENE-9641: LatLonPoint query support for spatial relationships. (Ignacio Vera)
+
* LUCENE-9553: New XYPoint query that accepts an array of XYGeometries. (Ignacio Vera)
* LUCENE-9594: FeatureField supports newLinearQuery that for scoring uses raw indexed
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
index e263f78..b1e7329 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
@@ -23,6 +23,7 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import org.apache.lucene.geo.Circle;
import org.apache.lucene.geo.LatLonGeometry;
+import org.apache.lucene.geo.Point;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.DocValuesType;
@@ -33,55 +34,63 @@ 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:
+ *
+ * <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.
+ * <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).
+ *
+ * <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.
+ *
+ * <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
+ * @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
@@ -89,19 +98,28 @@ public class LatLonDocValuesField extends Field {
public void setLocationValue(double latitude, double longitude) {
int latitudeEncoded = encodeLatitude(latitude);
int longitudeEncoded = encodeLongitude(longitude);
- fieldsData = Long.valueOf((((long)latitudeEncoded) << 32) | (longitudeEncoded & 0xFFFFFFFFL));
+ fieldsData = Long.valueOf((((long) latitudeEncoded) << 32) | (longitudeEncoded & 0xFFFFFFFFL));
}
- /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */
+ /**
+ * 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?");
+ // 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();
@@ -110,10 +128,10 @@ public class LatLonDocValuesField extends Field {
result.append(name);
result.append(':');
- long currentValue = (Long)fieldsData;
- result.append(decodeLatitude((int)(currentValue >> 32)));
+ long currentValue = (Long) fieldsData;
+ result.append(decodeLatitude((int) (currentValue >> 32)));
result.append(',');
- result.append(decodeLongitude((int)(currentValue & 0xFFFFFFFF)));
+ result.append(decodeLongitude((int) (currentValue & 0xFFFFFFFF)));
result.append('>');
return result.toString();
@@ -121,15 +139,16 @@ public class LatLonDocValuesField extends Field {
/**
* 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.
- *
+ *
+ * <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.
@@ -141,15 +160,21 @@ public class LatLonDocValuesField extends Field {
}
/**
- * 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
+ * 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?
+ 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");
@@ -157,7 +182,8 @@ public class LatLonDocValuesField extends Field {
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");
+ return new MatchNoDocsQuery(
+ "LatLonDocValuesField.newBoxQuery with minLongitude=maxLongitude=180.0");
} else if (maxLongitude < minLongitude) {
// encodeCeil() with dateline wrapping!
minLongitude = -180.0;
@@ -167,55 +193,75 @@ public class LatLonDocValuesField extends Field {
}
/**
- * 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}.
+ * 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.
+ * @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.
+ * @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) {
+ public static Query newSlowDistanceQuery(
+ String field, double latitude, double longitude, double radiusMeters) {
Circle circle = new Circle(latitude, longitude, radiusMeters);
- return newSlowGeometryQuery(field, circle);
+ return newSlowGeometryQuery(field, ShapeField.QueryRelation.INTERSECTS, circle);
}
/**
- * Create a query for matching points within the supplied polygons.
- * 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
+ * Create a query for matching points within the supplied polygons. 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#newPolygonQuery(String, Polygon...)}.
+ *
* @param field field name. must not be null.
* @param polygons array of polygons. must not be null or empty.
* @return query matching points within the given polygons.
- * @throws IllegalArgumentException if {@code field} is null or polygons is empty or contain a null polygon.
+ * @throws IllegalArgumentException if {@code field} is null or polygons is empty or contain a
+ * null polygon.
*/
public static Query newSlowPolygonQuery(String field, Polygon... polygons) {
- return newSlowGeometryQuery(field, polygons);
+ return newSlowGeometryQuery(field, ShapeField.QueryRelation.INTERSECTS, polygons);
}
/**
- * Create a query for matching points within the supplied geometries. Line geometries are not supported.
- * 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#newGeometryQuery(String, LatLonGeometry...)}.
+ * Create a query for matching one or more geometries against the provided {@link
+ * ShapeField.QueryRelation}. Line geometries are not supported for WITHIN relationship. 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#newGeometryQuery(String,
+ * ShapeField.QueryRelation, LatLonGeometry...)}.
+ *
* @param field field name. must not be null.
+ * @param queryRelation The relation the points needs to satisfy with the provided geometries,
+ * must not be null.
* @param latLonGeometries array of LatLonGeometries. must not be null or empty.
* @return query matching points within the given polygons.
- * @throws IllegalArgumentException if {@code field} is null, {@code latLonGeometries} is null, empty or contain a null or line geometry.
+ * @throws IllegalArgumentException if {@code field} is null, {@code queryRelation} is null,
+ * {@code latLonGeometries} is null, empty or contain a null or line geometry.
*/
- public static Query newSlowGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
- if (latLonGeometries.length == 1 && latLonGeometries[0] instanceof Rectangle) {
+ public static Query newSlowGeometryQuery(
+ String field, ShapeField.QueryRelation queryRelation, LatLonGeometry... latLonGeometries) {
+ if (queryRelation == ShapeField.QueryRelation.INTERSECTS
+ && latLonGeometries.length == 1
+ && latLonGeometries[0] instanceof Rectangle) {
LatLonGeometry geometry = latLonGeometries[0];
Rectangle rect = (Rectangle) geometry;
return newSlowBoxQuery(field, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
}
- return new LatLonDocValuesPointInGeometryQuery(field, latLonGeometries);
+ if (queryRelation == ShapeField.QueryRelation.CONTAINS) {
+ for (LatLonGeometry geometry : latLonGeometries) {
+ if ((geometry instanceof Point) == false) {
+ return new MatchNoDocsQuery(
+ "Contains LatLonDocValuesField.newSlowGeometryQuery with non-point geometries");
+ }
+ }
+ }
+ return new LatLonDocValuesQuery(field, queryRelation, latLonGeometries);
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java
deleted file mode 100644
index 04cb612..0000000
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java
+++ /dev/null
@@ -1,166 +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.Component2D;
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.LatLonGeometry;
-import org.apache.lucene.geo.Line;
-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.QueryVisitor;
-import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-/** Geometry query for {@link LatLonDocValuesField}. */
-public class LatLonDocValuesPointInGeometryQuery extends Query {
-
- private final String field;
- private final LatLonGeometry[] geometries;
-
-
- LatLonDocValuesPointInGeometryQuery(String field, LatLonGeometry... geometries) {
- if (field == null) {
- throw new IllegalArgumentException("field must not be null");
- }
- if (geometries == null) {
- throw new IllegalArgumentException("geometries must not be null");
- }
- if (geometries.length == 0) {
- throw new IllegalArgumentException("geometries must not be empty");
- }
- for (int i = 0; i < geometries.length; i++) {
- if (geometries[i] == null) {
- throw new IllegalArgumentException("geometries[" + i + "] must not be null");
- }
- if (geometries[i] instanceof Line) {
- throw new IllegalArgumentException("LatLonDocValuesPointInGeometryQuery does not support queries with line geometries");
- }
- }
- this.field = field;
- this.geometries = geometries;
- }
-
- @Override
- public String toString(String field) {
- StringBuilder sb = new StringBuilder();
- if (!this.field.equals(field)) {
- sb.append(this.field);
- sb.append(':');
- }
- sb.append("geometries(").append(Arrays.toString(geometries));
- return sb.append(")").toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (sameClassAs(obj) == false) {
- return false;
- }
- LatLonDocValuesPointInGeometryQuery other = (LatLonDocValuesPointInGeometryQuery) obj;
- return field.equals(other.field) &&
- Arrays.equals(geometries, other.geometries);
- }
-
- @Override
- public int hashCode() {
- int h = classHash();
- h = 31 * h + field.hashCode();
- h = 31 * h + Arrays.hashCode(geometries);
- return h;
- }
-
- @Override
- public void visit(QueryVisitor visitor) {
- if (visitor.acceptField(field)) {
- visitor.visitLeaf(this);
- }
- }
-
- @Override
- public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
- final Component2D tree = LatLonGeometry.create(geometries);
-
- if (tree.getMinY() > tree.getMaxY()) {
- // encodeLatitudeCeil may cause minY to be > maxY iff
- // the delta between the longitude < the encoding resolution
- return new ConstantScoreWeight(this, boost) {
- @Override
- public Scorer scorer(LeafReaderContext context) {
- return null;
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return false;
- }
- };
- }
-
- final GeoEncodingUtils.Component2DPredicate component2DPredicate = GeoEncodingUtils.createComponentPredicate(tree);
-
- 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);
- final int lon = (int) (value & 0xFFFFFFFF);
- if (component2DPredicate.test(lat, lon)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public float matchCost() {
- return 1000f; // TODO: what should it be?
- }
- };
- return new ConstantScoreScorer(this, boost, scoreMode, iterator);
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return DocValues.isCacheable(ctx, field);
- }
-
- };
- }
-}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesQuery.java
new file mode 100644
index 0000000..8e8120f
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesQuery.java
@@ -0,0 +1,272 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.LatLonGeometry;
+import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Point;
+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.QueryVisitor;
+import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+
+/**
+ * Finds all previously indexed geo points that comply the given {@link ShapeField.QueryRelation}
+ * with the specified array of {@link LatLonGeometry}.
+ *
+ * <p>The field must be indexed using {@link LatLonDocValuesField} added per document.
+ */
+class LatLonDocValuesQuery extends Query {
+
+ private final String field;
+ private final LatLonGeometry[] geometries;
+ private final ShapeField.QueryRelation queryRelation;
+ private final Component2D component2D;
+
+ LatLonDocValuesQuery(
+ String field, ShapeField.QueryRelation queryRelation, LatLonGeometry... geometries) {
+ if (field == null) {
+ throw new IllegalArgumentException("field must not be null");
+ }
+ if (queryRelation == null) {
+ throw new IllegalArgumentException("queryRelation must not be null");
+ }
+ if (queryRelation == ShapeField.QueryRelation.WITHIN) {
+ for (LatLonGeometry geometry : geometries) {
+ if (geometry instanceof Line) {
+ // TODO: line queries do not support within relations
+ throw new IllegalArgumentException(
+ "LatLonDocValuesPointQuery does not support "
+ + ShapeField.QueryRelation.WITHIN
+ + " queries with line geometries");
+ }
+ }
+ }
+ if (queryRelation == ShapeField.QueryRelation.CONTAINS) {
+ for (LatLonGeometry geometry : geometries) {
+ if ((geometry instanceof Point) == false) {
+ throw new IllegalArgumentException(
+ "LatLonDocValuesPointQuery does not support "
+ + ShapeField.QueryRelation.CONTAINS
+ + " queries with non-points geometries");
+ }
+ }
+ }
+ this.field = field;
+ this.geometries = geometries;
+ this.queryRelation = queryRelation;
+ this.component2D = LatLonGeometry.create(geometries);
+ }
+
+ @Override
+ public String toString(String field) {
+ StringBuilder sb = new StringBuilder();
+ if (!this.field.equals(field)) {
+ sb.append(this.field);
+ sb.append(':');
+ }
+ sb.append(queryRelation).append(':');
+ sb.append("geometries(").append(Arrays.toString(geometries));
+ return sb.append(")").toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (sameClassAs(obj) == false) {
+ return false;
+ }
+ LatLonDocValuesQuery other = (LatLonDocValuesQuery) obj;
+ return field.equals(other.field)
+ && queryRelation == other.queryRelation
+ && Arrays.equals(geometries, other.geometries);
+ }
+
+ @Override
+ public int hashCode() {
+ int h = classHash();
+ h = 31 * h + field.hashCode();
+ h = 31 * h + queryRelation.hashCode();
+ h = 31 * h + Arrays.hashCode(geometries);
+ return h;
+ }
+
+ @Override
+ public void visit(QueryVisitor visitor) {
+ if (visitor.acceptField(field)) {
+ visitor.visitLeaf(this);
+ }
+ }
+
+ @Override
+ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
+ throws IOException {
+ final GeoEncodingUtils.Component2DPredicate component2DPredicate =
+ queryRelation == ShapeField.QueryRelation.CONTAINS
+ ? null
+ : GeoEncodingUtils.createComponentPredicate(component2D);
+ 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;
+ switch (queryRelation) {
+ case INTERSECTS:
+ iterator = intersects(values, component2DPredicate);
+ break;
+ case WITHIN:
+ iterator = within(values, component2DPredicate);
+ break;
+ case DISJOINT:
+ iterator = disjoint(values, component2DPredicate);
+ break;
+ case CONTAINS:
+ iterator = contains(values, geometries);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid query relationship:[" + queryRelation + "]");
+ }
+ return new ConstantScoreScorer(this, boost, scoreMode, iterator);
+ }
+
+ @Override
+ public boolean isCacheable(LeafReaderContext ctx) {
+ return DocValues.isCacheable(ctx, field);
+ }
+ };
+ }
+
+ private TwoPhaseIterator intersects(
+ SortedNumericDocValues values, GeoEncodingUtils.Component2DPredicate component2DPredicate) {
+ return 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 (component2DPredicate.test(lat, lon)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public float matchCost() {
+ return 1000f; // TODO: what should it be?
+ }
+ };
+ }
+
+ private TwoPhaseIterator within(
+ SortedNumericDocValues values, GeoEncodingUtils.Component2DPredicate component2DPredicate) {
+ return 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 (component2DPredicate.test(lat, lon) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public float matchCost() {
+ return 1000f; // TODO: what should it be?
+ }
+ };
+ }
+
+ private TwoPhaseIterator disjoint(
+ SortedNumericDocValues values, GeoEncodingUtils.Component2DPredicate component2DPredicate) {
+ return 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 (component2DPredicate.test(lat, lon)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public float matchCost() {
+ return 1000f; // TODO: what should it be?
+ }
+ };
+ }
+
+ private TwoPhaseIterator contains(SortedNumericDocValues values, LatLonGeometry[] geometries) {
+ final List<Component2D> component2Ds = new ArrayList<>(geometries.length);
+ for (int i = 0; i < geometries.length; i++) {
+ component2Ds.add(LatLonGeometry.create(geometries[i]));
+ }
+ return new TwoPhaseIterator(values) {
+ @Override
+ public boolean matches() throws IOException {
+ Component2D.WithinRelation answer = Component2D.WithinRelation.DISJOINT;
+ for (int i = 0, count = values.docValueCount(); i < count; ++i) {
+ final long value = values.nextValue();
+ final double lat = GeoEncodingUtils.decodeLatitude((int) (value >>> 32));
+ final double lon = GeoEncodingUtils.decodeLongitude((int) (value & 0xFFFFFFFF));
+ for (Component2D component2D : component2Ds) {
+ Component2D.WithinRelation relation = component2D.withinPoint(lon, lat);
+ if (relation == Component2D.WithinRelation.NOTWITHIN) {
+ return false;
+ } else if (relation != Component2D.WithinRelation.DISJOINT) {
+ answer = relation;
+ }
+ }
+ }
+ return answer == Component2D.WithinRelation.CANDIDATE;
+ }
+
+ @Override
+ public float matchCost() {
+ return 1000f; // TODO: what should it be?
+ }
+ };
+ }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
index 7dfb5a0..2ad1393 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -16,15 +16,23 @@
*/
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.encodeLatitudeCeil;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+
import org.apache.lucene.geo.Circle;
import org.apache.lucene.geo.LatLonGeometry;
+import org.apache.lucene.geo.Point;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
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.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
@@ -33,56 +41,58 @@ 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:
+ *
+ * <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 #newDistanceQuery newDistanceQuery()} for matching points within a specified
+ * distance.
* <li>{@link #newPolygonQuery newPolygonQuery()} for matching points within an arbitrary polygon.
- * <li>{@link #newGeometryQuery newGeometryQuery()} for matching points within an arbitrary geometry collection.
+ * <li>{@link #newGeometryQuery newGeometryQuery()} for matching points within an arbitrary
+ * geometry collection.
* </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).
+ *
+ * <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".
+// 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.
+ *
+ * <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
@@ -103,12 +113,14 @@ public class LatLonPoint extends Field {
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
+ * @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);
@@ -131,7 +143,7 @@ public class LatLonPoint extends Field {
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];
@@ -139,7 +151,7 @@ public class LatLonPoint extends Field {
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];
@@ -150,16 +162,28 @@ public class LatLonPoint extends Field {
/** 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?");
+ // 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?");
+ 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?");
}
}
@@ -167,8 +191,9 @@ public class LatLonPoint extends Field {
/**
* Create a query for matching a bounding box.
- * <p>
- * The box may cross over the dateline.
+ *
+ * <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.
@@ -177,9 +202,16 @@ public class LatLonPoint extends Field {
* @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?
+ 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");
@@ -197,7 +229,8 @@ public class LatLonPoint extends Field {
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:
+ // 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
@@ -217,7 +250,7 @@ public class LatLonPoint extends Field {
return newBoxInternal(field, lower, upper);
}
}
-
+
private static Query newBoxInternal(String field, byte[] min, byte[] max) {
return new PointRangeQuery(field, min, max, 2) {
@Override
@@ -232,22 +265,27 @@ public class LatLonPoint extends Field {
}
};
}
-
+
/**
* 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.
+ * @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.
+ * @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) {
+ 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
@@ -255,19 +293,25 @@ public class LatLonPoint extends Field {
* @see Polygon
*/
public static Query newPolygonQuery(String field, Polygon... polygons) {
- return newGeometryQuery(field, polygons);
+ return newGeometryQuery(field, ShapeField.QueryRelation.INTERSECTS, polygons);
}
/**
- * Create a query for matching one or more geometries. Line geometries are not supported.
+ * Create a query for matching one or more geometries against the provided {@link
+ * ShapeField.QueryRelation}. Line geometries are not supported for WITHIN relationship.
+ *
* @param field field name. must not be null.
+ * @param queryRelation The relation the points needs to satisfy with the provided geometries,
+ * must not be null.
* @param latLonGeometries array of LatLonGeometries. must not be null or empty.
* @return query matching points within at least one geometry.
- * @throws IllegalArgumentException if {@code field} is null, {@code latLonGeometries} is null, empty or contain a null or line geometry.
+ * @throws IllegalArgumentException if {@code field} is null, {@code queryRelation} is null,
+ * {@code latLonGeometries} is null, empty or contain a null.
* @see LatLonGeometry
*/
- public static Query newGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
- if (latLonGeometries.length == 1) {
+ public static Query newGeometryQuery(
+ String field, ShapeField.QueryRelation queryRelation, LatLonGeometry... latLonGeometries) {
+ if (queryRelation == ShapeField.QueryRelation.INTERSECTS && latLonGeometries.length == 1) {
if (latLonGeometries[0] instanceof Rectangle) {
final Rectangle rect = (Rectangle) latLonGeometries[0];
return newBoxQuery(field, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
@@ -277,29 +321,45 @@ public class LatLonPoint extends Field {
return newDistanceQuery(field, circle.getLat(), circle.getLon(), circle.getRadius());
}
}
- return new LatLonPointInGeometryQuery(field, latLonGeometries);
+ if (queryRelation == ShapeField.QueryRelation.CONTAINS) {
+ return makeContainsGeometryQuery(field, latLonGeometries);
+ }
+ return new LatLonPointQuery(field, queryRelation, latLonGeometries);
+ }
+
+ private static Query makeContainsGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (LatLonGeometry geometry : latLonGeometries) {
+ if ((geometry instanceof Point) == false) {
+ return new MatchNoDocsQuery(
+ "Contains LatLonPoint.newGeometryQuery with non-point geometries");
+ }
+ builder.add(
+ new LatLonPointQuery(field, ShapeField.QueryRelation.CONTAINS, geometry),
+ BooleanClause.Occur.MUST);
+ }
+ return new ConstantScoreQuery(builder.build());
}
/**
- * Given a field that indexes point values into a {@link LatLonPoint}
- * and doc values into {@link LatLonDocValuesField}, this returns a query that scores
- * documents based on their haversine distance in meters to {@code (originLat, originLon)}:
- * {@code score = weight * pivotDistanceMeters / (pivotDistanceMeters + distance)}, ie.
- * score is in the {@code [0, weight]} range, is equal to {@code weight} when
- * the document's value is equal to {@code (originLat, originLon)} and is equal to
- * {@code weight/2} when the document's value is distant of
- * {@code pivotDistanceMeters} from {@code (originLat, originLon)}.
- * In case of multi-valued fields, only the closest point to {@code (originLat, originLon)}
- * will be considered.
- * This query is typically useful to boost results based on distance by adding
- * this query to a {@link Occur#SHOULD} clause of a {@link BooleanQuery}.
+ * Given a field that indexes point values into a {@link LatLonPoint} and doc values into {@link
+ * LatLonDocValuesField}, this returns a query that scores documents based on their haversine
+ * distance in meters to {@code (originLat, originLon)}: {@code score = weight *
+ * pivotDistanceMeters / (pivotDistanceMeters + distance)}, ie. score is in the {@code [0,
+ * weight]} range, is equal to {@code weight} when the document's value is equal to {@code
+ * (originLat, originLon)} and is equal to {@code weight/2} when the document's value is distant
+ * of {@code pivotDistanceMeters} from {@code (originLat, originLon)}. In case of multi-valued
+ * fields, only the closest point to {@code (originLat, originLon)} will be considered. This query
+ * is typically useful to boost results based on distance by adding this query to a {@link
+ * Occur#SHOULD} clause of a {@link BooleanQuery}.
*/
- public static Query newDistanceFeatureQuery(String field, float weight, double originLat, double originLon, double pivotDistanceMeters) {
- Query query = new LatLonPointDistanceFeatureQuery(field, originLat, originLon, pivotDistanceMeters);
+ public static Query newDistanceFeatureQuery(
+ String field, float weight, double originLat, double originLon, double pivotDistanceMeters) {
+ Query query =
+ new LatLonPointDistanceFeatureQuery(field, originLat, originLon, pivotDistanceMeters);
if (weight != 1f) {
query = new BoostQuery(query, weight);
}
return query;
}
-
}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
deleted file mode 100644
index d0c831e..0000000
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
+++ /dev/null
@@ -1,275 +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.Component2D;
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.LatLonGeometry;
-import org.apache.lucene.geo.Line;
-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.QueryVisitor;
-import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.ScorerSupplier;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.FutureArrays;
-import org.apache.lucene.util.NumericUtils;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-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 geometries.
- *
- * <p>The field must be indexed with using {@link LatLonPoint} added per document.
- *
- * @lucene.experimental */
-
-final class LatLonPointInGeometryQuery extends Query {
- final String field;
- final LatLonGeometry[] geometries;
-
- LatLonPointInGeometryQuery(String field, LatLonGeometry[] geometries) {
- if (field == null) {
- throw new IllegalArgumentException("field must not be null");
- }
- if (geometries == null) {
- throw new IllegalArgumentException("geometries must not be null");
- }
- if (geometries.length == 0) {
- throw new IllegalArgumentException("geometries must not be empty");
- }
- for (int i = 0; i < geometries.length; i++) {
- if (geometries[i] == null) {
- throw new IllegalArgumentException("geometries[" + i + "] must not be null");
- }
- if (geometries[i] instanceof Line) {
- throw new IllegalArgumentException("LatLonPointInGeometryQuery does not support queries with line geometries");
- }
- }
- this.field = field;
- this.geometries = geometries.clone();
- }
-
- @Override
- public void visit(QueryVisitor visitor) {
- if (visitor.acceptField(field)) {
- visitor.visitLeaf(this);
- }
- }
-
- private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result, Component2D tree, GeoEncodingUtils.Component2DPredicate component2DPredicate,
- byte[] minLat, byte[] maxLat, byte[] minLon, byte[] maxLon) {
- 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) {
- if (component2DPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
- NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
- visit(docID);
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] packedValue) throws IOException {
- if (component2DPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
- NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- visit(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- if (FutureArrays.compareUnsigned(minPackedValue, 0, Integer.BYTES, maxLat, 0, Integer.BYTES) > 0 ||
- FutureArrays.compareUnsigned(maxPackedValue, 0, Integer.BYTES, minLat, 0, Integer.BYTES) < 0 ||
- FutureArrays.compareUnsigned(minPackedValue, Integer.BYTES, Integer.BYTES + Integer.BYTES, maxLon, 0, Integer.BYTES) > 0 ||
- FutureArrays.compareUnsigned(maxPackedValue, Integer.BYTES, Integer.BYTES + Integer.BYTES, minLon, 0, Integer.BYTES) < 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(cellMinLon, cellMaxLon, cellMinLat, cellMaxLat);
- }
- };
- }
-
- @Override
- public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
- final Component2D tree = LatLonGeometry.create(geometries);
- if (tree.getMinY() > tree.getMaxY()) {
- // encodeLatitudeCeil may cause minY to be > maxY iff
- // the delta between the longitude < the encoding resolution
- return new ConstantScoreWeight(this, boost) {
- @Override
- public Scorer scorer(LeafReaderContext context) {
- return null;
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return false;
- }
- };
- }
- final GeoEncodingUtils.Component2DPredicate component2DPredicate = GeoEncodingUtils.createComponentPredicate(tree);
- // bounding box over all geometries, this can speed up tree intersection/cheaply improve approximation for complex multi-geometries
- 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(tree.getMinY()), minLat, 0);
- NumericUtils.intToSortableBytes(encodeLatitude(tree.getMaxY()), maxLat, 0);
- NumericUtils.intToSortableBytes(encodeLongitude(tree.getMinX()), minLon, 0);
- NumericUtils.intToSortableBytes(encodeLongitude(tree.getMaxX()), maxLon, 0);
-
- return new ConstantScoreWeight(this, boost) {
-
- @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);
- final Weight weight = this;
-
- return new ScorerSupplier() {
-
- long cost = -1;
- DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
- final IntersectVisitor visitor = getIntersectVisitor(result, tree, component2DPredicate, minLat, maxLat, minLon, maxLon);
-
- @Override
- public Scorer get(long leadCost) throws IOException {
- values.intersect(visitor);
- return new ConstantScoreScorer(weight, score(), scoreMode, result.build().iterator());
- }
-
- @Override
- public long cost() {
- if (cost == -1) {
- // Computing the cost may be expensive, so only do it if necessary
- cost = values.estimateDocCount(visitor);
- assert cost >= 0;
- }
- return cost;
- }
- };
- }
-
- @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;
- }
- };
-
- }
-
- /** Returns the query field */
- public String getField() {
- return field;
- }
-
- /** Returns a copy of the internal geometry array */
- public LatLonGeometry[] getGeometries() {
- return geometries.clone();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = classHash();
- result = prime * result + field.hashCode();
- result = prime * result + Arrays.hashCode(geometries);
- return result;
- }
-
- @Override
- public boolean equals(Object other) {
- return sameClassAs(other) &&
- equalsTo(getClass().cast(other));
- }
-
- private boolean equalsTo(LatLonPointInGeometryQuery other) {
- return field.equals(other.field) &&
- Arrays.equals(geometries, other.geometries);
- }
-
- @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(geometries));
- return sb.toString();
- }
-}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointQuery.java
new file mode 100644
index 0000000..2b9bcd4
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointQuery.java
@@ -0,0 +1,183 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.LatLonGeometry;
+import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Point;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.FutureArrays;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * Finds all previously indexed geo points that comply the given {@link QueryRelation} with the
+ * specified array of {@link LatLonGeometry}.
+ *
+ * <p>The field must be indexed using one or more {@link LatLonPoint} added per document.
+ */
+final class LatLonPointQuery extends SpatialQuery {
+ private final LatLonGeometry[] geometries;
+ private final Component2D component2D;
+
+ /**
+ * Creates a query that matches all indexed shapes to the provided array of {@link LatLonGeometry}
+ */
+ LatLonPointQuery(String field, QueryRelation queryRelation, LatLonGeometry... geometries) {
+ super(field, queryRelation);
+ if (queryRelation == QueryRelation.WITHIN) {
+ for (LatLonGeometry geometry : geometries) {
+ if (geometry instanceof Line) {
+ // TODO: line queries do not support within relations
+ throw new IllegalArgumentException(
+ "LatLonPointQuery does not support "
+ + QueryRelation.WITHIN
+ + " queries with line geometries");
+ }
+ }
+ }
+ if (queryRelation == ShapeField.QueryRelation.CONTAINS) {
+ for (LatLonGeometry geometry : geometries) {
+ if ((geometry instanceof Point) == false) {
+ throw new IllegalArgumentException(
+ "LatLonPointQuery does not support "
+ + ShapeField.QueryRelation.CONTAINS
+ + " queries with non-points geometries");
+ }
+ }
+ }
+ this.component2D = LatLonGeometry.create(geometries);
+ this.geometries = geometries.clone();
+ }
+
+ @Override
+ protected SpatialVisitor getSpatialVisitor() {
+ final GeoEncodingUtils.Component2DPredicate component2DPredicate =
+ GeoEncodingUtils.createComponentPredicate(component2D);
+ // bounding box over all geometries, this can speed up tree intersection/cheaply improve
+ // approximation for complex multi-geometries
+ 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(component2D.getMinY()), minLat, 0);
+ NumericUtils.intToSortableBytes(encodeLatitude(component2D.getMaxY()), maxLat, 0);
+ NumericUtils.intToSortableBytes(encodeLongitude(component2D.getMinX()), minLon, 0);
+ NumericUtils.intToSortableBytes(encodeLongitude(component2D.getMaxX()), maxLon, 0);
+
+ return new SpatialVisitor() {
+ @Override
+ protected Relation relate(byte[] minPackedValue, byte[] maxPackedValue) {
+ if (FutureArrays.compareUnsigned(minPackedValue, 0, Integer.BYTES, maxLat, 0, Integer.BYTES) > 0
+ || FutureArrays.compareUnsigned(maxPackedValue, 0, Integer.BYTES, minLat, 0, Integer.BYTES)
+ < 0
+ || FutureArrays.compareUnsigned(
+ minPackedValue,
+ Integer.BYTES,
+ Integer.BYTES + Integer.BYTES,
+ maxLon,
+ 0,
+ Integer.BYTES)
+ > 0
+ || FutureArrays.compareUnsigned(
+ maxPackedValue,
+ Integer.BYTES,
+ Integer.BYTES + Integer.BYTES,
+ minLon,
+ 0,
+ Integer.BYTES)
+ < 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 component2D.relate(cellMinLon, cellMaxLon, cellMinLat, cellMaxLat);
+ }
+
+ @Override
+ protected Predicate<byte[]> intersects() {
+ return packedValue ->
+ component2DPredicate.test(
+ NumericUtils.sortableBytesToInt(packedValue, 0),
+ NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES));
+ }
+
+ @Override
+ protected Predicate<byte[]> within() {
+ return packedValue ->
+ component2DPredicate.test(
+ NumericUtils.sortableBytesToInt(packedValue, 0),
+ NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES));
+ }
+
+ @Override
+ protected Function<byte[], Component2D.WithinRelation> contains() {
+ return packedValue ->
+ component2D.withinPoint(
+ GeoEncodingUtils.decodeLongitude(
+ NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES)),
+ GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(packedValue, 0)));
+ }
+ };
+ }
+
+ @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("[");
+ for (int i = 0; i < geometries.length; i++) {
+ sb.append(geometries[i].toString());
+ sb.append(',');
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ @Override
+ protected boolean equalsTo(Object o) {
+ return super.equalsTo(o) && Arrays.equals(geometries, ((LatLonPointQuery) o).geometries);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + Arrays.hashCode(geometries);
+ return hash;
+ }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
index be3c7d3..5434958 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
@@ -16,14 +16,6 @@
*/
package org.apache.lucene.document;
-import org.apache.lucene.document.ShapeField.QueryRelation;
-import org.apache.lucene.geo.Component2D;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.util.FutureArrays;
-import org.apache.lucene.util.NumericUtils;
-
import static java.lang.Integer.BYTES;
import static org.apache.lucene.geo.GeoEncodingUtils.MAX_LON_ENCODED;
import static org.apache.lucene.geo.GeoEncodingUtils.MIN_LON_ENCODED;
@@ -32,117 +24,182 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.FutureArrays;
+import org.apache.lucene.util.NumericUtils;
+
/**
* Finds all previously indexed geo shapes that intersect the specified bounding box.
*
- * <p>The field must be indexed using
- * {@link org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
- **/
-final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
+ * <p>The field must be indexed using {@link
+ * org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
+ */
+final class LatLonShapeBoundingBoxQuery extends SpatialQuery {
private final Rectangle rectangle;
- private final EncodedRectangle encodedRectangle;
LatLonShapeBoundingBoxQuery(String field, QueryRelation queryRelation, Rectangle rectangle) {
super(field, queryRelation);
this.rectangle = rectangle;
- this.encodedRectangle = new EncodedRectangle(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
- }
-
- @Override
- protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
- if (queryRelation == QueryRelation.INTERSECTS || queryRelation == QueryRelation.DISJOINT) {
- return encodedRectangle.intersectRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
- }
- return encodedRectangle.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
- }
-
-
- @Override
- protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
- }
- case LINE: {
- int aY = scratchTriangle.aY;
- int aX = scratchTriangle.aX;
- int bY = scratchTriangle.bY;
- int bX = scratchTriangle.bX;
- return encodedRectangle.intersectsLine(aX, aY, bX, bY);
- }
- case TRIANGLE: {
- int aY = scratchTriangle.aY;
- int aX = scratchTriangle.aX;
- int bY = scratchTriangle.bY;
- int bX = scratchTriangle.bX;
- int cY = scratchTriangle.cY;
- int cX = scratchTriangle.cX;
- return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
- }
-
- @Override
- protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
- }
- case LINE: {
- int aY = scratchTriangle.aY;
- int aX = scratchTriangle.aX;
- int bY = scratchTriangle.bY;
- int bX = scratchTriangle.bX;
- return encodedRectangle.containsLine(aX, aY, bX, bY);
- }
- case TRIANGLE: {
- int aY = scratchTriangle.aY;
- int aX = scratchTriangle.aX;
- int bY = scratchTriangle.bY;
- int bX = scratchTriangle.bX;
- int cY = scratchTriangle.cY;
- int cX = scratchTriangle.cX;
- return encodedRectangle.containsTriangle(aX, aY, bX, bY, cX, cY);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
}
@Override
- protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- if (encodedRectangle.crossesDateline()) {
- throw new IllegalArgumentException("withinTriangle is not supported for rectangles crossing the date line");
- }
- // decode indexed triangle
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY)
- ? Component2D.WithinRelation.NOTWITHIN : Component2D.WithinRelation.DISJOINT;
- }
- case LINE: {
- return encodedRectangle.withinLine(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
- scratchTriangle.bX, scratchTriangle.bY);
- }
- case TRIANGLE: {
- return encodedRectangle.withinTriangle(scratchTriangle.aX, scratchTriangle.aY, scratchTriangle.ab,
- scratchTriangle.bX, scratchTriangle.bY, scratchTriangle.bc,
- scratchTriangle.cX, scratchTriangle.cY, scratchTriangle.ca);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
+ protected SpatialVisitor getSpatialVisitor() {
+ final EncodedRectangle encodedRectangle =
+ new EncodedRectangle(
+ rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
+ return new SpatialVisitor() {
+
+ @Override
+ protected Relation relate(byte[] minTriangle, byte[] maxTriangle) {
+ if (queryRelation == QueryRelation.INTERSECTS || queryRelation == QueryRelation.DISJOINT) {
+ return encodedRectangle.intersectRangeBBox(
+ ShapeField.BYTES,
+ 0,
+ minTriangle,
+ 3 * ShapeField.BYTES,
+ 2 * ShapeField.BYTES,
+ maxTriangle);
+ }
+ return encodedRectangle.relateRangeBBox(
+ ShapeField.BYTES,
+ 0,
+ minTriangle,
+ 3 * ShapeField.BYTES,
+ 2 * ShapeField.BYTES,
+ maxTriangle);
+ }
+
+ @Override
+ protected Predicate<byte[]> intersects() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
+ }
+ case LINE:
+ {
+ int aY = scratchTriangle.aY;
+ int aX = scratchTriangle.aX;
+ int bY = scratchTriangle.bY;
+ int bX = scratchTriangle.bX;
+ return encodedRectangle.intersectsLine(aX, aY, bX, bY);
+ }
+ case TRIANGLE:
+ {
+ int aY = scratchTriangle.aY;
+ int aX = scratchTriangle.aX;
+ int bY = scratchTriangle.bY;
+ int bX = scratchTriangle.bX;
+ int cY = scratchTriangle.cY;
+ int cX = scratchTriangle.cX;
+ return encodedRectangle.intersectsTriangle(aX, aY, bX, bY, cX, cY);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+
+ @Override
+ protected Predicate<byte[]> within() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY);
+ }
+ case LINE:
+ {
+ int aY = scratchTriangle.aY;
+ int aX = scratchTriangle.aX;
+ int bY = scratchTriangle.bY;
+ int bX = scratchTriangle.bX;
+ return encodedRectangle.containsLine(aX, aY, bX, bY);
+ }
+ case TRIANGLE:
+ {
+ int aY = scratchTriangle.aY;
+ int aX = scratchTriangle.aX;
+ int bY = scratchTriangle.bY;
+ int bX = scratchTriangle.bX;
+ int cY = scratchTriangle.cY;
+ int cX = scratchTriangle.cX;
+ return encodedRectangle.containsTriangle(aX, aY, bX, bY, cX, cY);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+
+ @Override
+ protected Function<byte[], Component2D.WithinRelation> contains() {
+ if (encodedRectangle.crossesDateline()) {
+ throw new IllegalArgumentException(
+ "withinTriangle is not supported for rectangles crossing the date line");
+ }
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+
+ // decode indexed triangle
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ return encodedRectangle.contains(scratchTriangle.aX, scratchTriangle.aY)
+ ? Component2D.WithinRelation.NOTWITHIN
+ : Component2D.WithinRelation.DISJOINT;
+ }
+ case LINE:
+ {
+ return encodedRectangle.withinLine(
+ scratchTriangle.aX,
+ scratchTriangle.aY,
+ scratchTriangle.ab,
+ scratchTriangle.bX,
+ scratchTriangle.bY);
+ }
+ case TRIANGLE:
+ {
+ return encodedRectangle.withinTriangle(
+ scratchTriangle.aX,
+ scratchTriangle.aY,
+ scratchTriangle.ab,
+ scratchTriangle.bX,
+ scratchTriangle.bY,
+ scratchTriangle.bc,
+ scratchTriangle.cX,
+ scratchTriangle.cY,
+ scratchTriangle.ca);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+ };
}
@Override
protected boolean equalsTo(Object o) {
- return super.equalsTo(o) && rectangle.equals(((LatLonShapeBoundingBoxQuery)o).rectangle);
+ return super.equalsTo(o) && rectangle.equals(((LatLonShapeBoundingBoxQuery) o).rectangle);
}
@Override
@@ -183,7 +240,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
this.minX = encodeLongitudeCeil(minLon);
this.maxX = encodeLongitude(maxLon);
- this.minY = encodeLatitudeCeil(minLat);
+ this.minY = encodeLatitudeCeil(minLat);
this.maxY = encodeLatitude(maxLat);
this.crossesDateline = minLon > maxLon;
if (this.crossesDateline) {
@@ -197,10 +254,9 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
}
- /**
- * encodes a bounding box into the provided byte array
- */
- private static void encode(final int minX, final int maxX, final int minY, final int maxY, byte[] b) {
+ /** encodes a bounding box into the provided byte array */
+ private static void encode(
+ final int minX, final int maxX, final int minY, final int maxY, byte[] b) {
if (b == null) {
b = new byte[4 * BYTES];
}
@@ -214,47 +270,71 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return crossesDateline;
}
- /**
- * compare this to a provided range bounding box
- **/
- Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
- Relation eastRelation = compareBBoxToRangeBBox(this.bbox,
- minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
+ /** compare this to a provided range bounding box */
+ Relation relateRangeBBox(
+ int minXOffset,
+ int minYOffset,
+ byte[] minTriangle,
+ int maxXOffset,
+ int maxYOffset,
+ byte[] maxTriangle) {
+ Relation eastRelation =
+ compareBBoxToRangeBBox(
+ this.bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
- return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
+ return compareBBoxToRangeBBox(
+ this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
}
- /**
- * intersects this to a provided range bounding box
- **/
- Relation intersectRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
- Relation eastRelation = intersectBBoxWithRangeBBox(this.bbox,
- minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
+ /** intersects this to a provided range bounding box */
+ Relation intersectRangeBBox(
+ int minXOffset,
+ int minYOffset,
+ byte[] minTriangle,
+ int maxXOffset,
+ int maxYOffset,
+ byte[] maxTriangle) {
+ Relation eastRelation =
+ intersectBBoxWithRangeBBox(
+ this.bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
if (this.crossesDateline() && eastRelation == Relation.CELL_OUTSIDE_QUERY) {
- return intersectBBoxWithRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
+ return intersectBBoxWithRangeBBox(
+ this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle);
}
return eastRelation;
}
/**
- * static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection)
- **/
- private static Relation compareBBoxToRangeBBox(final byte[] bbox,
- int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
+ * static utility method to compare a bbox with a range of triangles (just the bbox of the
+ * triangle collection)
+ */
+ private static Relation compareBBoxToRangeBBox(
+ final byte[] bbox,
+ int minXOffset,
+ int minYOffset,
+ byte[] minTriangle,
+ int maxXOffset,
+ int maxYOffset,
+ byte[] maxTriangle) {
// check bounding box (DISJOINT)
- if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
+ if (disjoint(
+ bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
- if (FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
- FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
- FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 &&
- FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
+ if (FutureArrays.compareUnsigned(
+ minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
+ >= 0
+ && FutureArrays.compareUnsigned(
+ maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
+ <= 0
+ && FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
+ >= 0
+ && FutureArrays.compareUnsigned(
+ maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
+ <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
@@ -262,37 +342,64 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
/**
- * static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection)
- * for intersection
- **/
- private static Relation intersectBBoxWithRangeBBox(final byte[] bbox,
- int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
+ * static utility method to compare a bbox with a range of triangles (just the bbox of the
+ * triangle collection) for intersection
+ */
+ private static Relation intersectBBoxWithRangeBBox(
+ final byte[] bbox,
+ int minXOffset,
+ int minYOffset,
+ byte[] minTriangle,
+ int maxXOffset,
+ int maxYOffset,
+ byte[] maxTriangle) {
// check bounding box (DISJOINT)
- if (disjoint(bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
+ if (disjoint(
+ bbox, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle)) {
return Relation.CELL_OUTSIDE_QUERY;
}
- if (FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
- FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0) {
- if (FutureArrays.compareUnsigned(maxTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
- FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
+ if (FutureArrays.compareUnsigned(
+ minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
+ >= 0
+ && FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
+ >= 0) {
+ if (FutureArrays.compareUnsigned(
+ maxTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
+ <= 0
+ && FutureArrays.compareUnsigned(
+ maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
+ <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
- if (FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
- FutureArrays.compareUnsigned(maxTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
+ if (FutureArrays.compareUnsigned(
+ maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
+ <= 0
+ && FutureArrays.compareUnsigned(
+ maxTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
+ <= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
- if (FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 &&
- FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) {
- if (FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
- FutureArrays.compareUnsigned(minTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) >= 0) {
+ if (FutureArrays.compareUnsigned(
+ maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
+ <= 0
+ && FutureArrays.compareUnsigned(
+ maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
+ <= 0) {
+ if (FutureArrays.compareUnsigned(
+ minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES)
+ >= 0
+ && FutureArrays.compareUnsigned(minTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES)
+ >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
- if (FutureArrays.compareUnsigned(minTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 &&
- FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0) {
+ if (FutureArrays.compareUnsigned(
+ minTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES)
+ >= 0
+ && FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES)
+ >= 0) {
return Relation.CELL_INSIDE_QUERY;
}
}
@@ -300,21 +407,29 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return Relation.CELL_CROSSES_QUERY;
}
- /**
- * static utility method to check a bbox is disjoint with a range of triangles
- **/
- private static boolean disjoint(final byte[] bbox,
- int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
- return FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) > 0 ||
- FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) < 0 ||
- FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) > 0 ||
- FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) < 0;
+ /** static utility method to check a bbox is disjoint with a range of triangles */
+ private static boolean disjoint(
+ final byte[] bbox,
+ int minXOffset,
+ int minYOffset,
+ byte[] minTriangle,
+ int maxXOffset,
+ int maxYOffset,
+ byte[] maxTriangle) {
+ return FutureArrays.compareUnsigned(
+ minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES)
+ > 0
+ || FutureArrays.compareUnsigned(
+ maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES)
+ < 0
+ || FutureArrays.compareUnsigned(
+ minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES)
+ > 0
+ || FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES)
+ < 0;
}
- /**
- * Checks if the rectangle contains the provided point
- **/
+ /** Checks if the rectangle contains the provided point */
boolean contains(int x, int y) {
if (y < minY || y > maxY) {
return false;
@@ -326,9 +441,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
}
- /**
- * Checks if the rectangle intersects the provided LINE
- **/
+ /** Checks if the rectangle intersects the provided LINE */
boolean intersectsLine(int aX, int aY, int bX, int bY) {
if (contains(aX, aY) || contains(bX, bY)) {
return true;
@@ -350,9 +463,7 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return edgeIntersectsQuery(aX, aY, bX, bY);
}
- /**
- * Checks if the rectangle intersects the provided triangle
- **/
+ /** Checks if the rectangle intersects the provided triangle */
boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
// query contains any triangle points
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
@@ -377,49 +488,38 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
}
// expensive part
- return Component2D.pointInTriangle(tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY) ||
- edgeIntersectsQuery(aX, aY, bX, bY) ||
- edgeIntersectsQuery(bX, bY, cX, cY) ||
- edgeIntersectsQuery(cX, cY, aX, aY);
+ return Component2D.pointInTriangle(
+ tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)
+ || edgeIntersectsQuery(aX, aY, bX, bY)
+ || edgeIntersectsQuery(bX, bY, cX, cY)
+ || edgeIntersectsQuery(cX, cY, aX, aY);
}
- /**
- * Checks if the rectangle contains the provided LINE
- **/
+ /** Checks if the rectangle contains the provided LINE */
boolean containsLine(int aX, int aY, int bX, int bY) {
- if (aY < minY || bY < minY ||
- aY > maxY || bY > maxY ) {
+ if (aY < minY || bY < minY || aY > maxY || bY > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
- return (aX >= minX && bX >= minX) ||
- (aX <= maxX && bX <= maxX);
+ return (aX >= minX && bX >= minX) || (aX <= maxX && bX <= maxX);
} else {
- return aX >= minX && bX >= minX &&
- aX <= maxX && bX <= maxX;
+ return aX >= minX && bX >= minX && aX <= maxX && bX <= maxX;
}
}
- /**
- * Checks if the rectangle contains the provided triangle
- **/
+ /** Checks if the rectangle contains the provided triangle */
boolean containsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
- if (aY < minY || bY < minY || cY < minY ||
- aY > maxY || bY > maxY || cY > maxY) {
+ if (aY < minY || bY < minY || cY < minY || aY > maxY || bY > maxY || cY > maxY) {
return false;
}
if (crossesDateline) { // crosses dateline
- return (aX >= minX && bX >= minX && cX >= minX) ||
- (aX <= maxX && bX <= maxX && cX <= maxX);
+ return (aX >= minX && bX >= minX && cX >= minX) || (aX <= maxX && bX <= maxX && cX <= maxX);
} else {
- return aX >= minX && bX >= minX && cX >= minX &&
- aX <= maxX && bX <= maxX && cX <= maxX;
+ return aX >= minX && bX >= minX && cX >= minX && aX <= maxX && bX <= maxX && cX <= maxX;
}
}
- /**
- * Returns the Within relation to the provided triangle
- */
+ /** Returns the Within relation to the provided triangle */
Component2D.WithinRelation withinLine(int ax, int ay, boolean ab, int bx, int by) {
if (ab == true && edgeIntersectsBox(ax, ay, bx, by, minX, maxX, minY, maxY) == true) {
return Component2D.WithinRelation.NOTWITHIN;
@@ -427,10 +527,9 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return Component2D.WithinRelation.DISJOINT;
}
- /**
- * Returns the Within relation to the provided triangle
- */
- Component2D.WithinRelation withinTriangle(int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) {
+ /** Returns the Within relation to the provided triangle */
+ Component2D.WithinRelation withinTriangle(
+ int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) {
// Points belong to the shape so if points are inside the rectangle then it cannot be within.
if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) {
return Component2D.WithinRelation.NOTWITHIN;
@@ -479,16 +578,15 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
}
}
// Check if shape is within the triangle
- if (relation == Component2D.WithinRelation.CANDIDATE ||
- Component2D.pointInTriangle(tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)) {
+ if (relation == Component2D.WithinRelation.CANDIDATE
+ || Component2D.pointInTriangle(
+ tMinX, tMaxX, tMinY, tMaxY, minX, minY, aX, aY, bX, bY, cX, cY)) {
return Component2D.WithinRelation.CANDIDATE;
}
return relation;
}
- /**
- * returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the query
- */
+ /** returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the query */
private boolean edgeIntersectsQuery(int aX, int aY, int bX, int bY) {
if (crossesDateline) {
return edgeIntersectsBox(aX, aY, bX, bY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY)
@@ -497,18 +595,22 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
return edgeIntersectsBox(aX, aY, bX, bY, this.minX, this.maxX, this.minY, this.maxY);
}
- /**
- * returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the box
- */
- private static boolean edgeIntersectsBox(int aX, int aY, int bX, int bY,
- int minX, int maxX, int minY, int maxY) {
- if (Math.max(aX, bX) < minX || Math.min(aX, bX) > maxX || Math.min(aY, bY) > maxY || Math.max(aY, bY) < minY) {
+ /** returns true if the edge (defined by (aX, aY) (bX, bY)) intersects the box */
+ private static boolean edgeIntersectsBox(
+ int aX, int aY, int bX, int bY, int minX, int maxX, int minY, int maxY) {
+ if (Math.max(aX, bX) < minX
+ || Math.min(aX, bX) > maxX
+ || Math.min(aY, bY) > maxY
+ || Math.max(aY, bY) < minY) {
return false;
}
- return GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, maxY, maxX, maxY) || // top
- GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, maxY, maxX, minY) || // bottom
- GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, minY, minX, minY) || // left
- GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, minY, minX, maxY); // right
+ return GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, maxY, maxX, maxY)
+ || // top
+ GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, maxY, maxX, minY)
+ || // bottom
+ GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, maxX, minY, minX, minY)
+ || // left
+ GeoUtils.lineCrossesLineWithBoundary(aX, aY, bX, bY, minX, minY, minX, maxY); // right
}
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java
index 400a6f2..beed2a6 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java
@@ -17,7 +17,8 @@
package org.apache.lucene.document;
import java.util.Arrays;
-
+import java.util.function.Function;
+import java.util.function.Predicate;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
@@ -27,15 +28,14 @@ import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
/**
- * Finds all previously indexed cartesian shapes that comply the given {@link QueryRelation} with
- * the specified array of {@link LatLonGeometry}.
+ * Finds all previously indexed geo shapes that comply the given {@link QueryRelation} with the
+ * specified array of {@link LatLonGeometry}.
*
* <p>The field must be indexed using {@link LatLonShape#createIndexableFields} added per document.
- *
- **/
-final class LatLonShapeQuery extends ShapeQuery {
- final private LatLonGeometry[] geometries;
- final private Component2D component2D;
+ */
+final class LatLonShapeQuery extends SpatialQuery {
+ private final LatLonGeometry[] geometries;
+ private final Component2D component2D;
/**
* Creates a query that matches all indexed shapes to the provided array of {@link LatLonGeometry}
@@ -46,116 +46,162 @@ final class LatLonShapeQuery extends ShapeQuery {
for (LatLonGeometry geometry : geometries) {
if (geometry instanceof Line) {
// TODO: line queries do not support within relations
- throw new IllegalArgumentException("LatLonShapeQuery does not support " + QueryRelation.WITHIN + " queries with line geometries");
+ throw new IllegalArgumentException(
+ "LatLonShapeQuery does not support "
+ + QueryRelation.WITHIN
+ + " queries with line geometries");
}
}
-
}
this.component2D = LatLonGeometry.create(geometries);
this.geometries = geometries.clone();
}
@Override
- protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
-
- double minLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
- double minLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
- double maxLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
- double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
-
- // check internal node against query
- return component2D.relate(minLon, maxLon, minLat, maxLat);
- }
-
- @Override
- protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- return component2D.contains(alon, alat);
+ protected SpatialVisitor getSpatialVisitor() {
+
+ return new SpatialVisitor() {
+ @Override
+ protected Relation relate(byte[] minTriangle, byte[] maxTriangle) {
+ double minLat =
+ GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(minTriangle, 0));
+ double minLon =
+ GeoEncodingUtils.decodeLongitude(
+ NumericUtils.sortableBytesToInt(minTriangle, ShapeField.BYTES));
+ double maxLat =
+ GeoEncodingUtils.decodeLatitude(
+ NumericUtils.sortableBytesToInt(maxTriangle, 2 * ShapeField.BYTES));
+ double maxLon =
+ GeoEncodingUtils.decodeLongitude(
+ NumericUtils.sortableBytesToInt(maxTriangle, 3 * ShapeField.BYTES));
+
+ // check internal node against query
+ return component2D.relate(minLon, maxLon, minLat, maxLat);
}
- case LINE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- return component2D.intersectsLine(alon, alat, blon, blat);
- }
- case TRIANGLE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
- double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
- return component2D.intersectsTriangle(alon, alat, blon, blat, clon, clat);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
- }
-
- @Override
- protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
- switch (scratchTriangle.type) {
- case POINT: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- return component2D.contains(alon, alat);
+ @Override
+ protected Predicate<byte[]> intersects() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ return component2D.contains(alon, alat);
+ }
+ case LINE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ return component2D.intersectsLine(alon, alat, blon, blat);
+ }
+ case TRIANGLE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
+ double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
+ return component2D.intersectsTriangle(alon, alat, blon, blat, clon, clat);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
}
- case LINE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- return component2D.containsLine(alon, alat, blon, blat);
- }
- case TRIANGLE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
- double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
- return component2D.containsTriangle(alon, alat, blon, blat, clon, clat);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
- }
-
- @Override
- protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
- switch (scratchTriangle.type) {
- case POINT: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- return component2D.withinPoint(alon, alat);
+ @Override
+ protected Predicate<byte[]> within() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ return component2D.contains(alon, alat);
+ }
+ case LINE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ return component2D.containsLine(alon, alat, blon, blat);
+ }
+ case TRIANGLE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
+ double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
+ return component2D.containsTriangle(alon, alat, blon, blat, clon, clat);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
}
- case LINE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- return component2D.withinLine(alon, alat, scratchTriangle.ab, blon, blat);
- }
- case TRIANGLE: {
- double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
- double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
- double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
- double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
- double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
- double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
- return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca);
+
+ @Override
+ protected Function<byte[], Component2D.WithinRelation> contains() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ return component2D.withinPoint(alon, alat);
+ }
+ case LINE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ return component2D.withinLine(alon, alat, scratchTriangle.ab, blon, blat);
+ }
+ case TRIANGLE:
+ {
+ double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle.aY);
+ double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle.aX);
+ double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle.bY);
+ double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle.bX);
+ double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
+ double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
+ return component2D.withinTriangle(
+ alon,
+ alat,
+ scratchTriangle.ab,
+ blon,
+ blat,
+ scratchTriangle.bc,
+ clon,
+ clat,
+ scratchTriangle.ca);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
}
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
+ };
}
@Override
@@ -179,7 +225,7 @@ final class LatLonShapeQuery extends ShapeQuery {
@Override
protected boolean equalsTo(Object o) {
- return super.equalsTo(o) && Arrays.equals(geometries, ((LatLonShapeQuery)o).geometries);
+ return super.equalsTo(o) && Arrays.equals(geometries, ((LatLonShapeQuery) o).geometries);
}
@Override
@@ -188,4 +234,4 @@ final class LatLonShapeQuery extends ShapeQuery {
hash = 31 * hash + Arrays.hashCode(geometries);
return hash;
}
-}
\ No newline at end of file
+}
diff --git a/lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java b/lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java
deleted file mode 100644
index e8ea282..0000000
--- a/lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java
+++ /dev/null
@@ -1,621 +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.Objects;
-
-import org.apache.lucene.document.ShapeField.QueryRelation;
-import org.apache.lucene.geo.Component2D;
-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.CollectionTerminatedException;
-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.QueryVisitor;
-import org.apache.lucene.search.ScoreMode;
-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;
-
-/**
- * Base query class for all spatial geometries: {@link LatLonShape} and {@link XYShape}.
- *
- * <p>The field must be indexed using either {@link LatLonShape#createIndexableFields} or
- * {@link XYShape#createIndexableFields} and the corresponding factory method must be used:
- * <ul>
- * <li>{@link LatLonShape#newBoxQuery newBoxQuery()} for matching geo shapes that have some {@link QueryRelation} with a bounding box.
- * <li>{@link LatLonShape#newLineQuery newLineQuery()} for matching geo shapes that have some {@link QueryRelation} with a linestring.
- * <li>{@link LatLonShape#newPolygonQuery newPolygonQuery()} for matching geo shapes that have some {@link QueryRelation} with a polygon.
- * <li>{@link XYShape#newBoxQuery newBoxQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a bounding box.
- * <li>{@link XYShape#newLineQuery newLineQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a linestring.
- * <li>{@link XYShape#newPolygonQuery newPolygonQuery()} for matching cartesian shapes that have some {@link QueryRelation} with a polygon.
- * </ul>
- **/
-abstract class ShapeQuery extends Query {
- /** field name */
- final String field;
- /** query relation
- * disjoint: {@link QueryRelation#DISJOINT},
- * intersects: {@link QueryRelation#INTERSECTS},
- * within: {@link QueryRelation#DISJOINT},
- * contains: {@link QueryRelation#CONTAINS}
- * */
- final QueryRelation queryRelation;
-
- protected ShapeQuery(String field, final QueryRelation queryType) {
- if (field == null) {
- throw new IllegalArgumentException("field must not be null");
- }
- this.field = field;
- this.queryRelation = queryType;
- }
-
- /**
- * relates an internal node (bounding box of a range of triangles) to the target query
- * Note: logic is specific to query type
- * see {@link LatLonShapeBoundingBoxQuery#relateRangeToQuery} and {@link LatLonShapeQuery#relateRangeToQuery}
- */
- protected abstract Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle);
-
- /** returns true if the provided triangle matches the query */
- protected boolean queryMatches(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation) {
- switch (queryRelation) {
- case INTERSECTS: return queryIntersects(triangle, scratchTriangle);
- case WITHIN: return queryContains(triangle, scratchTriangle);
- case DISJOINT: return queryIntersects(triangle, scratchTriangle) == false;
- default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
- }
- }
-
- /** returns true if the provided triangle intersects the query */
- protected abstract boolean queryIntersects(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
-
- /** returns true if the provided triangle is within the query */
- protected abstract boolean queryContains(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
-
- /** Return the within relationship between the query and the indexed shape.*/
- protected abstract Component2D.WithinRelation queryWithin(byte[] triangle, ShapeField.DecodedTriangle scratchTriangle);
-
- /** relates a range of triangles (internal node) to the query */
- protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) {
- // compute bounding box of internal node
- final Relation r = relateRangeBBoxToQuery(ShapeField.BYTES, 0, minTriangle, 3 * ShapeField.BYTES, 2 * ShapeField.BYTES, maxTriangle);
- if (queryRelation == QueryRelation.DISJOINT) {
- return transposeRelation(r);
- }
- return r;
- }
-
- @Override
- public void visit(QueryVisitor visitor) {
- if (visitor.acceptField(field)) {
- visitor.visitLeaf(this);
- }
- }
-
- @Override
- public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) {
- final ShapeQuery query = this;
- return new ConstantScoreWeight(query, boost) {
-
- @Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
- final ScorerSupplier scorerSupplier = scorerSupplier(context);
- if (scorerSupplier == null) {
- return null;
- }
- return scorerSupplier.get(Long.MAX_VALUE);
- }
-
- @Override
- public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
- final LeafReader reader = context.reader();
- final PointValues values = reader.getPointValues(field);
- if (values == null) {
- // No docs in this segment had any points fields
- return null;
- }
- final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
- if (fieldInfo == null) {
- // No docs in this segment indexed this field at all
- return null;
- }
-
- final Weight weight = this;
- final Relation rel = relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue(), queryRelation);
- if (rel == Relation.CELL_OUTSIDE_QUERY || (rel == Relation.CELL_INSIDE_QUERY && queryRelation == QueryRelation.CONTAINS)) {
- // no documents match the query
- return null;
- } else if (values.getDocCount() == reader.maxDoc() && rel == Relation.CELL_INSIDE_QUERY) {
- // all documents match the query
- return new ScorerSupplier() {
- @Override
- public Scorer get(long leadCost) {
- return new ConstantScoreScorer(weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc()));
- }
-
- @Override
- public long cost() {
- return reader.maxDoc();
- }
- };
- } else {
- if (queryRelation != QueryRelation.INTERSECTS
- && queryRelation != QueryRelation.CONTAINS
- && hasAnyHits(query, values) == false) {
- // First we check if we have any hits so we are fast in the adversarial case where
- // the shape does not match any documents and we are in the dense case
- return null;
- }
- // walk the tree to get matching documents
- return new RelationScorerSupplier(values, ShapeQuery.this) {
- @Override
- public Scorer get(long leadCost) throws IOException {
- return getScorer(reader, weight, score(), scoreMode);
- }
- };
- }
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return true;
- }
- };
- }
-
- /** returns the field name */
- public String getField() {
- return field;
- }
-
- /** returns the query relation */
- public QueryRelation getQueryRelation() {
- return queryRelation;
- }
-
- @Override
- public int hashCode() {
- int hash = classHash();
- hash = 31 * hash + field.hashCode();
- hash = 31 * hash + queryRelation.hashCode();
- return hash;
- }
-
- @Override
- public boolean equals(Object o) {
- return sameClassAs(o) && equalsTo(o);
- }
-
- /** class specific equals check */
- protected boolean equalsTo(Object o) {
- return Objects.equals(field, ((ShapeQuery)o).field) && this.queryRelation == ((ShapeQuery)o).queryRelation;
- }
-
- /** transpose the relation; INSIDE becomes OUTSIDE, OUTSIDE becomes INSIDE, CROSSES remains unchanged */
- private static Relation transposeRelation(Relation r) {
- if (r == Relation.CELL_INSIDE_QUERY) {
- return Relation.CELL_OUTSIDE_QUERY;
- } else if (r == Relation.CELL_OUTSIDE_QUERY) {
- return Relation.CELL_INSIDE_QUERY;
- }
- return Relation.CELL_CROSSES_QUERY;
- }
-
- /** utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT */
- private static abstract class RelationScorerSupplier extends ScorerSupplier {
- final private PointValues values;
- final private ShapeQuery query;
- private long cost = -1;
-
- RelationScorerSupplier(final PointValues values, final ShapeQuery query) {
- this.values = values;
- this.query = query;
- }
-
- protected Scorer getScorer(final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode) throws IOException {
- switch (query.getQueryRelation()) {
- case INTERSECTS: return getSparseScorer(reader, weight, boost, scoreMode);
- case WITHIN:
- case DISJOINT: return getDenseScorer(reader, weight, boost, scoreMode);
- case CONTAINS: return getContainsDenseScorer(reader, weight, boost, scoreMode);
- default: throw new IllegalArgumentException("Unsupported query type :[" + query.getQueryRelation() + "]");
- }
- }
-
- /** Scorer used for INTERSECTS **/
- private Scorer getSparseScorer(final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode) 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 query
- final FixedBitSet result = new FixedBitSet(reader.maxDoc());
- result.set(0, reader.maxDoc());
- final long[] cost = new long[]{reader.maxDoc()};
- values.intersect(getInverseDenseVisitor(query, result, cost));
- final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
- return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- }
- if (values.getDocCount() < (values.size() >>> 2)) {
- // we use a dense structure so we can skip already visited documents
- final FixedBitSet result = new FixedBitSet(reader.maxDoc());
- final long[] cost = new long[]{0};
- values.intersect(getIntersectsDenseVisitor(query, result, cost));
- assert cost[0] > 0 || result.cardinality() == 0;
- final DocIdSetIterator iterator = cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
- return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- } else {
- final DocIdSetBuilder docIdSetBuilder = new DocIdSetBuilder(reader.maxDoc(), values, query.getField());
- values.intersect(getSparseVisitor(query, docIdSetBuilder));
- final DocIdSetIterator iterator = docIdSetBuilder.build().iterator();
- return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- }
- }
-
- /** Scorer used for WITHIN and DISJOINT **/
- private Scorer getDenseScorer(LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode) throws IOException {
- final FixedBitSet result = new FixedBitSet(reader.maxDoc());
- final long[] cost;
- if (values.getDocCount() == reader.maxDoc()) {
- cost = new long[]{values.size()};
- // In this case we can spare one visit to the tree, all documents
- // are potential matches
- result.set(0, reader.maxDoc());
- // Remove false positives
- values.intersect(getInverseDenseVisitor(query, result, cost));
- } else {
- cost = new long[]{0};
- // Get potential documents.
- final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
- values.intersect(getDenseVisitor(query, result, excluded, cost));
- result.andNot(excluded);
- // Remove false positives, we only care about the inner nodes as intersecting
- // leaf nodes have been already taken into account. Unfortunately this
- // process still reads the leaf nodes.
- values.intersect(getShallowInverseDenseVisitor(query, result));
- }
- assert cost[0] > 0 || result.cardinality() == 0;
- final DocIdSetIterator iterator = cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
- return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- }
-
- private Scorer getContainsDenseScorer(LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode) throws IOException {
- final FixedBitSet result = new FixedBitSet(reader.maxDoc());
- final long[] cost = new long[]{0};
- // Get potential documents.
- final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
- values.intersect(getContainsDenseVisitor(query, result, excluded, cost));
- result.andNot(excluded);
- assert cost[0] > 0 || result.cardinality() == 0;
- final DocIdSetIterator iterator = cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
- return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- }
-
- @Override
- public long cost() {
- if (cost == -1) {
- // Computing the cost may be expensive, so only do it if necessary
- cost = values.estimateDocCount(getEstimateVisitor(query));
- assert cost >= 0;
- }
- return cost;
- }
- }
-
- /** create a visitor for calculating point count estimates for the provided relation */
- private static IntersectVisitor getEstimateVisitor(final ShapeQuery query) {
- return new IntersectVisitor() {
- @Override
- public void visit(int docID) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void visit(int docID, byte[] t) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
- }
- };
- }
-
- /** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT
- * when the number of docs <= 4 * number of points ) */
- private static IntersectVisitor getSparseVisitor(final ShapeQuery query, final DocIdSetBuilder result) {
- return new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
- 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[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- visit(docID);
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- visit(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
- }
- };
- }
-
- /** Scorer used for INTERSECTS when the number of points > 4 * number of docs **/
- private static IntersectVisitor getIntersectsDenseVisitor(final ShapeQuery query, final FixedBitSet result, final long[] cost) {
- return new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
- @Override
- public void visit(int docID) {
- result.set(docID);
- cost[0]++;
- }
-
- @Override
- public void visit(int docID, byte[] t) {
- if (result.get(docID) == false) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- visit(docID);
- }
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- visit(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
- }
- };
- }
-
- /** create a visitor that adds documents that match the query using a dense bitset; used with WITHIN & DISJOINT */
- private static IntersectVisitor getDenseVisitor(final ShapeQuery query, final FixedBitSet result, final FixedBitSet excluded, final long[] cost) {
- return new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
- @Override
- public void visit(int docID) {
- result.set(docID);
- cost[0]++;
- }
-
- @Override
- public void visit(int docID, byte[] t) {
- if (excluded.get(docID) == false) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- visit(docID);
- } else {
- excluded.set(docID);
- }
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- boolean matches = query.queryMatches(t, scratchTriangle, query.getQueryRelation());
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- if (matches) {
- visit(docID);
- } else {
- excluded.set(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
- }
- };
- }
-
- /** create a visitor that adds documents that match the query using a dense bitset; used with CONTAINS */
- private static IntersectVisitor getContainsDenseVisitor(final ShapeQuery query, final FixedBitSet result, final FixedBitSet excluded, final long[] cost) {
- return new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
- @Override
- public void visit(int docID) {
- excluded.set(docID);
- }
-
- @Override
- public void visit(int docID, byte[] t) {
- if (excluded.get(docID) == false) {
- Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
- if (within == Component2D.WithinRelation.CANDIDATE) {
- cost[0]++;
- result.set(docID);
- } else if (within == Component2D.WithinRelation.NOTWITHIN) {
- excluded.set(docID);
- }
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- if (within == Component2D.WithinRelation.CANDIDATE) {
- cost[0]++;
- result.set(docID);
- } else if (within == Component2D.WithinRelation.NOTWITHIN) {
- excluded.set(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
- }
- };
- }
-
- /** create a visitor that clears documents that do not match the polygon query using a dense bitset; used with WITHIN & DISJOINT */
- private static IntersectVisitor getInverseDenseVisitor(final ShapeQuery query, final FixedBitSet result, final long[] cost) {
- return new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
- @Override
- public void visit(int docID) {
- result.clear(docID);
- cost[0]--;
- }
-
- @Override
- public void visit(int docID, byte[] packedTriangle) {
- if (result.get(docID)) {
- if (query.queryMatches(packedTriangle, scratchTriangle, query.getQueryRelation()) == false) {
- visit(docID);
- }
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation()) == false) {
- int docID;
- while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
- visit(docID);
- }
- }
- }
-
- @Override
- public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
- }
- };
- }
-
- /** create a visitor that clears documents that do not match the polygon query using a dense bitset; used with WITHIN & DISJOINT.
- * This visitor only takes into account inner nodes */
- private static IntersectVisitor getShallowInverseDenseVisitor(final ShapeQuery query, final FixedBitSet result) {
- return new IntersectVisitor() {
-
- @Override
- public void visit(int docID) {
- result.clear(docID);
- }
-
- @Override
- public void visit(int docID, byte[] packedTriangle) {
- //NO-OP
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) {
- //NO-OP
- }
-
- @Override
- public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
- }
- };
- }
-
- /** Return true if the query matches at least one document. It creates a visitor that terminates as soon as one or more docs
- * are matched. */
- private static boolean hasAnyHits(final ShapeQuery query, final PointValues values) throws IOException {
- try {
- values.intersect(new IntersectVisitor() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
- @Override
- public void visit(int docID) {
- throw new CollectionTerminatedException();
- }
-
- @Override
- public void visit(int docID, byte[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- throw new CollectionTerminatedException();
- }
- }
-
- @Override
- public void visit(DocIdSetIterator iterator, byte[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
- throw new CollectionTerminatedException();
- }
- }
-
- @Override
- public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- Relation rel = query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation());
- if (rel == Relation.CELL_INSIDE_QUERY) {
- throw new CollectionTerminatedException();
- }
- return rel;
- }
- });
- } catch (CollectionTerminatedException e) {
- return true;
- }
- return false;
- }
-}
diff --git a/lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java b/lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java
new file mode 100644
index 0000000..f970862
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java
@@ -0,0 +1,722 @@
+/*
+ * 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.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+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.CollectionTerminatedException;
+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.QueryVisitor;
+import org.apache.lucene.search.ScoreMode;
+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;
+
+/**
+ * Base query class for all spatial geometries: {@link LatLonShape}, {@link LatLonPoint} and {@link
+ * XYShape}. In order to create a query, use the factory methods on those classes.
+ */
+abstract class SpatialQuery extends Query {
+ /** field name */
+ final String field;
+ /**
+ * query relation disjoint: {@link QueryRelation#DISJOINT}, intersects: {@link
+ * QueryRelation#INTERSECTS}, within: {@link QueryRelation#DISJOINT}, contains: {@link
+ * QueryRelation#CONTAINS}
+ */
+ final QueryRelation queryRelation;
+
+ protected SpatialQuery(String field, final QueryRelation queryRelation) {
+ if (field == null) {
+ throw new IllegalArgumentException("field must not be null");
+ }
+ if (queryRelation == null) {
+ throw new IllegalArgumentException("queryRelation must not be null");
+ }
+ this.field = field;
+ this.queryRelation = queryRelation;
+ }
+
+ /**
+ * returns the spatial visitor to be used for this query. Called before generating the query
+ * {@link Weight}
+ */
+ protected abstract SpatialVisitor getSpatialVisitor();
+
+ /** Visitor used for walking the BKD tree. */
+ protected abstract static class SpatialVisitor {
+ /** relates a range of points (internal node) to the query */
+ protected abstract Relation relate(byte[] minPackedValue, byte[] maxPackedValue);
+
+ /** Gets a intersects predicate. Called when constructing a {@link Scorer} */
+ protected abstract Predicate<byte[]> intersects();
+
+ /** Gets a within predicate. Called when constructing a {@link Scorer} */
+ protected abstract Predicate<byte[]> within();
+
+ /** Gets a contains function. Called when constructing a {@link Scorer} */
+ protected abstract Function<byte[], Component2D.WithinRelation> contains();
+
+ private Predicate<byte[]> containsPredicate() {
+ final Function<byte[], Component2D.WithinRelation> contains = contains();
+ return bytes -> contains.apply(bytes) == Component2D.WithinRelation.CANDIDATE;
+ }
+
+ private BiFunction<byte[], byte[], Relation> getInnerFunction(
+ ShapeField.QueryRelation queryRelation) {
+ if (queryRelation == QueryRelation.DISJOINT) {
+ return (minPackedValue, maxPackedValue) ->
+ transposeRelation(relate(minPackedValue, maxPackedValue));
+ }
+ return (minPackedValue, maxPackedValue) -> relate(minPackedValue, maxPackedValue);
+ }
+
+ private Predicate<byte[]> getLeafPredicate(ShapeField.QueryRelation queryRelation) {
+ switch (queryRelation) {
+ case INTERSECTS:
+ return intersects();
+ case WITHIN:
+ return within();
+ case DISJOINT:
+ return intersects().negate();
+ case CONTAINS:
+ return containsPredicate();
+ default:
+ throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
+ }
+ }
+ }
+
+ @Override
+ public void visit(QueryVisitor visitor) {
+ if (visitor.acceptField(field)) {
+ visitor.visitLeaf(this);
+ }
+ }
+
+ @Override
+ public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) {
+ final SpatialQuery query = this;
+ final SpatialVisitor spatialVisitor = getSpatialVisitor();
+ return new ConstantScoreWeight(query, boost) {
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ final ScorerSupplier scorerSupplier = scorerSupplier(context);
+ if (scorerSupplier == null) {
+ return null;
+ }
+ return scorerSupplier.get(Long.MAX_VALUE);
+ }
+
+ @Override
+ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+ final LeafReader reader = context.reader();
+ final PointValues values = reader.getPointValues(field);
+ if (values == null) {
+ // No docs in this segment had any points fields
+ return null;
+ }
+ final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
+ if (fieldInfo == null) {
+ // No docs in this segment indexed this field at all
+ return null;
+ }
+ final Weight weight = this;
+ final Relation rel =
+ spatialVisitor
+ .getInnerFunction(queryRelation)
+ .apply(values.getMinPackedValue(), values.getMaxPackedValue());
+ if (rel == Relation.CELL_OUTSIDE_QUERY
+ || (rel == Relation.CELL_INSIDE_QUERY && queryRelation == QueryRelation.CONTAINS)) {
+ // no documents match the query
+ return null;
+ } else if (values.getDocCount() == reader.maxDoc() && rel == Relation.CELL_INSIDE_QUERY) {
+ // all documents match the query
+ return new ScorerSupplier() {
+ @Override
+ public Scorer get(long leadCost) {
+ return new ConstantScoreScorer(
+ weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc()));
+ }
+
+ @Override
+ public long cost() {
+ return reader.maxDoc();
+ }
+ };
+ } else {
+ if (queryRelation != QueryRelation.INTERSECTS
+ && queryRelation != QueryRelation.CONTAINS
+ && values.getDocCount() != values.size()
+ && hasAnyHits(spatialVisitor, queryRelation, values) == false) {
+ // First we check if we have any hits so we are fast in the adversarial case where
+ // the shape does not match any documents and we are in the dense case
+ return null;
+ }
+ // walk the tree to get matching documents
+ return new RelationScorerSupplier(values, spatialVisitor, queryRelation, field) {
+ @Override
+ public Scorer get(long leadCost) throws IOException {
+ return getScorer(reader, weight, score(), scoreMode);
+ }
+ };
+ }
+ }
+
+ @Override
+ public boolean isCacheable(LeafReaderContext ctx) {
+ return true;
+ }
+ };
+ }
+
+ /** returns the field name */
+ public String getField() {
+ return field;
+ }
+
+ /** returns the query relation */
+ public QueryRelation getQueryRelation() {
+ return queryRelation;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = classHash();
+ hash = 31 * hash + field.hashCode();
+ hash = 31 * hash + queryRelation.hashCode();
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return sameClassAs(o) && equalsTo(o);
+ }
+
+ /** class specific equals check */
+ protected boolean equalsTo(Object o) {
+ return Objects.equals(field, ((SpatialQuery) o).field)
+ && this.queryRelation == ((SpatialQuery) o).queryRelation;
+ }
+
+ /**
+ * transpose the relation; INSIDE becomes OUTSIDE, OUTSIDE becomes INSIDE, CROSSES remains
+ * unchanged
+ */
+ protected static Relation transposeRelation(Relation r) {
+ if (r == Relation.CELL_INSIDE_QUERY) {
+ return Relation.CELL_OUTSIDE_QUERY;
+ } else if (r == Relation.CELL_OUTSIDE_QUERY) {
+ return Relation.CELL_INSIDE_QUERY;
+ }
+ return Relation.CELL_CROSSES_QUERY;
+ }
+
+ /**
+ * utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT
+ */
+ private abstract static class RelationScorerSupplier extends ScorerSupplier {
+ private final PointValues values;
+ private final SpatialVisitor spatialVisitor;
+ private final QueryRelation queryRelation;
+ private final String field;
+ private long cost = -1;
+
+ RelationScorerSupplier(
+ final PointValues values,
+ SpatialVisitor spatialVisitor,
+ final QueryRelation queryRelation,
+ final String field) {
+ this.values = values;
+ this.spatialVisitor = spatialVisitor;
+ this.queryRelation = queryRelation;
+ this.field = field;
+ }
+
+ protected Scorer getScorer(
+ final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode)
+ throws IOException {
+ switch (queryRelation) {
+ case INTERSECTS:
+ return getSparseScorer(reader, weight, boost, scoreMode);
+ case CONTAINS:
+ return getContainsDenseScorer(reader, weight, boost, scoreMode);
+ case WITHIN:
+ case DISJOINT:
+ return values.getDocCount() == values.size()
+ ? getSparseScorer(reader, weight, boost, scoreMode)
+ : getDenseScorer(reader, weight, boost, scoreMode);
+ default:
+ throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
+ }
+ }
+
+ /** Scorer used for INTERSECTS and single value points */
+ private Scorer getSparseScorer(
+ final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode)
+ throws IOException {
+ if (queryRelation == QueryRelation.DISJOINT
+ && 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 query
+ final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+ result.set(0, reader.maxDoc());
+ final long[] cost = new long[] {reader.maxDoc()};
+ values.intersect(getInverseDenseVisitor(spatialVisitor, queryRelation, result, cost));
+ final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
+ return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
+ } else if (values.getDocCount() < (values.size() >>> 2)) {
+ // we use a dense structure so we can skip already visited documents
+ final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+ final long[] cost = new long[] {0};
+ values.intersect(getIntersectsDenseVisitor(spatialVisitor, queryRelation, result, cost));
+ assert cost[0] > 0 || result.cardinality() == 0;
+ final DocIdSetIterator iterator =
+ cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
+ return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
+ } else {
+ final DocIdSetBuilder docIdSetBuilder = new DocIdSetBuilder(reader.maxDoc(), values, field);
+ values.intersect(getSparseVisitor(spatialVisitor, queryRelation, docIdSetBuilder));
+ final DocIdSetIterator iterator = docIdSetBuilder.build().iterator();
+ return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
+ }
+ }
+
+ /** Scorer used for WITHIN and DISJOINT */
+ private Scorer getDenseScorer(
+ LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode)
+ throws IOException {
+ final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+ final long[] cost;
+ if (values.getDocCount() == reader.maxDoc()) {
+ cost = new long[] {values.size()};
+ // In this case we can spare one visit to the tree, all documents
+ // are potential matches
+ result.set(0, reader.maxDoc());
+ // Remove false positives
+ values.intersect(getInverseDenseVisitor(spatialVisitor, queryRelation, result, cost));
+ } else {
+ cost = new long[] {0};
+ // Get potential documents.
+ final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
+ values.intersect(getDenseVisitor(spatialVisitor, queryRelation, result, excluded, cost));
+ result.andNot(excluded);
+ // Remove false positives, we only care about the inner nodes as intersecting
+ // leaf nodes have been already taken into account. Unfortunately this
+ // process still reads the leaf nodes.
+ values.intersect(getShallowInverseDenseVisitor(spatialVisitor, queryRelation, result));
+ }
+ assert cost[0] > 0 || result.cardinality() == 0;
+ final DocIdSetIterator iterator =
+ cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
+ return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
+ }
+
+ private Scorer getContainsDenseScorer(
+ LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode)
+ throws IOException {
+ final FixedBitSet result = new FixedBitSet(reader.maxDoc());
+ final long[] cost = new long[] {0};
+ // Get potential documents.
+ final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
+ values.intersect(
+ getContainsDenseVisitor(spatialVisitor, queryRelation, result, excluded, cost));
+ result.andNot(excluded);
+ assert cost[0] > 0 || result.cardinality() == 0;
+ final DocIdSetIterator iterator =
+ cost[0] == 0 ? DocIdSetIterator.empty() : new BitSetIterator(result, cost[0]);
+ return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
+ }
+
+ @Override
+ public long cost() {
+ if (cost == -1) {
+ // Computing the cost may be expensive, so only do it if necessary
+ cost = values.estimateDocCount(getEstimateVisitor(spatialVisitor, queryRelation));
+ assert cost >= 0;
+ }
+ return cost;
+ }
+ }
+
+ /** create a visitor for calculating point count estimates for the provided relation */
+ private static IntersectVisitor getEstimateVisitor(
+ final SpatialVisitor spatialVisitor, QueryRelation queryRelation) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ return new IntersectVisitor() {
+ @Override
+ public void visit(int docID) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void visit(int docID, byte[] t) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
+ return innerFunction.apply(minTriangle, maxTriangle);
+ }
+ };
+ }
+
+ /**
+ * create a visitor that adds documents that match the query using a sparse bitset. (Used by
+ * INTERSECT when the number of docs <= 4 * number of points )
+ */
+ private static IntersectVisitor getSparseVisitor(
+ final SpatialVisitor spatialVisitor,
+ QueryRelation queryRelation,
+ final DocIdSetBuilder result) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Predicate<byte[]> leafPredicate = spatialVisitor.getLeafPredicate(queryRelation);
+ 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[] t) {
+ if (leafPredicate.test(t)) {
+ visit(docID);
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
+ if (leafPredicate.test(t)) {
+ int docID;
+ while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+ visit(docID);
+ }
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
+ return innerFunction.apply(minTriangle, maxTriangle);
+ }
+ };
+ }
+
+ /** Scorer used for INTERSECTS when the number of points > 4 * number of docs */
+ private static IntersectVisitor getIntersectsDenseVisitor(
+ final SpatialVisitor spatialVisitor,
+ QueryRelation queryRelation,
+ final FixedBitSet result,
+ final long[] cost) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Predicate<byte[]> leafPredicate = spatialVisitor.getLeafPredicate(queryRelation);
+ return new IntersectVisitor() {
+
+ @Override
+ public void visit(int docID) {
+ result.set(docID);
+ cost[0]++;
+ }
+
+ @Override
+ public void visit(int docID, byte[] t) {
+ if (result.get(docID) == false) {
+ if (leafPredicate.test(t)) {
+ visit(docID);
+ }
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
+ if (leafPredicate.test(t)) {
+ int docID;
+ while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+ visit(docID);
+ }
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
+ return innerFunction.apply(minTriangle, maxTriangle);
+ }
+ };
+ }
+
+ /**
+ * create a visitor that adds documents that match the query using a dense bitset; used with
+ * WITHIN & DISJOINT
+ */
+ private static IntersectVisitor getDenseVisitor(
+ final SpatialVisitor spatialVisitor,
+ final QueryRelation queryRelation,
+ final FixedBitSet result,
+ final FixedBitSet excluded,
+ final long[] cost) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Predicate<byte[]> leafPredicate = spatialVisitor.getLeafPredicate(queryRelation);
+ return new IntersectVisitor() {
+ @Override
+ public void visit(int docID) {
+ result.set(docID);
+ cost[0]++;
+ }
+
+ @Override
+ public void visit(int docID, byte[] t) {
+ if (excluded.get(docID) == false) {
+ if (leafPredicate.test(t)) {
+ visit(docID);
+ } else {
+ excluded.set(docID);
+ }
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
+ boolean matches = leafPredicate.test(t);
+ int docID;
+ while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+ if (matches) {
+ visit(docID);
+ } else {
+ excluded.set(docID);
+ }
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
+ return innerFunction.apply(minTriangle, maxTriangle);
+ }
+ };
+ }
+
+ /**
+ * create a visitor that adds documents that match the query using a dense bitset; used with
+ * CONTAINS
+ */
+ private static IntersectVisitor getContainsDenseVisitor(
+ final SpatialVisitor spatialVisitor,
+ final QueryRelation queryRelation,
+ final FixedBitSet result,
+ final FixedBitSet excluded,
+ final long[] cost) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Function<byte[], Component2D.WithinRelation> leafFunction = spatialVisitor.contains();
+ return new IntersectVisitor() {
+ @Override
+ public void visit(int docID) {
+ excluded.set(docID);
+ }
+
+ @Override
+ public void visit(int docID, byte[] t) {
+ if (excluded.get(docID) == false) {
+ Component2D.WithinRelation within = leafFunction.apply(t);
+ if (within == Component2D.WithinRelation.CANDIDATE) {
+ cost[0]++;
+ result.set(docID);
+ } else if (within == Component2D.WithinRelation.NOTWITHIN) {
+ excluded.set(docID);
+ }
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
+ Component2D.WithinRelation within = leafFunction.apply(t);
+ int docID;
+ while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+ if (within == Component2D.WithinRelation.CANDIDATE) {
+ cost[0]++;
+ result.set(docID);
+ } else if (within == Component2D.WithinRelation.NOTWITHIN) {
+ excluded.set(docID);
+ }
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
+ return innerFunction.apply(minTriangle, maxTriangle);
+ }
+ };
+ }
+
+ /**
+ * create a visitor that clears documents that do not match the polygon query using a dense
+ * bitset; used with WITHIN & DISJOINT
+ */
+ private static IntersectVisitor getInverseDenseVisitor(
+ final SpatialVisitor spatialVisitor,
+ final QueryRelation queryRelation,
+ final FixedBitSet result,
+ final long[] cost) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Predicate<byte[]> leafPredicate = spatialVisitor.getLeafPredicate(queryRelation);
+ return new IntersectVisitor() {
+
+ @Override
+ public void visit(int docID) {
+ result.clear(docID);
+ cost[0]--;
+ }
+
+ @Override
+ public void visit(int docID, byte[] packedTriangle) {
+ if (result.get(docID)) {
+ if (leafPredicate.test(packedTriangle) == false) {
+ visit(docID);
+ }
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
+ if (leafPredicate.test(t) == false) {
+ int docID;
+ while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
+ visit(docID);
+ }
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+ return transposeRelation(innerFunction.apply(minPackedValue, maxPackedValue));
+ }
+ };
+ }
+
+ /**
+ * create a visitor that clears documents that do not match the polygon query using a dense
+ * bitset; used with WITHIN & DISJOINT. This visitor only takes into account inner nodes
+ */
+ private static IntersectVisitor getShallowInverseDenseVisitor(
+ final SpatialVisitor spatialVisitor, QueryRelation queryRelation, final FixedBitSet result) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ ;
+ return new IntersectVisitor() {
+
+ @Override
+ public void visit(int docID) {
+ result.clear(docID);
+ }
+
+ @Override
+ public void visit(int docID, byte[] packedTriangle) {
+ // NO-OP
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) {
+ // NO-OP
+ }
+
+ @Override
+ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+ return transposeRelation(innerFunction.apply(minPackedValue, maxPackedValue));
+ }
+ };
+ }
+
+ /**
+ * Return true if the query matches at least one document. It creates a visitor that terminates as
+ * soon as one or more docs are matched.
+ */
+ private static boolean hasAnyHits(
+ final SpatialVisitor spatialVisitor, QueryRelation queryRelation, final PointValues values)
+ throws IOException {
+ try {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ final Predicate<byte[]> leafPredicate = spatialVisitor.getLeafPredicate(queryRelation);
+ values.intersect(
+ new IntersectVisitor() {
+
+ @Override
+ public void visit(int docID) {
+ throw new CollectionTerminatedException();
+ }
+
+ @Override
+ public void visit(int docID, byte[] t) {
+ if (leafPredicate.test(t)) {
+ throw new CollectionTerminatedException();
+ }
+ }
+
+ @Override
+ public void visit(DocIdSetIterator iterator, byte[] t) {
+ if (leafPredicate.test(t)) {
+ throw new CollectionTerminatedException();
+ }
+ }
+
+ @Override
+ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+ Relation rel = innerFunction.apply(minPackedValue, maxPackedValue);
+ if (rel == Relation.CELL_INSIDE_QUERY) {
+ throw new CollectionTerminatedException();
+ }
+ return rel;
+ }
+ });
+ } catch (CollectionTerminatedException e) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java b/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java
index 14d5436..c577e3f 100644
--- a/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java
@@ -16,8 +16,11 @@
*/
package org.apache.lucene.document;
-import java.util.Arrays;
+import static org.apache.lucene.geo.XYEncodingUtils.decode;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.function.Predicate;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.XYEncodingUtils;
@@ -25,158 +28,196 @@ import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.NumericUtils;
-import static org.apache.lucene.geo.XYEncodingUtils.decode;
-
/**
* Finds all previously indexed cartesian shapes that comply the given {@link QueryRelation} with
* the specified array of {@link XYGeometry}.
*
* <p>The field must be indexed using {@link XYShape#createIndexableFields} added per document.
- **/
-final class XYShapeQuery extends ShapeQuery {
- final XYGeometry[] geometries;
- final private Component2D component2D;
-
- /**
- * Creates a query that matches all indexed shapes to the provided polygons
- */
- XYShapeQuery(String field, QueryRelation queryRelation, XYGeometry... geometries) {
- super(field, queryRelation);
- this.component2D = XYGeometry.create(geometries);
- this.geometries = geometries.clone();
- }
-
- @Override
- protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle,
- int maxXOffset, int maxYOffset, byte[] maxTriangle) {
-
- double minY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset));
- double minX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset));
- double maxY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset));
- double maxX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset));
-
- // check internal node against query
- return component2D.relate(minX, maxX, minY, maxY);
- }
-
- @Override
- protected boolean queryIntersects(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- double y = decode(scratchTriangle.aY);
- double x = decode(scratchTriangle.aX);
- return component2D.contains(x, y);
- }
- case LINE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- return component2D.intersectsLine(aX, aY, bX, bY);
- }
- case TRIANGLE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- double cY = decode(scratchTriangle.cY);
- double cX = decode(scratchTriangle.cX);
- return component2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
+ */
+final class XYShapeQuery extends SpatialQuery {
+ final XYGeometry[] geometries;
+ private final Component2D component2D;
+
+ /** Creates a query that matches all indexed shapes to the provided polygons */
+ XYShapeQuery(String field, QueryRelation queryRelation, XYGeometry... geometries) {
+ super(field, queryRelation);
+ this.component2D = XYGeometry.create(geometries);
+ this.geometries = geometries.clone();
}
- }
-
- @Override
- protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- double y = decode(scratchTriangle.aY);
- double x = decode(scratchTriangle.aX);
- return component2D.contains(x, y);
- }
- case LINE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- return component2D.containsLine(aX, aY, bX, bY);
- }
- case TRIANGLE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- double cY = decode(scratchTriangle.cY);
- double cX = decode(scratchTriangle.cX);
- return component2D.containsTriangle(aX, aY, bX, bY, cX, cY);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
+
+ @Override
+ protected SpatialVisitor getSpatialVisitor() {
+ return new SpatialVisitor() {
+ @Override
+ protected Relation relate(byte[] minTriangle, byte[] maxTriangle) {
+
+ double minY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, 0));
+ double minX =
+ XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, ShapeField.BYTES));
+ double maxY =
+ XYEncodingUtils.decode(
+ NumericUtils.sortableBytesToInt(maxTriangle, 2 * ShapeField.BYTES));
+ double maxX =
+ XYEncodingUtils.decode(
+ NumericUtils.sortableBytesToInt(maxTriangle, 3 * ShapeField.BYTES));
+
+ // check internal node against query
+ return component2D.relate(minX, maxX, minY, maxY);
+ }
+
+ @Override
+ protected Predicate<byte[]> intersects() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double y = decode(scratchTriangle.aY);
+ double x = decode(scratchTriangle.aX);
+ return component2D.contains(x, y);
+ }
+ case LINE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ return component2D.intersectsLine(aX, aY, bX, bY);
+ }
+ case TRIANGLE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ double cY = decode(scratchTriangle.cY);
+ double cX = decode(scratchTriangle.cX);
+ return component2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+
+ @Override
+ protected Predicate<byte[]> within() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double y = decode(scratchTriangle.aY);
+ double x = decode(scratchTriangle.aX);
+ return component2D.contains(x, y);
+ }
+ case LINE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ return component2D.containsLine(aX, aY, bX, bY);
+ }
+ case TRIANGLE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ double cY = decode(scratchTriangle.cY);
+ double cX = decode(scratchTriangle.cX);
+ return component2D.containsTriangle(aX, aY, bX, bY, cX, cY);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+
+ @Override
+ protected Function<byte[], Component2D.WithinRelation> contains() {
+ final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
+ return triangle -> {
+ ShapeField.decodeTriangle(triangle, scratchTriangle);
+
+ switch (scratchTriangle.type) {
+ case POINT:
+ {
+ double y = decode(scratchTriangle.aY);
+ double x = decode(scratchTriangle.aX);
+ return component2D.withinPoint(x, y);
+ }
+ case LINE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ return component2D.withinLine(aX, aY, scratchTriangle.ab, bX, bY);
+ }
+ case TRIANGLE:
+ {
+ double aY = decode(scratchTriangle.aY);
+ double aX = decode(scratchTriangle.aX);
+ double bY = decode(scratchTriangle.bY);
+ double bX = decode(scratchTriangle.bX);
+ double cY = decode(scratchTriangle.cY);
+ double cX = decode(scratchTriangle.cX);
+ return component2D.withinTriangle(
+ aX,
+ aY,
+ scratchTriangle.ab,
+ bX,
+ bY,
+ scratchTriangle.bc,
+ cX,
+ cY,
+ scratchTriangle.ca);
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + scratchTriangle.type + "]");
+ }
+ };
+ }
+ };
}
- }
-
- @Override
- protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
-
- switch (scratchTriangle.type) {
- case POINT: {
- double y = decode(scratchTriangle.aY);
- double x = decode(scratchTriangle.aX);
- return component2D.withinPoint(x, y);
- }
- case LINE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- return component2D.withinLine(aX, aY, scratchTriangle.ab, bX, bY);
- }
- case TRIANGLE: {
- double aY = decode(scratchTriangle.aY);
- double aX = decode(scratchTriangle.aX);
- double bY = decode(scratchTriangle.bY);
- double bX = decode(scratchTriangle.bX);
- double cY = decode(scratchTriangle.cY);
- double cX = decode(scratchTriangle.cX);
- return component2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca);
- }
- default: throw new IllegalArgumentException("Unsupported triangle type :[" + scratchTriangle.type + "]");
+
+ @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("[");
+ for (int i = 0; i < geometries.length; i++) {
+ sb.append(geometries[i].toString());
+ sb.append(',');
+ }
+ sb.append(']');
+ return sb.toString();
}
- }
-
- @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(':');
+
+ @Override
+ protected boolean equalsTo(Object o) {
+ return super.equalsTo(o) && Arrays.equals(geometries, ((XYShapeQuery) o).geometries);
}
- sb.append("[");
- for (int i = 0; i < geometries.length; i++) {
- sb.append(geometries[i].toString());
- sb.append(',');
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + Arrays.hashCode(geometries);
+ return hash;
}
- sb.append(']');
- return sb.toString();
- }
-
- @Override
- protected boolean equalsTo(Object o) {
- return super.equalsTo(o) && Arrays.equals(geometries, ((XYShapeQuery)o).geometries);
- }
-
- @Override
- public int hashCode() {
- int hash = super.hashCode();
- hash = 31 * hash + Arrays.hashCode(geometries);
- return hash;
- }
-}
\ No newline at end of file
+}
diff --git a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
index da63880..2a6a332 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
@@ -16,18 +16,17 @@
*/
package org.apache.lucene.geo;
-import org.apache.lucene.index.PointValues.Relation;
-import org.apache.lucene.util.NumericUtils;
-import org.apache.lucene.util.SloppyMath;
-
import static org.apache.lucene.geo.GeoUtils.MAX_LAT_INCL;
import static org.apache.lucene.geo.GeoUtils.MAX_LON_INCL;
-import static org.apache.lucene.geo.GeoUtils.MIN_LON_INCL;
import static org.apache.lucene.geo.GeoUtils.MIN_LAT_INCL;
+import static org.apache.lucene.geo.GeoUtils.MIN_LON_INCL;
import static org.apache.lucene.geo.GeoUtils.checkLatitude;
import static org.apache.lucene.geo.GeoUtils.checkLongitude;
import java.util.function.Function;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.util.SloppyMath;
/**
* reusable geopoint encoding methods
@@ -38,21 +37,20 @@ public final class GeoEncodingUtils {
/** number of bits used for quantizing latitude and longitude values */
public static final short BITS = 32;
- private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
- private static final double LAT_DECODE = 1/LAT_SCALE;
- private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
- private static final double LON_DECODE = 1/LON_SCALE;
+ private static final double LAT_SCALE = (0x1L << BITS) / 180.0D;
+ private static final double LAT_DECODE = 1 / LAT_SCALE;
+ private static final double LON_SCALE = (0x1L << BITS) / 360.0D;
+ private static final double LON_DECODE = 1 / LON_SCALE;
public static final int MIN_LON_ENCODED = encodeLongitude(MIN_LON_INCL);
public static final int MAX_LON_ENCODED = encodeLongitude(MAX_LON_INCL);
-
// No instance:
- private GeoEncodingUtils() {
- }
+ private GeoEncodingUtils() {}
/**
* Quantizes double (64 bit) latitude into 32 bits (rounding down: in the direction of -90)
+ *
* @param latitude latitude value: must be within standard +/-90 coordinate bounds.
* @return encoded value as a 32-bit {@code int}
* @throws IllegalArgumentException if latitude is out of bounds
@@ -68,6 +66,7 @@ public final class GeoEncodingUtils {
/**
* Quantizes double (64 bit) latitude into 32 bits (rounding up: in the direction of +90)
+ *
* @param latitude latitude value: must be within standard +/-90 coordinate bounds.
* @return encoded value as a 32-bit {@code int}
* @throws IllegalArgumentException if latitude is out of bounds
@@ -83,6 +82,7 @@ public final class GeoEncodingUtils {
/**
* Quantizes double (64 bit) longitude into 32 bits (rounding down: in the direction of -180)
+ *
* @param longitude longitude value: must be within standard +/-180 coordinate bounds.
* @return encoded value as a 32-bit {@code int}
* @throws IllegalArgumentException if longitude is out of bounds
@@ -98,6 +98,7 @@ public final class GeoEncodingUtils {
/**
* Quantizes double (64 bit) longitude into 32 bits (rounding up: in the direction of +180)
+ *
* @param longitude longitude value: must be within standard +/-180 coordinate bounds.
* @return encoded value as a 32-bit {@code int}
* @throws IllegalArgumentException if longitude is out of bounds
@@ -113,6 +114,7 @@ public final class GeoEncodingUtils {
/**
* Turns quantized value from {@link #encodeLatitude} back into a double.
+ *
* @param encoded encoded value: 32-bit quantized value.
* @return decoded latitude value.
*/
@@ -124,6 +126,7 @@ public final class GeoEncodingUtils {
/**
* Turns quantized value from byte array back into a double.
+ *
* @param src byte array containing 4 bytes to decode at {@code offset}
* @param offset offset into {@code src} to decode from.
* @return decoded latitude value.
@@ -134,6 +137,7 @@ public final class GeoEncodingUtils {
/**
* Turns quantized value from {@link #encodeLongitude} back into a double.
+ *
* @param encoded encoded value: 32-bit quantized value.
* @return decoded longitude value.
*/
@@ -145,6 +149,7 @@ public final class GeoEncodingUtils {
/**
* Turns quantized value from byte array back into a double.
+ *
* @param src byte array containing 4 bytes to decode at {@code offset}
* @param offset offset into {@code src} to decode from.
* @return decoded longitude value.
@@ -153,55 +158,83 @@ public final class GeoEncodingUtils {
return decodeLongitude(NumericUtils.sortableBytesToInt(src, offset));
}
- /** Create a predicate that checks whether points are within a distance of a given point.
- * It works by computing the bounding box around the circle that is defined
- * by the given points/distance and splitting it into between 1024 and 4096
- * smaller boxes (4096*0.75^2=2304 on average). Then for each sub box, it
- * computes the relation between this box and the distance query. Finally at
- * search time, it first computes the sub box that the point belongs to,
- * most of the time, no distance computation will need to be performed since
- * all points from the sub box will either be in or out of the circle.
- * @lucene.internal */
- public static DistancePredicate createDistancePredicate(double lat, double lon, double radiusMeters) {
+ /**
+ * Create a predicate that checks whether points are within a distance of a given point. It works
+ * by computing the bounding box around the circle that is defined by the given points/distance
+ * and splitting it into between 1024 and 4096 smaller boxes (4096*0.75^2=2304 on average). Then
+ * for each sub box, it computes the relation between this box and the distance query. Finally at
+ * search time, it first computes the sub box that the point belongs to, most of the time, no
+ * distance computation will need to be performed since all points from the sub box will either be
+ * in or out of the circle.
+ *
+ * @lucene.internal
+ */
+ public static DistancePredicate createDistancePredicate(
+ double lat, double lon, double radiusMeters) {
final Rectangle boundingBox = Rectangle.fromPointDistance(lat, lon, radiusMeters);
final double axisLat = Rectangle.axisLat(lat, radiusMeters);
final double distanceSortKey = GeoUtils.distanceQuerySortKey(radiusMeters);
- final Function<Rectangle, Relation> boxToRelation = box -> GeoUtils.relate(
- box.minLat, box.maxLat, box.minLon, box.maxLon, lat, lon, distanceSortKey, axisLat);
- final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
+ final Function<Rectangle, Relation> boxToRelation =
+ box ->
+ GeoUtils.relate(
+ box.minLat, box.maxLat, box.minLon, box.maxLon, lat, lon, distanceSortKey, axisLat);
+ final Grid subBoxes =
+ createSubBoxes(
+ boundingBox.minLat,
+ boundingBox.maxLat,
+ boundingBox.minLon,
+ boundingBox.maxLon,
+ boxToRelation);
return new DistancePredicate(
- subBoxes.latShift, subBoxes.lonShift,
- subBoxes.latBase, subBoxes.lonBase,
- subBoxes.maxLatDelta, subBoxes.maxLonDelta,
- subBoxes.relations,
- lat, lon, distanceSortKey);
+ subBoxes.latShift,
+ subBoxes.lonShift,
+ subBoxes.latBase,
+ subBoxes.lonBase,
+ subBoxes.maxLatDelta,
+ subBoxes.maxLonDelta,
+ subBoxes.relations,
+ lat,
+ lon,
+ distanceSortKey);
}
- /** Create a predicate that checks whether points are within a geometry.
- * It works the same way as {@link #createDistancePredicate}.
- * @lucene.internal */
+ /**
+ * Create a predicate that checks whether points are within a geometry. It works the same way as
+ * {@link #createDistancePredicate}.
+ *
+ * @lucene.internal
+ */
public static Component2DPredicate createComponentPredicate(Component2D tree) {
- final Rectangle boundingBox = new Rectangle(tree.getMinY(), tree.getMaxY(), tree.getMinX(), tree.getMaxX());
- final Function<Rectangle, Relation> boxToRelation = box -> tree.relate(
- box.minLon, box.maxLon, box.minLat, box.maxLat);
- final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
+ final Function<Rectangle, Relation> boxToRelation =
+ box -> tree.relate(box.minLon, box.maxLon, box.minLat, box.maxLat);
+ final Grid subBoxes =
+ createSubBoxes(
+ tree.getMinY(), tree.getMaxY(), tree.getMinX(), tree.getMaxX(), boxToRelation);
return new Component2DPredicate(
- subBoxes.latShift, subBoxes.lonShift,
- subBoxes.latBase, subBoxes.lonBase,
- subBoxes.maxLatDelta, subBoxes.maxLonDelta,
- subBoxes.relations,
- tree);
+ subBoxes.latShift,
+ subBoxes.lonShift,
+ subBoxes.latBase,
+ subBoxes.lonBase,
+ subBoxes.maxLatDelta,
+ subBoxes.maxLonDelta,
+ subBoxes.relations,
+ tree);
}
- private static Grid createSubBoxes(Rectangle boundingBox, Function<Rectangle, Relation> boxToRelation) {
- final int minLat = encodeLatitudeCeil(boundingBox.minLat);
- final int maxLat = encodeLatitude(boundingBox.maxLat);
- final int minLon = encodeLongitudeCeil(boundingBox.minLon);
- final int maxLon = encodeLongitude(boundingBox.maxLon);
-
- if (maxLat < minLat || (boundingBox.crossesDateline() == false && maxLon < minLon)) {
+ private static Grid createSubBoxes(
+ double shapeMinLat,
+ double shapeMaxLat,
+ double shapeMinLon,
+ double shapeMaxLon,
+ Function<Rectangle, Relation> boxToRelation) {
+ final int minLat = encodeLatitudeCeil(shapeMinLat);
+ final int maxLat = encodeLatitude(shapeMaxLat);
+ final int minLon = encodeLongitudeCeil(shapeMinLon);
+ final int maxLon = encodeLongitude(shapeMaxLon);
+
+ if (maxLat < minLat || (shapeMaxLon >= shapeMinLon && maxLon < minLon)) {
// the box cannot match any quantized point
return new Grid(1, 1, 0, 0, 0, 0, new byte[0]);
}
@@ -220,7 +253,7 @@ public final class GeoEncodingUtils {
{
long minLon2 = (long) minLon - Integer.MIN_VALUE;
long maxLon2 = (long) maxLon - Integer.MIN_VALUE;
- if (boundingBox.crossesDateline()) {
+ if (shapeMaxLon < shapeMinLon) { // crosses dateline
maxLon2 += 1L << 32; // wrap
}
lonShift = computeShift(minLon2, maxLon2);
@@ -237,22 +270,24 @@ public final class GeoEncodingUtils {
final int boxMaxLat = boxMinLat + (1 << latShift) - 1;
final int boxMaxLon = boxMinLon + (1 << lonShift) - 1;
- relations[i * maxLonDelta + j] = (byte) boxToRelation.apply(new Rectangle(
- decodeLatitude(boxMinLat), decodeLatitude(boxMaxLat),
- decodeLongitude(boxMinLon), decodeLongitude(boxMaxLon))
- ).ordinal();
+ relations[i * maxLonDelta + j] =
+ (byte)
+ boxToRelation
+ .apply(
+ new Rectangle(
+ decodeLatitude(boxMinLat), decodeLatitude(boxMaxLat),
+ decodeLongitude(boxMinLon), decodeLongitude(boxMaxLon)))
+ .ordinal();
}
}
- return new Grid(
- latShift, lonShift,
- latBase, lonBase,
- maxLatDelta, maxLonDelta,
- relations);
+ return new Grid(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations);
}
- /** Compute the minimum shift value so that
- * {@code (b>>>shift)-(a>>>shift)} is less that {@code ARITY}. */
+ /**
+ * Compute the minimum shift value so that {@code (b>>>shift)-(a>>>shift)} is less that {@code
+ * ARITY}.
+ */
private static int computeShift(long a, long b) {
assert a <= b;
// We enforce a shift of at least 1 so that when we work with unsigned ints
@@ -276,10 +311,13 @@ public final class GeoEncodingUtils {
final byte[] relations;
private Grid(
- int latShift, int lonShift,
- int latBase, int lonBase,
- int maxLatDelta, int maxLonDelta,
- byte[] relations) {
+ int latShift,
+ int lonShift,
+ int latBase,
+ int lonBase,
+ int maxLatDelta,
+ int maxLonDelta,
+ byte[] relations) {
if (latShift < 1 || latShift > 31) {
throw new IllegalArgumentException();
}
@@ -303,19 +341,26 @@ public final class GeoEncodingUtils {
private final double distanceKey;
private DistancePredicate(
- int latShift, int lonShift,
- int latBase, int lonBase,
- int maxLatDelta, int maxLonDelta,
- byte[] relations,
- double lat, double lon, double distanceKey) {
+ int latShift,
+ int lonShift,
+ int latBase,
+ int lonBase,
+ int maxLatDelta,
+ int maxLonDelta,
+ byte[] relations,
+ double lat,
+ double lon,
+ double distanceKey) {
super(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations);
this.lat = lat;
this.lon = lon;
this.distanceKey = distanceKey;
}
- /** Check whether the given point is within a distance of another point.
- * NOTE: this operates directly on the encoded representation of points. */
+ /**
+ * Check whether the given point is within a distance of another point. NOTE: this operates
+ * directly on the encoded representation of points.
+ */
public boolean test(int lat, int lon) {
final int lat2 = ((lat - Integer.MIN_VALUE) >>> latShift);
if (lat2 < latBase || lat2 >= latBase + maxLatDelta) {
@@ -334,8 +379,8 @@ public final class GeoEncodingUtils {
final int relation = relations[(lat2 - latBase) * maxLonDelta + (lon2 - lonBase)];
if (relation == Relation.CELL_CROSSES_QUERY.ordinal()) {
return SloppyMath.haversinSortKey(
- decodeLatitude(lat), decodeLongitude(lon),
- this.lat, this.lon) <= distanceKey;
+ decodeLatitude(lat), decodeLongitude(lon), this.lat, this.lon)
+ <= distanceKey;
} else {
return relation == Relation.CELL_INSIDE_QUERY.ordinal();
}
@@ -348,17 +393,22 @@ public final class GeoEncodingUtils {
private final Component2D tree;
private Component2DPredicate(
- int latShift, int lonShift,
- int latBase, int lonBase,
- int maxLatDelta, int maxLonDelta,
- byte[] relations,
- Component2D tree) {
+ int latShift,
+ int lonShift,
+ int latBase,
+ int lonBase,
+ int maxLatDelta,
+ int maxLonDelta,
+ byte[] relations,
+ Component2D tree) {
super(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations);
this.tree = tree;
}
- /** Check whether the given point is within the considered polygon.
- * NOTE: this operates directly on the encoded representation of points. */
+ /**
+ * Check whether the given point is within the considered polygon. NOTE: this operates directly
+ * on the encoded representation of points.
+ */
public boolean test(int lat, int lon) {
final int lat2 = ((lat - Integer.MIN_VALUE) >>> latShift);
if (lat2 < latBase || lat2 >= latBase + maxLatDelta) {
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Point2D.java b/lucene/core/src/java/org/apache/lucene/geo/Point2D.java
index 3757f0a..9d384d9 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Point2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Point2D.java
@@ -17,17 +17,15 @@
package org.apache.lucene.geo;
-import org.apache.lucene.index.PointValues;
-
import static org.apache.lucene.geo.GeoUtils.orient;
-/**
- * 2D point implementation containing geo spatial logic.
- */
+import org.apache.lucene.index.PointValues;
+
+/** 2D point implementation containing geo spatial logic. */
final class Point2D implements Component2D {
- final private double x;
- final private double y;
+ private final double x;
+ private final double y;
private Point2D(double x, double y) {
this.x = x;
@@ -68,27 +66,59 @@ final class Point2D implements Component2D {
}
@Override
- public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
- double aX, double aY, double bX, double bY) {
- return Component2D.containsPoint(x, y, minX, maxX, minY, maxY) &&
- orient(aX, aY, bX, bY, x, y) == 0;
+ public boolean intersectsLine(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ double bX,
+ double bY) {
+ return Component2D.containsPoint(x, y, minX, maxX, minY, maxY)
+ && orient(aX, aY, bX, bY, x, y) == 0;
}
@Override
- public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
- double aX, double aY, double bX, double bY, double cX, double cY) {
+ public boolean intersectsTriangle(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ double bX,
+ double bY,
+ double cX,
+ double cY) {
return Component2D.pointInTriangle(minX, maxX, minY, maxY, x, y, aX, aY, bX, bY, cX, cY);
}
@Override
- public boolean containsLine(double minX, double maxX, double minY, double maxY,
- double aX, double aY, double bX, double bY) {
+ public boolean containsLine(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ double bX,
+ double bY) {
return false;
}
@Override
- public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
- double aX, double aY, double bX, double bY, double cX, double cY) {
+ public boolean containsTriangle(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ double bX,
+ double bY,
+ double cX,
+ double cY) {
return false;
}
@@ -98,15 +128,37 @@ final class Point2D implements Component2D {
}
@Override
- public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
- double aX, double aY, boolean ab, double bX, double bY) {
+ public WithinRelation withinLine(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ boolean ab,
+ double bX,
+ double bY) {
// can be improved?
- return intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY) ? WithinRelation.CANDIDATE : WithinRelation.DISJOINT;
+ return intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)
+ ? WithinRelation.CANDIDATE
+ : WithinRelation.DISJOINT;
}
@Override
- public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
- double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
+ public WithinRelation withinTriangle(
+ double minX,
+ double maxX,
+ double minY,
+ double maxY,
+ double aX,
+ double aY,
+ boolean ab,
+ double bX,
+ double bY,
+ boolean bc,
+ double cX,
+ double cY,
+ boolean ca) {
if (Component2D.pointInTriangle(minX, maxX, minY, maxY, x, y, aX, aY, bX, bY, cX, cY)) {
return WithinRelation.CANDIDATE;
}
@@ -115,8 +167,17 @@ final class Point2D implements Component2D {
/** create a Point2D component tree from a LatLon point */
static Component2D create(Point point) {
- return new Point2D(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(point.getLon())),
- GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(point.getLat())));
+ // Points behave as rectangles
+ double qLat =
+ point.getLat() == GeoUtils.MAX_LAT_INCL
+ ? point.getLat()
+ : GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitudeCeil(point.getLat()));
+ double qLon =
+ point.getLon() == GeoUtils.MAX_LON_INCL
+ ? point.getLon()
+ : GeoEncodingUtils.decodeLongitude(
+ GeoEncodingUtils.encodeLongitudeCeil(point.getLon()));
+ return new Point2D(qLon, qLat);
}
/** create a Point2D component tree from a XY point */
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonDocValueTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonDocValueTestCase.java
new file mode 100644
index 0000000..2e96c3a
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonDocValueTestCase.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Circle;
+import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Point;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.search.Query;
+
+/**
+ * Base test case for testing geospatial indexing and search functionality for {@link
+ * LatLonDocValuesField} *
+ */
+public abstract class BaseLatLonDocValueTestCase extends BaseLatLonSpatialTestCase {
+
+ @Override
+ protected Query newRectQuery(
+ String field,
+ QueryRelation queryRelation,
+ double minLon,
+ double maxLon,
+ double minLat,
+ double maxLat) {
+ return LatLonDocValuesField.newSlowGeometryQuery(
+ field, queryRelation, new Rectangle(minLat, maxLat, minLon, maxLon));
+ }
+
+ @Override
+ protected Query newLineQuery(String field, QueryRelation queryRelation, Object... lines) {
+ return LatLonDocValuesField.newSlowGeometryQuery(
+ field, queryRelation, Arrays.stream(lines).toArray(Line[]::new));
+ }
+
+ @Override
+ protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
+ return LatLonDocValuesField.newSlowGeometryQuery(
+ field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
+ }
+
+ @Override
+ protected Query newDistanceQuery(String field, QueryRelation queryRelation, Object circle) {
+ return LatLonDocValuesField.newSlowGeometryQuery(field, queryRelation, (Circle) circle);
+ }
+
+ @Override
+ protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
+ Point[] pointsArray = new Point[points.length];
+ for (int i = 0; i < points.length; i++) {
+ double[] point = (double[]) points[i];
+ pointsArray[i] = new Point(point[0], point[1]);
+ }
+ return LatLonDocValuesField.newSlowGeometryQuery(field, queryRelation, pointsArray);
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonPointTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonPointTestCase.java
new file mode 100644
index 0000000..d886979
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonPointTestCase.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 com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import java.util.Arrays;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Circle;
+import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Point;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryUtils;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+
+/**
+ * Base test case for testing geospatial indexing and search functionality for {@link LatLonPoint} *
+ */
+public abstract class BaseLatLonPointTestCase extends BaseLatLonSpatialTestCase {
+
+ @Override
+ protected Query newRectQuery(
+ String field,
+ QueryRelation queryRelation,
+ double minLon,
+ double maxLon,
+ double minLat,
+ double maxLat) {
+ return LatLonPoint.newGeometryQuery(
+ field, queryRelation, new Rectangle(minLat, maxLat, minLon, maxLon));
+ }
+
+ @Override
+ protected Query newLineQuery(String field, QueryRelation queryRelation, Object... lines) {
+ return LatLonPoint.newGeometryQuery(
+ field, queryRelation, Arrays.stream(lines).toArray(Line[]::new));
+ }
+
+ @Override
+ protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
+ return LatLonPoint.newGeometryQuery(
+ field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
+ }
+
+ @Override
+ protected Query newDistanceQuery(String field, QueryRelation queryRelation, Object circle) {
+ return LatLonPoint.newGeometryQuery(field, queryRelation, (Circle) circle);
+ }
+
+ @Override
+ protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
+ Point[] pointsArray = new Point[points.length];
+ for (int i = 0; i < points.length; i++) {
+ double[] point = (double[]) points[i];
+ pointsArray[i] = new Point(point[0], point[1]);
+ }
+ return LatLonPoint.newGeometryQuery(field, queryRelation, pointsArray);
+ }
+
+ public void testBoundingBoxQueriesEquivalence() throws Exception {
+ int numShapes = atLeast(20);
+
+ Directory dir = newDirectory();
+ RandomIndexWriter w = new RandomIndexWriter(random(), dir);
+
+ for (int i = 0; i < numShapes; i++) {
+ indexRandomShapes(w.w, nextShape());
+ }
+ if (random().nextBoolean()) {
+ w.forceMerge(1);
+ }
+
+ ///// search //////
+ IndexReader reader = w.getReader();
+ w.close();
+ IndexSearcher searcher = newSearcher(reader);
+
+ Rectangle box = GeoTestUtil.nextBox();
+
+ Query q1 = LatLonPoint.newBoxQuery(FIELD_NAME, box.minLat, box.maxLat, box.minLon, box.maxLon);
+ Query q2 = new LatLonPointQuery(FIELD_NAME, QueryRelation.INTERSECTS, box);
+ assertEquals(searcher.count(q1), searcher.count(q2));
+
+ IOUtils.close(w, reader, dir);
+ }
+
+ public void testQueryEqualsAndHashcode() {
+ Polygon polygon = GeoTestUtil.nextPolygon();
+ QueryRelation queryRelation =
+ RandomPicks.randomFrom(
+ random(), new QueryRelation[] {QueryRelation.INTERSECTS, QueryRelation.DISJOINT});
+ String fieldName = "foo";
+ Query q1 = newPolygonQuery(fieldName, queryRelation, polygon);
+ Query q2 = newPolygonQuery(fieldName, queryRelation, polygon);
+ QueryUtils.checkEqual(q1, q2);
+ // different field name
+ Query q3 = newPolygonQuery("bar", queryRelation, polygon);
+ QueryUtils.checkUnequal(q1, q3);
+ // different query relation
+ QueryRelation newQueryRelation =
+ RandomPicks.randomFrom(
+ random(), new QueryRelation[] {QueryRelation.INTERSECTS, QueryRelation.DISJOINT});
+ Query q4 = newPolygonQuery(fieldName, newQueryRelation, polygon);
+ if (queryRelation == newQueryRelation) {
+ QueryUtils.checkEqual(q1, q4);
+ } else {
+ QueryUtils.checkUnequal(q1, q4);
+ }
+ // different shape
+ Polygon newPolygon = GeoTestUtil.nextPolygon();
+ ;
+ Query q5 = newPolygonQuery(fieldName, queryRelation, newPolygon);
+ if (polygon.equals(newPolygon)) {
+ QueryUtils.checkEqual(q1, q5);
+ } else {
+ QueryUtils.checkUnequal(q1, q5);
+ }
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
index 4277d61..ad71f67 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
@@ -16,19 +16,14 @@
*/
package org.apache.lucene.document;
-import java.util.Arrays;
-
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import java.util.Arrays;
import org.apache.lucene.document.ShapeField.QueryRelation;
-import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.Circle;
import org.apache.lucene.geo.GeoTestUtil;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
-import org.apache.lucene.geo.Point;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
-import org.apache.lucene.geo.Tessellator;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
@@ -36,73 +31,39 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryUtils;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
-import org.apache.lucene.util.TestUtil;
-import org.apache.lucene.geo.Circle;
-
-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;
-import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
-import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
-
-/** Base test case for testing geospatial indexing and search functionality **/
-public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
-
- protected abstract ShapeType getShapeType();
- protected Object nextShape() {
- return getShapeType().nextShape();
- }
+/**
+ * Base test case for testing geospatial indexing and search functionality for {@link LatLonShape} *
+ */
+public abstract class BaseLatLonShapeTestCase extends BaseLatLonSpatialTestCase {
- /** factory method to create a new bounding box query */
@Override
- protected Query newRectQuery(String field, QueryRelation queryRelation, double minLon, double maxLon, double minLat, double maxLat) {
+ protected Query newRectQuery(
+ String field,
+ QueryRelation queryRelation,
+ double minLon,
+ double maxLon,
+ double minLat,
+ double maxLat) {
return LatLonShape.newBoxQuery(field, queryRelation, minLat, maxLat, minLon, maxLon);
}
- /** factory method to create a new line query */
@Override
protected Query newLineQuery(String field, QueryRelation queryRelation, Object... lines) {
- return LatLonShape.newLineQuery(field, queryRelation, Arrays.stream(lines).toArray(Line[]::new));
+ return LatLonShape.newLineQuery(
+ field, queryRelation, Arrays.stream(lines).toArray(Line[]::new));
}
- /** factory method to create a new polygon query */
@Override
protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
- return LatLonShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
+ return LatLonShape.newPolygonQuery(
+ field, queryRelation, Arrays.stream(polygons).toArray(Polygon[]::new));
}
@Override
protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
- return LatLonShape.newPointQuery(field, queryRelation, Arrays.stream(points).toArray(double[][]::new));
- }
-
- @Override
- protected Component2D toLine2D(Object... lines) {
- return LatLonGeometry.create(Arrays.stream(lines).toArray(Line[]::new));
- }
-
- @Override
- protected Component2D toPolygon2D(Object... polygons) {
- return LatLonGeometry.create(Arrays.stream(polygons).toArray(Polygon[]::new));
- }
-
- @Override
- protected Component2D toRectangle2D(double minX, double maxX, double minY, double maxY) {
- return LatLonGeometry.create(new Rectangle(minY, maxY, minX, maxX));
- }
-
- @Override
- protected Component2D toPoint2D(Object... points) {
- double[][] p = Arrays.stream(points).toArray(double[][]::new);
- org.apache.lucene.geo.Point[] pointArray = new org.apache.lucene.geo.Point[points.length];
- for (int i =0; i < points.length; i++) {
- pointArray[i] = new org.apache.lucene.geo.Point(p[i][0], p[i][1]);
- }
- return LatLonGeometry.create(pointArray);
+ return LatLonShape.newPointQuery(
+ field, queryRelation, Arrays.stream(points).toArray(double[][]::new));
}
@Override
@@ -110,69 +71,110 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
return LatLonShape.newDistanceQuery(field, queryRelation, (Circle) circle);
}
- @Override
- protected Component2D toCircle2D(Object circle) {
- return LatLonGeometry.create((Circle) circle);
- }
-
- @Override
- protected Circle nextCircle() {
- final double radiusMeters = random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
- return new Circle(nextLatitude(), nextLongitude(), radiusMeters);
- }
+ public void testBoundingBoxQueriesEquivalence() throws Exception {
+ int numShapes = atLeast(20);
- @Override
- public Rectangle randomQueryBox() {
- return GeoTestUtil.nextBox();
- }
+ Directory dir = newDirectory();
+ RandomIndexWriter w = new RandomIndexWriter(random(), dir);
- @Override
- protected Object[] nextPoints() {
- int numPoints = TestUtil.nextInt(random(), 1, 20);
- double[][] points = new double[numPoints][2];
- for (int i = 0; i < numPoints; i++) {
- points[i][0] = nextLatitude();
- points[i][1] = nextLongitude();
+ for (int i = 0; i < numShapes; i++) {
+ indexRandomShapes(w.w, nextShape());
+ }
+ if (random().nextBoolean()) {
+ w.forceMerge(1);
}
- return points;
- }
- @Override
- protected double rectMinX(Object rect) {
- return ((Rectangle)rect).minLon;
- }
+ ///// search //////
+ IndexReader reader = w.getReader();
+ w.close();
+ IndexSearcher searcher = newSearcher(reader);
- @Override
- protected double rectMaxX(Object rect) {
- return ((Rectangle)rect).maxLon;
- }
+ Rectangle box = GeoTestUtil.nextBox();
- @Override
- protected double rectMinY(Object rect) {
- return ((Rectangle)rect).minLat;
+ Query q1 =
+ LatLonShape.newBoxQuery(
+ FIELD_NAME, QueryRelation.INTERSECTS, box.minLat, box.maxLat, box.minLon, box.maxLon);
+ Query q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.INTERSECTS, box);
+ assertEquals(searcher.count(q1), searcher.count(q2));
+ q1 =
+ LatLonShape.newBoxQuery(
+ FIELD_NAME, QueryRelation.WITHIN, box.minLat, box.maxLat, box.minLon, box.maxLon);
+ q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.WITHIN, box);
+ assertEquals(searcher.count(q1), searcher.count(q2));
+ q1 =
+ LatLonShape.newBoxQuery(
+ FIELD_NAME, QueryRelation.CONTAINS, box.minLat, box.maxLat, box.minLon, box.maxLon);
+ if (box.crossesDateline()) {
+ q2 = LatLonShape.newGeometryQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
+ } else {
+ q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
+ }
+ assertEquals(searcher.count(q1), searcher.count(q2));
+ q1 =
+ LatLonShape.newBoxQuery(
+ FIELD_NAME, QueryRelation.DISJOINT, box.minLat, box.maxLat, box.minLon, box.maxLon);
+ q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.DISJOINT, box);
+ assertEquals(searcher.count(q1), searcher.count(q2));
+
+ IOUtils.close(w, reader, dir);
}
public void testBoxQueryEqualsAndHashcode() {
Rectangle rectangle = GeoTestUtil.nextBox();
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
String fieldName = "foo";
- Query q1 = newRectQuery(fieldName, queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
- Query q2 = newRectQuery(fieldName, queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
+ Query q1 =
+ newRectQuery(
+ fieldName,
+ queryRelation,
+ rectangle.minLon,
+ rectangle.maxLon,
+ rectangle.minLat,
+ rectangle.maxLat);
+ Query q2 =
+ newRectQuery(
+ fieldName,
+ queryRelation,
+ rectangle.minLon,
+ rectangle.maxLon,
+ rectangle.minLat,
+ rectangle.maxLat);
QueryUtils.checkEqual(q1, q2);
- //different field name
- Query q3 = newRectQuery("bar", queryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
+ // different field name
+ Query q3 =
+ newRectQuery(
+ "bar",
+ queryRelation,
+ rectangle.minLon,
+ rectangle.maxLon,
+ rectangle.minLat,
+ rectangle.maxLat);
QueryUtils.checkUnequal(q1, q3);
- //different query relation
+ // different query relation
QueryRelation newQueryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
- Query q4 = newRectQuery(fieldName, newQueryRelation, rectangle.minLon, rectangle.maxLon, rectangle.minLat, rectangle.maxLat);
+ Query q4 =
+ newRectQuery(
+ fieldName,
+ newQueryRelation,
+ rectangle.minLon,
+ rectangle.maxLon,
+ rectangle.minLat,
+ rectangle.maxLat);
if (queryRelation == newQueryRelation) {
QueryUtils.checkEqual(q1, q4);
} else {
QueryUtils.checkUnequal(q1, q4);
}
- //different shape
+ // different shape
Rectangle newRectangle = GeoTestUtil.nextBox();
- Query q5 = newRectQuery(fieldName, queryRelation, newRectangle.minLon, newRectangle.maxLon, newRectangle.minLat, newRectangle.maxLat);
+ Query q5 =
+ newRectQuery(
+ fieldName,
+ queryRelation,
+ newRectangle.minLon,
+ newRectangle.maxLon,
+ newRectangle.minLat,
+ newRectangle.maxLat);
if (rectangle.equals(newRectangle)) {
QueryUtils.checkEqual(q1, q5);
} else {
@@ -180,11 +182,6 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
}
}
- /** factory method to create a new line query */
- protected Query newLineQuery(String field, QueryRelation queryRelation, Line... lines) {
- return LatLonShape.newLineQuery(field, queryRelation, lines);
- }
-
public void testLineQueryEqualsAndHashcode() {
Line line = nextLine();
QueryRelation queryRelation = RandomPicks.randomFrom(random(), POINT_LINE_RELATIONS);
@@ -192,10 +189,10 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
Query q1 = newLineQuery(fieldName, queryRelation, line);
Query q2 = newLineQuery(fieldName, queryRelation, line);
QueryUtils.checkEqual(q1, q2);
- //different field name
+ // different field name
Query q3 = newLineQuery("bar", queryRelation, line);
QueryUtils.checkUnequal(q1, q3);
- //different query relation
+ // different query relation
QueryRelation newQueryRelation = RandomPicks.randomFrom(random(), POINT_LINE_RELATIONS);
Query q4 = newLineQuery(fieldName, newQueryRelation, line);
if (queryRelation == newQueryRelation) {
@@ -203,7 +200,7 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
} else {
QueryUtils.checkUnequal(q1, q4);
}
- //different shape
+ // different shape
Line newLine = nextLine();
Query q5 = newLineQuery(fieldName, queryRelation, newLine);
if (line.equals(newLine)) {
@@ -213,51 +210,6 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
}
}
- public void testBoundingBoxQueriesEquivalence() throws Exception {
- int numShapes = atLeast(20);
-
- Directory dir = newDirectory();
- RandomIndexWriter w = new RandomIndexWriter(random(), dir);
-
- for (int i =0; i < numShapes; i++) {
- indexRandomShapes(w.w, nextShape());
- }
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
-
- ///// search //////
- IndexReader reader = w.getReader();
- w.close();
- IndexSearcher searcher = newSearcher(reader);
-
- Rectangle box = GeoTestUtil.nextBox();
-
- Query q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.INTERSECTS, box.minLat, box.maxLat, box.minLon, box.maxLon);
- Query q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.INTERSECTS, box);
- assertEquals(searcher.count(q1), searcher.count(q2));
- q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.WITHIN, box.minLat, box.maxLat, box.minLon, box.maxLon);
- q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.WITHIN, box);
- assertEquals(searcher.count(q1), searcher.count(q2));
- q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.CONTAINS, box.minLat, box.maxLat, box.minLon, box.maxLon);
- if (box.crossesDateline()) {
- q2 = LatLonShape.newGeometryQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
- } else {
- q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.CONTAINS, box);
- }
- assertEquals(searcher.count(q1), searcher.count(q2));
- q1 = LatLonShape.newBoxQuery(FIELD_NAME, QueryRelation.DISJOINT, box.minLat, box.maxLat, box.minLon, box.maxLon);
- q2 = new LatLonShapeQuery(FIELD_NAME, QueryRelation.DISJOINT, box);
- assertEquals(searcher.count(q1), searcher.count(q2));
-
- IOUtils.close(w, reader, dir);
- }
-
- /** factory method to create a new polygon query */
- protected Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
- return LatLonShape.newPolygonQuery(field, queryRelation, polygons);
- }
-
public void testPolygonQueryEqualsAndHashcode() {
Polygon polygon = GeoTestUtil.nextPolygon();
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
@@ -265,10 +217,10 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
Query q1 = newPolygonQuery(fieldName, queryRelation, polygon);
Query q2 = newPolygonQuery(fieldName, queryRelation, polygon);
QueryUtils.checkEqual(q1, q2);
- //different field name
+ // different field name
Query q3 = newPolygonQuery("bar", queryRelation, polygon);
QueryUtils.checkUnequal(q1, q3);
- //different query relation
+ // different query relation
QueryRelation newQueryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
Query q4 = newPolygonQuery(fieldName, newQueryRelation, polygon);
if (queryRelation == newQueryRelation) {
@@ -276,8 +228,9 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
} else {
QueryUtils.checkUnequal(q1, q4);
}
- //different shape
- Polygon newPolygon = GeoTestUtil.nextPolygon();;
+ // different shape
+ Polygon newPolygon = GeoTestUtil.nextPolygon();
+ ;
Query q5 = newPolygonQuery(fieldName, queryRelation, newPolygon);
if (polygon.equals(newPolygon)) {
QueryUtils.checkEqual(q1, q5);
@@ -285,98 +238,4 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
QueryUtils.checkUnequal(q1, q5);
}
}
-
- @Override
- protected double rectMaxY(Object rect) {
- return ((Rectangle)rect).maxLat;
- }
-
- @Override
- protected boolean rectCrossesDateline(Object rect) {
- return ((Rectangle)rect).crossesDateline();
- }
-
- @Override
- public Line nextLine() {
- return GeoTestUtil.nextLine();
- }
-
- @Override
- protected Polygon nextPolygon() {
- return GeoTestUtil.nextPolygon();
- }
-
- @Override
- protected Encoder getEncoder() {
- return new Encoder() {
- @Override
- double decodeX(int encoded) {
- return decodeLongitude(encoded);
- }
-
- @Override
- double decodeY(int encoded) {
- return decodeLatitude(encoded);
- }
-
- @Override
- double quantizeX(double raw) {
- return decodeLongitude(encodeLongitude(raw));
- }
-
- @Override
- double quantizeXCeil(double raw) {
- return decodeLongitude(encodeLongitudeCeil(raw));
- }
-
- @Override
- double quantizeY(double raw) {
- return decodeLatitude(encodeLatitude(raw));
- }
-
- @Override
- double quantizeYCeil(double raw) {
- return decodeLatitude(encodeLatitudeCeil(raw));
- }
- };
- }
-
- /** internal shape type for testing different shape types */
- protected enum ShapeType {
- POINT() {
- public Point nextShape() {
- return GeoTestUtil.nextPoint();
- }
- },
- LINE() {
- public Line nextShape() {
- return GeoTestUtil.nextLine();
- }
- },
- POLYGON() {
- public Polygon nextShape() {
- while (true) {
- Polygon p = GeoTestUtil.nextPolygon();
- try {
- Tessellator.tessellate(p);
- return p;
- } catch (IllegalArgumentException e) {
- // if we can't tessellate; then random polygon generator created a malformed shape
- }
- }
- }
- },
- MIXED() {
- public Object nextShape() {
- return RandomPicks.randomFrom(random(), subList).nextShape();
- }
- };
-
- static ShapeType[] subList;
- static {
- subList = new ShapeType[] {POINT, LINE, POLYGON};
- }
-
- public abstract Object nextShape();
- }
}
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonSpatialTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonSpatialTestCase.java
new file mode 100644
index 0000000..518c9be
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonSpatialTestCase.java
@@ -0,0 +1,220 @@
+/*
+ * 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.encodeLatitudeCeil;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
+import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
+import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import java.util.Arrays;
+import org.apache.lucene.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Circle;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.geo.GeoUtils;
+import org.apache.lucene.geo.LatLonGeometry;
+import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Point;
+import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Rectangle;
+import org.apache.lucene.geo.Tessellator;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.TestUtil;
+
+/** Base test case for testing geospatial indexing and search functionality * */
+public abstract class BaseLatLonSpatialTestCase extends BaseSpatialTestCase {
+
+ protected abstract ShapeType getShapeType();
+
+ protected Object nextShape() {
+ return getShapeType().nextShape();
+ }
+
+ @Override
+ protected Component2D toLine2D(Object... lines) {
+ return LatLonGeometry.create(Arrays.stream(lines).toArray(Line[]::new));
+ }
+
+ @Override
+ protected Component2D toPolygon2D(Object... polygons) {
+ return LatLonGeometry.create(Arrays.stream(polygons).toArray(Polygon[]::new));
+ }
+
+ @Override
+ protected Component2D toRectangle2D(double minX, double maxX, double minY, double maxY) {
+ return LatLonGeometry.create(new Rectangle(minY, maxY, minX, maxX));
+ }
+
+ @Override
+ protected Component2D toPoint2D(Object... points) {
+ double[][] p = Arrays.stream(points).toArray(double[][]::new);
+ Point[] pointArray = new Point[points.length];
+ for (int i = 0; i < points.length; i++) {
+ pointArray[i] = new Point(p[i][0], p[i][1]);
+ }
+ return LatLonGeometry.create(pointArray);
+ }
+
+ @Override
+ protected Component2D toCircle2D(Object circle) {
+ return LatLonGeometry.create((Circle) circle);
+ }
+
+ @Override
+ protected Circle nextCircle() {
+ final double radiusMeters =
+ random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
+ return new Circle(nextLatitude(), nextLongitude(), radiusMeters);
+ }
+
+ @Override
+ public Rectangle randomQueryBox() {
+ return GeoTestUtil.nextBox();
+ }
+
+ @Override
+ protected Object[] nextPoints() {
+ int numPoints = TestUtil.nextInt(random(), 1, 20);
+ double[][] points = new double[numPoints][2];
+ for (int i = 0; i < numPoints; i++) {
+ points[i][0] = nextLatitude();
+ points[i][1] = nextLongitude();
+ }
+ return points;
+ }
+
+ @Override
+ protected double rectMinX(Object rect) {
+ return ((Rectangle) rect).minLon;
+ }
+
+ @Override
+ protected double rectMaxX(Object rect) {
+ return ((Rectangle) rect).maxLon;
+ }
+
+ @Override
+ protected double rectMinY(Object rect) {
+ return ((Rectangle) rect).minLat;
+ }
+
+ /** factory method to create a new polygon query */
+ protected Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) {
+ return LatLonShape.newPolygonQuery(field, queryRelation, polygons);
+ }
+
+ @Override
+ protected double rectMaxY(Object rect) {
+ return ((Rectangle) rect).maxLat;
+ }
+
+ @Override
+ protected boolean rectCrossesDateline(Object rect) {
+ return ((Rectangle) rect).crossesDateline();
+ }
+
+ @Override
+ public Line nextLine() {
+ return GeoTestUtil.nextLine();
+ }
+
+ @Override
+ protected Polygon nextPolygon() {
+ return GeoTestUtil.nextPolygon();
+ }
+
+ @Override
+ protected Encoder getEncoder() {
+ return new Encoder() {
+ @Override
+ double decodeX(int encoded) {
+ return decodeLongitude(encoded);
+ }
+
+ @Override
+ double decodeY(int encoded) {
+ return decodeLatitude(encoded);
+ }
+
+ @Override
+ double quantizeX(double raw) {
+ return decodeLongitude(encodeLongitude(raw));
+ }
+
+ @Override
+ double quantizeXCeil(double raw) {
+ return decodeLongitude(encodeLongitudeCeil(raw));
+ }
+
+ @Override
+ double quantizeY(double raw) {
+ return decodeLatitude(encodeLatitude(raw));
+ }
+
+ @Override
+ double quantizeYCeil(double raw) {
+ return decodeLatitude(encodeLatitudeCeil(raw));
+ }
+ };
+ }
+
+ /** internal shape type for testing different shape types */
+ protected enum ShapeType {
+ POINT() {
+ public Point nextShape() {
+ return GeoTestUtil.nextPoint();
+ }
+ },
+ LINE() {
+ public Line nextShape() {
+ return GeoTestUtil.nextLine();
+ }
+ },
+ POLYGON() {
+ public Polygon nextShape() {
+ while (true) {
+ Polygon p = GeoTestUtil.nextPolygon();
+ try {
+ Tessellator.tessellate(p);
+ return p;
+ } catch (IllegalArgumentException e) {
+ // if we can't tessellate; then random polygon generator created a malformed shape
+ }
+ }
+ }
+ },
+ MIXED() {
+ public Object nextShape() {
+ return RandomPicks.randomFrom(random(), subList).nextShape();
+ }
+ };
+
+ static ShapeType[] subList;
+
+ static {
+ subList = new ShapeType[] {POINT, LINE, POLYGON};
+ }
+
+ public abstract Object nextShape();
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java
similarity index 85%
rename from lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
rename to lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java
index 4412472..9662a2f 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java
@@ -16,12 +16,14 @@
*/
package org.apache.lucene.document;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+
+import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
-
-import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
@@ -46,24 +48,24 @@ import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
-import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
-import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
-
/**
* Base test case for testing spherical and cartesian geometry indexing and search functionality
- * <p>
- * This class is implemented by {@link BaseXYShapeTestCase} for testing XY cartesian geometry
- * and {@link BaseLatLonShapeTestCase} for testing Lat Lon geospatial geometry
- **/
-public abstract class BaseShapeTestCase extends LuceneTestCase {
+ *
+ * <p>This class is implemented by {@link BaseXYShapeTestCase} for testing XY cartesian geometry and
+ * {@link BaseLatLonSpatialTestCase} for testing Lat Lon geospatial geometry
+ */
+public abstract class BaseSpatialTestCase extends LuceneTestCase {
/** name of the LatLonShape indexed field */
protected static final String FIELD_NAME = "shape";
+
public final Encoder ENCODER;
public final Validator VALIDATOR;
- protected static final QueryRelation[] POINT_LINE_RELATIONS = {QueryRelation.INTERSECTS, QueryRelation.DISJOINT, QueryRelation.CONTAINS};
+ protected static final QueryRelation[] POINT_LINE_RELATIONS = {
+ QueryRelation.INTERSECTS, QueryRelation.DISJOINT, QueryRelation.CONTAINS
+ };
- public BaseShapeTestCase() {
+ public BaseSpatialTestCase() {
ENCODER = getEncoder();
VALIDATOR = getValidator();
}
@@ -94,7 +96,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
Object[] shapes = new Object[numShapes];
for (int i = 0; i < numShapes; i++) {
- shapes[i] = diffShapes[random().nextInt(cardinality)];
+ shapes[i] = diffShapes[random().nextInt(cardinality)];
}
verify(shapes);
@@ -140,6 +142,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
protected abstract Object getShapeType();
+
protected abstract Object nextShape();
protected abstract Encoder getEncoder();
@@ -155,7 +158,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
}
- /** return a semi-random line used for queries **/
+ /** return a semi-random line used for queries * */
protected abstract Object nextLine();
protected abstract Object nextPolygon();
@@ -167,16 +170,20 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
protected abstract Object nextCircle();
protected abstract double rectMinX(Object rect);
+
protected abstract double rectMaxX(Object rect);
+
protected abstract double rectMinY(Object rect);
+
protected abstract double rectMaxY(Object rect);
+
protected abstract boolean rectCrossesDateline(Object rect);
/**
* return a semi-random line used for queries
*
- * note: shapes parameter may be used to ensure some queries intersect indexed shapes
- **/
+ * <p>note: shapes parameter may be used to ensure some queries intersect indexed shapes
+ */
protected Object randomQueryLine(Object... shapes) {
return nextLine();
}
@@ -190,19 +197,28 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
/** factory method to create a new bounding box query */
- protected abstract Query newRectQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY);
+ protected abstract Query newRectQuery(
+ String field,
+ QueryRelation queryRelation,
+ double minX,
+ double maxX,
+ double minY,
+ double maxY);
/** factory method to create a new line query */
protected abstract Query newLineQuery(String field, QueryRelation queryRelation, Object... lines);
/** factory method to create a new polygon query */
- protected abstract Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons);
+ protected abstract Query newPolygonQuery(
+ String field, QueryRelation queryRelation, Object... polygons);
/** factory method to create a new point query */
- protected abstract Query newPointsQuery(String field, QueryRelation queryRelation, Object... points);
+ protected abstract Query newPointsQuery(
+ String field, QueryRelation queryRelation, Object... points);
/** factory method to create a new distance query */
- protected abstract Query newDistanceQuery(String field, QueryRelation queryRelation, Object circle);
+ protected abstract Query newDistanceQuery(
+ String field, QueryRelation queryRelation, Object circle);
protected abstract Component2D toLine2D(Object... line);
@@ -239,7 +255,6 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
IOUtils.close(w, reader, dir);
}
-
protected void indexRandomShapes(IndexWriter w, Object... shapes) throws Exception {
Set<Integer> deleted = new HashSet<>();
for (int id = 0; id < shapes.length; ++id) {
@@ -252,7 +267,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
w.addDocument(doc);
if (id > 0 && random().nextInt(100) == 42) {
int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
+ w.deleteDocuments(new Term("id", "" + idToDelete));
deleted.add(idToDelete);
if (VERBOSE) {
System.out.println(" delete id=" + idToDelete);
@@ -289,20 +304,27 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
for (int iter = 0; iter < iters; ++iter) {
if (VERBOSE) {
- System.out.println("\nTEST: iter=" + (iter+1) + " of " + iters + " s=" + s);
+ System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
}
// BBox
Object rect = randomQueryBox();
QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values());
- Query query = newRectQuery(FIELD_NAME, queryRelation, rectMinX(rect), rectMaxX(rect), rectMinY(rect), rectMaxY(rect));
+ Query query =
+ newRectQuery(
+ FIELD_NAME,
+ queryRelation,
+ rectMinX(rect),
+ rectMaxX(rect),
+ rectMinY(rect),
+ rectMaxY(rect));
if (VERBOSE) {
System.out.println(" query=" + query + ", relation=" + queryRelation);
}
final FixedBitSet hits = searchIndex(s, query, maxDoc);
-
+
boolean fail = false;
NumericDocValues docIDToID = MultiDocValues.getNumericValues(reader, "id");
for (int docID = 0; docID < maxDoc; ++docID) {
@@ -320,15 +342,17 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
expected = false;
} else {
if (queryRelation == QueryRelation.CONTAINS && rectCrossesDateline(rect)) {
- // For contains we need to call the validator for each section.
+ // For contains we need to call the validator for each section.
// It is only expected if both sides are contained.
Component2D left = toRectangle2D(minX, GeoUtils.MAX_LON_INCL, minY, maxY);
Component2D right = toRectangle2D(GeoUtils.MIN_LON_INCL, maxX, minY, maxY);
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(left, shapes[id]) &&
- VALIDATOR.setRelation(queryRelation).testComponentQuery(right, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(left, shapes[id])
+ && VALIDATOR.setRelation(queryRelation).testComponentQuery(right, shapes[id]);
} else {
Component2D component2D = toRectangle2D(minX, maxX, minY, maxY);
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(component2D, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(component2D, shapes[id]);
}
}
@@ -348,7 +372,16 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
b.append(" shape=" + shapes[id] + "\n");
}
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" rect=Rectangle(lat=" + ENCODER.quantizeYCeil(rectMinY(rect)) + " TO " + ENCODER.quantizeY(rectMaxY(rect)) + " lon=" + minX + " TO " + ENCODER.quantizeX(rectMaxX(rect)) + ")\n");
+ b.append(
+ " rect=Rectangle(lat="
+ + ENCODER.quantizeYCeil(rectMinY(rect))
+ + " TO "
+ + ENCODER.quantizeY(rectMaxY(rect))
+ + " lon="
+ + minX
+ + " TO "
+ + ENCODER.quantizeX(rectMaxX(rect))
+ + ")\n");
if (true) {
fail("wrong hit (first of possibly more):\n\n" + b);
} else {
@@ -401,7 +434,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
} else if (shapes[id] == null) {
expected = false;
} else {
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(queryLine2D, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(queryLine2D, shapes[id]);
}
if (hits.get(docID) != expected) {
@@ -473,7 +507,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
} else if (shapes[id] == null) {
expected = false;
} else {
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(queryPoly2D, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(queryPoly2D, shapes[id]);
}
if (hits.get(docID) != expected) {
@@ -518,7 +553,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
for (int iter = 0; iter < iters; ++iter) {
if (VERBOSE) {
- System.out.println("\nTEST: iter=" + (iter+1) + " of " + iters + " s=" + s);
+ System.out.println("\nTEST: iter=" + (iter + 1) + " of " + iters + " s=" + s);
}
Object[] queryPoints = nextPoints();
@@ -552,7 +587,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
} else if (shapes[id] == null) {
expected = false;
} else {
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(queryPoly2D, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(queryPoly2D, shapes[id]);
}
if (hits.get(docID) != expected) {
@@ -587,7 +623,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
/** test random generated circles */
- protected void verifyRandomDistanceQueries(IndexReader reader, Object... shapes) throws Exception {
+ protected void verifyRandomDistanceQueries(IndexReader reader, Object... shapes)
+ throws Exception {
IndexSearcher s = newSearcher(reader);
final int iters = scaledIterationCount(shapes.length);
@@ -624,7 +661,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
} else if (shapes[id] == null) {
expected = false;
} else {
- expected = VALIDATOR.setRelation(queryRelation).testComponentQuery(queryCircle2D, shapes[id]);
+ expected =
+ VALIDATOR.setRelation(queryRelation).testComponentQuery(queryCircle2D, shapes[id]);
}
if (hits.get(docID) != expected) {
@@ -660,36 +698,43 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
private FixedBitSet searchIndex(IndexSearcher s, Query query, int maxDoc) throws IOException {
final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
+ s.search(
+ query,
+ new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public ScoreMode scoreMode() {
+ return ScoreMode.COMPLETE_NO_SCORES;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) {
+ hits.set(docBase + doc);
+ }
+ });
return hits;
}
-
+
protected abstract Validator getValidator();
- protected static abstract class Encoder {
+ protected abstract static class Encoder {
abstract double decodeX(int encoded);
+
abstract double decodeY(int encoded);
+
abstract double quantizeX(double raw);
+
abstract double quantizeXCeil(double raw);
+
abstract double quantizeY(double raw);
+
abstract double quantizeYCeil(double raw);
}
@@ -706,7 +751,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
/** validator class used to test query results against "ground truth" */
- protected static abstract class Validator {
+ protected abstract static class Validator {
Encoder encoder;
Validator(Encoder encoder) {
@@ -714,7 +759,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
}
protected QueryRelation queryRelation = QueryRelation.INTERSECTS;
-
+
public abstract boolean testComponentQuery(Component2D line2d, Object shape);
public Validator setRelation(QueryRelation relation) {
@@ -729,14 +774,16 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
boolean contains;
ShapeField.decodeTriangle(field.binaryValue().bytes, decodedTriangle);
switch (decodedTriangle.type) {
- case POINT: {
+ case POINT:
+ {
double y = encoder.decodeY(decodedTriangle.aY);
double x = encoder.decodeX(decodedTriangle.aX);
intersects = query.contains(x, y);
contains = intersects;
break;
}
- case LINE: {
+ case LINE:
+ {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
@@ -745,7 +792,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
contains = query.containsLine(aX, aY, bX, bY);
break;
}
- case TRIANGLE: {
+ case TRIANGLE:
+ {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
@@ -757,7 +805,8 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
break;
}
default:
- throw new IllegalArgumentException("Unsupported triangle type :[" + decodedTriangle.type + "]");
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + decodedTriangle.type + "]");
}
assertTrue((contains == intersects) || (contains == false && intersects == true));
if (queryRelation == QueryRelation.DISJOINT && intersects) {
@@ -778,13 +827,15 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
ShapeField.decodeTriangle(field.binaryValue().bytes, decodedTriangle);
Component2D.WithinRelation relation;
switch (decodedTriangle.type) {
- case POINT: {
+ case POINT:
+ {
double y = encoder.decodeY(decodedTriangle.aY);
double x = encoder.decodeX(decodedTriangle.aX);
relation = query.withinPoint(x, y);
break;
}
- case LINE: {
+ case LINE:
+ {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
@@ -792,18 +843,30 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
relation = query.withinLine(aX, aY, decodedTriangle.ab, bX, bY);
break;
}
- case TRIANGLE: {
+ case TRIANGLE:
+ {
double aY = encoder.decodeY(decodedTriangle.aY);
double aX = encoder.decodeX(decodedTriangle.aX);
double bY = encoder.decodeY(decodedTriangle.bY);
double bX = encoder.decodeX(decodedTriangle.bX);
double cY = encoder.decodeY(decodedTriangle.cY);
double cX = encoder.decodeX(decodedTriangle.cX);
- relation = query.withinTriangle(aX, aY, decodedTriangle.ab, bX, bY, decodedTriangle.bc, cX, cY, decodedTriangle.ca);
+ relation =
+ query.withinTriangle(
+ aX,
+ aY,
+ decodedTriangle.ab,
+ bX,
+ bY,
+ decodedTriangle.bc,
+ cX,
+ cY,
+ decodedTriangle.ca);
break;
}
default:
- throw new IllegalArgumentException("Unsupported triangle type :[" + decodedTriangle.type + "]");
+ throw new IllegalArgumentException(
+ "Unsupported triangle type :[" + decodedTriangle.type + "]");
}
if (relation == Component2D.WithinRelation.NOTWITHIN) {
return relation;
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
index 3e44813..a591483 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
@@ -16,10 +16,12 @@
*/
package org.apache.lucene.document;
-import java.util.Arrays;
-import java.util.Random;
+import static org.apache.lucene.geo.XYEncodingUtils.decode;
+import static org.apache.lucene.geo.XYEncodingUtils.encode;
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+import java.util.Arrays;
+import java.util.Random;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.ShapeTestUtil;
@@ -33,11 +35,8 @@ import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.TestUtil;
-import static org.apache.lucene.geo.XYEncodingUtils.decode;
-import static org.apache.lucene.geo.XYEncodingUtils.encode;
-
-/** Base test case for testing indexing and search functionality of cartesian geometry **/
-public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
+/** Base test case for testing indexing and search functionality of cartesian geometry * */
+public abstract class BaseXYShapeTestCase extends BaseSpatialTestCase {
protected abstract ShapeType getShapeType();
protected Object nextShape() {
@@ -46,8 +45,15 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
/** factory method to create a new bounding box query */
@Override
- protected Query newRectQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) {
- return XYShape.newBoxQuery(field, queryRelation, (float)minX, (float)maxX, (float)minY, (float)maxY);
+ protected Query newRectQuery(
+ String field,
+ QueryRelation queryRelation,
+ double minX,
+ double maxX,
+ double minY,
+ double maxY) {
+ return XYShape.newBoxQuery(
+ field, queryRelation, (float) minX, (float) maxX, (float) minY, (float) maxY);
}
/** factory method to create a new line query */
@@ -59,12 +65,14 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
/** factory method to create a new polygon query */
@Override
protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
- return XYShape.newPolygonQuery(field, queryRelation, Arrays.stream(polygons).toArray(XYPolygon[]::new));
+ return XYShape.newPolygonQuery(
+ field, queryRelation, Arrays.stream(polygons).toArray(XYPolygon[]::new));
}
@Override
protected Query newPointsQuery(String field, QueryRelation queryRelation, Object... points) {
- return XYShape.newPointQuery(field, queryRelation, Arrays.stream(points).toArray(float[][]::new));
+ return XYShape.newPointQuery(
+ field, queryRelation, Arrays.stream(points).toArray(float[][]::new));
}
@Override
@@ -76,7 +84,7 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
protected Component2D toPoint2D(Object... points) {
float[][] p = Arrays.stream(points).toArray(float[][]::new);
XYPoint[] pointArray = new XYPoint[points.length];
- for (int i =0; i < points.length; i++) {
+ for (int i = 0; i < points.length; i++) {
pointArray[i] = new XYPoint(p[i][0], p[i][1]);
}
return XYGeometry.create(pointArray);
@@ -94,7 +102,8 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
@Override
protected Component2D toRectangle2D(double minX, double maxX, double minY, double maxY) {
- return XYGeometry.create(new XYRectangle((float)minX, (float)maxX, (float)minY, (float)maxY));
+ return XYGeometry.create(
+ new XYRectangle((float) minX, (float) maxX, (float) minY, (float) maxY));
}
@Override
@@ -109,22 +118,22 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
@Override
protected double rectMinX(Object rect) {
- return ((XYRectangle)rect).minX;
+ return ((XYRectangle) rect).minX;
}
@Override
protected double rectMaxX(Object rect) {
- return ((XYRectangle)rect).maxX;
+ return ((XYRectangle) rect).maxX;
}
@Override
protected double rectMinY(Object rect) {
- return ((XYRectangle)rect).minY;
+ return ((XYRectangle) rect).minY;
}
@Override
protected double rectMaxY(Object rect) {
- return ((XYRectangle)rect).maxY;
+ return ((XYRectangle) rect).maxY;
}
@Override
@@ -149,8 +158,8 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
int numPoints = TestUtil.nextInt(random, 1, 20);
float[][] points = new float[numPoints][2];
for (int i = 0; i < numPoints; i++) {
- points[i][0] = ShapeTestUtil.nextFloat(random);
- points[i][1] = ShapeTestUtil.nextFloat(random);
+ points[i][0] = ShapeTestUtil.nextFloat(random);
+ points[i][1] = ShapeTestUtil.nextFloat(random);
}
return points;
}
@@ -172,6 +181,7 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
double decodeY(int encoded) {
return decode(encoded);
}
+
@Override
double quantizeX(double raw) {
return decode(encode((float) raw));
@@ -226,6 +236,7 @@ public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
};
static ShapeType[] subList;
+
static {
subList = new ShapeType[] {POINT, LINE, POLYGON};
}
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesMultiPointPointQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesMultiPointPointQueries.java
new file mode 100644
index 0000000..e66a420
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesMultiPointPointQueries.java
@@ -0,0 +1,99 @@
+/*
+ * 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.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.Point;
+
+/**
+ * random bounding box, line, and polygon query tests for random indexed arrays of {@code latitude,
+ * longitude} points
+ */
+public class TestLatLonDocValuesMultiPointPointQueries extends BaseLatLonDocValueTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected Object nextShape() {
+ int n = random().nextInt(4) + 1;
+ Point[] points = new Point[n];
+ for (int i = 0; i < n; i++) {
+ points[i] = (Point) ShapeType.POINT.nextShape();
+ }
+ return points;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ Point[] points = (Point[]) o;
+ Field[] fields = new Field[points.length];
+ for (int i = 0; i < points.length; i++) {
+ fields[i] = new LatLonDocValuesField(FIELD_NAME, points[i].getLat(), points[i].getLon());
+ }
+ return fields;
+ }
+
+ @Override
+ public Validator getValidator() {
+ return new MultiPointValidator(ENCODER);
+ }
+
+ protected class MultiPointValidator extends Validator {
+ TestLatLonPointShapeQueries.PointValidator POINTVALIDATOR;
+
+ MultiPointValidator(Encoder encoder) {
+ super(encoder);
+ POINTVALIDATOR = new TestLatLonPointShapeQueries.PointValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POINTVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
+ @Override
+ public boolean testComponentQuery(Component2D query, Object shape) {
+ Point[] points = (Point[]) shape;
+ for (Point p : points) {
+ boolean b = POINTVALIDATOR.testComponentQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == true && queryRelation == QueryRelation.CONTAINS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesPointPointQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesPointPointQueries.java
new file mode 100644
index 0000000..1e8532b
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonDocValuesPointPointQueries.java
@@ -0,0 +1,71 @@
+/*
+ * 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.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.Point;
+
+/**
+ * random bounding box, line, and polygon query tests for random indexed arrays of {@code latitude,
+ * longitude} points
+ */
+public class TestLatLonDocValuesPointPointQueries extends BaseLatLonDocValueTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ Point point = (Point) o;
+ Field[] fields = new Field[1];
+ fields[0] = new LatLonDocValuesField(FIELD_NAME, point.getLat(), point.getLon());
+ return fields;
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new TestLatLonPointShapeQueries.PointValidator(this.ENCODER);
+ }
+
+ protected static class PointValidator extends Validator {
+ protected PointValidator(Encoder encoder) {
+ super(encoder);
+ }
+
+ @Override
+ public boolean testComponentQuery(Component2D query, Object shape) {
+ Point p = (Point) shape;
+ if (queryRelation == QueryRelation.CONTAINS) {
+ return testWithinQuery(
+ query, LatLonShape.createIndexableFields("dummy", p.getLat(), p.getLon()))
+ == Component2D.WithinRelation.CANDIDATE;
+ }
+ return testComponentQuery(
+ query, LatLonShape.createIndexableFields("dummy", p.getLat(), p.getLon()));
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonMultiPointPointQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonMultiPointPointQueries.java
new file mode 100644
index 0000000..808fb9d
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonMultiPointPointQueries.java
@@ -0,0 +1,99 @@
+/*
+ * 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.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.Point;
+
+/**
+ * random bounding box, line, and polygon query tests for random indexed arrays of {@code latitude,
+ * longitude} points
+ */
+public class TestLatLonMultiPointPointQueries extends BaseLatLonPointTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected Object nextShape() {
+ int n = random().nextInt(4) + 1;
+ Point[] points = new Point[n];
+ for (int i = 0; i < n; i++) {
+ points[i] = (Point) ShapeType.POINT.nextShape();
+ }
+ return points;
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ Point[] points = (Point[]) o;
+ Field[] fields = new Field[points.length];
+ for (int i = 0; i < points.length; i++) {
+ fields[i] = new LatLonPoint(FIELD_NAME, points[i].getLat(), points[i].getLon());
+ }
+ return fields;
+ }
+
+ @Override
+ public Validator getValidator() {
+ return new MultiPointValidator(ENCODER);
+ }
+
+ protected class MultiPointValidator extends Validator {
+ TestLatLonPointShapeQueries.PointValidator POINTVALIDATOR;
+
+ MultiPointValidator(Encoder encoder) {
+ super(encoder);
+ POINTVALIDATOR = new TestLatLonPointShapeQueries.PointValidator(encoder);
+ }
+
+ @Override
+ public Validator setRelation(QueryRelation relation) {
+ super.setRelation(relation);
+ POINTVALIDATOR.queryRelation = relation;
+ return this;
+ }
+
+ @Override
+ public boolean testComponentQuery(Component2D query, Object shape) {
+ Point[] points = (Point[]) shape;
+ for (Point p : points) {
+ boolean b = POINTVALIDATOR.testComponentQuery(query, p);
+ if (b == true && queryRelation == QueryRelation.INTERSECTS) {
+ return true;
+ } else if (b == true && queryRelation == QueryRelation.CONTAINS) {
+ return true;
+ } else if (b == false && queryRelation == QueryRelation.DISJOINT) {
+ return false;
+ } else if (b == false && queryRelation == QueryRelation.WITHIN) {
+ return false;
+ }
+ }
+ return queryRelation != QueryRelation.INTERSECTS && queryRelation != QueryRelation.CONTAINS;
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointPointQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointPointQueries.java
new file mode 100644
index 0000000..752f9a0
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointPointQueries.java
@@ -0,0 +1,69 @@
+/*
+ * 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.document.ShapeField.QueryRelation;
+import org.apache.lucene.geo.Component2D;
+import org.apache.lucene.geo.Point;
+
+/**
+ * random bounding box, line, and polygon query tests for random indexed arrays of {@code latitude,
+ * longitude} points
+ */
+public class TestLatLonPointPointQueries extends BaseLatLonPointTestCase {
+
+ @Override
+ protected ShapeType getShapeType() {
+ return ShapeType.POINT;
+ }
+
+ @Override
+ protected Validator getValidator() {
+ return new TestLatLonPointShapeQueries.PointValidator(this.ENCODER);
+ }
+
+ @Override
+ protected Field[] createIndexableFields(String name, Object o) {
+ Point point = (Point) o;
+ return new Field[] {new LatLonPoint(FIELD_NAME, point.getLat(), point.getLon())};
+ }
+
+ protected static class PointValidator extends Validator {
+ protected PointValidator(Encoder encoder) {
+ super(encoder);
+ }
+
+ @Override
+ public boolean testComponentQuery(Component2D query, Object shape) {
+ Point p = (Point) shape;
+ if (queryRelation == QueryRelation.CONTAINS) {
+ return testWithinQuery(
+ query, LatLonShape.createIndexableFields("dummy", p.getLat(), p.getLon()))
+ == Component2D.WithinRelation.CANDIDATE;
+ }
+ return testComponentQuery(
+ query, LatLonShape.createIndexableFields("dummy", p.getLat(), p.getLon()));
+ }
+ }
+
+ @Slow
+ @Nightly
+ @Override
+ public void testRandomBig() throws Exception {
+ doTestRandom(10000);
+ }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
index fc08130..0d00a44 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
@@ -425,6 +425,15 @@ public class TestLatLonShape extends LuceneTestCase {
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
Document document = new Document();
Point p = GeoTestUtil.nextPoint();
+ double qLat =
+ p.getLat() == GeoUtils.MAX_LAT_INCL
+ ? p.getLat()
+ : GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitudeCeil(p.getLat()));
+ double qLon =
+ p.getLon() == GeoUtils.MAX_LON_INCL
+ ? p.getLon()
+ : GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitudeCeil(p.getLon()));
+ p = new Point(qLat, qLon);
Field[] fields = LatLonShape.createIndexableFields(FIELDNAME, p.getLat(), p.getLon());
for (Field f : fields) {
document.add(f);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
index ebb5deb..53b1c43 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
@@ -18,6 +18,7 @@ package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonDocValuesField;
+import org.apache.lucene.document.ShapeField;
import org.apache.lucene.geo.BaseGeoPointTestCase;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
@@ -47,7 +48,8 @@ public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
@Override
protected Query newGeometryQuery(String field, LatLonGeometry... geometry) {
- return LatLonDocValuesField.newSlowGeometryQuery(field, geometry);
+ return LatLonDocValuesField.newSlowGeometryQuery(
+ field, ShapeField.QueryRelation.INTERSECTS, geometry);
}
@Override
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
index 265367f..643a24f 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
+import org.apache.lucene.document.ShapeField;
import org.apache.lucene.geo.BaseGeoPointTestCase;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
@@ -54,7 +55,7 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
@Override
protected Query newGeometryQuery(String field, LatLonGeometry... geometry) {
- return LatLonPoint.newGeometryQuery(field, geometry);
+ return LatLonPoint.newGeometryQuery(field, ShapeField.QueryRelation.INTERSECTS, geometry);
}
@Override