Add support for multipoint shape queries (#52564) (#52705)

This commit is contained in:
Ignacio Vera 2020-02-24 13:46:51 +01:00 committed by GitHub
parent 98bcf06bae
commit ba9d3c6389
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 39 deletions

View File

@ -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

View File

@ -15,7 +15,6 @@ import org.apache.lucene.search.ConstantScoreQuery;
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.AbstractGeometryFieldMapper;
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 class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro
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;
}
@ -87,13 +94,7 @@ public class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro
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 class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro
@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 class ShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryPro
// 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

View File

@ -10,6 +10,7 @@ import org.apache.lucene.search.ConstantScoreQuery;
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;
@ -82,11 +83,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
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();
@ -111,11 +108,22 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
}
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()) {

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.geo.GeoJson;
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;
@ -298,10 +299,10 @@ public class ShapeQueryTests extends ESSingleNodeTestCase {
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();
@ -318,8 +319,10 @@ public class ShapeQueryTests extends ESSingleNodeTestCase {
{
// 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();