Skip to content

Commit 107f00a

Browse files
authored
Add support for multipoint geoshape queries (#52133) (#52553)
Currently multi-point queries are not supported when indexing your data using BKD-backed geoshape strategy. This commit removes this limitation.
1 parent 4bc7545 commit 107f00a

File tree

4 files changed

+39
-66
lines changed

4 files changed

+39
-66
lines changed

docs/reference/mapping/types/geo-shape.asciidoc

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,10 @@ and will be removed in a future version.
134134

135135
*IMPORTANT NOTES*
136136

137-
The following features are not yet supported with the new indexing approach:
137+
`CONTAINS` relation query - when using the new default vector indexing strategy, `geo_shape`
138+
queries with `relation` defined as `contains` are supported for indices created with
139+
ElasticSearch 7.5.0 or higher.
138140

139-
* `geo_shape` query with `MultiPoint` geometry types - Elasticsearch currently prevents searching
140-
geo_shape fields with a MultiPoint geometry type to avoid a brute force linear search
141-
over each individual point. For now, if this is absolutely needed, this can be achieved
142-
using a `bool` query with each individual point.
143-
144-
* `CONTAINS` relation query - when using the new default vector indexing strategy, `geo_shape`
145-
queries with `relation` defined as `contains` are supported for indices created with
146-
ElasticSearch 7.5.0 or higher.
147141

148142
[[prefix-trees]]
149143
[float]

server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.apache.lucene.search.MatchNoDocsQuery;
2929
import org.apache.lucene.search.Query;
3030
import org.elasticsearch.Version;
31-
import org.elasticsearch.common.geo.GeoShapeType;
3231
import org.elasticsearch.common.geo.ShapeRelation;
3332
import org.elasticsearch.geometry.Circle;
3433
import org.elasticsearch.geometry.Geometry;
@@ -106,13 +105,7 @@ private void visit(BooleanQuery.Builder bqb, GeometryCollection<?> collection) {
106105
occur = BooleanClause.Occur.SHOULD;
107106
}
108107
for (Geometry shape : collection) {
109-
if (shape instanceof MultiPoint) {
110-
// Flatten multi-points
111-
// We do not support multi-point queries?
112-
visit(bqb, (GeometryCollection<?>) shape);
113-
} else {
114-
bqb.add(shape.visit(this), occur);
115-
}
108+
bqb.add(shape.visit(this), occur);
116109
}
117110
}
118111

@@ -139,8 +132,11 @@ public Query visit(MultiLine multiLine) {
139132

140133
@Override
141134
public Query visit(MultiPoint multiPoint) {
142-
throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + GeoShapeType.MULTIPOINT +
143-
" queries");
135+
double[][] points = new double[multiPoint.size()][2];
136+
for (int i = 0; i < multiPoint.size(); i++) {
137+
points[i] = new double[] {multiPoint.get(i).getLat(), multiPoint.get(i).getLon()};
138+
}
139+
return LatLonShape.newPointQuery(fieldName, relation.getLuceneRelation(), points);
144140
}
145141

146142
@Override
@@ -161,8 +157,8 @@ public Query visit(Point point) {
161157
// intersects is more efficient.
162158
luceneRelation = ShapeField.QueryRelation.INTERSECTS;
163159
}
164-
return LatLonShape.newBoxQuery(fieldName, luceneRelation,
165-
point.getY(), point.getY(), point.getX(), point.getX());
160+
return LatLonShape.newPointQuery(fieldName, luceneRelation,
161+
new double[] {point.getY(), point.getX()});
166162
}
167163

168164
@Override

server/src/test/java/org/elasticsearch/index/query/GeoShapeQueryBuilderTests.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,8 @@ protected GeoShapeQueryBuilder doCreateTestQueryBuilder() {
8484
}
8585

8686
protected GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
87-
// LatLonShape does not support MultiPoint queries
8887
RandomShapeGenerator.ShapeType shapeType =
89-
randomFrom(ShapeType.POINT, ShapeType.LINESTRING, ShapeType.MULTILINESTRING, ShapeType.POLYGON);
88+
randomFrom(ShapeType.POINT, ShapeType.MULTIPOINT, ShapeType.LINESTRING, ShapeType.MULTILINESTRING, ShapeType.POLYGON);
9089
ShapeBuilder<?, ?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null, shapeType);
9190
GeoShapeQueryBuilder builder;
9291
clearShapeFields();
@@ -111,11 +110,20 @@ protected GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
111110
}
112111
}
113112
if (randomBoolean()) {
114-
if (shapeType == ShapeType.LINESTRING || shapeType == ShapeType.MULTILINESTRING) {
115-
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS));
113+
QueryShardContext context = createShardContext();
114+
if (context.indexVersionCreated().onOrAfter(Version.V_7_5_0)) { // CONTAINS is only supported from version 7.5
115+
if (shapeType == ShapeType.LINESTRING || shapeType == ShapeType.MULTILINESTRING) {
116+
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.CONTAINS));
117+
} else {
118+
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS,
119+
ShapeRelation.WITHIN, ShapeRelation.CONTAINS));
120+
}
116121
} else {
117-
// LatLonShape does not support CONTAINS:
118-
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN));
122+
if (shapeType == ShapeType.LINESTRING || shapeType == ShapeType.MULTILINESTRING) {
123+
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS));
124+
} else {
125+
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN));
126+
}
119127
}
120128
}
121129

