diff --git a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java index 769b22a2f3b..ac4099d80a3 100644 --- a/src/main/java/org/elasticsearch/index/query/FilterBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/FilterBuilders.java @@ -349,6 +349,10 @@ public abstract class FilterBuilders { return new GeoShapeFilterBuilder(name, shape); } + public static GeoShapeFilterBuilder geoShapeFilter(String name, String indexedShapeId, String indexedShapeType) { + return new GeoShapeFilterBuilder(name, indexedShapeId, indexedShapeType); + } + /** * A filter to filter only documents where a field exists in them. * diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java index ac73fddc3c5..35ec86ecaf1 100644 --- a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterBuilder.java @@ -23,16 +23,40 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder { private String filterName; + private final String indexedShapeId; + private final String indexedShapeType; + + private String indexedShapeIndex; + private String indexedShapeFieldName; + /** * Creates a new GeoShapeFilterBuilder whose Filter will be against the - * given field name + * given field name using the given Shape * * @param name Name of the field that will be filtered * @param shape Shape used in the filter */ public GeoShapeFilterBuilder(String name, Shape shape) { + this(name, shape, null, null); + } + + /** + * Creates a new GeoShapeFilterBuilder whose Filter will be against the given field name + * and will use the Shape found with the given ID in the given type + * + * @param name Name of the field that will be filtered + * @param indexedShapeId ID of the indexed Shape that will be used in the Filter + * @param indexedShapeType Index type of the indexed Shapes + */ + public GeoShapeFilterBuilder(String name, String indexedShapeId, String indexedShapeType) { + this(name, null, indexedShapeId, indexedShapeType); + } + + private GeoShapeFilterBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType) { this.name = name; this.shape = shape; + this.indexedShapeId = indexedShapeId; + this.indexedShapeType = indexedShapeType; } /** @@ -80,6 +104,28 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder { return this; } + /** + * Sets the name of the index where the indexed Shape can be found + * + * @param indexedShapeIndex Name of the index where the indexed Shape is + * @return this + */ + public GeoShapeFilterBuilder indexedShapeIndex(String indexedShapeIndex) { + this.indexedShapeIndex = indexedShapeIndex; + return this; + } + + /** + * Sets the name of the field in the indexed Shape document that has the Shape itself + * + * @param indexedShapeFieldName Name of the field where the Shape itself is defined + * @return this + */ + public GeoShapeFilterBuilder indexedShapeFieldName(String indexedShapeFieldName) { + this.indexedShapeFieldName = indexedShapeFieldName; + return this; + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(GeoShapeFilterParser.NAME); @@ -87,9 +133,22 @@ public class GeoShapeFilterBuilder extends BaseFilterBuilder { builder.startObject(name); builder.field("relation", relation.getRelationName()); - builder.startObject("shape"); - GeoJSONShapeSerializer.serialize(shape, builder); - builder.endObject(); + if (shape != null) { + builder.startObject("shape"); + GeoJSONShapeSerializer.serialize(shape, builder); + builder.endObject(); + } else { + builder.startObject("indexed_shape") + .field("id", indexedShapeId) + .field("type", indexedShapeType); + if (indexedShapeIndex != null) { + builder.field("index", indexedShapeIndex); + } + if (indexedShapeFieldName != null) { + builder.field("shape_field_name", indexedShapeFieldName); + } + builder.endObject(); + } builder.endObject(); diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java index 62d444583f0..53ebeb3db70 100644 --- a/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/GeoShapeFilterParser.java @@ -4,11 +4,14 @@ import com.spatial4j.core.shape.Shape; import org.apache.lucene.search.Filter; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.GeoJSONShapeParser; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.cache.filter.support.CacheKeyFilter; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper; +import org.elasticsearch.index.search.shape.ShapeFetchService; import java.io.IOException; @@ -36,6 +39,13 @@ public class GeoShapeFilterParser implements FilterParser { public static final String NAME = "geo_shape"; + private ShapeFetchService fetchService; + + public static class DEFAULTS { + public static final String INDEX_NAME = "shapes"; + public static final String SHAPE_FIELD_NAME = "shape"; + } + @Override public String[] names() { return new String[]{NAME, "geoShape"}; @@ -52,6 +62,11 @@ public class GeoShapeFilterParser implements FilterParser { CacheKeyFilter.Key cacheKey = null; String filterName = null; + String id = null; + String type = null; + String index = DEFAULTS.INDEX_NAME; + String shapeFieldName = DEFAULTS.SHAPE_FIELD_NAME; + XContentParser.Token token; String currentFieldName = null; @@ -73,6 +88,28 @@ public class GeoShapeFilterParser implements FilterParser { if (shapeRelation == null) { throw new QueryParsingException(parseContext.index(), "Unknown shape operation [" + parser.text() + " ]"); } + } else if ("indexed_shape".equals(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if ("id".equals(currentFieldName)) { + id = parser.text(); + } else if ("type".equals(currentFieldName)) { + type = parser.text(); + } else if ("index".equals(currentFieldName)) { + index = parser.text(); + } else if ("shape_field_name".equals(currentFieldName)) { + shapeFieldName = parser.text(); + } + } + } + if (id == null) { + throw new QueryParsingException(parseContext.index(), "ID for indexed shape not provided"); + } else if (type == null) { + throw new QueryParsingException(parseContext.index(), "Type for indexed shape not provided"); + } + shape = fetchService.fetch(id, type, index, shapeFieldName); } } } @@ -119,4 +156,9 @@ public class GeoShapeFilterParser implements FilterParser { return filter; } + + @Inject(optional = true) + public void setFetchService(@Nullable ShapeFetchService fetchService) { + this.fetchService = fetchService; + } } diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java index 27fdf308af7..ec2c594d56c 100644 --- a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java @@ -7,6 +7,9 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; +/** + * {@link QueryBuilder} that builds a GeoShape Query + */ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder { private final String name; @@ -16,22 +19,84 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ private float boost = -1; + private final String indexedShapeId; + private final String indexedShapeType; + + private String indexedShapeIndex; + private String indexedShapeFieldName; + + /** + * Creates a new GeoShapeQueryBuilder whose Query will be against the + * given field name using the given Shape + * + * @param name Name of the field that will be queried + * @param shape Shape used in the query + */ public GeoShapeQueryBuilder(String name, Shape shape) { - this.name = name; - this.shape = shape; + this(name, shape, null, null); + } + /** + * Creates a new GeoShapeQueryBuilder whose Query will be against the given field name + * and will use the Shape found with the given ID in the given type + * + * @param name Name of the field that will be queried + * @param indexedShapeId ID of the indexed Shape that will be used in the Query + * @param indexedShapeType Index type of the indexed Shapes + */ + public GeoShapeQueryBuilder(String name, String indexedShapeId, String indexedShapeType) { + this(name, null, indexedShapeId, indexedShapeType); } + private GeoShapeQueryBuilder(String name, Shape shape, String indexedShapeId, String indexedShapeType) { + this.name = name; + this.shape = shape; + this.indexedShapeId = indexedShapeId; + this.indexedShapeType = indexedShapeType; + } + + /** + * Sets the {@link ShapeRelation} that defines how the Shape used in the + * Query must relate to indexed Shapes + * + * @param relation ShapeRelation used in the filter + * @return this + */ public GeoShapeQueryBuilder relation(ShapeRelation relation) { this.relation = relation; return this; } + /** + * {@inheritDoc} + */ @Override public GeoShapeQueryBuilder boost(float boost) { this.boost = boost; return this; } + /** + * Sets the name of the index where the indexed Shape can be found + * + * @param indexedShapeIndex Name of the index where the indexed Shape is + * @return this + */ + public GeoShapeQueryBuilder indexedShapeIndex(String indexedShapeIndex) { + this.indexedShapeIndex = indexedShapeIndex; + return this; + } + + /** + * Sets the name of the field in the indexed Shape document that has the Shape itself + * + * @param indexedShapeFieldName Name of the field where the Shape itself is defined + * @return this + */ + public GeoShapeQueryBuilder indexedShapeFieldName(String indexedShapeFieldName) { + this.indexedShapeFieldName = indexedShapeFieldName; + return this; + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(GeoShapeQueryParser.NAME); @@ -39,9 +104,22 @@ public class GeoShapeQueryBuilder extends BaseQueryBuilder implements BoostableQ builder.startObject(name); builder.field("relation", relation.getRelationName()); - builder.startObject("shape"); - GeoJSONShapeSerializer.serialize(shape, builder); - builder.endObject(); + if (shape != null) { + builder.startObject("shape"); + GeoJSONShapeSerializer.serialize(shape, builder); + builder.endObject(); + } else { + builder.startObject("indexed_shape") + .field("id", indexedShapeId) + .field("type", indexedShapeType); + if (indexedShapeIndex != null) { + builder.field("index", indexedShapeIndex); + } + if (indexedShapeFieldName != null) { + builder.field("shape_field_name", indexedShapeFieldName); + } + builder.endObject(); + } if (boost != -1) { builder.field("boost", boost); diff --git a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java index 7724e1660ff..afa12ca6e36 100644 --- a/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/GeoShapeQueryParser.java @@ -2,13 +2,16 @@ package org.elasticsearch.index.query; import com.spatial4j.core.shape.Shape; import org.apache.lucene.search.Query; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.geo.GeoJSONShapeParser; import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper; +import org.elasticsearch.index.search.shape.ShapeFetchService; import java.io.IOException; @@ -16,6 +19,13 @@ public class GeoShapeQueryParser implements QueryParser { public static final String NAME = "geo_shape"; + private ShapeFetchService fetchService; + + public static class DEFAULTS { + public static final String INDEX_NAME = "shapes"; + public static final String SHAPE_FIELD_NAME = "shape"; + } + @Override public String[] names() { return new String[]{NAME, Strings.toCamelCase(NAME)}; @@ -29,6 +39,11 @@ public class GeoShapeQueryParser implements QueryParser { ShapeRelation shapeRelation = null; Shape shape = null; + String id = null; + String type = null; + String index = DEFAULTS.INDEX_NAME; + String shapeFieldName = DEFAULTS.SHAPE_FIELD_NAME; + XContentParser.Token token; String currentFieldName = null; float boost = 1f; @@ -51,6 +66,28 @@ public class GeoShapeQueryParser implements QueryParser { if (shapeRelation == null) { throw new QueryParsingException(parseContext.index(), "Unknown shape operation [" + parser.text() + " ]"); } + } else if ("indexed_shape".equals(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if ("id".equals(currentFieldName)) { + id = parser.text(); + } else if ("type".equals(currentFieldName)) { + type = parser.text(); + } else if ("index".equals(currentFieldName)) { + index = parser.text(); + } else if ("shape_field_name".equals(currentFieldName)) { + shapeFieldName = parser.text(); + } + } + } + if (id == null) { + throw new QueryParsingException(parseContext.index(), "ID for indexed shape not provided"); + } else if (type == null) { + throw new QueryParsingException(parseContext.index(), "Type for indexed shape not provided"); + } + shape = fetchService.fetch(id, type, index, shapeFieldName); } } } @@ -84,4 +121,9 @@ public class GeoShapeQueryParser implements QueryParser { query.setBoost(boost); return query; } + + @Inject(optional = true) + public void setFetchService(@Nullable ShapeFetchService fetchService) { + this.fetchService = fetchService; + } } diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index c106ea21faf..7711d8c9d9f 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -723,6 +723,10 @@ public abstract class QueryBuilders { return new GeoShapeQueryBuilder(name, shape); } + public static GeoShapeQueryBuilder geoShapeQuery(String name, String indexedShapeId, String indexedShapeType) { + return new GeoShapeQueryBuilder(name, indexedShapeId, indexedShapeType); + } + private QueryBuilders() { } diff --git a/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java b/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java new file mode 100644 index 00000000000..8d2e8ad7d39 --- /dev/null +++ b/src/main/java/org/elasticsearch/index/search/shape/ShapeFetchService.java @@ -0,0 +1,86 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.index.search.shape; + +import com.spatial4j.core.shape.Shape; +import org.elasticsearch.ElasticSearchIllegalArgumentException; +import org.elasticsearch.ElasticSearchIllegalStateException; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.geo.GeoJSONShapeParser; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; + +import java.io.IOException; + +/** + * Service which retrieves pre-indexed Shapes from another index + */ +public class ShapeFetchService extends AbstractComponent { + + private final Client client; + + @Inject + public ShapeFetchService(Client client, Settings settings) { + super(settings); + this.client = client; + } + + /** + * Fetches the Shape with the given ID in the given type and index. + * + * @param id ID of the Shape to fetch + * @param type Index type where the Shape is indexed + * @param index Index where the Shape is indexed + * @param shapeField Name of the field in the Shape Document where the Shape itself is located + * @return Shape with the given ID + * @throws IOException Can be thrown while parsing the Shape Document and extracting the Shape + */ + public Shape fetch(String id, String type, String index, String shapeField) throws IOException { + GetResponse response = client.prepareGet(index, type, id).setPreference("_local").execute().actionGet(); + if (!response.exists()) { + throw new ElasticSearchIllegalArgumentException("Shape with ID [" + id + "] in type [" + type + "] not found"); + } + + XContentParser parser = null; + try { + parser = JsonXContent.jsonXContent.createParser(response.source()); + XContentParser.Token currentToken; + while ((currentToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (currentToken == XContentParser.Token.FIELD_NAME) { + if (shapeField.equals(parser.currentName())) { + parser.nextToken(); + return GeoJSONShapeParser.parse(parser); + } else { + parser.skipChildren(); + } + } + } + throw new ElasticSearchIllegalStateException("Shape with name [" + id + "] found but missing " + shapeField + " field"); + } finally { + if (parser != null) { + parser.close(); + } + } + } +} diff --git a/src/main/java/org/elasticsearch/index/search/shape/ShapeModule.java b/src/main/java/org/elasticsearch/index/search/shape/ShapeModule.java new file mode 100644 index 00000000000..952e512e549 --- /dev/null +++ b/src/main/java/org/elasticsearch/index/search/shape/ShapeModule.java @@ -0,0 +1,34 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.index.search.shape; + +import org.elasticsearch.common.geo.ShapesAvailability; +import org.elasticsearch.common.inject.AbstractModule; + +public class ShapeModule extends AbstractModule { + + @Override + protected void configure() { + // TODO: We could wrap this entire module in a JTS_AVAILABILITY check + if (ShapesAvailability.JTS_AVAILABLE) { + bind(ShapeFetchService.class).asEagerSingleton(); + } + } +} diff --git a/src/main/java/org/elasticsearch/node/internal/InternalNode.java b/src/main/java/org/elasticsearch/node/internal/InternalNode.java index 1d68d5003e6..f7d882d1dc0 100644 --- a/src/main/java/org/elasticsearch/node/internal/InternalNode.java +++ b/src/main/java/org/elasticsearch/node/internal/InternalNode.java @@ -60,6 +60,7 @@ import org.elasticsearch.gateway.GatewayModule; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.http.HttpServer; import org.elasticsearch.http.HttpServerModule; +import org.elasticsearch.index.search.shape.ShapeModule; import org.elasticsearch.indices.IndicesModule; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.cache.filter.IndicesFilterCache; @@ -151,6 +152,7 @@ public final class InternalNode implements Node { modules.add(new GatewayModule(settings)); modules.add(new NodeClientModule()); modules.add(new BulkUdpModule()); + modules.add(new ShapeModule()); injector = modules.createInjector(); diff --git a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java index 1c5ca293b1f..4697cb92670 100644 --- a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java +++ b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoShapeIntegrationTests.java @@ -3,9 +3,10 @@ package org.elasticsearch.test.integration.search.geo; import com.spatial4j.core.shape.Shape; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; +import org.elasticsearch.common.geo.GeoJSONShapeSerializer; import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.query.GeoShapeFilterParser; import org.elasticsearch.test.integration.AbstractNodesTests; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -43,11 +44,7 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests { @Test public void testIndexPointsFilterRectangle() throws Exception { - try { - client.admin().indices().prepareDelete("test").execute().actionGet(); - } catch (Exception e) { - // ignore - } + client.admin().indices().prepareDelete().execute().actionGet(); String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1") .startObject("properties").startObject("location") @@ -95,4 +92,56 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests { assertThat(searchResponse.hits().hits().length, equalTo(1)); assertThat(searchResponse.hits().getAt(0).id(), equalTo("1")); } + + @Test + public void testIndexedShapeReference() throws Exception { + client.admin().indices().prepareDelete().execute().actionGet(); + + String mapping = XContentFactory.jsonBuilder().startObject().startObject("type1") + .startObject("properties").startObject("location") + .field("type", "geo_shape") + .field("tree", "quadtree") + .endObject().endObject() + .endObject().endObject().string(); + client.admin().indices().prepareCreate("test").addMapping("type1", mapping).execute().actionGet(); + client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet(); + + 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()).execute().actionGet(); + + client.admin().indices().prepareRefresh("test").execute().actionGet(); + + Shape shape = newRectangle().topLeft(-45, 45).bottomRight(45, -45).build(); + XContentBuilder shapeContent = jsonBuilder().startObject() + .startObject("shape"); + GeoJSONShapeSerializer.serialize(shape, shapeContent); + shapeContent.endObject(); + + client.prepareIndex("shapes", "shape_type", "Big_Rectangle").setSource(shapeContent).execute().actionGet(); + client.admin().indices().prepareRefresh().execute().actionGet(); + + client.admin().indices().prepareRefresh("shapes").execute().actionGet(); + + SearchResponse searchResponse = client.prepareSearch("test") + .setQuery(filteredQuery(matchAllQuery(), + geoShapeFilter("location", "Big_Rectangle", "shape_type").relation(ShapeRelation.INTERSECTS))) + .execute().actionGet(); + + assertThat(searchResponse.hits().getTotalHits(), equalTo(1l)); + assertThat(searchResponse.hits().hits().length, equalTo(1)); + assertThat(searchResponse.hits().getAt(0).id(), equalTo("1")); + + searchResponse = client.prepareSearch() + .setQuery(geoShapeQuery("location", "Big_Rectangle", "shape_type").relation(ShapeRelation.INTERSECTS)) + .execute().actionGet(); + + assertThat(searchResponse.hits().getTotalHits(), equalTo(1l)); + assertThat(searchResponse.hits().hits().length, equalTo(1)); + assertThat(searchResponse.hits().getAt(0).id(), equalTo("1")); + } }