diff --git a/docs/reference/mapping/types/shape.asciidoc b/docs/reference/mapping/types/shape.asciidoc index 9969021c7853d..abe5f2dc060e7 100644 --- a/docs/reference/mapping/types/shape.asciidoc +++ b/docs/reference/mapping/types/shape.asciidoc @@ -67,15 +67,8 @@ depends on the number of vertices that define the geometry. *IMPORTANT NOTES* -The following features are not yet supported: - -* `shape` query with `MultiPoint` geometry types - Elasticsearch currently prevents searching - `shape` fields with a MultiPoint geometry type to avoid a brute force linear search - over each individual point. For now, if this is absolutely needed, this can be achieved - using a `bool` query with each individual point. (Note: this could be very costly) - -* `CONTAINS` relation query - `shape` queries with `relation` defined as `contains` are supported - for indices created with ElasticSearch 7.5.0 or higher. +`CONTAINS` relation query - `shape` queries with `relation` defined as `contains` are supported +for indices created with ElasticSearch 7.5.0 or higher. [float] ===== Example diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java index ee4cb2f5ba3ea..006577bde0266 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryProcessor.java @@ -15,7 +15,6 @@ import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.elasticsearch.Version; -import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.geometry.Circle; import org.elasticsearch.geometry.Geometry; @@ -33,6 +32,7 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardException; +import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper; import static org.elasticsearch.xpack.spatial.index.mapper.ShapeIndexer.toLucenePolygon; @@ -40,6 +40,7 @@ public class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro @Override public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) { + validateIsShapeFieldType(fieldName, context); if (shape == null) { return new MatchNoDocsQuery(); } @@ -52,15 +53,21 @@ public Query process(Geometry shape, String fieldName, ShapeRelation relation, Q return new ConstantScoreQuery(shape.visit(new ShapeVisitor(context, fieldName, relation))); } + private void validateIsShapeFieldType(String fieldName, QueryShardContext context) { + MappedFieldType fieldType = context.fieldMapper(fieldName); + if (fieldType instanceof ShapeFieldMapper.ShapeFieldType == false) { + throw new QueryShardException(context, "Expected " + ShapeFieldMapper.CONTENT_TYPE + + " field type for Field [" + fieldName + "] but found " + fieldType.typeName()); + } + } + private class ShapeVisitor implements GeometryVisitor { QueryShardContext context; - MappedFieldType fieldType; String fieldName; ShapeRelation relation; ShapeVisitor(QueryShardContext context, String fieldName, ShapeRelation relation) { this.context = context; - this.fieldType = context.fieldMapper(fieldName); this.fieldName = fieldName; this.relation = relation; } @@ -87,13 +94,7 @@ private void visit(BooleanQuery.Builder bqb, GeometryCollection collection) { occur = BooleanClause.Occur.SHOULD; } for (Geometry shape : collection) { - if (shape instanceof MultiPoint) { - // Flatten multipoints - // We do not support multi-point queries? - visit(bqb, (GeometryCollection) shape); - } else { - bqb.add(shape.visit(this), occur); - } + bqb.add(shape.visit(this), occur); } } @@ -120,8 +121,11 @@ public Query visit(MultiLine multiLine) { @Override public Query visit(MultiPoint multiPoint) { - throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + GeoShapeType.MULTIPOINT + - " queries"); + float[][] points = new float[multiPoint.size()][2]; + for (int i = 0; i < multiPoint.size(); i++) { + points[i] = new float[] {(float) multiPoint.get(i).getX(), (float) multiPoint.get(i).getY()}; + } + return XYShape.newPointQuery(fieldName, relation.getLuceneRelation(), points); } @Override @@ -145,8 +149,8 @@ public Query visit(Point point) { // intersects is more efficient. luceneRelation = ShapeField.QueryRelation.INTERSECTS; } - return XYShape.newBoxQuery(fieldName, luceneRelation, - (float)point.getX(), (float)point.getX(), (float)point.getY(), (float)point.getY()); + float[][] pointArray = new float[][] {{(float)point.getX(), (float)point.getY()}}; + return XYShape.newPointQuery(fieldName, luceneRelation, pointArray); } @Override diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java index c28189548d045..a12bbcd5232bb 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java @@ -10,6 +10,7 @@ import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; @@ -81,11 +82,7 @@ protected ShapeQueryBuilder doCreateTestQueryBuilder() { } protected ShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) { - Geometry shape; - // multipoint queries not (yet) supported - do { - shape = ShapeTestUtils.randomGeometry(false); - } while (shape.type() == ShapeType.MULTIPOINT || shape.type() == ShapeType.GEOMETRYCOLLECTION); + Geometry shape = ShapeTestUtils.randomGeometry(false); ShapeQueryBuilder builder; clearShapeFields(); @@ -109,11 +106,22 @@ protected ShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) { } } - if (shape.type() == ShapeType.LINESTRING || shape.type() == ShapeType.MULTILINESTRING) { - builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS)); - } else { - // XYShape does not support CONTAINS: - builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN)); + if (randomBoolean()) { + QueryShardContext context = createShardContext(); + if (context.indexVersionCreated().onOrAfter(Version.V_7_5_0)) { // CONTAINS is only supported from version 7.5 + if (shape.type() == ShapeType.LINESTRING || shape.type() == ShapeType.MULTILINESTRING) { + builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.CONTAINS)); + } else { + builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, + ShapeRelation.WITHIN, ShapeRelation.CONTAINS)); + } + } else { + if (shape.type() == ShapeType.LINESTRING || shape.type() == ShapeType.MULTILINESTRING) { + builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS)); + } else { + builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN)); + } + } } if (randomBoolean()) { diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java index 7f36cd6d2c82d..77c48e9f51fc8 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.builders.EnvelopeBuilder; import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; +import org.elasticsearch.common.geo.builders.MultiPointBuilder; import org.elasticsearch.common.geo.builders.PointBuilder; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -297,10 +298,10 @@ public void testGeometryCollectionRelations() throws IOException { assertEquals(0, response.getHits().getTotalHits().value); } { - // A geometry collection that is partially within the indexed shape - GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); - builder.shape(new PointBuilder(1, 2)); - builder.shape(new PointBuilder(20, 30)); + // A geometry collection (as multi point) that is partially within the indexed shape + MultiPointBuilder builder = new MultiPointBuilder(); + builder.coordinate(1, 2); + builder.coordinate(20, 30); SearchResponse response = client().prepareSearch("test_collections") .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) .get(); @@ -317,8 +318,10 @@ public void testGeometryCollectionRelations() throws IOException { { // A geometry collection that is disjoint with the indexed shape GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); - builder.shape(new PointBuilder(-20, -30)); - builder.shape(new PointBuilder(20, 30)); + MultiPointBuilder innerBuilder = new MultiPointBuilder(); + innerBuilder.coordinate(-20, -30); + innerBuilder.coordinate(20, 30); + builder.shape(innerBuilder); SearchResponse response = client().prepareSearch("test_collections") .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) .get();