Skip to content

Commit

Permalink
Support for geo_bounding_box queries on geo_shape fields (#2506)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
nknize authored Mar 22, 2022
1 parent d2bdcde commit bd2d935
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import org.opensearch.test.OpenSearchIntegTestCase;
import org.opensearch.test.VersionUtils;

import java.io.IOException;

import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.opensearch.index.query.QueryBuilders.boolQuery;
Expand All @@ -52,7 +54,15 @@
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;

public class GeoBoundingBoxQueryIT extends OpenSearchIntegTestCase {
abstract class AbstractGeoBoundingBoxQueryIT extends OpenSearchIntegTestCase {

public abstract XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException;

public XContentBuilder getMapping() throws IOException {
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("properties");
mapping = addGeoMapping(mapping);
return mapping.endObject().endObject();
}

@Override
protected boolean forbidPrivateIndexSettings() {
Expand All @@ -62,110 +72,55 @@ protected boolean forbidPrivateIndexSettings() {
public void testSimpleBoundingBoxTest() throws Exception {
Version version = VersionUtils.randomIndexCompatibleVersion(random());
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("location")
.field("type", "geo_point");
xContentBuilder.endObject().endObject().endObject();
XContentBuilder xContentBuilder = getMapping();
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
ensureGreen();

client().prepareIndex("test")
.setId("1")
.setSource(
jsonBuilder().startObject()
.field("name", "New York")
.startObject("location")
.field("lat", 40.7143528)
.field("lon", -74.0059731)
.endObject()
.endObject()
)
.setSource(jsonBuilder().startObject().field("name", "New York").field("location", "POINT(-74.0059731 40.7143528)").endObject())
.get();

// to NY: 5.286 km
client().prepareIndex("test")
.setId("2")
.setSource(
jsonBuilder().startObject()
.field("name", "Times Square")
.startObject("location")
.field("lat", 40.759011)
.field("lon", -73.9844722)
.endObject()
.endObject()
jsonBuilder().startObject().field("name", "Times Square").field("location", "POINT(-73.9844722 40.759011)").endObject()
)
.get();

// to NY: 0.4621 km
client().prepareIndex("test")
.setId("3")
.setSource(
jsonBuilder().startObject()
.field("name", "Tribeca")
.startObject("location")
.field("lat", 40.718266)
.field("lon", -74.007819)
.endObject()
.endObject()
)
.setSource(jsonBuilder().startObject().field("name", "Tribeca").field("location", "POINT(-74.007819 40.718266)").endObject())
.get();

// to NY: 1.055 km
client().prepareIndex("test")
.setId("4")
.setSource(
jsonBuilder().startObject()
.field("name", "Wall Street")
.startObject("location")
.field("lat", 40.7051157)
.field("lon", -74.0088305)
.endObject()
.endObject()
jsonBuilder().startObject().field("name", "Wall Street").field("location", "POINT(-74.0088305 40.7051157)").endObject()
)
.get();

// to NY: 1.258 km
client().prepareIndex("test")
.setId("5")
.setSource(
jsonBuilder().startObject()
.field("name", "Soho")
.startObject("location")
.field("lat", 40.7247222)
.field("lon", -74)
.endObject()
.endObject()
)
.setSource(jsonBuilder().startObject().field("name", "Soho").field("location", "POINT(-74 40.7247222)").endObject())
.get();

// to NY: 2.029 km
client().prepareIndex("test")
.setId("6")
.setSource(
jsonBuilder().startObject()
.field("name", "Greenwich Village")
.startObject("location")
.field("lat", 40.731033)
.field("lon", -73.9962255)
.endObject()
.endObject()
jsonBuilder().startObject().field("name", "Greenwich Village").field("location", "POINT(-73.9962255 40.731033)").endObject()
)
.get();

// to NY: 8.572 km
client().prepareIndex("test")
.setId("7")
.setSource(
jsonBuilder().startObject()
.field("name", "Brooklyn")
.startObject("location")
.field("lat", 40.65)
.field("lon", -73.95)
.endObject()
.endObject()
)
.setSource(jsonBuilder().startObject().field("name", "Brooklyn").field("location", "POINT(-73.95 40.65)").endObject())
.get();

client().admin().indices().prepareRefresh().get();
Expand All @@ -192,12 +147,7 @@ public void testSimpleBoundingBoxTest() throws Exception {
public void testLimit2BoundingBox() throws Exception {
Version version = VersionUtils.randomIndexCompatibleVersion(random());
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("location")
.field("type", "geo_point");
xContentBuilder.endObject().endObject().endObject();
XContentBuilder xContentBuilder = getMapping();
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
ensureGreen();

Expand All @@ -207,10 +157,7 @@ public void testLimit2BoundingBox() throws Exception {
jsonBuilder().startObject()
.field("userid", 880)
.field("title", "Place in Stockholm")
.startObject("location")
.field("lat", 59.328355000000002)
.field("lon", 18.036842)
.endObject()
.field("location", "POINT(59.328355000000002 18.036842)")
.endObject()
)
.setRefreshPolicy(IMMEDIATE)
Expand All @@ -222,10 +169,7 @@ public void testLimit2BoundingBox() throws Exception {
jsonBuilder().startObject()
.field("userid", 534)
.field("title", "Place in Montreal")
.startObject("location")
.field("lat", 45.509526999999999)
.field("lon", -73.570986000000005)
.endObject()
.field("location", "POINT(-73.570986000000005 45.509526999999999)")
.endObject()
)
.setRefreshPolicy(IMMEDIATE)
Expand Down Expand Up @@ -271,12 +215,7 @@ public void testLimit2BoundingBox() throws Exception {
public void testCompleteLonRange() throws Exception {
Version version = VersionUtils.randomIndexCompatibleVersion(random());
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, version).build();
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("location")
.field("type", "geo_point");
xContentBuilder.endObject().endObject().endObject();
XContentBuilder xContentBuilder = getMapping();
assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder));
ensureGreen();

Expand All @@ -286,10 +225,7 @@ public void testCompleteLonRange() throws Exception {
jsonBuilder().startObject()
.field("userid", 880)
.field("title", "Place in Stockholm")
.startObject("location")
.field("lat", 59.328355000000002)
.field("lon", 18.036842)
.endObject()
.field("location", "POINT(18.036842 59.328355000000002)")
.endObject()
)
.setRefreshPolicy(IMMEDIATE)
Expand All @@ -301,10 +237,7 @@ public void testCompleteLonRange() throws Exception {
jsonBuilder().startObject()
.field("userid", 534)
.field("title", "Place in Montreal")
.startObject("location")
.field("lat", 45.509526999999999)
.field("lon", -73.570986000000005)
.endObject()
.field("location", "POINT(-73.570986000000005 45.509526999999999)")
.endObject()
)
.setRefreshPolicy(IMMEDIATE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.search.geo;

import org.opensearch.common.xcontent.XContentBuilder;

import java.io.IOException;

public class GeoBoundingBoxQueryGeoPointsIT extends AbstractGeoBoundingBoxQueryIT {

@Override
public XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException {
return parentMapping.startObject("location").field("type", "geo_point").endObject();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.search.geo;

import org.opensearch.common.xcontent.XContentBuilder;

import java.io.IOException;

public class GeoBoundingBoxQueryGeoShapesIT extends AbstractGeoBoundingBoxQueryIT {

@Override
public XContentBuilder addGeoMapping(XContentBuilder parentMapping) throws IOException {
parentMapping = parentMapping.startObject("location").field("type", "geo_shape");
if (randomBoolean()) {
parentMapping.field("strategy", "recursive");
}
return parentMapping.endObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@

package org.opensearch.index.query;

import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.opensearch.OpenSearchParseException;
Expand All @@ -44,13 +41,17 @@
import org.opensearch.common.geo.GeoBoundingBox;
import org.opensearch.common.geo.GeoPoint;
import org.opensearch.common.geo.GeoUtils;
import org.opensearch.common.geo.ShapeRelation;
import org.opensearch.common.geo.SpatialStrategy;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.geometry.Rectangle;
import org.opensearch.geometry.utils.Geohash;
import org.opensearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType;
import org.opensearch.index.mapper.GeoPointFieldMapper;
import org.opensearch.index.mapper.GeoShapeFieldMapper;
import org.opensearch.index.mapper.GeoShapeQueryable;
import org.opensearch.index.mapper.MappedFieldType;

import java.io.IOException;
Expand Down Expand Up @@ -315,11 +316,24 @@ public Query doToQuery(QueryShardContext context) {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
throw new QueryShardException(context, "failed to find geo field [" + fieldName + "]");
}
}
if (!(fieldType instanceof GeoPointFieldType)) {
throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
if (fieldType instanceof GeoShapeQueryable == false) {
throw new QueryShardException(
context,
"type ["
+ fieldType
+ "] for field ["
+ fieldName
+ "] is not supported for ["
+ NAME
+ "] queries. Must be one of ["
+ GeoPointFieldMapper.CONTENT_TYPE
+ "] or ["
+ GeoShapeFieldMapper.CONTENT_TYPE
+ "]"
);
}

QueryValidationException exception = checkLatLon();
Expand All @@ -344,24 +358,14 @@ public Query doToQuery(QueryShardContext context) {
}
}

Query query = LatLonPoint.newBoxQuery(
fieldType.name(),
luceneBottomRight.getLat(),
luceneTopLeft.getLat(),
final GeoShapeQueryable geoShapeQueryable = (GeoShapeQueryable) fieldType;
final Rectangle rectangle = new Rectangle(
luceneTopLeft.getLon(),
luceneBottomRight.getLon()
luceneBottomRight.getLon(),
luceneTopLeft.getLat(),
luceneBottomRight.getLat()
);
if (fieldType.hasDocValues()) {
Query dvQuery = LatLonDocValuesField.newSlowBoxQuery(
fieldType.name(),
luceneBottomRight.getLat(),
luceneTopLeft.getLat(),
luceneTopLeft.getLon(),
luceneBottomRight.getLon()
);
query = new IndexOrDocValuesQuery(query, dvQuery);
}
return query;
return geoShapeQueryable.geoShapeQuery(rectangle, fieldType.name(), SpatialStrategy.RECURSIVE, ShapeRelation.INTERSECTS, context);
}

@Override
Expand Down
Loading

0 comments on commit bd2d935

Please sign in to comment.