parent
98bcf06bae
commit
ba9d3c6389
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue