Skip to content

Commit

Permalink
Add support for multipoint shape queries (#52564) (#52705)
Browse files Browse the repository at this point in the history
  • Loading branch information
iverase authored Feb 24, 2020
1 parent 98bcf06 commit ba9d3c6
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 39 deletions.
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 @@ -82,11 +83,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 @@ -111,11 +108,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 @@ -298,10 +299,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 @@ -318,8 +319,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

0 comments on commit ba9d3c6

Please sign in to comment.