server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -206,23 +206,8 @@ public void testShapeFetchingPath() throws Exception {
206206
}
207207

208208
public void testRandomGeoCollectionQuery() throws Exception {
209-
boolean usePrefixTrees = randomBoolean();
210209
// Create a random geometry collection to index.
211-
GeometryCollectionBuilder gcb;
212-
if (usePrefixTrees) {
213-
gcb = RandomShapeGenerator.createGeometryCollection(random());
214-
} else {
215-
// vector strategy does not yet support multipoint queries
216-
gcb = new GeometryCollectionBuilder();
217-
int numShapes = RandomNumbers.randomIntBetween(random(), 1, 4);
218-
for (int i = 0; i < numShapes; ++i) {
219-
ShapeBuilder shape;
220-
do {
221-
shape = RandomShapeGenerator.createShape(random());
222-
} while (shape instanceof MultiPointBuilder);
223-
gcb.shape(shape);
224-
}
225-
}
210+
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
226211
org.apache.lucene.geo.Polygon randomPoly = GeoTestUtil.nextPolygon();
227212

228213
assumeTrue("Skipping the check for the polygon with a degenerated dimension",
@@ -234,10 +219,9 @@ public void testRandomGeoCollectionQuery() throws Exception {
234219
}
235220
gcb.shape(new PolygonBuilder(cb));
236221

237-
logger.info("Created Random GeometryCollection containing {} shapes using {} tree", gcb.numShapes(),
238-
usePrefixTrees ? "geohash" : "quadtree");
222+
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
239223

240-
XContentBuilder mapping = createPrefixTreeMapping(usePrefixTrees ? "geohash" : "quadtree");
224+
XContentBuilder mapping = createRandomMapping();
241225
Settings settings = Settings.builder().put("index.number_of_shards", 1).build();
242226
client().admin().indices().prepareCreate("test").addMapping("_doc",mapping).setSettings(settings).get();
243227
ensureGreen();
@@ -321,8 +305,7 @@ public void testEnvelopeSpanningDateline() throws Exception {
321305
}
322306

323307
public void testGeometryCollectionRelations() throws Exception {
324-
XContentBuilder mapping = createPrefixTreeMapping(LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.GEOHASH);
325-
308+
XContentBuilder mapping = createDefaultMapping();
326309
createIndex("test", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping);
327310

328311
EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(-10, 10), new Coordinate(10, -10));
@@ -441,13 +424,13 @@ public void testIndexedShapeReferenceSourceDisabled() throws Exception {
441424

442425
public void testReusableBuilder() throws IOException {
443426
PolygonBuilder polygon = new PolygonBuilder(new CoordinatesBuilder()
444-
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close())
445-
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5)
446-
.coordinate(175, 5).close()));
427+
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close())
428+
.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5)
429+
.coordinate(175, 5).close()));
447430
assertUnmodified(polygon);
448431

449432
LineStringBuilder linestring = new LineStringBuilder(new CoordinatesBuilder()
450-
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close());
433+
.coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close());
451434
assertUnmodified(linestring);
452435
}
453436

@@ -534,13 +517,9 @@ public void testExistsQuery() throws Exception {
534517
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
535518
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
536519

537-
if (randomBoolean()) {
538-
client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape")
539-
.execute().actionGet();
540-
} else {
541-
client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape,tree=quadtree")
542-
.execute().actionGet();
543-
}
520+
XContentBuilder builder = createRandomMapping();
521+
client().admin().indices().prepareCreate("test").addMapping("type", builder)
522+
.execute().actionGet();
544523

545524
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject();
546525
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
@@ -696,13 +675,9 @@ public void testQueryRandomGeoCollection() throws Exception {
696675

697676
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
698677

699-
if (randomBoolean()) {
700-
client().admin().indices().prepareCreate("test")
701-
.addMapping("type", "geo", "type=geo_shape").get();
702-
} else {
703-
client().admin().indices().prepareCreate("test")
704-
.addMapping("type", "geo", "type=geo_shape,tree=quadtree").get();
705-
}
678+
XContentBuilder builder = createRandomMapping();
679+
client().admin().indices().prepareCreate("test")
680+
.addMapping("type", builder).get();
706681

707682
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject();
708683
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();

0 commit comments

Comments
 (0)