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 07:17:23 UTC
[lucene-solr] branch master updated: LUCENE-9641: Support for
spatial relationships in LatLonPoint (#2155)
This is an automated email from the ASF dual-hosted git repository.
ivera pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/master by this push:
new 14009f4 LUCENE-9641: Support for spatial relationships in LatLonPoint (#2155)
14009f4 is described below
commit 14009f4424d847ce84fa23a71d8066dfe6c70da0
Author: Ignacio Vera <iv...@apache.org>
AuthorDate: Fri Jan 8 08:16:58 2021 +0100
LUCENE-9641: Support for spatial relationships in LatLonPoint (#2155)
Equivalent to LatLonShape, LatLonPoint can be queried now using spatial relationships.
---
lucene/CHANGES.txt | 4 +-
.../lucene/document/LatLonDocValuesField.java | 37 ++-
.../LatLonDocValuesPointInGeometryQuery.java | 166 -----------
.../lucene/document/LatLonDocValuesQuery.java | 272 ++++++++++++++++++
.../org/apache/lucene/document/LatLonPoint.java | 36 ++-
.../document/LatLonPointInGeometryQuery.java | 300 --------------------
.../apache/lucene/document/LatLonPointQuery.java | 182 ++++++++++++
.../document/LatLonShapeBoundingBoxQuery.java | 274 +++++++++---------
.../apache/lucene/document/LatLonShapeQuery.java | 270 +++++++++---------
.../{ShapeQuery.java => SpatialQuery.java} | 314 +++++++++++----------
.../org/apache/lucene/document/XYShapeQuery.java | 268 ++++++++++--------
.../org/apache/lucene/geo/GeoEncodingUtils.java | 40 +--
.../src/java/org/apache/lucene/geo/Point2D.java | 14 +-
.../document/BaseLatLonDocValueTestCase.java | 72 +++++
.../lucene/document/BaseLatLonPointTestCase.java | 139 +++++++++
.../lucene/document/BaseLatLonShapeTestCase.java | 283 +++----------------
.../lucene/document/BaseLatLonSpatialTestCase.java | 220 +++++++++++++++
...ShapeTestCase.java => BaseSpatialTestCase.java} | 6 +-
.../lucene/document/BaseXYShapeTestCase.java | 2 +-
.../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 +-
26 files changed, 1973 insertions(+), 1280 deletions(-)
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index bb5bdb3..0bda4fc 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -237,7 +237,9 @@ New Features
* LUCENE-9572: TypeAsSynonymFilter has been enhanced support ignoring some types, and to allow
the generated synonyms to copy some or all flags from the original token (Gus Heck).
-* LUCENE-9552: New LatLonPoint query that accepts an array of LatLonGeometries. (Ignacio Vera)
+* 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)
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 8c80363..1a5dfa8 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;
@@ -209,7 +210,7 @@ public class LatLonDocValuesField extends Field {
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);
}
/**
@@ -225,28 +226,42 @@ public class LatLonDocValuesField extends Field {
* 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
+ * 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,
- * LatLonGeometry...)}.
+ * 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 5bb1550..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 java.io.IOException;
-import java.util.Arrays;
-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;
-
-/** 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 06de3cf..f2d5940 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -25,6 +25,7 @@ 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;
@@ -292,21 +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);
@@ -316,7 +321,24 @@ 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());
}
/**
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 66ad777..0000000
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.document;
-
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
-import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-
-import java.io.IOException;
-import java.util.Arrays;
-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.NumericUtils;
-
-/**
- * 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 (Arrays.compareUnsigned(minPackedValue, 0, Integer.BYTES, maxLat, 0, Integer.BYTES) > 0
- || Arrays.compareUnsigned(maxPackedValue, 0, Integer.BYTES, minLat, 0, Integer.BYTES)
- < 0
- || Arrays.compareUnsigned(
- minPackedValue,
- Integer.BYTES,
- Integer.BYTES + Integer.BYTES,
- maxLon,
- 0,
- Integer.BYTES)
- > 0
- || Arrays.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..83b3bf9
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointQuery.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.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.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 (Arrays.compareUnsigned(minPackedValue, 0, Integer.BYTES, maxLat, 0, Integer.BYTES) > 0
+ || Arrays.compareUnsigned(maxPackedValue, 0, Integer.BYTES, minLat, 0, Integer.BYTES)
+ < 0
+ || Arrays.compareUnsigned(
+ minPackedValue,
+ Integer.BYTES,
+ Integer.BYTES + Integer.BYTES,
+ maxLon,
+ 0,
+ Integer.BYTES)
+ > 0
+ || Arrays.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 4dcdc47..0d6bae4 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
@@ -25,6 +25,8 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
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.GeoUtils;
@@ -38,143 +40,161 @@ import org.apache.lucene.util.NumericUtils;
* <p>The field must be indexed using {@link
* org.apache.lucene.document.LatLonShape#createIndexableFields} added per document.
*/
-final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
+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);
+ 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);
}
- case TRIANGLE:
- {
- return encodedRectangle.withinTriangle(
- scratchTriangle.aX,
- scratchTriangle.aY,
- scratchTriangle.ab,
- scratchTriangle.bX,
- scratchTriangle.bY,
- scratchTriangle.bc,
- scratchTriangle.cX,
- scratchTriangle.cY,
- scratchTriangle.ca);
+ 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");
}
- default:
- throw new IllegalArgumentException(
- "Unsupported triangle type :[" + scratchTriangle.type + "]");
- }
+ 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
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 5b7ed54..af7c87b 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java
@@ -17,6 +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;
@@ -26,12 +28,12 @@ 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 class LatLonShapeQuery extends SpatialQuery {
private final LatLonGeometry[] geometries;
private final Component2D component2D;
@@ -56,140 +58,150 @@ final class LatLonShapeQuery extends ShapeQuery {
}
@Override
- protected Relation relateRangeBBoxToQuery(
- int minXOffset,
- int minYOffset,
- byte[] minTriangle,
- int maxXOffset,
- int maxYOffset,
- byte[] maxTriangle) {
+ protected SpatialVisitor getSpatialVisitor() {
- 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));
+ 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);
- }
+ // 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);
+ @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 + "]");
- }
- }
+ 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 + "]");
+ }
+ };
+ }
- @Override
- protected boolean queryContains(byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
+ @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 + "]");
- }
- }
+ 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 + "]");
+ }
+ };
+ }
- @Override
- protected Component2D.WithinRelation queryWithin(
- byte[] t, ShapeField.DecodedTriangle scratchTriangle) {
- ShapeField.decodeTriangle(t, scratchTriangle);
+ @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 + "]");
- }
+ 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 + "]");
+ }
+ };
+ }
+ };
}
@Override
diff --git a/lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java b/lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java
similarity index 67%
rename from lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java
rename to lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java
index b9052e9..17208d5 100644
--- a/lucene/core/src/java/org/apache/lucene/document/ShapeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java
@@ -18,6 +18,9 @@ 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;
@@ -42,29 +45,10 @@ 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>
- *
- * <p>
+ * 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 ShapeQuery extends Query {
+abstract class SpatialQuery extends Query {
/** field name */
final String field;
/**
@@ -74,72 +58,65 @@ abstract class ShapeQuery extends Query {
*/
final QueryRelation queryRelation;
- protected ShapeQuery(String field, final QueryRelation queryType) {
+ 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 = queryType;
+ this.queryRelation = queryRelation;
}
/**
- * 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}
+ * returns the spatial visitor to be used for this query. Called before generating the query
+ * {@link Weight}
*/
- 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 + "]");
+ 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);
}
- }
- /** 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);
+ 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 + "]");
+ }
}
- return r;
}
@Override
@@ -151,7 +128,8 @@ abstract class ShapeQuery extends Query {
@Override
public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) {
- final ShapeQuery query = this;
+ final SpatialQuery query = this;
+ final SpatialVisitor spatialVisitor = getSpatialVisitor();
return new ConstantScoreWeight(query, boost) {
@Override
@@ -176,11 +154,11 @@ abstract class ShapeQuery extends Query {
// 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);
+ 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
@@ -202,13 +180,14 @@ abstract class ShapeQuery extends Query {
} else {
if (queryRelation != QueryRelation.INTERSECTS
&& queryRelation != QueryRelation.CONTAINS
- && hasAnyHits(query, values) == false) {
+ && 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, ShapeQuery.this) {
+ return new RelationScorerSupplier(values, spatialVisitor, queryRelation, field) {
@Override
public Scorer get(long leadCost) throws IOException {
return getScorer(reader, weight, score(), scoreMode);
@@ -249,15 +228,15 @@ abstract class ShapeQuery extends Query {
/** class specific equals check */
protected boolean equalsTo(Object o) {
- return Objects.equals(field, ((ShapeQuery) o).field)
- && this.queryRelation == ((ShapeQuery) o).queryRelation;
+ return Objects.equals(field, ((SpatialQuery) o).field)
+ && this.queryRelation == ((SpatialQuery) o).queryRelation;
}
/**
* transpose the relation; INSIDE becomes OUTSIDE, OUTSIDE becomes INSIDE, CROSSES remains
* unchanged
*/
- private static Relation transposeRelation(Relation r) {
+ protected static Relation transposeRelation(Relation r) {
if (r == Relation.CELL_INSIDE_QUERY) {
return Relation.CELL_OUTSIDE_QUERY;
} else if (r == Relation.CELL_OUTSIDE_QUERY) {
@@ -271,36 +250,46 @@ abstract class ShapeQuery extends Query {
*/
private abstract static class RelationScorerSupplier extends ScorerSupplier {
private final PointValues values;
- private final ShapeQuery query;
+ private final SpatialVisitor spatialVisitor;
+ private final QueryRelation queryRelation;
+ private final String field;
private long cost = -1;
- RelationScorerSupplier(final PointValues values, final ShapeQuery query) {
+ RelationScorerSupplier(
+ final PointValues values,
+ SpatialVisitor spatialVisitor,
+ final QueryRelation queryRelation,
+ final String field) {
this.values = values;
- this.query = query;
+ 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 (query.getQueryRelation()) {
+ switch (queryRelation) {
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);
+ 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 :[" + query.getQueryRelation() + "]");
+ throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
}
}
- /** Scorer used for INTERSECTS * */
+ /** 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 (values.getDocCount() == reader.maxDoc()
+ 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
@@ -309,29 +298,27 @@ abstract class ShapeQuery extends 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));
+ values.intersect(getInverseDenseVisitor(spatialVisitor, queryRelation, result, cost));
final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
- }
- if (values.getDocCount() < (values.size() >>> 2)) {
+ } 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(query, result, cost));
+ 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, query.getField());
- values.intersect(getSparseVisitor(query, docIdSetBuilder));
+ 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 * */
+ /** Scorer used for WITHIN and DISJOINT */
private Scorer getDenseScorer(
LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode)
throws IOException {
@@ -343,17 +330,17 @@ abstract class ShapeQuery extends Query {
// are potential matches
result.set(0, reader.maxDoc());
// Remove false positives
- values.intersect(getInverseDenseVisitor(query, result, cost));
+ 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(query, result, excluded, cost));
+ 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(query, result));
+ values.intersect(getShallowInverseDenseVisitor(spatialVisitor, queryRelation, result));
}
assert cost[0] > 0 || result.cardinality() == 0;
final DocIdSetIterator iterator =
@@ -368,7 +355,8 @@ abstract class ShapeQuery extends Query {
final long[] cost = new long[] {0};
// Get potential documents.
final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
- values.intersect(getContainsDenseVisitor(query, result, excluded, cost));
+ values.intersect(
+ getContainsDenseVisitor(spatialVisitor, queryRelation, result, excluded, cost));
result.andNot(excluded);
assert cost[0] > 0 || result.cardinality() == 0;
final DocIdSetIterator iterator =
@@ -380,7 +368,7 @@ abstract class ShapeQuery extends Query {
public long cost() {
if (cost == -1) {
// Computing the cost may be expensive, so only do it if necessary
- cost = values.estimateDocCount(getEstimateVisitor(query));
+ cost = values.estimateDocCount(getEstimateVisitor(spatialVisitor, queryRelation));
assert cost >= 0;
}
return cost;
@@ -388,7 +376,10 @@ abstract class ShapeQuery extends Query {
}
/** create a visitor for calculating point count estimates for the provided relation */
- private static IntersectVisitor getEstimateVisitor(final ShapeQuery query) {
+ 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) {
@@ -402,7 +393,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
+ return innerFunction.apply(minTriangle, maxTriangle);
}
};
}
@@ -412,9 +403,13 @@ abstract class ShapeQuery extends Query {
* INTERSECT when the number of docs <= 4 * number of points )
*/
private static IntersectVisitor getSparseVisitor(
- final ShapeQuery query, final DocIdSetBuilder result) {
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
DocIdSetBuilder.BulkAdder adder;
@Override
@@ -429,14 +424,14 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
visit(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
@@ -446,16 +441,21 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
+ return innerFunction.apply(minTriangle, maxTriangle);
}
};
}
- /** Scorer used for INTERSECTS when the number of points > 4 * number of docs * */
+ /** 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) {
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
@@ -466,7 +466,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] t) {
if (result.get(docID) == false) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
visit(docID);
}
}
@@ -474,7 +474,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
@@ -484,7 +484,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
+ return innerFunction.apply(minTriangle, maxTriangle);
}
};
}
@@ -494,13 +494,15 @@ abstract class ShapeQuery extends Query {
* WITHIN & DISJOINT
*/
private static IntersectVisitor getDenseVisitor(
- final ShapeQuery query,
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
@Override
public void visit(int docID) {
result.set(docID);
@@ -510,7 +512,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] t) {
if (excluded.get(docID) == false) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
visit(docID);
} else {
excluded.set(docID);
@@ -520,7 +522,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- boolean matches = query.queryMatches(t, scratchTriangle, query.getQueryRelation());
+ boolean matches = leafPredicate.test(t);
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (matches) {
@@ -533,7 +535,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
+ return innerFunction.apply(minTriangle, maxTriangle);
}
};
}
@@ -543,13 +545,15 @@ abstract class ShapeQuery extends Query {
* CONTAINS
*/
private static IntersectVisitor getContainsDenseVisitor(
- final ShapeQuery query,
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
-
@Override
public void visit(int docID) {
excluded.set(docID);
@@ -558,7 +562,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] t) {
if (excluded.get(docID) == false) {
- Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
+ Component2D.WithinRelation within = leafFunction.apply(t);
if (within == Component2D.WithinRelation.CANDIDATE) {
cost[0]++;
result.set(docID);
@@ -570,7 +574,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- Component2D.WithinRelation within = query.queryWithin(t, scratchTriangle);
+ Component2D.WithinRelation within = leafFunction.apply(t);
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (within == Component2D.WithinRelation.CANDIDATE) {
@@ -584,7 +588,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
- return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
+ return innerFunction.apply(minTriangle, maxTriangle);
}
};
}
@@ -594,9 +598,14 @@ abstract class ShapeQuery extends Query {
* bitset; used with WITHIN & DISJOINT
*/
private static IntersectVisitor getInverseDenseVisitor(
- final ShapeQuery query, final FixedBitSet result, final long[] cost) {
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
@@ -607,8 +616,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] packedTriangle) {
if (result.get(docID)) {
- if (query.queryMatches(packedTriangle, scratchTriangle, query.getQueryRelation())
- == false) {
+ if (leafPredicate.test(packedTriangle) == false) {
visit(docID);
}
}
@@ -616,7 +624,7 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation()) == false) {
+ if (leafPredicate.test(t) == false) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
@@ -626,8 +634,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- return transposeRelation(
- query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
+ return transposeRelation(innerFunction.apply(minPackedValue, maxPackedValue));
}
};
}
@@ -637,7 +644,10 @@ abstract class ShapeQuery extends Query {
* bitset; used with WITHIN & DISJOINT. This visitor only takes into account inner nodes
*/
private static IntersectVisitor getShallowInverseDenseVisitor(
- final ShapeQuery query, final FixedBitSet result) {
+ final SpatialVisitor spatialVisitor, QueryRelation queryRelation, final FixedBitSet result) {
+ final BiFunction<byte[], byte[], Relation> innerFunction =
+ spatialVisitor.getInnerFunction(queryRelation);
+ ;
return new IntersectVisitor() {
@Override
@@ -657,8 +667,7 @@ abstract class ShapeQuery extends Query {
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- return transposeRelation(
- query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
+ return transposeRelation(innerFunction.apply(minPackedValue, maxPackedValue));
}
};
}
@@ -667,12 +676,15 @@ abstract class ShapeQuery extends Query {
* 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)
+ 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() {
- final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
@@ -681,23 +693,21 @@ abstract class ShapeQuery extends Query {
@Override
public void visit(int docID, byte[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
throw new CollectionTerminatedException();
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) {
- if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
+ if (leafPredicate.test(t)) {
throw new CollectionTerminatedException();
}
}
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
- Relation rel =
- query.relateRangeToQuery(
- minPackedValue, maxPackedValue, query.getQueryRelation());
+ Relation rel = innerFunction.apply(minPackedValue, maxPackedValue);
if (rel == Relation.CELL_INSIDE_QUERY) {
throw new CollectionTerminatedException();
}
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 e5cf429..9726d0b 100644
--- a/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java
@@ -19,6 +19,8 @@ package org.apache.lucene.document;
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;
@@ -32,7 +34,7 @@ import org.apache.lucene.util.NumericUtils;
*
* <p>The field must be indexed using {@link XYShape#createIndexableFields} added per document.
*/
-final class XYShapeQuery extends ShapeQuery {
+final class XYShapeQuery extends SpatialQuery {
final XYGeometry[] geometries;
private final Component2D component2D;
@@ -44,128 +46,148 @@ final class XYShapeQuery extends ShapeQuery {
}
@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 + "]");
- }
- }
-
- @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 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 + "]");
- }
+ 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
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 e673c9e..d7ff62b 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
@@ -178,7 +178,13 @@ public final class GeoEncodingUtils {
box ->
GeoUtils.relate(
box.minLat, box.maxLat, box.minLon, box.maxLon, lat, lon, distanceSortKey, axisLat);
- final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
+ final Grid subBoxes =
+ createSubBoxes(
+ boundingBox.minLat,
+ boundingBox.maxLat,
+ boundingBox.minLon,
+ boundingBox.maxLon,
+ boxToRelation);
return new DistancePredicate(
subBoxes.latShift,
@@ -200,11 +206,11 @@ public final class GeoEncodingUtils {
* @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 Grid subBoxes =
+ createSubBoxes(
+ tree.getMinY(), tree.getMaxY(), tree.getMinX(), tree.getMaxX(), boxToRelation);
return new Component2DPredicate(
subBoxes.latShift,
@@ -218,13 +224,17 @@ public final class GeoEncodingUtils {
}
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)) {
+ 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]);
}
@@ -243,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);
@@ -265,10 +275,8 @@ public final class GeoEncodingUtils {
boxToRelation
.apply(
new Rectangle(
- decodeLatitude(boxMinLat),
- decodeLatitude(boxMaxLat),
- decodeLongitude(boxMinLon),
- decodeLongitude(boxMaxLon)))
+ decodeLatitude(boxMinLat), decodeLatitude(boxMaxLat),
+ decodeLongitude(boxMinLon), decodeLongitude(boxMaxLon)))
.ordinal();
}
}
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 377b5b4..32b9f42 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Point2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Point2D.java
@@ -167,9 +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 0426ac0..36898a8 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
@@ -16,28 +16,14 @@
*/
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.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher;
@@ -45,18 +31,12 @@ 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;
-
-/** 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,
@@ -68,14 +48,12 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
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));
}
- /** factory method to create a new polygon query */
@Override
protected Query newPolygonQuery(String field, QueryRelation queryRelation, Object... polygons) {
return LatLonShape.newPolygonQuery(
@@ -89,76 +67,56 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
}
@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);
- }
-
- @Override
protected Query newDistanceQuery(String field, QueryRelation queryRelation, Object circle) {
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() {
@@ -224,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);
@@ -257,59 +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());
@@ -338,99 +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 99%
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 90d11d1..2d2da74 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java
@@ -52,9 +52,9 @@ import org.apache.lucene.util.TestUtil;
* 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
+ * {@link BaseLatLonSpatialTestCase} for testing Lat Lon geospatial geometry
*/
-public abstract class BaseShapeTestCase extends LuceneTestCase {
+public abstract class BaseSpatialTestCase extends LuceneTestCase {
/** name of the LatLonShape indexed field */
protected static final String FIELD_NAME = "shape";
@@ -65,7 +65,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
QueryRelation.INTERSECTS, QueryRelation.DISJOINT, QueryRelation.CONTAINS
};
- public BaseShapeTestCase() {
+ public BaseSpatialTestCase() {
ENCODER = getEncoder();
VALIDATOR = getValidator();
}
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 850a718..410c836 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseXYShapeTestCase.java
@@ -36,7 +36,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.util.TestUtil;
/** Base test case for testing indexing and search functionality of cartesian geometry * */
-public abstract class BaseXYShapeTestCase extends BaseShapeTestCase {
+public abstract class BaseXYShapeTestCase extends BaseSpatialTestCase {
protected abstract ShapeType getShapeType();
protected Object nextShape() {
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 70ddb7c..a916602 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
@@ -456,6 +456,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 f52c769..2d3e6b2 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;
@@ -49,7 +50,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 9abbe64..c2cea11 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -19,6 +19,7 @@ package org.apache.lucene.search;
import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
+import org.apache.lucene.document.ShapeField;
import org.apache.lucene.geo.BaseGeoPointTestCase;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
@@ -55,7 +56,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