Skip to content

Commit b0884ba

Browse files
authored
Geo shape query vs geo point backport (#53774)
Backport to 7x Enable geo_shape query to work on geo_point fields for shapes: circle, polygon, multipolygon, rectangle see: #48928 Co-Authored-By: @iverase
1 parent 8f4a3eb commit b0884ba

21 files changed

+1039
-201
lines changed

docs/reference/query-dsl/geo-queries.asciidoc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ Finds documents with geo-points within the specified distance of a central point
1818
Find documents with geo-points within the specified polygon.
1919

2020
<<query-dsl-geo-shape-query,`geo_shape`>> query::
21-
Finds documents with geo-shapes which either intersect, are contained by, or do not intersect with the specified
22-
geo-shape.
23-
21+
Finds documents with:
22+
* `geo-shapes` which either intersect, are contained by, or do not intersect
23+
with the specified geo-shape
24+
* `geo-points` which intersect the specified
25+
geo-shape
2426

2527
include::geo-bounding-box-query.asciidoc[]
2628

docs/reference/query-dsl/geo-shape-query.asciidoc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<titleabbrev>Geo-shape</titleabbrev>
55
++++
66

7-
Filter documents indexed using the `geo_shape` type.
7+
Filter documents indexed using the `geo_shape` or `geo_point` type.
88

9-
Requires the <<geo-shape,`geo_shape` Mapping>>.
9+
Requires the <<geo-shape,`geo_shape` Mapping>> or the <<geo-point,`geo_point` Mapping>>.
1010

1111
The `geo_shape` query uses the same grid square representation as the
1212
`geo_shape` mapping to find documents that have a shape that intersects
@@ -142,7 +142,7 @@ GET /example/_search
142142
The <<spatial-strategy, geo_shape strategy>> mapping parameter determines
143143
which spatial relation operators may be used at search time.
144144

145-
The following is a complete list of spatial relation operators available:
145+
The following is a complete list of spatial relation operators available when searching a field of type `geo_shape`:
146146

147147
* `INTERSECTS` - (default) Return all documents whose `geo_shape` field
148148
intersects the query geometry.
@@ -153,6 +153,11 @@ is within the query geometry.
153153
* `CONTAINS` - Return all documents whose `geo_shape` field
154154
contains the query geometry.
155155

156+
When searching a field of type `geo_point` there is a single supported spatial relation operator:
157+
158+
* `INTERSECTS` - (default) Return all documents whose `geo_point` field
159+
intersects the query geometry.
160+
156161
[float]
157162
==== Ignore Unmapped
158163

@@ -162,6 +167,15 @@ querying multiple indexes which might have different mappings. When set to
162167
`false` (the default value) the query will throw an exception if the field
163168
is not mapped.
164169

170+
==== Shape Types supported for Geo-Point
171+
172+
When searching a field of type `geo_point` the following shape types are not supported:
173+
174+
* `POINT`
175+
* `LINE`
176+
* `MULTIPOINT`
177+
* `MULTILINE`
178+
165179
==== Notes
166180
Geo-shape queries on geo-shapes implemented with <<prefix-trees, `PrefixTrees`>> will not be executed if
167181
<<query-dsl-allow-expensive-queries, `search.allow_expensive_queries`>> is set to false.

libs/geo/src/main/java/org/elasticsearch/geometry/LinearRing.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,6 @@ public <T, E extends Exception> T visit(GeometryVisitor<T, E> visitor) throws E
6464
public String toString() {
6565
return "linearring(x=" + Arrays.toString(getX()) +
6666
", y=" + Arrays.toString(getY()) +
67-
(hasZ() ? ", z=" + Arrays.toString(getZ()) : "");
67+
(hasZ() ? ", z=" + Arrays.toString(getZ()) : "") + ")";
6868
}
6969
}

server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,13 @@
2626
import org.elasticsearch.Version;
2727
import org.elasticsearch.common.Explicit;
2828
import org.elasticsearch.common.ParseField;
29-
import org.elasticsearch.common.geo.ShapeRelation;
30-
import org.elasticsearch.common.geo.SpatialStrategy;
3129
import org.elasticsearch.common.geo.builders.ShapeBuilder;
3230
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
3331
import org.elasticsearch.common.settings.Settings;
3432
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
3533
import org.elasticsearch.common.xcontent.XContentBuilder;
3634
import org.elasticsearch.common.xcontent.XContentParser;
3735
import org.elasticsearch.common.xcontent.support.XContentMapValues;
38-
import org.elasticsearch.geometry.Geometry;
3936
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
4037
import org.elasticsearch.index.query.QueryShardContext;
4138
import org.elasticsearch.index.query.QueryShardException;
@@ -91,19 +88,6 @@ public interface Parser<Parsed> {
9188

9289
}
9390

