diff --git a/docs/reference/query-dsl/geo-bounding-box-query.asciidoc b/docs/reference/query-dsl/geo-bounding-box-query.asciidoc index ca355413b2e5c..afa1175055719 100644 --- a/docs/reference/query-dsl/geo-bounding-box-query.asciidoc +++ b/docs/reference/query-dsl/geo-bounding-box-query.asciidoc @@ -4,8 +4,13 @@ Geo-bounding box ++++ -A query allowing to filter hits based on a point location using a -bounding box. Assuming the following indexed document: +Matches <> and <> values that +intersect a bounding box. + +[discrete] +[[geo-bounding-box-query-ex]] +==== Example +Assume the following the following documents are indexed: [source,console] -------------------------------------------------- @@ -33,11 +38,36 @@ PUT /my_locations/_doc/1 } } } + +PUT /my_geoshapes +{ + "mappings": { + "properties": { + "pin": { + "properties": { + "location": { + "type": "geo_shape" + } + } + } + } + } +} + +PUT /my_geoshapes/_doc/1 +{ + "pin": { + "location": { + "type" : "polygon", + "coordinates" : [[[13.0 ,51.5], [15.0, 51.5], [15.0, 54.0], [13.0, 54.0], [13.0 ,51.5]]] + } + } +} -------------------------------------------------- // TESTSETUP -Then the following simple query can be executed with a -`geo_bounding_box` filter: +Use a `geo_bounding_box` filter to match `geo_point` values that intersect a bounding +box. To define the box, provide geopoint values for two opposite corners. [source,console] -------------------------------------------------- @@ -67,6 +97,66 @@ GET my_locations/_search } -------------------------------------------------- +Use the same filter to match `geo_shape` values that intersect the bounding box: + +[source,console] +-------------------------------------------------- +GET my_geoshapes/_search +{ + "query": { + "bool": { + "must": { + "match_all": {} + }, + "filter": { + "geo_bounding_box": { + "pin.location": { + "top_left": { + "lat": 40.73, + "lon": -74.1 + }, + "bottom_right": { + "lat": 40.01, + "lon": -71.12 + } + } + } + } + } + } +} +-------------------------------------------------- + +To match both `geo_point` and `geo_shape` values, search both indices: + +[source,console] +-------------------------------------------------- +GET my_locations,my_geoshapes/_search +{ + "query": { + "bool": { + "must": { + "match_all": {} + }, + "filter": { + "geo_bounding_box": { + "pin.location": { + "top_left": { + "lat": 40.73, + "lon": -74.1 + }, + "bottom_right": { + "lat": 40.01, + "lon": -71.12 + } + } + } + } + } + } +} +-------------------------------------------------- + [discrete] ==== Query Options @@ -291,13 +381,6 @@ GET my_locations/_search } -------------------------------------------------- - -[discrete] -==== geo_point Type - -The filter *requires* the `geo_point` type to be set on the relevant -field. - [discrete] ==== Multi Location Per Document @@ -366,3 +449,8 @@ the upper bounds (top and right edges) might be selected by the query even if they are located slightly outside the edge. The rounding error should be less than 4.20e-8 degrees on the latitude and less than 8.39e-8 degrees on the longitude, which translates to less than 1cm error even at the equator. + +Geoshapes also have limited precision due to rounding. Geoshape edges along the +bounding box's bottom and left edges may not match a `geo_bounding_box` query. +Geoshape edges slightly outside the box's top and right edges may still match +the query. diff --git a/docs/reference/query-dsl/geo-distance-query.asciidoc b/docs/reference/query-dsl/geo-distance-query.asciidoc index cfb2779659e2b..be76c24402c7a 100644 --- a/docs/reference/query-dsl/geo-distance-query.asciidoc +++ b/docs/reference/query-dsl/geo-distance-query.asciidoc @@ -4,9 +4,14 @@ Geo-distance ++++ -Filters documents that include only hits that exists within a specific -distance from a geo point. Assuming the following mapping and indexed -document: +Matches <> and <> values within +a given distance of a geopoint. + +[discrete] +[[geo-distance-query-ex]] +==== Example + +Assume the following the following documents are indexed: [source,console] -------------------------------------------------- @@ -34,12 +39,37 @@ PUT /my_locations/_doc/1 } } } + +PUT /my_geoshapes +{ + "mappings": { + "properties": { + "pin": { + "properties": { + "location": { + "type": "geo_shape" + } + } + } + } + } +} + +PUT /my_geoshapes/_doc/1 +{ + "pin": { + "location": { + "type" : "polygon", + "coordinates" : [[[13.0 ,51.5], [15.0, 51.5], [15.0, 54.0], [13.0, 54.0], [13.0 ,51.5]]] + } + } +} -------------------------------------------------- // TESTSETUP -Then the following simple query can be executed with a `geo_distance` -filter: +Use a `geo_distance` filter to match `geo_point` values within a specified +distance of another geopoint: [source,console] -------------------------------------------------- @@ -64,6 +94,57 @@ GET /my_locations/_search } -------------------------------------------------- +Use the same filter to match `geo_shape` values within the given distance: + +[source,console] +-------------------------------------------------- +GET my_geoshapes/_search +{ + "query": { + "bool": { + "must": { + "match_all": {} + }, + "filter": { + "geo_distance": { + "distance": "200km", + "pin.location": { + "lat": 40, + "lon": -70 + } + } + } + } + } +} +-------------------------------------------------- + +To match both `geo_point` and `geo_shape` values, search both indices: + +[source,console] +-------------------------------------------------- +GET my_locations,my_geoshapes/_search +{ + "query": { + "bool": { + "must": { + "match_all": {} + }, + "filter": { + "geo_distance": { + "distance": "200km", + "pin.location": { + "lat": 40, + "lon": -70 + } + } + } + } + } +} +-------------------------------------------------- + + [discrete] ==== Accepted Formats @@ -198,12 +279,6 @@ The following are options allowed on the filter: longitude, set to `COERCE` to additionally try and infer correct coordinates (default is `STRICT`). -[discrete] -==== geo_point Type - -The filter *requires* the `geo_point` type to be set on the relevant -field. - [discrete] ==== Multi Location Per Document diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/AbstractGeoBoundingBoxQueryIT.java similarity index 79% rename from server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryIT.java rename to server/src/internalClusterTest/java/org/elasticsearch/search/geo/AbstractGeoBoundingBoxQueryIT.java index 94b40a5fd7268..cebc61a28d908 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/AbstractGeoBoundingBoxQueryIT.java @@ -7,7 +7,7 @@ * not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an @@ -23,77 +23,80 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.GeoValidationMethod; import org.elasticsearch.search.SearchHit; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.VersionUtils; +import java.io.IOException; + import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.geoBoundingBoxQuery; +import static org.elasticsearch.index.query.QueryBuilders.geoDistanceQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.equalTo; -public class GeoBoundingBoxQueryIT extends ESIntegTestCase { +abstract class AbstractGeoBoundingBoxQueryIT extends ESIntegTestCase { @Override protected boolean forbidPrivateIndexSettings() { return false; } + public abstract XContentBuilder getMapping() throws IOException; + 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("_doc") - .startObject("properties").startObject("location").field("type", "geo_point"); - xContentBuilder.endObject().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() + .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() + .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() + .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() + .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() + .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() + .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() + .field("location", "POINT(-73.95 40.65)") .endObject()).get(); client().admin().indices().prepareRefresh().get(); @@ -115,21 +118,28 @@ public void testSimpleBoundingBoxTest() throws Exception { for (SearchHit hit : searchResponse.getHits()) { assertThat(hit.getId(), anyOf(equalTo("1"), equalTo("3"), equalTo("5"))); } + // Distance query + searchResponse = client().prepareSearch() // from NY + .setQuery(geoDistanceQuery("location").point(40.5, -73.9).distance(25, DistanceUnit.KILOMETERS)) + .get(); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(2L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + for (SearchHit hit : searchResponse.getHits()) { + assertThat(hit.getId(), anyOf(equalTo("7"), equalTo("4"))); + } } 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("_doc") - .startObject("properties").startObject("location").field("type", "geo_point"); - xContentBuilder.endObject().endObject().endObject().endObject(); + XContentBuilder xContentBuilder = getMapping(); assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder)); ensureGreen(); client().prepareIndex("test").setId("1").setSource(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) .get(); @@ -137,7 +147,7 @@ public void testLimit2BoundingBox() throws Exception { client().prepareIndex("test").setId("2").setSource(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) .get(); @@ -169,21 +179,34 @@ public void testLimit2BoundingBox() throws Exception { .type("indexed")) ).get(); assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + + // Distance query + searchResponse = client().prepareSearch() + .setQuery( + boolQuery().must(termQuery("userid", 880)).filter( + geoDistanceQuery("location").point(20, 60.0).distance(500, DistanceUnit.MILES)) + ).get(); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + + searchResponse = client().prepareSearch() + .setQuery( + boolQuery().must(termQuery("userid", 534)).filter( + geoDistanceQuery("location").point(45.0, -73.0).distance(500, DistanceUnit.MILES)) + ).get(); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); } 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("_doc") - .startObject("properties").startObject("location").field("type", "geo_point"); - xContentBuilder.endObject().endObject().endObject().endObject(); + XContentBuilder xContentBuilder = getMapping(); assertAcked(prepareCreate("test").setSettings(settings).setMapping(xContentBuilder)); ensureGreen(); client().prepareIndex("test").setId("1").setSource(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) .get(); @@ -191,7 +214,7 @@ public void testCompleteLonRange() throws Exception { client().prepareIndex("test").setId("2").setSource(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) .get(); @@ -241,6 +264,13 @@ public void testCompleteLonRange() throws Exception { .type("indexed") ).get(); assertThat(searchResponse.getHits().getTotalHits().value, equalTo(2L)); + + // Distance query + searchResponse = client().prepareSearch() + .setQuery( + geoDistanceQuery("location").point(60.0, -20.0).distance(1800, DistanceUnit.MILES) + ).get(); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); } } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoPointIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoPointIT.java new file mode 100644 index 0000000000000..a1e5c95c30205 --- /dev/null +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoPointIT.java @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.geo; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; + +public class GeoBoundingBoxQueryGeoPointIT extends AbstractGeoBoundingBoxQueryIT { + + @Override + public XContentBuilder getMapping() throws IOException { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("_doc") + .startObject("properties").startObject("location").field("type", "geo_point"); + xContentBuilder.endObject().endObject().endObject().endObject(); + return xContentBuilder; + } +} + diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoShapeIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoShapeIT.java new file mode 100644 index 0000000000000..5c7c26249c51b --- /dev/null +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryGeoShapeIT.java @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.geo; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; + +public class GeoBoundingBoxQueryGeoShapeIT extends AbstractGeoBoundingBoxQueryIT { + + @Override + public XContentBuilder getMapping() throws IOException { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("_doc") + .startObject("properties").startObject("location").field("type", "geo_shape"); + if (randomBoolean()) { + xContentBuilder.field("strategy", "recursive"); + } + xContentBuilder.endObject().endObject().endObject().endObject(); + return xContentBuilder; + } +} + diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java index df2f6b6e4267b..76a2218da3966 100644 --- a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java @@ -19,9 +19,6 @@ package org.elasticsearch.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.elasticsearch.ElasticsearchParseException; @@ -31,13 +28,15 @@ import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.utils.Geohash; -import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; +import org.elasticsearch.index.mapper.GeoShapeQueryable; import org.elasticsearch.index.mapper.MappedFieldType; import java.io.IOException; @@ -309,11 +308,12 @@ 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)) { + throw new QueryShardException(context, + "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "] for [" + NAME + "] query"); } QueryValidationException exception = checkLatLon(); @@ -338,15 +338,10 @@ public Query doToQuery(QueryShardContext context) { } } - Query query = LatLonPoint.newBoxQuery(fieldType.name(), luceneBottomRight.getLat(), luceneTopLeft.getLat(), - luceneTopLeft.getLon(), luceneBottomRight.getLon()); - if (fieldType.hasDocValues()) { - Query dvQuery = LatLonDocValuesField.newSlowBoxQuery(fieldType.name(), - luceneBottomRight.getLat(), luceneTopLeft.getLat(), - luceneTopLeft.getLon(), luceneBottomRight.getLon()); - query = new IndexOrDocValuesQuery(query, dvQuery); - } - return query; + final GeoShapeQueryable geoShapeQueryable = (GeoShapeQueryable) fieldType; + final Rectangle rectangle = + new Rectangle(luceneTopLeft.getLon(), luceneBottomRight.getLon(), luceneTopLeft.getLat(), luceneBottomRight.getLat()); + return geoShapeQueryable.geoShapeQuery(rectangle, fieldType.name(), SpatialStrategy.RECURSIVE, ShapeRelation.INTERSECTS, context); } @Override diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java index f4c0da2c5256a..527e927299d51 100644 --- a/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/GeoDistanceQueryBuilder.java @@ -19,9 +19,6 @@ package org.elasticsearch.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.elasticsearch.common.ParseField; @@ -30,12 +27,15 @@ import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; +import org.elasticsearch.geometry.Circle; +import org.elasticsearch.index.mapper.GeoShapeQueryable; import org.elasticsearch.index.mapper.MappedFieldType; import java.io.IOException; @@ -232,12 +232,13 @@ protected Query doToQuery(QueryShardContext shardContext) throws IOException { if (ignoreUnmapped) { return new MatchNoDocsQuery(); } else { - throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]"); + throw new QueryShardException(shardContext, "failed to find geo field [" + fieldName + "]"); } } - if (!(fieldType instanceof GeoPointFieldType)) { - throw new QueryShardException(shardContext, "field [" + fieldName + "] is not a geo_point field"); + if (!(fieldType instanceof GeoShapeQueryable)) { + throw new QueryShardException(shardContext, + "Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "] for [" + NAME + "] query"); } QueryValidationException exception = checkLatLon(); @@ -249,12 +250,11 @@ protected Query doToQuery(QueryShardContext shardContext) throws IOException { GeoUtils.normalizePoint(center, true, true); } - Query query = LatLonPoint.newDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); - if (fieldType.hasDocValues()) { - Query dvQuery = LatLonDocValuesField.newSlowDistanceQuery(fieldType.name(), center.lat(), center.lon(), this.distance); - query = new IndexOrDocValuesQuery(query, dvQuery); - } - return query; + final GeoShapeQueryable geoShapeQueryable = (GeoShapeQueryable) fieldType; + final Circle circle = + new Circle(center.lon(), center.lat(), this.distance); + return geoShapeQueryable.geoShapeQuery(circle, fieldType.name(), + SpatialStrategy.RECURSIVE, ShapeRelation.INTERSECTS, shardContext); } @Override diff --git a/server/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java index c4fa6a3cc2371..a8ca282d7a64e 100644 --- a/server/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilderTests.java @@ -27,6 +27,8 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.index.mapper.GeoPointFieldMapper; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.test.AbstractQueryTestCase; import org.elasticsearch.test.geo.RandomShapeGenerator; @@ -45,7 +47,7 @@ public class GeoBoundingBoxQueryBuilderTests extends AbstractQueryTestCase qb.toQuery(context)); - assertEquals("failed to find geo_point field [" + qb.fieldName() + "]", e.getMessage()); + assertEquals("failed to find geo field [" + qb.fieldName() + "]", e.getMessage()); } public void testBrokenCoordinateCannotBeSet() { @@ -206,10 +208,11 @@ public void testStrictnessDefault() { @Override protected void doAssertLuceneQuery(GeoBoundingBoxQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { - MappedFieldType fieldType = context.getFieldType(queryBuilder.fieldName()); + final MappedFieldType fieldType = context.getFieldType(queryBuilder.fieldName()); if (fieldType == null) { assertTrue("Found no indexed geo query.", query instanceof MatchNoDocsQuery); - } else if (query instanceof IndexOrDocValuesQuery) { // TODO: remove the if statement once we always use LatLonPoint + } else if (fieldType instanceof GeoPointFieldMapper.GeoPointFieldType) { + assertEquals(IndexOrDocValuesQuery.class, query.getClass()); Query indexQuery = ((IndexOrDocValuesQuery) query).getIndexQuery(); String expectedFieldName = expectedFieldName(queryBuilder.fieldName()); assertEquals(LatLonPoint.newBoxQuery(expectedFieldName, @@ -223,6 +226,8 @@ protected void doAssertLuceneQuery(GeoBoundingBoxQueryBuilder queryBuilder, Quer queryBuilder.topLeft().lat(), queryBuilder.topLeft().lon(), queryBuilder.bottomRight().lon()), dvQuery); + } else { + assertEquals(GeoShapeFieldMapper.GeoShapeFieldType.class, fieldType.getClass()); } } @@ -535,6 +540,6 @@ public void testIgnoreUnmapped() throws IOException { final GeoBoundingBoxQueryBuilder failingQueryBuilder = new GeoBoundingBoxQueryBuilder("unmapped").setCorners(1.0, 0.0, 0.0, 1.0); failingQueryBuilder.ignoreUnmapped(false); QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(shardContext)); - assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]")); + assertThat(e.getMessage(), containsString("failed to find geo field [unmapped]")); } } diff --git a/server/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java index dfb8211463ea5..8bba3a6967540 100644 --- a/server/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/GeoDistanceQueryBuilderTests.java @@ -28,6 +28,9 @@ import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.index.mapper.GeoPointFieldMapper; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; +import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.test.AbstractQueryTestCase; import org.elasticsearch.test.geo.RandomShapeGenerator; import org.locationtech.spatial4j.shape.Point; @@ -42,7 +45,7 @@ public class GeoDistanceQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(shardContext)); - assertThat(e.getMessage(), containsString("failed to find geo_point field [unmapped]")); + assertThat(e.getMessage(), containsString("failed to find geo field [unmapped]")); } public void testParseFailsWithMultipleFields() throws IOException { diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/100_geo_point.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/100_geo_point.yml index 6803b79c79ea7..328470c806de4 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/100_geo_point.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/100_geo_point.yml @@ -86,6 +86,23 @@ setup: --- "geo bounding box query": + - do: + search: + index: locations + body: + query: + geo_bounding_box: + location_from_source: + top_left: + lat: 10 + lon: -10 + bottom_right: + lat: -10 + lon: 10 + - match: {hits.total.value: 1} + +--- +"geo shape query": - do: search: index: locations @@ -98,6 +115,20 @@ setup: coordinates: [ [ -10, 10 ], [ 10, -10 ] ] - match: {hits.total.value: 1} +--- +"geo distance query": + - do: + search: + index: locations + body: + query: + geo_distance: + distance: "2000km" + location_from_source: + lat: 0 + lon: 0 + - match: {hits.total.value: 1} + --- "bounds agg": - do: