Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multipoint shape queries #52564

Merged
merged 4 commits into from
Feb 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions docs/reference/mapping/types/shape.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,13 +32,15 @@
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;

public class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryProcessor {

@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
validateIsShapeFieldType(fieldName, context);
if (shape == null) {
return new MatchNoDocsQuery();
}
Expand All @@ -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<Query, RuntimeException> {
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;
}
Expand All @@ -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);
}
}

Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down