Skip to content

Commit

Permalink
Geo shape query vs geo point (#52382)
Browse files Browse the repository at this point in the history
Enable geo_shape query to work on geo_point fields for shapes: circle, polygon, multipolygon, rectangle

see: #48928

Co-Authored-By:  @iverase
  • Loading branch information
djptek authored Mar 18, 2020
1 parent 024deb4 commit d1cbdfb
Show file tree
Hide file tree
Showing 21 changed files with 1,002 additions and 157 deletions.
8 changes: 5 additions & 3 deletions docs/reference/query-dsl/geo-queries.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ Finds documents with geo-points within the specified distance of a central point
Find documents with geo-points within the specified polygon.

<<query-dsl-geo-shape-query,`geo_shape`>> query::
Finds documents with geo-shapes which either intersect, are contained by, or do not intersect with the specified
geo-shape.

Finds documents with:
* `geo-shapes` which either intersect, are contained by, or do not intersect
with the specified geo-shape
* `geo-points` which intersect the specified
geo-shape

include::geo-bounding-box-query.asciidoc[]

Expand Down
20 changes: 17 additions & 3 deletions docs/reference/query-dsl/geo-shape-query.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<titleabbrev>Geo-shape</titleabbrev>
++++

Filter documents indexed using the `geo_shape` type.
Filter documents indexed using the `geo_shape` or `geo_point` type.

Requires the <<geo-shape,`geo_shape` Mapping>>.
Requires the <<geo-shape,`geo_shape` Mapping>> or the <<geo-point,`geo_point` Mapping>>.

The `geo_shape` query uses the same grid square representation as the
`geo_shape` mapping to find documents that have a shape that intersects
Expand Down Expand Up @@ -142,7 +142,7 @@ GET /example/_search
The <<spatial-strategy, geo_shape strategy>> mapping parameter determines
which spatial relation operators may be used at search time.

The following is a complete list of spatial relation operators available:
The following is a complete list of spatial relation operators available when searching a field of type `geo_shape`:

* `INTERSECTS` - (default) Return all documents whose `geo_shape` field
intersects the query geometry.
Expand All @@ -153,6 +153,11 @@ is within the query geometry.
* `CONTAINS` - Return all documents whose `geo_shape` field
contains the query geometry.

When searching a field of type `geo_point` there is a single supported spatial relation operator:

* `INTERSECTS` - (default) Return all documents whose `geo_point` field
intersects the query geometry.

[float]
==== Ignore Unmapped

Expand All @@ -162,6 +167,15 @@ querying multiple indexes which might have different mappings. When set to
`false` (the default value) the query will throw an exception if the field
is not mapped.

==== Shape Types supported for Geo-Point

When searching a field of type `geo_point` the following shape types are not supported:

* `POINT`
* `LINE`
* `MULTIPOINT`
* `MULTILINE`

==== Notes
Geo-shape queries on geo-shapes implemented with <<prefix-trees, `PrefixTrees`>> will not be executed if
<<query-dsl-allow-expensive-queries, `search.allow_expensive_queries`>> is set to false.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ public <T, E extends Exception> T visit(GeometryVisitor<T, E> visitor) throws E
public String toString() {
return "linearring(x=" + Arrays.toString(getX()) +
", y=" + Arrays.toString(getY()) +
(hasZ() ? ", z=" + Arrays.toString(getZ()) : "");
(hasZ() ? ", z=" + Arrays.toString(getZ()) : "") + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@
import org.elasticsearch.Version;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
Expand Down Expand Up @@ -91,19 +88,6 @@ public interface Parser<Parsed> {

}

/**
* interface representing a query builder that generates a query from the given shape
*/
public interface QueryProcessor {
Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);

@Deprecated
default Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
QueryShardContext context) {
return process(shape, fieldName, relation, context);
}
}

public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper>
extends FieldMapper.Builder<T, Y> {
protected Boolean coerce;
Expand Down Expand Up @@ -272,14 +256,14 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
}
}

public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends AbstractSearchableGeometryFieldType {
protected Orientation orientation = Defaults.ORIENTATION.value();

protected Indexer<Parsed, Processed> geometryIndexer;

protected Parser<Parsed> geometryParser;

protected QueryProcessor geometryQueryBuilder;


protected AbstractGeometryFieldType() {
setIndexOptions(IndexOptions.DOCS);
Expand Down Expand Up @@ -339,14 +323,6 @@ public void setGeometryParser(Parser<Parsed> geometryParser) {
protected Parser<Parsed> geometryParser() {
return geometryParser;
}

public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
this.geometryQueryBuilder = geometryQueryBuilder;
}

public QueryProcessor geometryQueryBuilder() {
return geometryQueryBuilder;
}
}

protected Explicit<Boolean> coerce;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.index.mapper;

import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;

/**
* a base class for geometry types that support shape query builder
*/
public abstract class AbstractSearchableGeometryFieldType extends MappedFieldType {

protected QueryProcessor geometryQueryBuilder;

protected AbstractSearchableGeometryFieldType() {
}

protected AbstractSearchableGeometryFieldType(AbstractSearchableGeometryFieldType ref) {
super(ref);
}

public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
this.geometryQueryBuilder = geometryQueryBuilder;
}

public QueryProcessor geometryQueryBuilder() {
return geometryQueryBuilder;
}

/**
* interface representing a query builder that generates a query from the given shape
*/
public interface QueryProcessor {
Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context);

@Deprecated
default Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation,
QueryShardContext context) {
return process(shape, fieldName, relation, context);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointDVIndexFieldData;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.index.query.VectorGeoPointShapeQueryProcessor;
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;

Expand Down Expand Up @@ -123,6 +124,14 @@ public GeoPointFieldMapper build(BuilderContext context, String simpleName, Mapp
ignoreMalformed, ignoreZValue, copyTo);
}

