diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index d049e55645b..163c09c3db2 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -214,6 +214,8 @@ New Features * LUCENE-9552: New LatLonPoint query that accepts an array of LatLonGeometries. (Ignacio Vera) +* LUCENE-9553: New XYPoint query that accepts an array of XYGeometries. (Ignacio Vera) + Improvements --------------------- diff --git a/lucene/core/src/java/org/apache/lucene/document/XYDocValuesField.java b/lucene/core/src/java/org/apache/lucene/document/XYDocValuesField.java index 3f1bb66c086..1310159200d 100644 --- a/lucene/core/src/java/org/apache/lucene/document/XYDocValuesField.java +++ b/lucene/core/src/java/org/apache/lucene/document/XYDocValuesField.java @@ -18,6 +18,7 @@ package org.apache.lucene.document; import org.apache.lucene.geo.XYCircle; import org.apache.lucene.geo.XYEncodingUtils; +import org.apache.lucene.geo.XYGeometry; import org.apache.lucene.geo.XYPolygon; import org.apache.lucene.geo.XYRectangle; import org.apache.lucene.index.DocValuesType; @@ -36,6 +37,10 @@ import org.apache.lucene.search.SortField; *
* This field defines static factory methods for common operations: *
@@ -173,6 +178,21 @@ public class XYDocValuesField extends Field { * @throws IllegalArgumentException if {@code field} is null or polygons is empty or contain a null polygon. */ public static Query newSlowPolygonQuery(String field, XYPolygon... polygons) { - return new XYDocValuesPointInGeometryQuery(field, polygons); + return newSlowGeometryQuery(field, polygons); + } + + /** + * Create a query for matching points within the supplied geometries. XYLine 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 XYPointField#newGeometryQuery(String, XYGeometry...)}. + * @param field field name. must not be null. + * @param geometries array of XY geometries. must not be null or empty. + * @return query matching points within the given geometries. + * @throws IllegalArgumentException if {@code field} is null, {@code polygons} is null, empty or contains a null or XYLine geometry. + */ + public static Query newSlowGeometryQuery(String field, XYGeometry... geometries) { + return new XYDocValuesPointInGeometryQuery(field, geometries); } } diff --git a/lucene/core/src/java/org/apache/lucene/document/XYPointField.java b/lucene/core/src/java/org/apache/lucene/document/XYPointField.java index 1c43774fc40..6c4e0bb9d2c 100644 --- a/lucene/core/src/java/org/apache/lucene/document/XYPointField.java +++ b/lucene/core/src/java/org/apache/lucene/document/XYPointField.java @@ -19,6 +19,7 @@ package org.apache.lucene.document; import org.apache.lucene.geo.Polygon; import org.apache.lucene.geo.XYCircle; import org.apache.lucene.geo.XYEncodingUtils; +import org.apache.lucene.geo.XYGeometry; import org.apache.lucene.geo.XYPolygon; import org.apache.lucene.geo.XYRectangle; import org.apache.lucene.index.FieldInfo; @@ -40,6 +41,7 @@ import org.apache.lucene.util.NumericUtils; *
* If you also need per-document operations such as sort by distance, add a separate {@link XYDocValuesField} instance.
@@ -167,6 +169,17 @@ public class XYPointField extends Field {
* @see Polygon
*/
public static Query newPolygonQuery(String field, XYPolygon... polygons) {
- return new XYPointInGeometryQuery(field, polygons);
+ return newGeometryQuery(field, polygons);
+ }
+
+ /** create a query to find all indexed shapes that intersect a provided geometry collection. XYLine geometries are not supported.
+ * @param field field name. must not be null.
+ * @param xyGeometries array of geometries. must not be null or empty.
+ * @return query matching points within this geometry collection.
+ * @throws IllegalArgumentException if {@code field} is null, {@code polygons} is null, empty or contains a null or XYLine geometry.
+ * @see XYGeometry
+ **/
+ public static Query newGeometryQuery(String field, XYGeometry... xyGeometries) {
+ return new XYPointInGeometryQuery(field, xyGeometries);
}
}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestXYDocValuesQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestXYDocValuesQueries.java
index 4ca1e5e2a96..8a7c8f75a26 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestXYDocValuesQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestXYDocValuesQueries.java
@@ -19,6 +19,7 @@ package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.XYDocValuesField;
import org.apache.lucene.geo.BaseXYPointTestCase;
+import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPolygon;
public class TestXYDocValuesQueries extends BaseXYPointTestCase {
@@ -42,4 +43,9 @@ public class TestXYDocValuesQueries extends BaseXYPointTestCase {
protected Query newPolygonQuery(String field, XYPolygon... polygons) {
return XYDocValuesField.newSlowPolygonQuery(field, polygons);
}
+
+ @Override
+ protected Query newGeometryQuery(String field, XYGeometry... geometries) {
+ return XYDocValuesField.newSlowGeometryQuery(field, geometries);
+ }
}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestXYPointQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestXYPointQueries.java
index 208c3bd01bb..faff88f89d6 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestXYPointQueries.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestXYPointQueries.java
@@ -19,6 +19,7 @@ package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.XYPointField;
import org.apache.lucene.geo.BaseXYPointTestCase;
+import org.apache.lucene.geo.XYGeometry;
import org.apache.lucene.geo.XYPolygon;
public class TestXYPointQueries extends BaseXYPointTestCase {
@@ -42,4 +43,9 @@ public class TestXYPointQueries extends BaseXYPointTestCase {
protected Query newPolygonQuery(String field, XYPolygon... polygons) {
return XYPointField.newPolygonQuery(field, polygons);
}
+
+ @Override
+ protected Query newGeometryQuery(String field, XYGeometry... geometries) {
+ return XYPointField.newGeometryQuery(field, geometries);
+ }
}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseXYPointTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseXYPointTestCase.java
index c9240d7e7ae..e31134ca7ea 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseXYPointTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseXYPointTestCase.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;
@@ -91,9 +92,23 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
return ShapeTestUtil.nextPolygon();
}
- /** Whether this impl supports polygons. */
- protected boolean supportsPolygons() {
- return true;
+ protected XYGeometry[] nextGeometry() {
+ final int len = random().nextInt(4) + 1;
+ XYGeometry[] geometries = new XYGeometry[len];
+ for (int i = 0; i < len; i++) {
+ switch (random().nextInt(3)) {
+ case 0:
+ geometries[i] = new XYPoint(nextX(), nextY());
+ break;
+ case 1:
+ geometries[i] = nextBox();
+ break;
+ default:
+ geometries[i] = nextPolygon();
+ break;
+ }
+ }
+ return geometries;
}
/** Valid values that should not cause exception */
@@ -253,7 +268,6 @@ public abstract class BaseXYPointTestCase 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);
@@ -276,7 +290,6 @@ public abstract class BaseXYPointTestCase 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);
@@ -301,7 +314,6 @@ public abstract class BaseXYPointTestCase 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);
@@ -326,7 +338,6 @@ public abstract class BaseXYPointTestCase 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);
@@ -351,7 +362,6 @@ public abstract class BaseXYPointTestCase 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 XYPolygon(
new float[] { 18, 18, 19, 19, 18 },
@@ -542,26 +552,7 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
Query query = newRectQuery(FIELD_NAME, rect.minX, rect.maxX, rect.minY, rect.maxY);
- 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;
@@ -570,7 +561,7 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
float xDoc1 = xs[2*docID];
float yDoc2 = ys[2*docID+1];
float xDoc2 = xs[2*docID+1];
-
+
boolean result1 = rectContainsPoint(rect, xDoc1, yDoc1);
boolean result2 = rectContainsPoint(rect, xDoc2, yDoc2);
@@ -691,6 +682,8 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
protected abstract Query newPolygonQuery(String field, XYPolygon... polygon);
+ protected abstract Query newGeometryQuery(String field, XYGeometry... geometries);
+
static final boolean rectContainsPoint(XYRectangle rect, double x, double y) {
if (y < rect.minY || y > rect.maxY) {
return false;
@@ -708,9 +701,8 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
// NaN means missing for the doc!!!!!
verifyRandomRectangles(xs, ys);
verifyRandomDistances(xs, ys);
- if (supportsPolygons()) {
- verifyRandomPolygons(xs, ys);
- }
+ verifyRandomPolygons(xs, ys);
+ verifyRandomGeometries(xs, ys);
}
protected void verifyRandomRectangles(float[] xs, float[] ys) throws Exception {
@@ -732,27 +724,7 @@ public abstract class BaseXYPointTestCase extends LuceneTestCase {
Set