diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index eef4de49469..d049e55645b 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -212,6 +212,8 @@ 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)
+
Improvements
---------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
deleted file mode 100644
index 5ea137b15a2..00000000000
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesDistanceQuery.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.document;
-
-import java.io.IOException;
-
-import org.apache.lucene.geo.GeoEncodingUtils;
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.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;
-
-/** Distance query for {@link LatLonDocValuesField}. */
-final class LatLonDocValuesDistanceQuery extends Query {
-
- private final String field;
- private final double latitude, longitude;
- private final double radiusMeters;
-
- LatLonDocValuesDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
- if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
- throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
- }
- GeoUtils.checkLatitude(latitude);
- GeoUtils.checkLongitude(longitude);
- if (field == null) {
- throw new IllegalArgumentException("field must not be null");
- }
- this.field = field;
- this.latitude = latitude;
- this.longitude = longitude;
- this.radiusMeters = radiusMeters;
- }
-
- @Override
- public void visit(QueryVisitor visitor) {
- if (visitor.acceptField(field)) {
- visitor.visitLeaf(this);
- }
- }
-
- @Override
- public String toString(String field) {
- StringBuilder sb = new StringBuilder();
- if (!this.field.equals(field)) {
- sb.append(this.field);
- sb.append(':');
- }
- sb.append(latitude);
- sb.append(",");
- sb.append(longitude);
- sb.append(" +/- ");
- sb.append(radiusMeters);
- sb.append(" meters");
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (sameClassAs(obj) == false) {
- return false;
- }
- LatLonDocValuesDistanceQuery other = (LatLonDocValuesDistanceQuery) obj;
- return field.equals(other.field) &&
- Double.doubleToLongBits(latitude) == Double.doubleToLongBits(other.latitude) &&
- Double.doubleToLongBits(longitude) == Double.doubleToLongBits(other.longitude) &&
- Double.doubleToLongBits(radiusMeters) == Double.doubleToLongBits(other.radiusMeters);
- }
-
- @Override
- public int hashCode() {
- int h = classHash();
- h = 31 * h + field.hashCode();
- h = 31 * h + Double.hashCode(latitude);
- h = 31 * h + Double.hashCode(longitude);
- h = 31 * h + Double.hashCode(radiusMeters);
- return h;
- }
-
- @Override
- public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
- return new ConstantScoreWeight(this, boost) {
-
- private final GeoEncodingUtils.DistancePredicate distancePredicate = GeoEncodingUtils.createDistancePredicate(latitude, longitude, radiusMeters);
-
- @Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
- final SortedNumericDocValues values = context.reader().getSortedNumericDocValues(field);
- if (values == null) {
- return null;
- }
-
- final TwoPhaseIterator iterator = new TwoPhaseIterator(values) {
-
- @Override
- public boolean matches() throws IOException {
- for (int i = 0, count = values.docValueCount(); i < count; ++i) {
- final long value = values.nextValue();
- final int lat = (int) (value >>> 32);
- final int lon = (int) (value & 0xFFFFFFFF);
- if (distancePredicate.test(lat, lon)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public float matchCost() {
- return 100f; // TODO: what should it be?
- }
-
- };
- return new ConstantScoreScorer(this, boost, scoreMode, iterator);
- }
-
- @Override
- public boolean isCacheable(LeafReaderContext ctx) {
- return DocValues.isCacheable(ctx, field);
- }
-
- };
- }
-
-}
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 07f1bb8b73a..e263f78b850 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesField.java
@@ -21,7 +21,10 @@ import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
+import org.apache.lucene.geo.Circle;
+import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.search.FieldDoc;
@@ -177,7 +180,8 @@ public class LatLonDocValuesField extends Field {
* @throws IllegalArgumentException if {@code field} is null, location has invalid coordinates, or radius is invalid.
*/
public static Query newSlowDistanceQuery(String field, double latitude, double longitude, double radiusMeters) {
- return new LatLonDocValuesDistanceQuery(field, latitude, longitude, radiusMeters);
+ Circle circle = new Circle(latitude, longitude, radiusMeters);
+ return newSlowGeometryQuery(field, circle);
}
/**
@@ -192,6 +196,26 @@ public class LatLonDocValuesField extends Field {
* @throws IllegalArgumentException if {@code field} is null or polygons is empty or contain a null polygon.
*/
public static Query newSlowPolygonQuery(String field, Polygon... polygons) {
- return new LatLonDocValuesPointInPolygonQuery(field, polygons);
+ return newSlowGeometryQuery(field, polygons);
+ }
+
+ /**
+ * Create a query for matching points within the supplied geometries. Line geometries are not supported.
+ * This query is usually slow as it does not use an index structure and needs
+ * to verify documents one-by-one in order to know whether they match. It is
+ * best used wrapped in an {@link IndexOrDocValuesQuery} alongside a
+ * {@link LatLonPoint#newGeometryQuery(String, LatLonGeometry...)}.
+ * @param field field name. 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.
+ */
+ public static Query newSlowGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
+ if (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);
}
}
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java
similarity index 72%
rename from lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java
rename to lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java
index abf1c883884..3d6f25b9c04 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInGeometryQuery.java
@@ -17,13 +17,10 @@
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.Polygon;
+import org.apache.lucene.geo.Line;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
@@ -37,30 +34,36 @@ import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.Weight;
-/** Polygon query for {@link LatLonDocValuesField}. */
-public class LatLonDocValuesPointInPolygonQuery extends Query {
+import java.io.IOException;
+import java.util.Arrays;
+
+/** Geometry query for {@link LatLonDocValuesField}. */
+public class LatLonDocValuesPointInGeometryQuery extends Query {
private final String field;
- private final Polygon[] polygons;
+ private final LatLonGeometry[] geometries;
- LatLonDocValuesPointInPolygonQuery(String field, Polygon... polygons) {
+ LatLonDocValuesPointInGeometryQuery(String field, LatLonGeometry... geometries) {
if (field == null) {
throw new IllegalArgumentException("field must not be null");
}
- if (polygons == null) {
- throw new IllegalArgumentException("polygons must not be null");
+ if (geometries == null) {
+ throw new IllegalArgumentException("geometries must not be null");
}
- if (polygons.length == 0) {
- throw new IllegalArgumentException("polygons must not be empty");
+ if (geometries.length == 0) {
+ throw new IllegalArgumentException("geometries must not be empty");
}
- for (int i = 0; i < polygons.length; i++) {
- if (polygons[i] == null) {
- throw new IllegalArgumentException("polygon[" + i + "] must not be null");
+ 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.polygons = polygons;
+ this.geometries = geometries;
}
@Override
@@ -70,7 +73,7 @@ public class LatLonDocValuesPointInPolygonQuery extends Query {
sb.append(this.field);
sb.append(':');
}
- sb.append("polygons(").append(Arrays.toString(polygons));
+ sb.append("geometries(").append(Arrays.toString(geometries));
return sb.append(")").toString();
}
@@ -79,16 +82,16 @@ public class LatLonDocValuesPointInPolygonQuery extends Query {
if (sameClassAs(obj) == false) {
return false;
}
- LatLonDocValuesPointInPolygonQuery other = (LatLonDocValuesPointInPolygonQuery) obj;
+ LatLonDocValuesPointInGeometryQuery other = (LatLonDocValuesPointInGeometryQuery) obj;
return field.equals(other.field) &&
- Arrays.equals(polygons, other.polygons);
+ Arrays.equals(geometries, other.geometries);
}
@Override
public int hashCode() {
int h = classHash();
h = 31 * h + field.hashCode();
- h = 31 * h + Arrays.hashCode(polygons);
+ h = 31 * h + Arrays.hashCode(geometries);
return h;
}
@@ -104,8 +107,8 @@ public class LatLonDocValuesPointInPolygonQuery extends Query {
return new ConstantScoreWeight(this, boost) {
- final Component2D tree = LatLonGeometry.create(polygons);
- final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree);
+ final Component2D tree = LatLonGeometry.create(geometries);
+ final GeoEncodingUtils.Component2DPredicate component2DPredicate = GeoEncodingUtils.createComponentPredicate(tree);
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
@@ -122,7 +125,7 @@ public class LatLonDocValuesPointInPolygonQuery extends Query {
final long value = values.nextValue();
final int lat = (int) (value >>> 32);
final int lon = (int) (value & 0xFFFFFFFF);
- if (polygonPredicate.test(lat, lon)) {
+ if (component2DPredicate.test(lat, lon)) {
return true;
}
}
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 22f20c994c1..7dfb5a006e3 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -16,7 +16,10 @@
*/
package org.apache.lucene.document;
+import org.apache.lucene.geo.Circle;
+import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
+import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.search.BooleanClause;
@@ -49,6 +52,7 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil;
*
{@link #newGeometryQuery newGeometryQuery()} for matching points within an arbitrary geometry collection.
*
*
* If you also need per-document operations such as sort by distance, add a separate {@link LatLonDocValuesField} instance.
@@ -251,7 +255,29 @@ public class LatLonPoint extends Field {
* @see Polygon
*/
public static Query newPolygonQuery(String field, Polygon... polygons) {
- return new LatLonPointInPolygonQuery(field, polygons);
+ return newGeometryQuery(field, polygons);
+ }
+
+ /**
+ * Create a query for matching one or more geometries. Line geometries are not supported.
+ * @param field field name. 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.
+ * @see LatLonGeometry
+ */
+ public static Query newGeometryQuery(String field, LatLonGeometry... latLonGeometries) {
+ if (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);
+ }
+ if (latLonGeometries[0] instanceof Circle) {
+ final Circle circle = (Circle) latLonGeometries[0];
+ return newDistanceQuery(field, circle.getLat(), circle.getLon(), circle.getRadius());
+ }
+ }
+ return new LatLonPointInGeometryQuery(field, latLonGeometries);
}
/**
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
similarity index 80%
rename from lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
rename to lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
index 4285e096577..adeba14e5d4 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInGeometryQuery.java
@@ -16,13 +16,10 @@
*/
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.Polygon;
+import org.apache.lucene.geo.Line;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
@@ -42,39 +39,44 @@ import org.apache.lucene.search.Weight;
import org.apache.lucene.util.DocIdSetBuilder;
import org.apache.lucene.util.NumericUtils;
+import java.io.IOException;
+import java.util.Arrays;
+
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;
-/** Finds all previously indexed points that fall within the specified polygons.
+/** Finds all previously indexed points that fall within the specified geometries.
*
- *
The field must be indexed with using {@link org.apache.lucene.document.LatLonPoint} added per document.
+ *
The field must be indexed with using {@link LatLonPoint} added per document.
*
* @lucene.experimental */
-final class LatLonPointInPolygonQuery extends Query {
+final class LatLonPointInGeometryQuery extends Query {
final String field;
- final Polygon[] polygons;
+ final LatLonGeometry[] geometries;
- LatLonPointInPolygonQuery(String field, Polygon[] polygons) {
+ LatLonPointInGeometryQuery(String field, LatLonGeometry[] geometries) {
if (field == null) {
throw new IllegalArgumentException("field must not be null");
}
- if (polygons == null) {
- throw new IllegalArgumentException("polygons must not be null");
+ if (geometries == null) {
+ throw new IllegalArgumentException("geometries must not be null");
}
- if (polygons.length == 0) {
- throw new IllegalArgumentException("polygons must not be empty");
+ if (geometries.length == 0) {
+ throw new IllegalArgumentException("geometries must not be empty");
}
- for (int i = 0; i < polygons.length; i++) {
- if (polygons[i] == null) {
- throw new IllegalArgumentException("polygon[" + i + "] must not be null");
+ 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.polygons = polygons.clone();
- // TODO: we could also compute the maximal inner bounding box, to make relations faster to compute?
+ this.geometries = geometries.clone();
}
@Override
@@ -84,7 +86,7 @@ final class LatLonPointInPolygonQuery extends Query {
}
}
- private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result, Component2D tree, GeoEncodingUtils.PolygonPredicate polygonPredicate,
+ private IntersectVisitor getIntersectVisitor(DocIdSetBuilder result, Component2D tree, GeoEncodingUtils.Component2DPredicate component2DPredicate,
byte[] minLat, byte[] maxLat, byte[] minLon, byte[] maxLon) {
return new IntersectVisitor() {
DocIdSetBuilder.BulkAdder adder;
@@ -101,7 +103,7 @@ final class LatLonPointInPolygonQuery extends Query {
@Override
public void visit(int docID, byte[] packedValue) {
- if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
+ if (component2DPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
visit(docID);
}
@@ -109,7 +111,7 @@ final class LatLonPointInPolygonQuery extends Query {
@Override
public void visit(DocIdSetIterator iterator, byte[] packedValue) throws IOException {
- if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
+ if (component2DPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
@@ -141,9 +143,9 @@ final class LatLonPointInPolygonQuery extends Query {
@Override
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
- final Component2D tree = LatLonGeometry.create(polygons);
- final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree);
- // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons
+ final Component2D tree = LatLonGeometry.create(geometries);
+ 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];
@@ -175,7 +177,7 @@ final class LatLonPointInPolygonQuery extends Query {
long cost = -1;
DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
- final IntersectVisitor visitor = getIntersectVisitor(result, tree, polygonPredicate, minLat, maxLat, minLon, maxLon);
+ final IntersectVisitor visitor = getIntersectVisitor(result, tree, component2DPredicate, minLat, maxLat, minLon, maxLon);
@Override
public Scorer get(long leadCost) throws IOException {
@@ -217,9 +219,9 @@ final class LatLonPointInPolygonQuery extends Query {
return field;
}
- /** Returns a copy of the internal polygon array */
- public Polygon[] getPolygons() {
- return polygons.clone();
+ /** Returns a copy of the internal geometry array */
+ public LatLonGeometry[] getGeometries() {
+ return geometries.clone();
}
@Override
@@ -227,7 +229,7 @@ final class LatLonPointInPolygonQuery extends Query {
final int prime = 31;
int result = classHash();
result = prime * result + field.hashCode();
- result = prime * result + Arrays.hashCode(polygons);
+ result = prime * result + Arrays.hashCode(geometries);
return result;
}
@@ -237,9 +239,9 @@ final class LatLonPointInPolygonQuery extends Query {
equalsTo(getClass().cast(other));
}
- private boolean equalsTo(LatLonPointInPolygonQuery other) {
+ private boolean equalsTo(LatLonPointInGeometryQuery other) {
return field.equals(other.field) &&
- Arrays.equals(polygons, other.polygons);
+ Arrays.equals(geometries, other.geometries);
}
@Override
@@ -252,7 +254,7 @@ final class LatLonPointInPolygonQuery extends Query {
sb.append(this.field);
sb.append(':');
}
- sb.append(Arrays.toString(polygons));
+ sb.append(Arrays.toString(geometries));
return sb.toString();
}
}
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 465cf511637..d3ad3e253bc 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonShapeBoundingBoxQuery.java
@@ -178,37 +178,23 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
EncodedRectangle(double minLat, double maxLat, double minLon, double maxLon) {
this.bbox = new byte[4 * BYTES];
- int minXenc = encodeLongitudeCeil(minLon);
- int maxXenc = encodeLongitude(maxLon);
- int minYenc = encodeLatitudeCeil(minLat);
- int maxYenc = encodeLatitude(maxLat);
- if (minYenc > maxYenc) {
- minYenc = maxYenc;
+ if (minLon == 180.0 && minLon > maxLon) {
+ minLon = -180;
}
- this.minY = minYenc;
- this.maxY = maxYenc;
-
- if (minLon > maxLon == true) {
- this.crossesDateline = true;
+ this.minX = encodeLongitudeCeil(minLon);
+ this.maxX = encodeLongitude(maxLon);
+ this.minY = encodeLatitudeCeil(minLat);
+ this.maxY = encodeLatitude(maxLat);
+ this.crossesDateline = minLon > maxLon;
+ if (this.crossesDateline) {
// crossing dateline is split into east/west boxes
this.west = new byte[4 * BYTES];
- this.minX = minXenc;
- this.maxX = maxXenc;
encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west);
encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox);
} else {
- this.crossesDateline = false;
- // encodeLongitudeCeil may cause minX to be > maxX iff
- // the delta between the longitude < the encoding resolution
- if (minXenc > maxXenc) {
- minXenc = maxXenc;
- }
this.west = null;
- this.minX = minXenc;
- this.maxX = maxXenc;
encode(this.minX, this.maxX, this.minY, this.maxY, bbox);
}
-
}
/**
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Circle.java b/lucene/core/src/java/org/apache/lucene/geo/Circle.java
index 4bb63c845b4..30121c20e75 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Circle.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Circle.java
@@ -35,9 +35,6 @@ public final class Circle extends LatLonGeometry {
private final double lon;
/** radius in meters */
private final double radiusMeters;
- /** Max radius allowed, half of the earth mean radius.*/
- public static double MAX_RADIUS = GeoUtils.EARTH_MEAN_RADIUS_METERS / 2.0;
-
/**
* Creates a new circle from the supplied latitude/longitude center and a radius in meters..
@@ -45,11 +42,8 @@ public final class Circle extends LatLonGeometry {
public Circle(double lat, double lon, double radiusMeters) {
GeoUtils.checkLatitude(lat);
GeoUtils.checkLongitude(lon);
- if (radiusMeters <= 0) {
- throw new IllegalArgumentException("radius must be bigger than 0, got " + radiusMeters);
- }
- if (radiusMeters < MAX_RADIUS == false) {
- throw new IllegalArgumentException("radius must be lower than " + MAX_RADIUS + ", got " + radiusMeters);
+ if (Double.isFinite(radiusMeters) == false || radiusMeters < 0) {
+ throw new IllegalArgumentException("radiusMeters: '" + radiusMeters + "' is invalid");
}
this.lat = lat;
this.lon = lon;
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 5c7078d061a..da638805276 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java
@@ -178,16 +178,16 @@ public final class GeoEncodingUtils {
lat, lon, distanceSortKey);
}
- /** Create a predicate that checks whether points are within a polygon.
+ /** Create a predicate that checks whether points are within a geometry.
* It works the same way as {@link #createDistancePredicate}.
* @lucene.internal */
- public static PolygonPredicate createComponentPredicate(Component2D tree) {
+ public static Component2DPredicate createComponentPredicate(Component2D tree) {
final Rectangle boundingBox = new Rectangle(tree.getMinY(), tree.getMaxY(), tree.getMinX(), tree.getMaxX());
final Function boxToRelation = box -> tree.relate(
box.minLon, box.maxLon, box.minLat, box.maxLat);
final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation);
- return new PolygonPredicate(
+ return new Component2DPredicate(
subBoxes.latShift, subBoxes.lonShift,
subBoxes.latBase, subBoxes.lonBase,
subBoxes.maxLatDelta, subBoxes.maxLonDelta,
@@ -342,12 +342,12 @@ public final class GeoEncodingUtils {
}
}
- /** A predicate that checks whether a given point is within a polygon. */
- public static class PolygonPredicate extends Grid {
+ /** A predicate that checks whether a given point is within a component2D geometry. */
+ public static class Component2DPredicate extends Grid {
private final Component2D tree;
- private PolygonPredicate(
+ private Component2DPredicate(
int latShift, int lonShift,
int latBase, int lonBase,
int maxLatDelta, int maxLonDelta,
diff --git a/lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java b/lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java
index b1beb5d4fdd..37b5f032991 100644
--- a/lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java
+++ b/lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java
@@ -232,28 +232,25 @@ final class Rectangle2D implements Component2D {
/** create a component2D from the provided LatLon rectangle */
static Component2D create(Rectangle rectangle) {
+ // behavior of LatLonPoint.newBoxQuery()
+ double minLongitude = rectangle.minLon;
+ boolean crossesDateline = rectangle.minLon > rectangle.maxLon;
+ if (minLongitude == 180.0 && crossesDateline) {
+ minLongitude = -180;
+ crossesDateline = false;
+ }
// need to quantize!
double qMinLat = decodeLatitude(encodeLatitudeCeil(rectangle.minLat));
double qMaxLat = decodeLatitude(encodeLatitude(rectangle.maxLat));
- double qMinLon = decodeLongitude(encodeLongitudeCeil(rectangle.minLon));
+ double qMinLon = decodeLongitude(encodeLongitudeCeil(minLongitude));
double qMaxLon = decodeLongitude(encodeLongitude(rectangle.maxLon));
- if (qMinLat > qMaxLat) {
- // encodeLatitudeCeil may cause minY to be > maxY iff
- // the delta between the longitude < the encoding resolution
- qMinLat = qMaxLat;
- }
- if (rectangle.minLon > rectangle.maxLon) {
+ if (crossesDateline) {
// for rectangles that cross the dateline we need to create two components
Component2D[] components = new Component2D[2];
components[0] = new Rectangle2D(MIN_LON_INCL_QUANTIZE, qMaxLon, qMinLat, qMaxLat);
components[1] = new Rectangle2D(qMinLon, MAX_LON_INCL_QUANTIZE, qMinLat, qMaxLat);
return ComponentTree.create(components);
} else {
- // encodeLongitudeCeil may cause minX to be > maxX iff
- // the delta between the longitude < the encoding resolution
- if (qMinLon > qMaxLon) {
- qMinLon = qMaxLon;
- }
return new Rectangle2D(qMinLon, qMaxLon, qMinLat, qMaxLat);
}
}
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 785faecab6a..831c01645db 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java
@@ -22,6 +22,7 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.document.ShapeField.QueryRelation;
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.Polygon;
@@ -109,7 +110,8 @@ public abstract class BaseLatLonShapeTestCase extends BaseShapeTestCase {
@Override
protected Circle nextCircle() {
- return new Circle(nextLatitude(), nextLongitude(), random().nextDouble() * Circle.MAX_RADIUS);
+ final double radiusMeters = random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
+ return new Circle(nextLatitude(), nextLongitude(), radiusMeters);
}
@Override
diff --git a/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java b/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
index ada90f7376a..3374ea25963 100644
--- a/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
+++ b/lucene/core/src/test/org/apache/lucene/document/BaseShapeTestCase.java
@@ -326,34 +326,23 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
assertEquals(docID, docIDToID.nextDoc());
int id = (int) docIDToID.longValue();
boolean expected;
- double qMinLon = ENCODER.quantizeXCeil(rectMinX(rect));
- double qMaxLon = ENCODER.quantizeX(rectMaxX(rect));
- double qMinLat = ENCODER.quantizeYCeil(rectMinY(rect));
- double qMaxLat = ENCODER.quantizeY(rectMaxY(rect));
+ double minLon = rectMinX(rect);
+ double maxLon = rectMaxX(rect);
+ double minLat = rectMinY(rect);
+ double maxLat = rectMaxY(rect);
if (liveDocs != null && liveDocs.get(docID) == false) {
// document is deleted
expected = false;
} else if (shapes[id] == null) {
expected = false;
} else {
- if (qMinLat > qMaxLat) {
- qMinLat = ENCODER.quantizeY(rectMaxY(rect));
- }
if (queryRelation == QueryRelation.CONTAINS && rectCrossesDateline(rect)) {
- //For contains we need to call the validator for each section. It is only expected
- //if both sides are contained.
- expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, GeoUtils.MAX_LON_INCL, shapes[id]);
- if (expected) {
- expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, GeoUtils.MIN_LON_INCL, qMaxLon, shapes[id]);
- }
+ // For contains we need to call the validator for each section.
+ // It is only expected if both sides are contained.
+ expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(minLat, maxLat, minLon, GeoUtils.MAX_LON_INCL, shapes[id]) &&
+ VALIDATOR.setRelation(queryRelation).testBBoxQuery(minLat, maxLat, GeoUtils.MIN_LON_INCL, maxLon, shapes[id]);
} else {
- // check quantized poly against quantized query
- if (qMinLon > qMaxLon && rectCrossesDateline(rect) == false) {
- // if the quantization creates a false dateline crossing (because of encodeCeil):
- // then do not use encodeCeil
- qMinLon = ENCODER.quantizeX(rectMinX(rect));
- }
- expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(qMinLat, qMaxLat, qMinLon, qMaxLon, shapes[id]);
+ expected = VALIDATOR.setRelation(queryRelation).testBBoxQuery(minLat, maxLat, minLon, maxLon, shapes[id]);
}
}
@@ -373,7 +362,7 @@ public abstract class BaseShapeTestCase extends LuceneTestCase {
b.append(" shape=" + shapes[id] + "\n");
}
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
- b.append(" rect=Rectangle(lat=" + ENCODER.quantizeYCeil(rectMinY(rect)) + " TO " + ENCODER.quantizeY(rectMaxY(rect)) + " lon=" + qMinLon + " TO " + ENCODER.quantizeX(rectMaxX(rect)) + ")\n");
+ b.append(" rect=Rectangle(lat=" + ENCODER.quantizeYCeil(rectMinY(rect)) + " TO " + ENCODER.quantizeY(rectMaxY(rect)) + " lon=" + minLon + " TO " + ENCODER.quantizeX(rectMaxX(rect)) + ")\n");
if (true) {
fail("wrong hit (first of possibly more):\n\n" + b);
} else {
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
index 11acf62c07b..9ada38916b1 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java
@@ -20,7 +20,9 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoTestUtil;
+import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Line;
+import org.apache.lucene.geo.Rectangle;
/** random bounding box, line, and polygon query tests for random generated {@code latitude, longitude} points */
public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@@ -73,21 +75,8 @@ public class TestLatLonPointShapeQueries extends BaseLatLonShapeTestCase {
@Override
public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape) {
- if (queryRelation == QueryRelation.CONTAINS) {
- return false;
- }
- Point p = (Point)shape;
- double lat = encoder.quantizeY(p.lat);
- double lon = encoder.quantizeX(p.lon);
- boolean isDisjoint = lat < minLat || lat > maxLat;
-
- isDisjoint = isDisjoint || ((minLon > maxLon)
- ? lon < minLon && lon > maxLon
- : lon < minLon || lon > maxLon);
- if (queryRelation == QueryRelation.DISJOINT) {
- return isDisjoint;
- }
- return isDisjoint == false;
+ Component2D rectangle2D = LatLonGeometry.create(new Rectangle(minLat, maxLat, minLon, maxLon));
+ return testComponentQuery(rectangle2D, shape);
}
@Override
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 e6443586e25..24d7784ab79 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonShape.java
@@ -22,6 +22,7 @@ import org.apache.lucene.geo.Circle;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoEncodingUtils;
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.Polygon;
@@ -433,7 +434,7 @@ public class TestLatLonShape extends LuceneTestCase {
IndexSearcher s = newSearcher(r);
// search by same point
- Query q = LatLonShape.newBoxQuery(FIELDNAME, QueryRelation.INTERSECTS, p.lat, p.lat, p.lon, p.lon);
+ Query q = LatLonShape.newPointQuery(FIELDNAME, QueryRelation.INTERSECTS, new double[] {p.lat, p.lon});
assertEquals(1, s.count(q));
IOUtils.close(r, dir);
}
@@ -777,10 +778,7 @@ public class TestLatLonShape extends LuceneTestCase {
double lat = GeoTestUtil.nextLatitude();
double lon = GeoTestUtil.nextLongitude();
- double radiusMeters = random().nextDouble() * Circle.MAX_RADIUS;
- while (radiusMeters == 0 || radiusMeters == Circle.MAX_RADIUS) {
- radiusMeters = random().nextDouble() * Circle.MAX_RADIUS;
- }
+ final double radiusMeters = random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
Circle circle = new Circle(lat, lon, radiusMeters);
Component2D circle2D = LatLonGeometry.create(circle);
int expected;
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestCircle.java b/lucene/core/src/test/org/apache/lucene/geo/TestCircle.java
index 8005d728e8f..37f545b2772 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestCircle.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestCircle.java
@@ -41,15 +41,15 @@ public class TestCircle extends LuceneTestCase {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
new Circle(43.5, 45.23, -1000);
});
- assertTrue(expected.getMessage().contains("radius must be bigger than 0, got -1000.0"));
+ assertTrue(expected.getMessage().contains("radiusMeters: '-1000.0' is invalid"));
}
- /** radius must be lower than 3185504.3857 */
+ /** radius must cannot be infinite */
public void testInfiniteRadius() {
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
new Circle(43.5, 45.23, Double.POSITIVE_INFINITY);
});
- assertTrue(expected.getMessage().contains("radius must be lower than 3185504.3857, got Infinity"));
+ assertTrue(expected.getMessage().contains("radiusMeters: 'Infinity' is invalid"));
}
/** equals and hashcode */
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 af113e1adc9..ebb5debda97 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonDocValuesQueries.java
@@ -20,15 +20,11 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.geo.BaseGeoPointTestCase;
import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
- @Override
- protected boolean supportsPolygons() {
- return true;
- }
-
@Override
protected void addPointToDoc(String field, Document doc, double lat, double lon) {
doc.add(new LatLonDocValuesField(field, lat, lon));
@@ -49,6 +45,11 @@ public class TestLatLonDocValuesQueries extends BaseGeoPointTestCase {
return LatLonDocValuesField.newSlowPolygonQuery(field, polygons);
}
+ @Override
+ protected Query newGeometryQuery(String field, LatLonGeometry... geometry) {
+ return LatLonDocValuesField.newSlowGeometryQuery(field, geometry);
+ }
+
@Override
protected double quantizeLat(double latRaw) {
return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
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 d3fce00a010..265367fe197 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -22,6 +22,7 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.geo.BaseGeoPointTestCase;
import org.apache.lucene.geo.GeoEncodingUtils;
+import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
@@ -51,6 +52,11 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
return LatLonPoint.newPolygonQuery(field, polygons);
}
+ @Override
+ protected Query newGeometryQuery(String field, LatLonGeometry... geometry) {
+ return LatLonPoint.newGeometryQuery(field, geometry);
+ }
+
@Override
protected double quantizeLat(double latRaw) {
return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(latRaw));
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
index c080db19b08..a4b47f0cc05 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
@@ -24,6 +24,7 @@ import java.util.BitSet;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
+import java.util.function.Consumer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.codecs.Codec;
@@ -96,14 +97,34 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
protected Rectangle nextBox() {
return org.apache.lucene.geo.GeoTestUtil.nextBox();
}
+
+ protected Circle nextCircle() {
+ return org.apache.lucene.geo.GeoTestUtil.nextCircle();
+ }
protected Polygon nextPolygon() {
return org.apache.lucene.geo.GeoTestUtil.nextPolygon();
}
- /** Whether this impl supports polygons. */
- protected boolean supportsPolygons() {
- return true;
+ protected LatLonGeometry[] nextGeometry() {
+ final int length = random().nextInt(4) + 1;
+ final LatLonGeometry[] geometries = new LatLonGeometry[length];
+ for (int i = 0; i < length; i++) {
+ final LatLonGeometry geometry;
+ switch (random().nextInt(3)) {
+ case 0:
+ geometry = nextBox();
+ break;
+ case 1:
+ geometry = nextCircle();
+ break;
+ default:
+ geometry = nextPolygon();
+ break;
+ }
+ geometries[i] = geometry;
+ }
+ return geometries;
}
/** Valid values that should not cause exception */
@@ -291,7 +312,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
/** test we can search for a polygon */
public void testPolygonBasics() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
@@ -314,7 +334,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
/** test we can search for a polygon with a hole (but still includes the doc) */
public void testPolygonHole() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
@@ -339,7 +358,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
/** test we can search for a polygon with a hole (that excludes the doc) */
public void testPolygonHoleExcludes() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
@@ -364,7 +382,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
/** test we can search for a multi-polygon */
public void testMultiPolygonBasics() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
Directory dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
@@ -389,7 +406,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
/** null field name not allowed */
public void testPolygonNullField() {
- assumeTrue("Impl does not support polygons", supportsPolygons());
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
newPolygonQuery(null, new Polygon(
new double[] { 18, 18, 19, 19, 18 },
@@ -583,27 +599,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
- final FixedBitSet hits = new FixedBitSet(r.maxDoc());
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
-
+ final FixedBitSet hits = searchIndex(s, query, r.maxDoc());
+
boolean fail = false;
for(int docID=0;docID deleted = new HashSet<>();
// RandomIndexWriter is too slow here:
IndexWriter w = new IndexWriter(dir, iwc);
- for(int id=0;id 0 && random().nextInt(100) == 42) {
- int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
- deleted.add(idToDelete);
- if (VERBOSE) {
- System.out.println(" delete id=" + idToDelete);
- }
- }
- }
-
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
+ indexPoints(lats, lons, deleted, w);
+
final IndexReader r = DirectoryReader.open(w);
w.close();
@@ -842,26 +821,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
System.out.println(" query=" + query);
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
+ final FixedBitSet hits = searchIndex(s, query, maxDoc);
boolean fail = false;
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
@@ -879,24 +839,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
}
if (hits.get(docID) != expected) {
- StringBuilder b = new StringBuilder();
- b.append("docID=(").append(docID).append(")\n");
-
- if (expected) {
- b.append("FAIL: id=").append(id).append(" should match but did not\n");
- } else {
- b.append("FAIL: id=").append(id).append(" should not match but did\n");
- }
- b.append(" box=").append(rect).append("\n");
- b.append(" query=").append(query).append(" docID=").append(docID).append("\n");
- b.append(" lat=").append(lats[id]).append(" lon=").append(lons[id]).append("\n");
- b.append(" deleted?=").append(liveDocs != null && liveDocs.get(docID) == false);
- if (true) {
- fail("wrong hit (first of possibly more):\n\n" + b);
- } else {
- System.out.println(b.toString());
- fail = true;
- }
+ buildError(docID, expected, id, lats, lons, query, liveDocs, (b) -> b.append(" rect=").append(rect));
+ fail = true;
}
}
if (fail) {
@@ -926,27 +870,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
Set deleted = new HashSet<>();
// RandomIndexWriter is too slow here:
IndexWriter w = new IndexWriter(dir, iwc);
- for(int id=0;id 0 && random().nextInt(100) == 42) {
- int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
- deleted.add(idToDelete);
- if (VERBOSE) {
- System.out.println(" delete id=" + idToDelete);
- }
- }
- }
-
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
+ indexPoints(lats, lons, deleted, w);
+
final IndexReader r = DirectoryReader.open(w);
w.close();
@@ -981,27 +906,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
System.out.println(" query=" + query);
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
-
+ final FixedBitSet hits = searchIndex(s, query, maxDoc);
+
boolean fail = false;
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
for(int docID=0;docID explain = (b) -> {
+ if (Double.isNaN(lats[id]) == false) {
+ double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lats[id], lons[id]);
+ b.append(" centerLat=").append(centerLat).append(" centerLon=").append(centerLon).append(" distanceMeters=").append(distanceMeters).append(" vs radiusMeters=").append(radiusMeters);
+ }
+ };
+ buildError(docID, expected, id, lats, lons, query, liveDocs, explain);
+ fail = true;
}
}
if (fail) {
@@ -1067,27 +961,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
Set deleted = new HashSet<>();
// RandomIndexWriter is too slow here:
IndexWriter w = new IndexWriter(dir, iwc);
- for(int id=0;id 0 && random().nextInt(100) == 42) {
- int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
- deleted.add(idToDelete);
- if (VERBOSE) {
- System.out.println(" delete id=" + idToDelete);
- }
- }
- }
-
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
+ indexPoints(lats, lons, deleted, w);
+
final IndexReader r = DirectoryReader.open(w);
w.close();
@@ -1113,26 +988,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
System.out.println(" query=" + query);
}
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public ScoreMode scoreMode() {
- return ScoreMode.COMPLETE_NO_SCORES;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
+ final FixedBitSet hits = searchIndex(s, query, maxDoc);
boolean fail = false;
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
@@ -1150,23 +1006,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
}
if (hits.get(docID) != expected) {
- StringBuilder b = new StringBuilder();
-
- if (expected) {
- b.append("FAIL: id=").append(id).append(" should match but did not\n");
- } else {
- b.append("FAIL: id=").append(id).append(" should not match but did\n");
- }
- b.append(" query=").append(query).append(" docID=").append(docID).append("\n");
- b.append(" lat=").append(lats[id]).append(" lon=").append(lons[id]).append("\n");
- b.append(" deleted?=").append(liveDocs != null && liveDocs.get(docID) == false);
- b.append(" polygon=").append(polygon);
- if (true) {
- fail("wrong hit (first of possibly more):\n\n" + b);
- } else {
- System.out.println(b.toString());
- fail = true;
- }
+ buildError(docID, expected, id, lats, lons, query, liveDocs, (b) -> b.append(" polygon=").append(polygon));
+ fail = true;
}
}
if (fail) {
@@ -1176,6 +1017,152 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
IOUtils.close(r, dir);
}
+
+ protected void verifyRandomGeometries(double[] lats, double[] lons) throws Exception {
+ IndexWriterConfig iwc = newIndexWriterConfig();
+ // Else seeds may not reproduce:
+ iwc.setMergeScheduler(new SerialMergeScheduler());
+ // Else we can get O(N^2) merging:
+ int mbd = iwc.getMaxBufferedDocs();
+ if (mbd != -1 && mbd < lats.length/100) {
+ iwc.setMaxBufferedDocs(lats.length/100);
+ }
+ Directory dir;
+ if (lats.length > 100000) {
+ dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
+ } else {
+ dir = newDirectory();
+ }
+
+ Set deleted = new HashSet<>();
+
+ // RandomIndexWriter is too slow here:
+ IndexWriter w = new IndexWriter(dir, iwc);
+ indexPoints(lats, lons, deleted, w);
+
+ final IndexReader r = DirectoryReader.open(w);
+ w.close();
+
+ // We can't wrap with "exotic" readers because points needs to work:
+ IndexSearcher s = newSearcher(r);
+
+ final int iters = atLeast(75);
+
+ Bits liveDocs = MultiBits.getLiveDocs(s.getIndexReader());
+ int maxDoc = s.getIndexReader().maxDoc();
+
+ for (int iter=0;iter b.append(" geometry=").append(Arrays.toString(geometries)));
+ fail = true;
+ }
+ }
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+
+ IOUtils.close(r, dir);
+ }
+
+ private void indexPoints(double[] lats, double[] lons, Set deleted, IndexWriter w) throws IOException {
+ for(int id=0;id 0 && random().nextInt(100) == 42) {
+ int idToDelete = random().nextInt(id);
+ w.deleteDocuments(new Term("id", ""+idToDelete));
+ deleted.add(idToDelete);
+ if (VERBOSE) {
+ System.out.println(" delete id=" + idToDelete);
+ }
+ }
+ }
+
+ if (random().nextBoolean()) {
+ w.forceMerge(1);
+ }
+ }
+
+ private FixedBitSet searchIndex(IndexSearcher s, Query query, int maxDoc) throws IOException {
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public ScoreMode scoreMode() {
+ return ScoreMode.COMPLETE_NO_SCORES;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) {
+ hits.set(docBase+doc);
+ }
+ });
+ return hits;
+ }
+
+ private void buildError(int docID, boolean expected, int id, double[] lats, double[] lons, Query query,
+ Bits liveDocs, Consumer explain) {
+ StringBuilder b = new StringBuilder();
+ if (expected) {
+ b.append("FAIL: id=").append(id).append(" should match but did not\n");
+ } else {
+ b.append("FAIL: id=").append(id).append(" should not match but did\n");
+ }
+ b.append(" query=").append(query).append(" docID=").append(docID).append("\n");
+ b.append(" lat=").append(lats[id]).append(" lon=").append(lons[id]).append("\n");
+ b.append(" deleted?=").append(liveDocs != null && liveDocs.get(docID) == false);
+ explain.accept(b);
+ if (true) {
+ fail("wrong hit (first of possibly more):\n\n" + b);
+ } else {
+ System.out.println(b.toString());
+ }
+ }
public void testRectBoundariesAreInclusive() throws Exception {
Rectangle rect;
@@ -1350,7 +1337,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
dir.close();
}
- public void testEquals() throws Exception {
+ public void testEquals() throws Exception {
Query q1, q2;
Rectangle rect = nextBox();
@@ -1383,12 +1370,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
lons[3] = rect.maxLon;
lats[4] = rect.minLat;
lons[4] = rect.minLon;
- if (supportsPolygons()) {
- q1 = newPolygonQuery("field", new Polygon(lats, lons));
- q2 = newPolygonQuery("field", new Polygon(lats, lons));
- assertEquals(q1, q2);
- assertFalse(q1.equals(newPolygonQuery("field2", new Polygon(lats, lons))));
- }
+ q1 = newPolygonQuery("field", new Polygon(lats, lons));
+ q2 = newPolygonQuery("field", new Polygon(lats, lons));
+ assertEquals(q1, q2);
+ assertFalse(q1.equals(newPolygonQuery("field2", new Polygon(lats, lons))));
}
/** return topdocs over a small set of points in field "point" */
@@ -1477,7 +1462,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
}
public void testSmallSetPoly() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
TopDocs td = searchSmallSet(newPolygonQuery("point",
new Polygon(
new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
@@ -1489,7 +1473,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
}
public void testSmallSetPolyWholeMap() throws Exception {
- assumeTrue("Impl does not support polygons", supportsPolygons());
TopDocs td = searchSmallSet(newPolygonQuery("point",
new Polygon(
new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL},
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
index 39bfe97d5f8..cbcc4c1f1e4 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
@@ -394,7 +394,7 @@ public class GeoTestUtil {
public static Circle nextCircle() {
double lat = nextLatitude();
double lon = nextLongitude();
- double radiusMeters = random().nextDouble() * Circle.MAX_RADIUS;
+ double radiusMeters = random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
return new Circle(lat, lon, radiusMeters);
}