From d7e1215e429aff40157eb016e581bfadd026ba0f Mon Sep 17 00:00:00 2001 From: Dominic Page <11043991+djptek@users.noreply.github.com> Date: Fri, 31 Jan 2020 11:55:10 +0100 Subject: [PATCH] Backport of #50737 to 7.x (#51662) * Refactor GeoShape tests to GeoShape and GeoPoint (#50737) Backport to 7.x --- .../search/geo/GeoPointShapeQueryTests.java | 52 + .../search/geo/GeoQueryTests.java | 98 ++ .../search/geo/GeoShapeQueryTests.java | 1075 ++++++++--------- 3 files changed, 654 insertions(+), 571 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/search/geo/GeoPointShapeQueryTests.java create mode 100644 server/src/test/java/org/elasticsearch/search/geo/GeoQueryTests.java diff --git a/server/src/test/java/org/elasticsearch/search/geo/GeoPointShapeQueryTests.java b/server/src/test/java/org/elasticsearch/search/geo/GeoPointShapeQueryTests.java new file mode 100644 index 00000000000..0f5f2fd257c --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/geo/GeoPointShapeQueryTests.java @@ -0,0 +1,52 @@ +/* + * 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; + +public class GeoPointShapeQueryTests extends GeoQueryTests { + + @Override + protected XContentBuilder createTypedMapping() throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("location") + .field("type", "geo_point") + .endObject().endObject().endObject().endObject(); + + return xcb; + } + + @Override + protected XContentBuilder createDefaultMapping() throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject() + .startObject("properties").startObject("location") + .field("type", "geo_point") + .endObject().endObject().endObject(); + + return xcb; + } + + @Override + public void testIndexPointsFilterRectangle(String mapping) throws Exception { + //super.testIndexPointsFilterRectangle(Strings.toString(createDefaultMapping())); + } + +} diff --git a/server/src/test/java/org/elasticsearch/search/geo/GeoQueryTests.java b/server/src/test/java/org/elasticsearch/search/geo/GeoQueryTests.java new file mode 100644 index 00000000000..7bd90c2e5f5 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/geo/GeoQueryTests.java @@ -0,0 +1,98 @@ +/* + * 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.action.get.GetResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.geo.builders.EnvelopeBuilder; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.test.ESSingleNodeTestCase; +import org.locationtech.jts.geom.Coordinate; + +import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.index.query.QueryBuilders.geoIntersectionQuery; +import static org.elasticsearch.index.query.QueryBuilders.geoShapeQuery; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; + +public abstract class GeoQueryTests extends ESSingleNodeTestCase { + + + protected abstract XContentBuilder createTypedMapping() throws Exception; + + protected abstract XContentBuilder createDefaultMapping() throws Exception; + + public void testNullShape() throws Exception { + String mapping = Strings.toString(createTypedMapping()); + client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); + ensureGreen(); + + client().prepareIndex("test", "type1", "aNullshape").setSource("{\"location\": null}", XContentType.JSON) + .setRefreshPolicy(IMMEDIATE).get(); + GetResponse result = client().prepareGet("test", "type1", "aNullshape").get(); + assertThat(result.getField("location"), nullValue()); + } + + public void testIndexPointsFilterRectangle(String mapping) throws Exception { + client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); + ensureGreen(); + + client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject() + .field("name", "Document 1") + .startObject("geo") + .field("type", "point") + .startArray("coordinates").value(-30).value(-30).endArray() + .endObject() + .endObject()).setRefreshPolicy(IMMEDIATE).get(); + + client().prepareIndex("test", "type1", "2").setSource(jsonBuilder().startObject() + .field("name", "Document 2") + .startObject("geo") + .field("type", "point") + .startArray("coordinates").value(-45).value(-50).endArray() + .endObject() + .endObject()).setRefreshPolicy(IMMEDIATE).get(); + + EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); + + SearchResponse searchResponse = client().prepareSearch("test") + .setQuery(geoIntersectionQuery("geo", shape)) + .get(); + + assertSearchResponse(searchResponse); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); + + searchResponse = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", shape)) + .get(); + + assertSearchResponse(searchResponse); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); + } + +} diff --git a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java index 456f52b99d5..69bfc36b3b9 100644 --- a/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java +++ b/server/src/test/java/org/elasticsearch/search/geo/GeoShapeQueryTests.java @@ -21,7 +21,6 @@ package org.elasticsearch.search.geo; import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import org.apache.lucene.geo.GeoTestUtil; -import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.CheckedSupplier; @@ -46,7 +45,6 @@ import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.query.ExistsQueryBuilder; import org.elasticsearch.index.query.GeoShapeQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.geo.RandomShapeGenerator; import org.locationtech.jts.geom.Coordinate; import org.locationtech.spatial4j.shape.Rectangle; @@ -67,340 +65,146 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSear import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.nullValue; -public class GeoShapeQueryTests extends ESSingleNodeTestCase { - private static final String[] PREFIX_TREES = new String[] { +public class GeoShapeQueryTests extends GeoQueryTests { + protected static final String[] PREFIX_TREES = new String[] { LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.GEOHASH, LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.QUADTREE }; - private XContentBuilder createMapping() throws Exception { + @Override + protected XContentBuilder createTypedMapping() throws Exception { XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1") - .startObject("properties").startObject("location") + .startObject("properties").startObject("geo") .field("type", "geo_shape"); if (randomBoolean()) { xcb = xcb.field("tree", randomFrom(PREFIX_TREES)) - .field("strategy", randomFrom(SpatialStrategy.RECURSIVE, SpatialStrategy.TERM)); + .field("strategy", randomFrom(SpatialStrategy.RECURSIVE, SpatialStrategy.TERM)); } xcb = xcb.endObject().endObject().endObject().endObject(); return xcb; } - public void testNullShape() throws Exception { - String mapping = Strings.toString(createMapping()); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); - ensureGreen(); + @Override + protected XContentBuilder createDefaultMapping() throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject() + .startObject("properties").startObject("geo") + .field("type", "geo_shape") + .endObject() + .endObject() + .endObject(); + return xcb; + } - client().prepareIndex("test", "type1", "aNullshape").setSource("{\"location\": null}", XContentType.JSON) - .setRefreshPolicy(IMMEDIATE).get(); - GetResponse result = client().prepareGet("test", "type1", "aNullshape").get(); - assertThat(result.getField("location"), nullValue()); + protected XContentBuilder createPrefixTreeMapping(String tree) throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject() + .startObject("properties").startObject("geo") + .field("type", "geo_shape") + .field("tree", tree) + .endObject() + .endObject() + .endObject(); + return xcb; + } + + protected XContentBuilder createRandomMapping() throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject() + .startObject("properties").startObject("geo") + .field("type", "geo_shape"); + if (randomBoolean()) { + xcb = xcb.field("tree", randomFrom(PREFIX_TREES)) + .field("strategy", randomFrom(SpatialStrategy.RECURSIVE, SpatialStrategy.TERM)); + } + xcb = xcb.endObject().endObject().endObject(); + + return xcb; } public void testIndexPointsFilterRectangle() throws Exception { - String mapping = Strings.toString(createMapping()); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); - ensureGreen(); - - client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject() - .field("name", "Document 1") - .startObject("location") - .field("type", "point") - .startArray("coordinates").value(-30).value(-30).endArray() - .endObject() - .endObject()).setRefreshPolicy(IMMEDIATE).get(); - - client().prepareIndex("test", "type1", "2").setSource(jsonBuilder().startObject() - .field("name", "Document 2") - .startObject("location") - .field("type", "point") - .startArray("coordinates").value(-45).value(-50).endArray() - .endObject() - .endObject()).setRefreshPolicy(IMMEDIATE).get(); - - EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); - - SearchResponse searchResponse = client().prepareSearch("test").setTypes("type1") - .setQuery(geoIntersectionQuery("location", shape)) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - - searchResponse = client().prepareSearch("test").setTypes("type1") - .setQuery(geoShapeQuery("location", shape)) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - } - - public void testEdgeCases() throws Exception { - XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1") - .startObject("properties").startObject("location") - .field("type", "geo_shape") - .endObject().endObject().endObject().endObject(); - String mapping = Strings.toString(xcb); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); - ensureGreen(); - - client().prepareIndex("test", "type1", "blakely").setSource(jsonBuilder().startObject() - .field("name", "Blakely Island") - .startObject("location") - .field("type", "polygon") - .startArray("coordinates").startArray() - .startArray().value(-122.83).value(48.57).endArray() - .startArray().value(-122.77).value(48.56).endArray() - .startArray().value(-122.79).value(48.53).endArray() - .startArray().value(-122.83).value(48.57).endArray() // close the polygon - .endArray().endArray() - .endObject() - .endObject()).setRefreshPolicy(IMMEDIATE).get(); - - EnvelopeBuilder query = new EnvelopeBuilder(new Coordinate(-122.88, 48.62), new Coordinate(-122.82, 48.54)); - - // This search would fail if both geoshape indexing and geoshape filtering - // used the bottom-level optimization in SpatialPrefixTree#recursiveGetNodes. - SearchResponse searchResponse = client().prepareSearch("test").setTypes("type1") - .setQuery(geoIntersectionQuery("location", query)) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("blakely")); - } - - public void testIndexedShapeReference() throws Exception { - String mapping = Strings.toString(createMapping()); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); - createIndex("shapes"); - ensureGreen(); - - EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); - - client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject() - .field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get(); - client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject() - .field("name", "Document 1") - .startObject("location") - .field("type", "point") - .startArray("coordinates").value(-30).value(-30).endArray() - .endObject() - .endObject()).setRefreshPolicy(IMMEDIATE).get(); - - SearchResponse searchResponse = client().prepareSearch("test") - .setQuery(geoIntersectionQuery("location", "Big_Rectangle")) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - - searchResponse = client().prepareSearch("test") - .setQuery(geoShapeQuery("location", "Big_Rectangle")) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - } - - public void testIndexedShapeReferenceWithTypes() throws Exception { - String mapping = Strings.toString(createMapping()); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); - createIndex("shapes"); - ensureGreen(); - - EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); - - client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject() - .field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get(); - client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject() - .field("name", "Document 1") - .startObject("location") - .field("type", "point") - .startArray("coordinates").value(-30).value(-30).endArray() - .endObject() - .endObject()).setRefreshPolicy(IMMEDIATE).get(); - - SearchResponse searchResponse = client().prepareSearch("test") - .setQuery(geoIntersectionQuery("location", "Big_Rectangle", "shape_type")) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - - searchResponse = client().prepareSearch("test") - .setQuery(geoShapeQuery("location", "Big_Rectangle", "shape_type")) - .get(); - - assertSearchResponse(searchResponse); - assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); - assertThat(searchResponse.getHits().getHits().length, equalTo(1)); - assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); - } - - public void testIndexedShapeReferenceSourceDisabled() throws Exception { - XContentBuilder mapping = createMapping(); - client().admin().indices().prepareCreate("test").addMapping("type1", mapping).get(); - createIndex("shapes", Settings.EMPTY, "shape_type", "_source", "enabled=false"); - ensureGreen(); - - EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); - - client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject() - .field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get(); - - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().prepareSearch("test") - .setQuery(geoIntersectionQuery("location", "Big_Rectangle")).get()); - assertThat(e.getMessage(), containsString("source disabled")); - } - - public void testReusableBuilder() throws IOException { - PolygonBuilder polygon = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close()) - .hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5) - .coordinate(175, 5).close())); - assertUnmodified(polygon); - - LineStringBuilder linestring = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close()); - assertUnmodified(linestring); - } - - private void assertUnmodified(ShapeBuilder builder) throws IOException { - String before = Strings.toString(jsonBuilder().startObject().field("area", builder).endObject()); - builder.buildS4J(); - String after = Strings.toString(jsonBuilder().startObject().field("area", builder).endObject()); - assertThat(before, equalTo(after)); + super.testIndexPointsFilterRectangle(Strings.toString(createTypedMapping())); } public void testShapeFetchingPath() throws Exception { createIndex("shapes"); - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape").get(); + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape").get(); - String location = "\"location\" : {\"type\":\"polygon\", \"coordinates\":[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]}"; + String location = "\"geo\" : {\"type\":\"polygon\", \"coordinates\":[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]}"; client().prepareIndex("shapes", "type", "1") - .setSource( - String.format( - Locale.ROOT, "{ %s, \"1\" : { %s, \"2\" : { %s, \"3\" : { %s } }} }", location, location, location, location - ), XContentType.JSON) + .setSource( + String.format( + Locale.ROOT, "{ %s, \"1\" : { %s, \"2\" : { %s, \"3\" : { %s } }} }", location, location, location, location + ), XContentType.JSON) .setRefreshPolicy(IMMEDIATE).get(); client().prepareIndex("test", "type", "1") - .setSource(jsonBuilder().startObject().startObject("location") - .field("type", "polygon") - .startArray("coordinates").startArray() - .startArray().value(-20).value(-20).endArray() - .startArray().value(20).value(-20).endArray() - .startArray().value(20).value(20).endArray() - .startArray().value(-20).value(20).endArray() - .startArray().value(-20).value(-20).endArray() - .endArray().endArray() - .endObject().endObject()).setRefreshPolicy(IMMEDIATE).get(); + .setSource(jsonBuilder().startObject().startObject("geo") + .field("type", "polygon") + .startArray("coordinates").startArray() + .startArray().value(-20).value(-20).endArray() + .startArray().value(20).value(-20).endArray() + .startArray().value(20).value(20).endArray() + .startArray().value(-20).value(20).endArray() + .startArray().value(-20).value(-20).endArray() + .endArray().endArray() + .endObject().endObject()).setRefreshPolicy(IMMEDIATE).get(); - GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", "1").relation(ShapeRelation.INTERSECTS) - .indexedShapeIndex("shapes") - .indexedShapePath("location"); + GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("geo", "1").relation(ShapeRelation.INTERSECTS) + .indexedShapeIndex("shapes") + .indexedShapePath("geo"); SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); + .setPostFilter(filter).get(); assertSearchResponse(result); assertHitCount(result, 1); - filter = QueryBuilders.geoShapeQuery("location", "1").relation(ShapeRelation.INTERSECTS) - .indexedShapeIndex("shapes") - .indexedShapePath("1.location"); + filter = QueryBuilders.geoShapeQuery("geo", "1").relation(ShapeRelation.INTERSECTS) + .indexedShapeIndex("shapes") + .indexedShapePath("1.geo"); result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); + .setPostFilter(filter).get(); assertSearchResponse(result); assertHitCount(result, 1); - filter = QueryBuilders.geoShapeQuery("location", "1").relation(ShapeRelation.INTERSECTS) - .indexedShapeIndex("shapes") - .indexedShapePath("1.2.location"); + filter = QueryBuilders.geoShapeQuery("geo", "1").relation(ShapeRelation.INTERSECTS) + .indexedShapeIndex("shapes") + .indexedShapePath("1.2.geo"); result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); + .setPostFilter(filter).get(); assertSearchResponse(result); assertHitCount(result, 1); - filter = QueryBuilders.geoShapeQuery("location", "1").relation(ShapeRelation.INTERSECTS) - .indexedShapeIndex("shapes") - .indexedShapePath("1.2.3.location"); + filter = QueryBuilders.geoShapeQuery("geo", "1").relation(ShapeRelation.INTERSECTS) + .indexedShapeIndex("shapes") + .indexedShapePath("1.2.3.geo"); result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); + .setPostFilter(filter).get(); assertSearchResponse(result); assertHitCount(result, 1); // now test the query variant - GeoShapeQueryBuilder query = QueryBuilders.geoShapeQuery("location", "1") - .indexedShapeIndex("shapes") - .indexedShapePath("location"); + GeoShapeQueryBuilder query = QueryBuilders.geoShapeQuery("geo", "1") + .indexedShapeIndex("shapes") + .indexedShapePath("geo"); result = client().prepareSearch("test").setQuery(query).get(); assertSearchResponse(result); assertHitCount(result, 1); - query = QueryBuilders.geoShapeQuery("location", "1") - .indexedShapeIndex("shapes") - .indexedShapePath("1.location"); + query = QueryBuilders.geoShapeQuery("geo", "1") + .indexedShapeIndex("shapes") + .indexedShapePath("1.geo"); result = client().prepareSearch("test").setQuery(query).get(); assertSearchResponse(result); assertHitCount(result, 1); - query = QueryBuilders.geoShapeQuery("location", "1") - .indexedShapeIndex("shapes") - .indexedShapePath("1.2.location"); + query = QueryBuilders.geoShapeQuery("geo", "1") + .indexedShapeIndex("shapes") + .indexedShapePath("1.2.geo"); result = client().prepareSearch("test").setQuery(query).get(); assertSearchResponse(result); assertHitCount(result, 1); - query = QueryBuilders.geoShapeQuery("location", "1") - .indexedShapeIndex("shapes") - .indexedShapePath("1.2.3.location"); + query = QueryBuilders.geoShapeQuery("geo", "1") + .indexedShapeIndex("shapes") + .indexedShapePath("1.2.3.geo"); result = client().prepareSearch("test").setQuery(query).get(); assertSearchResponse(result); assertHitCount(result, 1); } - public void testQueryRandomGeoCollection() throws Exception { - // Create a random geometry collection. - GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); - org.apache.lucene.geo.Polygon randomPoly = GeoTestUtil.nextPolygon(); - CoordinatesBuilder cb = new CoordinatesBuilder(); - for (int i = 0; i < randomPoly.numPoints(); ++i) { - cb.coordinate(randomPoly.getPolyLon(i), randomPoly.getPolyLat(i)); - } - gcb.shape(new PolygonBuilder(cb)); - - logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes()); - - if (randomBoolean()) { - client().admin().indices().prepareCreate("test") - .addMapping("type", "location", "type=geo_shape").get(); - } else { - client().admin().indices().prepareCreate("test") - .addMapping("type", "location", "type=geo_shape,tree=quadtree").get(); - } - - XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - ShapeBuilder filterShape = (gcb.getShapeAt(gcb.numShapes() - 1)); - - GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", filterShape); - geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS); - SearchResponse result = client().prepareSearch("test").setQuery(geoShapeQueryBuilder).get(); - assertSearchResponse(result); - assumeTrue("Skipping the check for the polygon with a degenerated dimension until " - +" https://issues.apache.org/jira/browse/LUCENE-8634 is fixed", - randomPoly.maxLat - randomPoly.minLat > 8.4e-8 && randomPoly.maxLon - randomPoly.minLon > 8.4e-8); - assertHitCount(result, 1); - } - public void testRandomGeoCollectionQuery() throws Exception { boolean usePrefixTrees = randomBoolean(); // Create a random geometry collection to index. @@ -431,24 +235,21 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase { gcb.shape(new PolygonBuilder(cb)); logger.info("Created Random GeometryCollection containing {} shapes using {} tree", gcb.numShapes(), - usePrefixTrees ? "default" : "quadtree"); + usePrefixTrees ? "geohash" : "quadtree"); - if (usePrefixTrees == false) { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") - .execute().actionGet(); - } else { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") - .execute().actionGet(); - } + XContentBuilder mapping = createPrefixTreeMapping(usePrefixTrees ? "geohash" : "quadtree"); + Settings settings = Settings.builder().put("index.number_of_shards", 1).build(); + client().admin().indices().prepareCreate("test").addMapping("_doc",mapping).setSettings(settings).get(); + ensureGreen(); - XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test","_doc").setId("1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); // Create a random geometry collection to query GeometryCollectionBuilder queryCollection = RandomShapeGenerator.createGeometryCollection(random()); queryCollection.shape(new PolygonBuilder(cb)); - GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", queryCollection); + GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("geo", queryCollection); geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS); SearchResponse result = client().prepareSearch("test").setQuery(geoShapeQueryBuilder).get(); assertSearchResponse(result); @@ -456,288 +257,23 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase { result.getHits().getTotalHits().value > 0); } - /** tests querying a random geometry collection with a point */ - public void testPointQuery() throws Exception { - // Create a random geometry collection to index. - GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); - double[] pt = new double[] {GeoTestUtil.nextLongitude(), GeoTestUtil.nextLatitude()}; - PointBuilder pb = new PointBuilder(pt[0], pt[1]); - gcb.shape(pb); - if (randomBoolean()) { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") - .execute().actionGet(); - } else { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") - .execute().actionGet(); - } - XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", pb); - geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS); - SearchResponse result = client().prepareSearch("test").setQuery(geoShapeQueryBuilder).get(); - assertSearchResponse(result); - assertHitCount(result, 1); - } - - public void testContainsShapeQuery() throws Exception { - // Create a random geometry collection. - Rectangle mbr = xRandomRectangle(random(), xRandomPoint(random()), true); - boolean usePrefixTrees = randomBoolean(); - GeometryCollectionBuilder gcb; - if (usePrefixTrees) { - gcb = createGeometryCollectionWithin(random(), mbr); - } else { - // vector strategy does not yet support multipoint queries - gcb = new GeometryCollectionBuilder(); - int numShapes = RandomNumbers.randomIntBetween(random(), 1, 4); - for (int i = 0; i < numShapes; ++i) { - ShapeBuilder shape; - do { - shape = RandomShapeGenerator.createShapeWithin(random(), mbr); - } while (shape instanceof MultiPointBuilder); - gcb.shape(shape); - } - } - - if (usePrefixTrees) { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") - .execute().actionGet(); - } else { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") - .execute().actionGet(); - } - - XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - // index the mbr of the collection - EnvelopeBuilder env = new EnvelopeBuilder(new Coordinate(mbr.getMinX(), mbr.getMaxY()), - new Coordinate(mbr.getMaxX(), mbr.getMinY())); - docSource = env.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "2").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - ShapeBuilder filterShape = (gcb.getShapeAt(randomIntBetween(0, gcb.numShapes() - 1))); - GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", filterShape) - .relation(ShapeRelation.CONTAINS); - SearchResponse response = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); - assertSearchResponse(response); - - assertThat(response.getHits().getTotalHits().value, greaterThan(0L)); - } - - public void testExistsQuery() throws Exception { - // Create a random geometry collection. - GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); - logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes()); - - if (randomBoolean()) { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape") - .execute().actionGet(); - } else { - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") - .execute().actionGet(); - } - - XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject(); - client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - ExistsQueryBuilder eqb = QueryBuilders.existsQuery("location"); - SearchResponse result = client().prepareSearch("test").setQuery(eqb).get(); - assertSearchResponse(result); - assertHitCount(result, 1); - } - - public void testShapeFilterWithDefinedGeoCollection() throws Exception { - createIndex("shapes"); - client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree") - .get(); - - XContentBuilder docSource = jsonBuilder().startObject().startObject("location") - .field("type", "geometrycollection") - .startArray("geometries") - .startObject() - .field("type", "point") - .startArray("coordinates") - .value(100.0).value(0.0) - .endArray() - .endObject() - .startObject() - .field("type", "linestring") - .startArray("coordinates") - .startArray() - .value(101.0).value(0.0) - .endArray() - .startArray() - .value(102.0).value(1.0) - .endArray() - .endArray() - .endObject() - .endArray() - .endObject().endObject(); - client().prepareIndex("test", "type", "1") - .setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); - - GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery( - "location", - new GeometryCollectionBuilder() - .polygon( - new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0) - .coordinate(103.0, 3.0).coordinate(103.0, -1.0) - .coordinate(99.0, -1.0)))).relation(ShapeRelation.INTERSECTS); - SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); - assertSearchResponse(result); - assertHitCount(result, 1); - filter = QueryBuilders.geoShapeQuery( - "location", - new GeometryCollectionBuilder().polygon( - new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0) - .coordinate(193.0, 13.0).coordinate(193.0, -11.0) - .coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS); - result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); - assertSearchResponse(result); - assertHitCount(result, 0); - filter = QueryBuilders.geoShapeQuery("location", new GeometryCollectionBuilder() - .polygon(new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0) - .coordinate(103.0, -1.0).coordinate(99.0, -1.0))) - .polygon( - new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0) - .coordinate(193.0, 13.0).coordinate(193.0, -11.0) - .coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS); - result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); - assertSearchResponse(result); - assertHitCount(result, 1); - // no shape - filter = QueryBuilders.geoShapeQuery("location", new GeometryCollectionBuilder()); - result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) - .setPostFilter(filter).get(); - assertSearchResponse(result); - assertHitCount(result, 0); - } - - public void testPointsOnly() throws Exception { - String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1") - .startObject("properties").startObject("location") - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .field("tree_levels", "6") - .field("distance_error_pct", "0.01") - .field("points_only", true) - .endObject().endObject() - .endObject().endObject()); - - client().admin().indices().prepareCreate("geo_points_only").addMapping("type1", mapping, XContentType.JSON).get(); - ensureGreen(); - - ShapeBuilder shape = RandomShapeGenerator.createShape(random()); - try { - client().prepareIndex("geo_points_only", "type1", "1") - .setSource(jsonBuilder().startObject().field("location", shape).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); - } catch (MapperParsingException e) { - // RandomShapeGenerator created something other than a POINT type, verify the correct exception is thrown - assertThat(e.getCause().getMessage(), containsString("is configured for points only")); - return; - } - - // test that point was inserted - SearchResponse response = client().prepareSearch("geo_points_only").setTypes("type1") - .setQuery(geoIntersectionQuery("location", shape)) - .get(); - - assertEquals(1, response.getHits().getTotalHits().value); - } - - public void testPointsOnlyExplicit() throws Exception { - String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1") - .startObject("properties").startObject("location") - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .field("tree_levels", "6") - .field("distance_error_pct", "0.01") - .field("points_only", true) - .endObject().endObject() - .endObject().endObject()); - - client().admin().indices().prepareCreate("geo_points_only").addMapping("type1", mapping, XContentType.JSON).get(); - ensureGreen(); - - // MULTIPOINT - ShapeBuilder shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.MULTIPOINT); - client().prepareIndex("geo_points_only", "type1", "1") - .setSource(jsonBuilder().startObject().field("location", shape).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); - - // POINT - shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POINT); - client().prepareIndex("geo_points_only", "type1", "2") - .setSource(jsonBuilder().startObject().field("location", shape).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); - - // test that point was inserted - SearchResponse response = client().prepareSearch("geo_points_only").setTypes("type1") - .setQuery(matchAllQuery()) - .get(); - - assertEquals(2, response.getHits().getTotalHits().value); - } - - public void testFieldAlias() throws IOException { - XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() - .startObject("type") - .startObject("properties") - .startObject("location") - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .endObject() - .startObject("alias") - .field("type", "alias") - .field("path", "location") - .endObject() - .endObject() - .endObject() - .endObject(); - - createIndex("test", Settings.EMPTY, "type", mapping); - - ShapeBuilder shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.MULTIPOINT); - client().prepareIndex("test", "type", "1") - .setSource(jsonBuilder().startObject().field("location", shape).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); - - SearchResponse response = client().prepareSearch("test") - .setQuery(geoShapeQuery("alias", shape)) - .get(); - assertEquals(1, response.getHits().getTotalHits().value); - } - // Test for issue #34418 - public void testEnvelopeSpanningDateline() throws IOException { - XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() - .startObject("doc") - .startObject("properties") - .startObject("geo").field("type", "geo_shape").endObject() - .endObject() - .endObject() - .endObject(); - - createIndex("test", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping); + public void testEnvelopeSpanningDateline() throws Exception { + XContentBuilder mapping = createDefaultMapping(); + client().admin().indices().prepareCreate("test").addMapping("_doc",mapping).get(); + ensureGreen(); String doc1 = "{\"geo\": {\r\n" + "\"coordinates\": [\r\n" + "-33.918711,\r\n" + "18.847685\r\n" + "],\r\n" + - "\"type\": \"Point\"\r\n" + "}}"; - client().index(new IndexRequest("test", "doc", "1").source(doc1, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); + "\"type\": \"Point\"\r\n" + "}}"; + client().index(new IndexRequest("test").id("1").source(doc1, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); String doc2 = "{\"geo\": {\r\n" + "\"coordinates\": [\r\n" + "-49.0,\r\n" + "18.847685\r\n" + "],\r\n" + "\"type\": \"Point\"\r\n" + "}}"; - client().index(new IndexRequest("test", "doc", "2").source(doc2, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); + client().index(new IndexRequest("test").id("2").source(doc2, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); String doc3 = "{\"geo\": {\r\n" + "\"coordinates\": [\r\n" + "49.0,\r\n" + "18.847685\r\n" + "],\r\n" + "\"type\": \"Point\"\r\n" + "}}"; - client().index(new IndexRequest("test", "doc", "3").source(doc3, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); + client().index(new IndexRequest("test").id("3").source(doc3, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet(); @SuppressWarnings("unchecked") CheckedSupplier querySupplier = randomFrom( () -> QueryBuilders.geoShapeQuery( @@ -784,14 +320,8 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase { assertNotEquals("1", response.getHits().getAt(1).getId()); } - public void testGeometryCollectionRelations() throws IOException { - XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() - .startObject("doc") - .startObject("properties") - .startObject("geo").field("type", "geo_shape").endObject() - .endObject() - .endObject() - .endObject(); + public void testGeometryCollectionRelations() throws Exception { + XContentBuilder mapping = createPrefixTreeMapping(LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.GEOHASH); createIndex("test", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping); @@ -856,4 +386,407 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase { assertEquals(1, response.getHits().getTotalHits().value); } } + + public void testEdgeCases() throws Exception { + XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("geo") + .field("type", "geo_shape") + .endObject().endObject().endObject().endObject(); + String mapping = Strings.toString(xcb); + client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); + ensureGreen(); + + client().prepareIndex("test", "type1", "blakely").setSource(jsonBuilder().startObject() + .field("name", "Blakely Island") + .startObject("geo") + .field("type", "polygon") + .startArray("coordinates").startArray() + .startArray().value(-122.83).value(48.57).endArray() + .startArray().value(-122.77).value(48.56).endArray() + .startArray().value(-122.79).value(48.53).endArray() + .startArray().value(-122.83).value(48.57).endArray() // close the polygon + .endArray().endArray() + .endObject() + .endObject()).setRefreshPolicy(IMMEDIATE).get(); + + EnvelopeBuilder query = new EnvelopeBuilder(new Coordinate(-122.88, 48.62), new Coordinate(-122.82, 48.54)); + + // This search would fail if both geoshape indexing and geoshape filtering + // used the bottom-level optimization in SpatialPrefixTree#recursiveGetNodes. + SearchResponse searchResponse = client().prepareSearch("test").setTypes("type1") + .setQuery(geoIntersectionQuery("geo", query)) + .get(); + + assertSearchResponse(searchResponse); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("blakely")); + } + + public void testIndexedShapeReferenceSourceDisabled() throws Exception { + XContentBuilder mapping = createTypedMapping(); + client().admin().indices().prepareCreate("test").addMapping("type1", mapping).get(); + createIndex("shapes", Settings.EMPTY, "shape_type", "_source", "enabled=false"); + ensureGreen(); + + EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); + + client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject() + .field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get(); + + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().prepareSearch("test") + .setQuery(geoIntersectionQuery("geo", "Big_Rectangle")).get()); + assertThat(e.getMessage(), containsString("source disabled")); + } + + public void testReusableBuilder() throws IOException { + PolygonBuilder polygon = new PolygonBuilder(new CoordinatesBuilder() + .coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close()) + .hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(175, -5).coordinate(185, -5).coordinate(185, 5) + .coordinate(175, 5).close())); + assertUnmodified(polygon); + + LineStringBuilder linestring = new LineStringBuilder(new CoordinatesBuilder() + .coordinate(170, -10).coordinate(190, -10).coordinate(190, 10).coordinate(170, 10).close()); + assertUnmodified(linestring); + } + + private void assertUnmodified(ShapeBuilder builder) throws IOException { + String before = Strings.toString(jsonBuilder().startObject().field("area", builder).endObject()); + builder.buildS4J(); + String after = Strings.toString(jsonBuilder().startObject().field("area", builder).endObject()); + assertThat(before, equalTo(after)); + } + + /** tests querying a random geometry collection with a point */ + public void testPointQuery() throws Exception { + // Create a random geometry collection to index. + GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); + double[] pt = new double[] {GeoTestUtil.nextLongitude(), GeoTestUtil.nextLatitude()}; + PointBuilder pb = new PointBuilder(pt[0], pt[1]); + gcb.shape(pb); + if (randomBoolean()) { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape") + .execute().actionGet(); + } else { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape,tree=quadtree") + .execute().actionGet(); + } + XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("geo", pb); + geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS); + SearchResponse result = client().prepareSearch("test").setQuery(geoShapeQueryBuilder).get(); + assertSearchResponse(result); + assertHitCount(result, 1); + } + + public void testContainsShapeQuery() throws Exception { + // Create a random geometry collection. + Rectangle mbr = xRandomRectangle(random(), xRandomPoint(random()), true); + boolean usePrefixTrees = randomBoolean(); + GeometryCollectionBuilder gcb; + if (usePrefixTrees) { + gcb = createGeometryCollectionWithin(random(), mbr); + } else { + // vector strategy does not yet support multipoint queries + gcb = new GeometryCollectionBuilder(); + int numShapes = RandomNumbers.randomIntBetween(random(), 1, 4); + for (int i = 0; i < numShapes; ++i) { + ShapeBuilder shape; + do { + shape = RandomShapeGenerator.createShapeWithin(random(), mbr); + } while (shape instanceof MultiPointBuilder); + gcb.shape(shape); + } + } + + if (usePrefixTrees) { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape,tree=quadtree") + .execute().actionGet(); + } else { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape") + .execute().actionGet(); + } + + XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + // index the mbr of the collection + EnvelopeBuilder env = new EnvelopeBuilder(new Coordinate(mbr.getMinX(), mbr.getMaxY()), + new Coordinate(mbr.getMaxX(), mbr.getMinY())); + docSource = env.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test", "type", "2").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + ShapeBuilder filterShape = (gcb.getShapeAt(randomIntBetween(0, gcb.numShapes() - 1))); + GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("geo", filterShape) + .relation(ShapeRelation.CONTAINS); + + SearchResponse response = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) + .setPostFilter(filter).get(); + assertSearchResponse(response); + assertThat(response.getHits().getTotalHits().value, greaterThan(0L)); + } + + public void testExistsQuery() throws Exception { + // Create a random geometry collection. + GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); + logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes()); + + if (randomBoolean()) { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape") + .execute().actionGet(); + } else { + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape,tree=quadtree") + .execute().actionGet(); + } + + XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + ExistsQueryBuilder eqb = QueryBuilders.existsQuery("geo"); + SearchResponse result = client().prepareSearch("test").setQuery(eqb).get(); + assertSearchResponse(result); + assertHitCount(result, 1); + } + + public void testPointsOnly() throws Exception { + String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("location") + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .field("tree_levels", "6") + .field("distance_error_pct", "0.01") + .field("points_only", true) + .endObject().endObject() + .endObject().endObject()); + + client().admin().indices().prepareCreate("geo_points_only").addMapping("type1", mapping, XContentType.JSON).get(); + ensureGreen(); + + ShapeBuilder shape = RandomShapeGenerator.createShape(random()); + try { + client().prepareIndex("geo_points_only", "type1", "1") + .setSource(jsonBuilder().startObject().field("location", shape).endObject()) + .setRefreshPolicy(IMMEDIATE).get(); + } catch (MapperParsingException e) { + // RandomShapeGenerator created something other than a POINT type, verify the correct exception is thrown + assertThat(e.getCause().getMessage(), containsString("is configured for points only")); + return; + } + + // test that point was inserted + SearchResponse response = client().prepareSearch("geo_points_only").setTypes("type1") + .setQuery(geoIntersectionQuery("location", shape)) + .get(); + + assertEquals(1, response.getHits().getTotalHits().value); + } + + public void testPointsOnlyExplicit() throws Exception { + String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("geo") + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .field("tree_levels", "6") + .field("distance_error_pct", "0.01") + .field("points_only", true) + .endObject().endObject() + .endObject().endObject()); + + client().admin().indices().prepareCreate("geo_points_only").addMapping("type1", mapping, XContentType.JSON).get(); + ensureGreen(); + + // MULTIPOINT + ShapeBuilder shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.MULTIPOINT); + client().prepareIndex("geo_points_only", "type1", "1") + .setSource(jsonBuilder().startObject().field("geo", shape).endObject()) + .setRefreshPolicy(IMMEDIATE).get(); + + // POINT + shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POINT); + client().prepareIndex("geo_points_only", "type1", "2") + .setSource(jsonBuilder().startObject().field("geo", shape).endObject()) + .setRefreshPolicy(IMMEDIATE).get(); + + // test that point was inserted + SearchResponse response = client().prepareSearch("geo_points_only").setTypes("type1") + .setQuery(matchAllQuery()) + .get(); + + assertEquals(2, response.getHits().getTotalHits().value); + } + + public void testIndexedShapeReference() throws Exception { + String mapping = Strings.toString(createTypedMapping()); + client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get(); + createIndex("shapes"); + ensureGreen(); + + EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45)); + + client().prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(jsonBuilder().startObject() + .field("shape", shape).endObject()).setRefreshPolicy(IMMEDIATE).get(); + client().prepareIndex("test", "type1", "1").setSource(jsonBuilder().startObject() + .field("name", "Document 1") + .startObject("geo") + .field("type", "point") + .startArray("coordinates").value(-30).value(-30).endArray() + .endObject() + .endObject()).setRefreshPolicy(IMMEDIATE).get(); + + SearchResponse searchResponse = client().prepareSearch("test") + .setQuery(geoIntersectionQuery("geo", "Big_Rectangle")) + .get(); + + assertSearchResponse(searchResponse); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); + + searchResponse = client().prepareSearch("test") + .setQuery(geoShapeQuery("geo", "Big_Rectangle")) + .get(); + + assertSearchResponse(searchResponse); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); + assertThat(searchResponse.getHits().getHits().length, equalTo(1)); + assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1")); + } + + public void testFieldAlias() throws IOException { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() + .startObject("type") + .startObject("properties") + .startObject("location") + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .endObject() + .startObject("alias") + .field("type", "alias") + .field("path", "location") + .endObject() + .endObject() + .endObject() + .endObject(); + + createIndex("test", Settings.EMPTY, "type", mapping); + + ShapeBuilder shape = RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.MULTIPOINT); + client().prepareIndex("test", "type", "1") + .setSource(jsonBuilder().startObject().field("location", shape).endObject()) + .setRefreshPolicy(IMMEDIATE).get(); + + SearchResponse response = client().prepareSearch("test") + .setQuery(geoShapeQuery("alias", shape)) + .get(); + assertEquals(1, response.getHits().getTotalHits().value); + } + + public void testQueryRandomGeoCollection() throws Exception { + // Create a random geometry collection. + GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); + org.apache.lucene.geo.Polygon randomPoly = GeoTestUtil.nextPolygon(); + CoordinatesBuilder cb = new CoordinatesBuilder(); + for (int i = 0; i < randomPoly.numPoints(); ++i) { + cb.coordinate(randomPoly.getPolyLon(i), randomPoly.getPolyLat(i)); + } + gcb.shape(new PolygonBuilder(cb)); + + logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes()); + + if (randomBoolean()) { + client().admin().indices().prepareCreate("test") + .addMapping("type", "geo", "type=geo_shape").get(); + } else { + client().admin().indices().prepareCreate("test") + .addMapping("type", "geo", "type=geo_shape,tree=quadtree").get(); + } + + XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("geo"), null).endObject(); + client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + ShapeBuilder filterShape = (gcb.getShapeAt(gcb.numShapes() - 1)); + + GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("geo", filterShape); + geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS); + SearchResponse result = client().prepareSearch("test").setQuery(geoShapeQueryBuilder).get(); + assertSearchResponse(result); + assumeTrue("Skipping the check for the polygon with a degenerated dimension until " + +" https://issues.apache.org/jira/browse/LUCENE-8634 is fixed", + randomPoly.maxLat - randomPoly.minLat > 8.4e-8 && randomPoly.maxLon - randomPoly.minLon > 8.4e-8); + assertHitCount(result, 1); + } + + public void testShapeFilterWithDefinedGeoCollection() throws Exception { + createIndex("shapes"); + client().admin().indices().prepareCreate("test").addMapping("type", "geo", "type=geo_shape,tree=quadtree") + .get(); + + XContentBuilder docSource = jsonBuilder().startObject().startObject("geo") + .field("type", "geometrycollection") + .startArray("geometries") + .startObject() + .field("type", "point") + .startArray("coordinates") + .value(100.0).value(0.0) + .endArray() + .endObject() + .startObject() + .field("type", "linestring") + .startArray("coordinates") + .startArray() + .value(101.0).value(0.0) + .endArray() + .startArray() + .value(102.0).value(1.0) + .endArray() + .endArray() + .endObject() + .endArray() + .endObject().endObject(); + client().prepareIndex("test", "type", "1") + .setSource(docSource).setRefreshPolicy(IMMEDIATE).get(); + + GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery( + "geo", + new GeometryCollectionBuilder() + .polygon( + new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0) + .coordinate(103.0, 3.0).coordinate(103.0, -1.0) + .coordinate(99.0, -1.0)))).relation(ShapeRelation.INTERSECTS); + SearchResponse result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) + .setPostFilter(filter).get(); + assertSearchResponse(result); + assertHitCount(result, 1); + filter = QueryBuilders.geoShapeQuery( + "geo", + new GeometryCollectionBuilder().polygon( + new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0) + .coordinate(193.0, 13.0).coordinate(193.0, -11.0) + .coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS); + result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) + .setPostFilter(filter).get(); + assertSearchResponse(result); + assertHitCount(result, 0); + filter = QueryBuilders.geoShapeQuery("geo", new GeometryCollectionBuilder() + .polygon(new PolygonBuilder(new CoordinatesBuilder().coordinate(99.0, -1.0).coordinate(99.0, 3.0).coordinate(103.0, 3.0) + .coordinate(103.0, -1.0).coordinate(99.0, -1.0))) + .polygon( + new PolygonBuilder(new CoordinatesBuilder().coordinate(199.0, -11.0).coordinate(199.0, 13.0) + .coordinate(193.0, 13.0).coordinate(193.0, -11.0) + .coordinate(199.0, -11.0)))).relation(ShapeRelation.INTERSECTS); + result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) + .setPostFilter(filter).get(); + assertSearchResponse(result); + assertHitCount(result, 1); + // no shape + filter = QueryBuilders.geoShapeQuery("geo", new GeometryCollectionBuilder()); + result = client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()) + .setPostFilter(filter).get(); + assertSearchResponse(result); + assertHitCount(result, 0); + } + }