94-
/**
95-
* interface representing a query builder that generates a query from the given shape
96-
*/
97-
public interface QueryProcessor {
98-
Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);
99-
100-
@Deprecated
101-
default Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
102-
QueryShardContext context) {
103-
return process(shape, fieldName, relation, context);
104-
}
105-
}
106-
10791
public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper>
10892
extends FieldMapper.Builder<T, Y> {
10993
protected Boolean coerce;
@@ -272,14 +256,14 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
272256
}
273257
}
274258

275-
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
259+
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends AbstractSearchableGeometryFieldType {
276260
protected Orientation orientation = Defaults.ORIENTATION.value();
277261

278262
protected Indexer<Parsed, Processed> geometryIndexer;
279263

280264
protected Parser<Parsed> geometryParser;
281265

282-
protected QueryProcessor geometryQueryBuilder;
266+
283267

284268
protected AbstractGeometryFieldType() {
285269
setIndexOptions(IndexOptions.DOCS);
@@ -339,14 +323,6 @@ public void setGeometryParser(Parser<Parsed> geometryParser) {
339323
protected Parser<Parsed> geometryParser() {
340324
return geometryParser;
341325
}
342-
343-
public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
344-
this.geometryQueryBuilder = geometryQueryBuilder;
345-
}
346-
347-
public QueryProcessor geometryQueryBuilder() {
348-
return geometryQueryBuilder;
349-
}
350326
}
351327

352328
protected Explicit<Boolean> coerce;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.mapper;
21+
22+
import org.apache.lucene.search.Query;
23+
import org.elasticsearch.common.geo.ShapeRelation;
24+
import org.elasticsearch.common.geo.SpatialStrategy;
25+
import org.elasticsearch.geometry.Geometry;
26+
import org.elasticsearch.index.query.QueryShardContext;
27+
28+
/**
29+
* a base class for geometry types that support shape query builder
30+
*/
31+
public abstract class AbstractSearchableGeometryFieldType extends MappedFieldType {
32+
33+
protected QueryProcessor geometryQueryBuilder;
34+
35+
protected AbstractSearchableGeometryFieldType() {
36+
}
37+
38+
protected AbstractSearchableGeometryFieldType(AbstractSearchableGeometryFieldType ref) {
39+
super(ref);
40+
}
41+
42+
public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
43+
this.geometryQueryBuilder = geometryQueryBuilder;
44+
}
45+
46+
public QueryProcessor geometryQueryBuilder() {
47+
return geometryQueryBuilder;
48+
}
49+
50+
/**
51+
* interface representing a query builder that generates a query from the given shape
52+
*/
53+
public interface QueryProcessor {
54+
Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);
55+
56+
@Deprecated
57+
default Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
58+
QueryShardContext context) {
59+
return process(shape, fieldName, relation, context);
60+
}
61+
}
62+
}
63+

server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointDVIndexFieldData;
4141
import org.elasticsearch.index.query.QueryShardContext;
4242
import org.elasticsearch.index.query.QueryShardException;
43+
import org.elasticsearch.index.query.VectorGeoPointShapeQueryProcessor;
4344

4445
import java.io.IOException;
4546
import java.util.ArrayList;
@@ -121,6 +122,14 @@ public GeoPointFieldMapper build(BuilderContext context, String simpleName, Mapp
121122
ignoreMalformed, ignoreZValue, copyTo);
122123
}
123124