@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);

GeoPointFieldType fieldType = (GeoPointFieldType)fieldType();
fieldType.setGeometryQueryBuilder(new VectorGeoPointShapeQueryProcessor());
}

@Override
public GeoPointFieldMapper build(BuilderContext context) {
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
Expand Down Expand Up @@ -213,7 +222,7 @@ protected void parseCreateField(ParseContext context, List<IndexableField> field
throw new UnsupportedOperationException("Parsing is implemented in parse(), this method should NEVER be called");
}

public static class GeoPointFieldType extends MappedFieldType {
public static class GeoPointFieldType extends AbstractSearchableGeometryFieldType {
public GeoPointFieldType() {
}

Expand Down Expand Up @@ -253,7 +262,8 @@ public Query existsQuery(QueryShardContext context) {

@Override
public Query termQuery(Object value, QueryShardContext context) {
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead: ["
throw new QueryShardException(context,
"Geo fields do not support exact searching, use dedicated geo queries instead: ["
+ name() + "]");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.common.geo.GeoLineDecomposer;
import org.elasticsearch.common.geo.GeoPolygonDecomposer;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.geometry.Circle;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
Expand Down Expand Up @@ -64,7 +65,7 @@ public Geometry prepareForIndexing(Geometry geometry) {
return geometry.visit(new GeometryVisitor<>() {
@Override
public Geometry visit(Circle circle) {
throw new UnsupportedOperationException("CIRCLE geometry is not supported");
throw new UnsupportedOperationException(GeoShapeType.CIRCLE + " geometry is not supported");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,9 @@ public boolean ignoreUnmapped() {
}

/** list of content types this shape query is compatible with */
protected abstract List validContentTypes();
protected abstract List<String> validContentTypes();
/** builds the appropriate lucene shape query */
protected abstract Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType);
/** returns expected content type for this query */
protected abstract String queryFieldType();
/** writes the xcontent specific to this shape query */
protected abstract void doShapeQueryXContent(XContentBuilder builder, Params params) throws IOException;
/** creates a new ShapeQueryBuilder from the provided field name and shape builder */
Expand All @@ -365,7 +363,9 @@ protected Query doToQuery(QueryShardContext context) {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "failed to find " + queryFieldType() + " field [" + fieldName + "]");
throw new QueryShardException(context, "failed to find "
+ String.join(" or ", validContentTypes())
+ " field [" + fieldName + "]");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
Expand All @@ -56,6 +58,12 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ

private SpatialStrategy strategy;

protected static final List<String> validContentTypes =
Collections.unmodifiableList(
Arrays.asList(
GeoShapeFieldMapper.CONTENT_TYPE,
GeoPointFieldMapper.CONTENT_TYPE));

/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* field name using the given Shape
Expand Down Expand Up @@ -168,13 +176,8 @@ public SpatialStrategy strategy() {
}

@Override
protected List validContentTypes() {
return Arrays.asList(GeoShapeFieldMapper.CONTENT_TYPE);
}

@Override
public String queryFieldType() {
return GeoShapeFieldMapper.CONTENT_TYPE;
protected List<String> validContentTypes() {
return validContentTypes;
}

@Override
Expand All @@ -197,12 +200,15 @@ protected GeoShapeQueryBuilder newShapeQueryBuilder(String fieldName, Supplier<G

@Override
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
if (fieldType.typeName().equals(GeoShapeFieldMapper.CONTENT_TYPE) == false) {
if (validContentTypes().contains(fieldType.typeName()) == false) {
throw new QueryShardException(context,
"Field [" + fieldName + "] is not of type [" + queryFieldType() + "] but of type [" + fieldType.typeName() + "]");
"Field [" + fieldName + "] is of unsupported type [" + fieldType.typeName() + "]. ["
+ NAME + "] query supports the following types ["
+ String.join(",", validContentTypes()) + "]");
}

final AbstractGeometryFieldMapper.AbstractGeometryFieldType ft = (AbstractGeometryFieldMapper.AbstractGeometryFieldType) fieldType;
final AbstractSearchableGeometryFieldType ft =
(AbstractSearchableGeometryFieldType) fieldType;
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.spatial4j.shape.Shape;
Expand All @@ -62,7 +63,7 @@

import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;

public class LegacyGeoShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryProcessor {
public class LegacyGeoShapeQueryProcessor implements AbstractSearchableGeometryFieldType.QueryProcessor {

private AbstractGeometryFieldMapper.AbstractGeometryFieldType ft;

Expand Down
Loading

0 comments on commit d1cbdfb

Please sign in to comment.