Skip to content

Commit bd2d935

Browse files
authored
Support for geo_bounding_box queries on geo_shape fields (#2506)
Adds support for using geo_bounding_box queries on geo_shape field types by lifting the geo_point restriction in the QueryBuilder. Bounding Box query integration tests are abstracted to test both geo_point and geo_shape types. Signed-off-by: Nicholas Walter Knize <nknize@apache.org>
1 parent d2bdcde commit bd2d935

File tree

5 files changed

+108
-120
lines changed

5 files changed

+108
-120
lines changed

server/src/internalClusterTest/java/org/opensearch/search/geo/GeoBoundingBoxQueryIT.java renamed to server/src/internalClusterTest/java/org/opensearch/search/geo/AbstractGeoBoundingBoxQueryIT.java

Lines changed: 25 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import org.opensearch.test.OpenSearchIntegTestCase;
4444
import org.opensearch.test.VersionUtils;
4545

46+
import java.io.IOException;
47+
4648
import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
4749
import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder;
4850
import static org.opensearch.index.query.QueryBuilders.boolQuery;
@@ -52,7 +54,15 @@
5254
import static org.hamcrest.Matchers.anyOf;
5355
import static org.hamcrest.Matchers.equalTo;
5456

55-
public class GeoBoundingBoxQueryIT extends OpenSearchIntegTestCase {
57+
abstract class AbstractGeoBoundingBoxQueryIT extends OpenSearchIntegTestCase {
58+
59+
public abstract XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException;
60+
61+
public XContentBuilder getMapping() throws IOException {
62+
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("properties");
63+
mapping = addGeoMapping(mapping);
64+
return mapping.endObject().endObject();
65+
}
5666

5767
@Override
5868
protected boolean forbidPrivateIndexSettings() {
@@ -62,110 +72,55 @@ protected boolean forbidPrivateIndexSettings() {
6272
public void testSimpleBoundingBoxTest() throws Exception {
6373
Version version = VersionUtils.randomIndexCompatibleVersion(random());
6474
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
65-
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
66-
.startObject()
67-
.startObject("properties")
68-
.startObject("location")
69-
.field("type", "geo_point");
70-
xContentBuilder.endObject().endObject().endObject();
75+
XContentBuilder xContentBuilder = getMapping();
7176
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
7277
ensureGreen();
7378

7479
client().prepareIndex("test")
7580
.setId("1")
76-
.setSource(
77-
jsonBuilder().startObject()
78-
.field("name", "New York")
79-
.startObject("location")
80-
.field("lat", 40.7143528)
81-
.field("lon", -74.0059731)
82-
.endObject()
83-
.endObject()
84-
)
81+
.setSource(jsonBuilder().startObject().field("name", "New York").field("location", "POINT(-74.0059731 40.7143528)").endObject())
8582
.get();
8683

8784
// to NY: 5.286 km
8885
client().prepareIndex("test")
8986
.setId("2")
9087
.setSource(
91-
jsonBuilder().startObject()
92-
.field("name", "Times Square")
93-
.startObject("location")
94-
.field("lat", 40.759011)
95-
.field("lon", -73.9844722)
96-
.endObject()
97-
.endObject()
88+
jsonBuilder().startObject().field("name", "Times Square").field("location", "POINT(-73.9844722 40.759011)").endObject()
9889
)
9990
.get();
10091

10192
// to NY: 0.4621 km
10293
client().prepareIndex("test")
10394
.setId("3")
104-
.setSource(
105-
jsonBuilder().startObject()
106-
.field("name", "Tribeca")
107-
.startObject("location")
108-
.field("lat", 40.718266)
109-
.field("lon", -74.007819)
110-
.endObject()
111-
.endObject()
112-
)
95+
.setSource(jsonBuilder().startObject().field("name", "Tribeca").field("location", "POINT(-74.007819 40.718266)").endObject())
11396
.get();
11497

11598
// to NY: 1.055 km
11699
client().prepareIndex("test")
117100
.setId("4")
118101
.setSource(
119-
jsonBuilder().startObject()
120-
.field("name", "Wall Street")
121-
.startObject("location")
122-
.field("lat", 40.7051157)
123-
.field("lon", -74.0088305)
124-
.endObject()
125-
.endObject()
102+
jsonBuilder().startObject().field("name", "Wall Street").field("location", "POINT(-74.0088305 40.7051157)").endObject()
126103
)
127104
.get();
128105

129106
// to NY: 1.258 km
130107
client().prepareIndex("test")
131108
.setId("5")
132-
.setSource(
133-
jsonBuilder().startObject()
134-
.field("name", "Soho")
135-
.startObject("location")
136-
.field("lat", 40.7247222)
137-
.field("lon", -74)
138-
.endObject()
139-
.endObject()
140-
)
109+
.setSource(jsonBuilder().startObject().field("name", "Soho").field("location", "POINT(-74 40.7247222)").endObject())
141110
.get();
142111

143112
// to NY: 2.029 km
144113
client().prepareIndex("test")
145114
.setId("6")
146115
.setSource(
147-
jsonBuilder().startObject()
148-
.field("name", "Greenwich Village")
149-
.startObject("location")
150-
.field("lat", 40.731033)
151-
.field("lon", -73.9962255)
152-
.endObject()
153-
.endObject()
116+
jsonBuilder().startObject().field("name", "Greenwich Village").field("location", "POINT(-73.9962255 40.731033)").endObject()
154117
)
155118
.get();
156119

157120
// to NY: 8.572 km
158121
client().prepareIndex("test")
159122
.setId("7")
160-
.setSource(
161-
jsonBuilder().startObject()
162-
.field("name", "Brooklyn")
163-
.startObject("location")
164-
.field("lat", 40.65)
165-
.field("lon", -73.95)
166-
.endObject()
167-
.endObject()
168-
)
123+
.setSource(jsonBuilder().startObject().field("name", "Brooklyn").field("location", "POINT(-73.95 40.65)").endObject())
169124
.get();
170125

171126
client().admin().indices().prepareRefresh().get();
@@ -192,12 +147,7 @@ public void testSimpleBoundingBoxTest() throws Exception {
192147
public void testLimit2BoundingBox() throws Exception {
193148
Version version = VersionUtils.randomIndexCompatibleVersion(random());
194149
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
195-
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
196-
.startObject()
197-
.startObject("properties")
198-
.startObject("location")
199-
.field("type", "geo_point");
200-
xContentBuilder.endObject().endObject().endObject();
150+
XContentBuilder xContentBuilder = getMapping();
201151
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
202152
ensureGreen();
203153

@@ -207,10 +157,7 @@ public void testLimit2BoundingBox() throws Exception {
207157
jsonBuilder().startObject()
208158
.field("userid", 880)
209159
.field("title", "Place in Stockholm")
210-
.startObject("location")
211-
.field("lat", 59.328355000000002)
212-
.field("lon", 18.036842)
213-
.endObject()
160+
.field("location", "POINT(59.328355000000002 18.036842)")
214161
.endObject()
215162
)
216163
.setRefreshPolicy(IMMEDIATE)
@@ -222,10 +169,7 @@ public void testLimit2BoundingBox() throws Exception {
222169
jsonBuilder().startObject()
223170
.field("userid", 534)
224171
.field("title", "Place in Montreal")
225-
.startObject("location")
226-
.field("lat", 45.509526999999999)
227-
.field("lon", -73.570986000000005)
228-
.endObject()
172+
.field("location", "POINT(-73.570986000000005 45.509526999999999)")
229173
.endObject()
230174
)
231175
.setRefreshPolicy(IMMEDIATE)
@@ -271,12 +215,7 @@ public void testLimit2BoundingBox() throws Exception {
271215
public void testCompleteLonRange() throws Exception {
272216
Version version = VersionUtils.randomIndexCompatibleVersion(random());
273217
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
274-
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
275-
.startObject()
276-
.startObject("properties")
277-
.startObject("location")
278-
.field("type", "geo_point");
279-
xContentBuilder.endObject().endObject().endObject();
218+
XContentBuilder xContentBuilder = getMapping();
280219
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
281220
ensureGreen();
282221

@@ -286,10 +225,7 @@ public void testCompleteLonRange() throws Exception {
286225
jsonBuilder().startObject()
287226
.field("userid", 880)
288227
.field("title", "Place in Stockholm")
289-
.startObject("location")
290-
.field("lat", 59.328355000000002)
291-
.field("lon", 18.036842)
292-
.endObject()
228+
.field("location", "POINT(18.036842 59.328355000000002)")
293229
.endObject()
294230
)
295231
.setRefreshPolicy(IMMEDIATE)
@@ -301,10 +237,7 @@ public void testCompleteLonRange() throws Exception {
301237
jsonBuilder().startObject()
302238
.field("userid", 534)
303239
.field("title", "Place in Montreal")
304-
.startObject("location")
305-
.field("lat", 45.509526999999999)
306-
.field("lon", -73.570986000000005)
307-
.endObject()
240+
.field("location", "POINT(-73.570986000000005 45.509526999999999)")
308241
.endObject()
309242
)
310243
.setRefreshPolicy(IMMEDIATE)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.search.geo;
10+
11+
import org.opensearch.common.xcontent.XContentBuilder;
12+
13+
import java.io.IOException;
14+
15+
public class GeoBoundingBoxQueryGeoPointsIT extends AbstractGeoBoundingBoxQueryIT {
16+
17+
@Override
18+
public XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException {
19+
return parentMapping.startObject("location").field("type", "geo_point").endObject();
20+
}
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.search.geo;
10+
11+
import org.opensearch.common.xcontent.XContentBuilder;
12+
13+
import java.io.IOException;
14+
15+
public class GeoBoundingBoxQueryGeoShapesIT extends AbstractGeoBoundingBoxQueryIT {
16+
17+
@Override
18+
public XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException {
19+
parentMapping = parentMapping.startObject("location").field("type", "geo_shape");
20+
if (randomBoolean()) {
21+
parentMapping.field("strategy", "recursive");
22+
}
23+
return parentMapping.endObject();
24+
}
25+
}

server/src/main/java/org/opensearch/index/query/GeoBoundingBoxQueryBuilder.java

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@
3232

3333
package org.opensearch.index.query;
3434

35-
import org.apache.lucene.document.LatLonDocValuesField;
36-
import org.apache.lucene.document.LatLonPoint;
37-
import org.apache.lucene.search.IndexOrDocValuesQuery;
3835
import org.apache.lucene.search.MatchNoDocsQuery;
3936
import org.apache.lucene.search.Query;
4037
import org.opensearch.OpenSearchParseException;
@@ -44,13 +41,17 @@
4441
import org.opensearch.common.geo.GeoBoundingBox;
4542
import org.opensearch.common.geo.GeoPoint;
4643
import org.opensearch.common.geo.GeoUtils;
44+
import org.opensearch.common.geo.ShapeRelation;
45+
import org.opensearch.common.geo.SpatialStrategy;
4746
import org.opensearch.common.io.stream.StreamInput;
4847
import org.opensearch.common.io.stream.StreamOutput;
4948
import org.opensearch.common.xcontent.XContentBuilder;
5049
import org.opensearch.common.xcontent.XContentParser;
5150
import org.opensearch.geometry.Rectangle;
5251
import org.opensearch.geometry.utils.Geohash;
53-
import org.opensearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType;
52+
import org.opensearch.index.mapper.GeoPointFieldMapper;
53+
import org.opensearch.index.mapper.GeoShapeFieldMapper;
54+
import org.opensearch.index.mapper.GeoShapeQueryable;
5455
import org.opensearch.index.mapper.MappedFieldType;
5556

5657
import java.io.IOException;
@@ -315,11 +316,24 @@ public Query doToQuery(QueryShardContext context) {
315316
if (ignoreUnmapped) {
316317
return new MatchNoDocsQuery();
317318
} else {
318-
throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
319+
throw new QueryShardException(context, "failed to find geo field [" + fieldName + "]");
319320
}
320321
}
321-
if (!(fieldType instanceof GeoPointFieldType)) {
322-
throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
322+
if (fieldType instanceof GeoShapeQueryable == false) {
323+
throw new QueryShardException(
324+
context,
325+
"type ["
326+
+ fieldType
327+
+ "] for field ["
328+
+ fieldName
329+
+ "] is not supported for ["
330+
+ NAME
331+
+ "] queries. Must be one of ["
332+
+ GeoPointFieldMapper.CONTENT_TYPE
333+
+ "] or ["
334+
+ GeoShapeFieldMapper.CONTENT_TYPE
335+
+ "]"
336+
);
323337
}
324338

325339
QueryValidationException exception = checkLatLon();
@@ -344,24 +358,14 @@ public Query doToQuery(QueryShardContext context) {
344358
}
345359
}
346360

347-
Query query = LatLonPoint.newBoxQuery(
348-
fieldType.name(),
349-
luceneBottomRight.getLat(),
350-
luceneTopLeft.getLat(),
361+
final GeoShapeQueryable geoShapeQueryable = (GeoShapeQueryable) fieldType;
362+
final Rectangle rectangle = new Rectangle(
351363
luceneTopLeft.getLon(),
352-
luceneBottomRight.getLon()
364+
luceneBottomRight.getLon(),
365+
luceneTopLeft.getLat(),
366+
luceneBottomRight.getLat()
353367
);
354-
if (fieldType.hasDocValues()) {
355-
Query dvQuery = LatLonDocValuesField.newSlowBoxQuery(
356-
fieldType.name(),
357-
luceneBottomRight.getLat(),
358-
luceneTopLeft.getLat(),
359-
luceneTopLeft.getLon(),
360-
luceneBottomRight.getLon()
361-
);
362-
query = new IndexOrDocValuesQuery(query, dvQuery);
363-
}
364-
return query;
368+
return geoShapeQueryable.geoShapeQuery(rectangle, fieldType.name(), SpatialStrategy.RECURSIVE, ShapeRelation.INTERSECTS, context);
365369
}
366370

367371
@Override

0 commit comments

Comments
 (0)