125+
@Override
126+
protected void setupFieldType(BuilderContext context) {
127+
super.setupFieldType(context);
128+
129+
GeoPointFieldType fieldType = (GeoPointFieldType)fieldType();
130+
fieldType.setGeometryQueryBuilder(new VectorGeoPointShapeQueryProcessor());
131+
}
132+
124133
@Override
125134
public GeoPointFieldMapper build(BuilderContext context) {
126135
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
@@ -210,7 +219,7 @@ protected void parseCreateField(ParseContext context, List<IndexableField> field
210219
throw new UnsupportedOperationException("Parsing is implemented in parse(), this method should NEVER be called");
211220
}
212221

213-
public static class GeoPointFieldType extends MappedFieldType {
222+
public static class GeoPointFieldType extends AbstractSearchableGeometryFieldType {
214223
public GeoPointFieldType() {
215224
}
216225

@@ -245,7 +254,8 @@ public Query existsQuery(QueryShardContext context) {
245254

246255
@Override
247256
public Query termQuery(Object value, QueryShardContext context) {
248-
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead: ["
257+
throw new QueryShardException(context,
258+
"Geo fields do not support exact searching, use dedicated geo queries instead: ["
249259
+ name() + "]");
250260
}
251261
}

server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.lucene.index.IndexableField;
2525
import org.elasticsearch.common.geo.GeoLineDecomposer;
2626
import org.elasticsearch.common.geo.GeoPolygonDecomposer;
27+
import org.elasticsearch.common.geo.GeoShapeType;
2728
import org.elasticsearch.geometry.Circle;
2829
import org.elasticsearch.geometry.Geometry;
2930
import org.elasticsearch.geometry.GeometryCollection;
@@ -64,7 +65,7 @@ public Geometry prepareForIndexing(Geometry geometry) {
6465
return geometry.visit(new GeometryVisitor<Geometry, RuntimeException>() {
6566
@Override
6667
public Geometry visit(Circle circle) {
67-
throw new UnsupportedOperationException("CIRCLE geometry is not supported");
68+
throw new UnsupportedOperationException(GeoShapeType.CIRCLE + " geometry is not supported");
6869
}
6970

7071
@Override

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,9 @@ public boolean ignoreUnmapped() {
372372
}
373373

374374
/** list of content types this shape query is compatible with */
375-
protected abstract List validContentTypes();
375+
protected abstract List<String> validContentTypes();
376376
/** builds the appropriate lucene shape query */
377377
protected abstract Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType);
378-
/** returns expected content type for this query */
379-
protected abstract String queryFieldType();
380378
/** writes the xcontent specific to this shape query */
381379
protected abstract void doShapeQueryXContent(XContentBuilder builder, Params params) throws IOException;
382380
/** creates a new ShapeQueryBuilder from the provided field name and shape builder */
@@ -400,7 +398,9 @@ protected Query doToQuery(QueryShardContext context) {
400398
if (ignoreUnmapped) {
401399
return new MatchNoDocsQuery();
402400
} else {
403-
throw new QueryShardException(context, "failed to find " + queryFieldType() + " field [" + fieldName + "]");
401+
throw new QueryShardException(context, "failed to find "
402+
+ String.join(" or ", validContentTypes())
403+
+ " field [" + fieldName + "]");
404404
}
405405
}
406406

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@
3535
import org.elasticsearch.common.xcontent.XContentBuilder;
3636
import org.elasticsearch.common.xcontent.XContentParser;
3737
import org.elasticsearch.geometry.Geometry;
38-
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
38+
import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
39+
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
3940
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
4041
import org.elasticsearch.index.mapper.MappedFieldType;
4142

4243
import java.io.IOException;
4344
import java.util.Arrays;
45+
import java.util.Collections;
4446
import java.util.List;
4547
import java.util.Objects;
4648
import java.util.function.Supplier;
@@ -57,6 +59,12 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
5759

5860
private SpatialStrategy strategy;
5961

62+
protected static final List<String> validContentTypes =
63+
Collections.unmodifiableList(
64+
Arrays.asList(
65+
GeoShapeFieldMapper.CONTENT_TYPE,
66+
GeoPointFieldMapper.CONTENT_TYPE));
67+
6068
/**
6169
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
6270
* field name using the given Shape
@@ -181,13 +189,8 @@ public SpatialStrategy strategy() {
181189
}
182190

183191
@Override
184-
protected List validContentTypes() {
185-
return Arrays.asList(GeoShapeFieldMapper.CONTENT_TYPE);
186-
}
187-
188-
@Override
189-
public String queryFieldType() {
190-
return GeoShapeFieldMapper.CONTENT_TYPE;
192+
protected List<String> validContentTypes() {
193+
return validContentTypes;
191194
}
192195

193196
@Override
@@ -210,12 +213,15 @@ protected GeoShapeQueryBuilder newShapeQueryBuilder(String fieldName, Supplier<G
210213

211214
@Override
212215
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
213-
if (fieldType.typeName().equals(GeoShapeFieldMapper.CONTENT_TYPE) == false) {
216+
if (validContentTypes().contains(fieldType.typeName()) == false) {
214217
throw new QueryShardException(context,
215-
"Field [" + fieldName + "] is not of type [" + queryFieldType() + "] but of type [" + fieldType.typeName() + "]");
218+
"Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. ["
219+
+ NAME + "] query supports the following types ["
220+
+ String.join(",", validContentTypes()) + "]");
216221
}
217222

218-
final AbstractGeometryFieldMapper.AbstractGeometryFieldType ft = (AbstractGeometryFieldMapper.AbstractGeometryFieldType) fieldType;
223+
final AbstractSearchableGeometryFieldType ft =
224+
(AbstractSearchableGeometryFieldType) fieldType;
219225
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context));
220226
}
221227

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.elasticsearch.geometry.Polygon;
5454
import org.elasticsearch.geometry.Rectangle;
5555
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
56+
import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
5657
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
5758
import org.locationtech.jts.geom.Coordinate;
5859
import org.locationtech.spatial4j.shape.Shape;
@@ -62,7 +63,7 @@
6263

6364
import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;
6465

65-
public class LegacyGeoShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryProcessor {
66+
public class LegacyGeoShapeQueryProcessor implements AbstractSearchableGeometryFieldType.QueryProcessor {
6667

6768
private AbstractGeometryFieldMapper.AbstractGeometryFieldType ft;
6869

0 commit comments

Comments
 (0)