diff --git a/docs/reference/mapping/types/point.asciidoc b/docs/reference/mapping/types/point.asciidoc
new file mode 100644
index 00000000000..1a78a121b41
--- /dev/null
+++ b/docs/reference/mapping/types/point.asciidoc
@@ -0,0 +1,99 @@
+[[point]]
+[role="xpack"]
+[testenv="basic"]
+=== Point datatype
+++++
+Point
+++++
+
+The `point` datatype facilitates the indexing of and searching
+arbitrary `x, y` pairs that fall in a 2-dimensional planar
+coordinate system.
+
+You can query documents using this type using
+<>.
+
+There are four ways that a point may be specified, as demonstrated below:
+
+[source,console]
+--------------------------------------------------
+PUT my_index
+{
+ "mappings": {
+ "properties": {
+ "location": {
+ "type": "point"
+ }
+ }
+ }
+}
+
+PUT my_index/_doc/1
+{
+ "text": "Point as an object",
+ "location": { <1>
+ "x": 41.12,
+ "y": -71.34
+ }
+}
+
+PUT my_index/_doc/2
+{
+ "text": "Point as a string",
+ "location": "41.12,-71.34" <2>
+}
+
+
+PUT my_index/_doc/4
+{
+ "text": "Point as an array",
+ "location": [41.12, -71.34] <3>
+}
+
+PUT my_index/_doc/5
+{
+ "text": "Point as a WKT POINT primitive",
+ "location" : "POINT (41.12 -71.34)" <4>
+}
+
+--------------------------------------------------
+
+<1> Point expressed as an object, with `x` and `y` keys.
+<2> Point expressed as a string with the format: `"x,y"`.
+<4> Point expressed as an array with the format: [ `x`, `y`]
+<5> Point expressed as a http://docs.opengeospatial.org/is/12-063r5/12-063r5.html[Well-Known Text]
+POINT with the format: `"POINT(x y)"`
+
+The coordinates provided to the indexer are single precision floating point values so
+the field guarantees the same accuracy provided by the java virtual machine (typically
+`1E-38`).
+
+[[geo-point-params]]
+==== Parameters for `geo_point` fields
+
+The following parameters are accepted by `point` fields:
+
+[horizontal]
+
+<>::
+
+ If `true`, malformed points are ignored. If `false` (default),
+ malformed points throw an exception and reject the whole document.
+
+`ignore_z_value`::
+
+ If `true` (default) three dimension points will be accepted (stored in source)
+ but only x and y values will be indexed; the third dimension is
+ ignored. If `false`, points containing any more than x and y
+ (two dimensions) values throw an exception and reject the whole document.
+
+<>::
+
+ Accepts an point value which is substituted for any explicit `null` values.
+ Defaults to `null`, which means the field is treated as missing.
+
+==== Sorting and Retrieving index Shapes
+
+It is currently not possible to sort shapes or retrieve their fields
+directly. The `point` value is only retrievable through the `_source`
+field.
diff --git a/docs/reference/query-dsl/shape-queries.asciidoc b/docs/reference/query-dsl/shape-queries.asciidoc
index 204ebab9cec..2e44069c066 100644
--- a/docs/reference/query-dsl/shape-queries.asciidoc
+++ b/docs/reference/query-dsl/shape-queries.asciidoc
@@ -3,16 +3,21 @@
[testenv="basic"]
== Shape queries
+
Like <> Elasticsearch supports the ability to index
arbitrary two dimension (non Geospatial) geometries making it possible to
-map out virtual worlds, sporting venues, theme parks, and CAD diagrams. The
-<> field type supports points, lines, polygons, multi-polygons,
-envelope, etc.
+map out virtual worlds, sporting venues, theme parks, and CAD diagrams.
+
+Elasticsearch supports two types of cartesian data:
+<> fields which support x/y pairs, and
+<> fields, which support points, lines, circles, polygons, multi-polygons, etc.
The queries in this group are:
<> query::
-Finds documents with shapes that either intersect, are within, or do not
-intersect a specified shape.
+Finds documents with:
+* `shapes` which either intersect, are contained by, are within or do not intersect
+with the specified shape
+* `points` which intersect the specified shape
include::shape-query.asciidoc[]
diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java
index a93fb75d709..417d8d54f04 100644
--- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java
+++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java
@@ -14,6 +14,7 @@ import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.plugins.SearchPlugin;
+import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
import org.elasticsearch.xpack.spatial.ingest.CircleProcessor;
@@ -41,6 +42,7 @@ public class SpatialPlugin extends Plugin implements MapperPlugin, SearchPlugin,
public Map getMappers() {
Map mappers = new LinkedHashMap<>();
mappers.put(ShapeFieldMapper.CONTENT_TYPE, new ShapeFieldMapper.TypeParser());
+ mappers.put(PointFieldMapper.CONTENT_TYPE, new PointFieldMapper.TypeParser());
return Collections.unmodifiableMap(mappers);
}
diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianPoint.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianPoint.java
new file mode 100644
index 00000000000..e49803bfc1f
--- /dev/null
+++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianPoint.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.spatial.index.mapper;
+
+import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
+import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.ToXContentFragment;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentSubParser;
+import org.elasticsearch.common.xcontent.support.MapXContentParser;
+import org.elasticsearch.geometry.Geometry;
+import org.elasticsearch.geometry.ShapeType;
+import org.elasticsearch.geometry.utils.StandardValidator;
+import org.elasticsearch.geometry.utils.WellKnownText;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Locale;
+
+import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
+
+/**
+ * Represents a point in the cartesian space.
+ */
+public final class CartesianPoint implements ToXContentFragment {
+
+ private static final ParseField X_FIELD = new ParseField("x");
+ private static final ParseField Y_FIELD = new ParseField("y");
+ private static final ParseField Z_FIELD = new ParseField("z");
+
+ private float x;
+ private float y;
+
+ public CartesianPoint() {
+ }
+
+ public CartesianPoint(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public CartesianPoint reset(float x, float y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ }
+
+ public CartesianPoint resetFromString(String value, final boolean ignoreZValue) {
+ if (value.toLowerCase(Locale.ROOT).contains("point")) {
+ return resetFromWKT(value, ignoreZValue);
+ } else {
+ return resetFromCoordinates(value, ignoreZValue);
+ }
+ }
+
+
+ public CartesianPoint resetFromCoordinates(String value, final boolean ignoreZValue) {
+ String[] vals = value.split(",");
+ if (vals.length > 3 || vals.length < 2) {
+ throw new ElasticsearchParseException("failed to parse [{}], expected 2 or 3 coordinates "
+ + "but found: [{}]", vals, vals.length);
+ }
+ final float x;
+ final float y;
+ try {
+ x = Float.parseFloat(vals[0].trim());
+ if (Float.isFinite(x) == false) {
+ throw new ElasticsearchParseException("invalid [{}] value [{}]; " +
+ "must be between -3.4028234663852886E38 and 3.4028234663852886E38",
+ X_FIELD.getPreferredName(),
+ x);
+ }
+ } catch (NumberFormatException ex) {
+ throw new ElasticsearchParseException("[{}]] must be a number", X_FIELD.getPreferredName());
+ }
+ try {
+ y = Float.parseFloat(vals[1].trim());
+ if (Float.isFinite(y) == false) {
+ throw new ElasticsearchParseException("invalid [{}] value [{}]; " +
+ "must be between -3.4028234663852886E38 and 3.4028234663852886E38",
+ Y_FIELD.getPreferredName(),
+ y);
+ }
+ } catch (NumberFormatException ex) {
+ throw new ElasticsearchParseException("[{}]] must be a number", Y_FIELD.getPreferredName());
+ }
+ if (vals.length > 2) {
+ try {
+ CartesianPoint.assertZValue(ignoreZValue, Float.parseFloat(vals[2].trim()));
+ } catch (NumberFormatException ex) {
+ throw new ElasticsearchParseException("[{}]] must be a number", Y_FIELD.getPreferredName());
+ }
+ }
+ return reset(x, y);
+ }
+
+ private CartesianPoint resetFromWKT(String value, boolean ignoreZValue) {
+ Geometry geometry;
+ try {
+ geometry = new WellKnownText(false, new StandardValidator(ignoreZValue))
+ .fromWKT(value);
+ } catch (Exception e) {
+ throw new ElasticsearchParseException("Invalid WKT format", e);
+ }
+ if (geometry.type() != ShapeType.POINT) {
+ throw new ElasticsearchParseException("[{}] supports only POINT among WKT primitives, " +
+ "but found {}", PointFieldMapper.CONTENT_TYPE, geometry.type());
+ }
+ org.elasticsearch.geometry.Point point = (org.elasticsearch.geometry.Point) geometry;
+ return reset((float) point.getX(), (float) point.getY());
+ }
+
+ public float getX() {
+ return this.x;
+ }
+
+ public float getY() {
+ return this.y;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CartesianPoint point = (CartesianPoint) o;
+
+ if (Float.compare(point.x, x) != 0) return false;
+ if (Float.compare(point.y, y) != 0) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ int temp;
+ temp = x != +0.0f ? Float.floatToIntBits(x) : 0;
+ result = Integer.hashCode(temp);
+ temp = y != +0.0f ? Float.floatToIntBits(y) : 0;
+ result = 31 * result + Integer.hashCode(temp);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return x + ", " + y;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder.startObject().field(X_FIELD.getPreferredName(), x).field(Y_FIELD.getPreferredName(), y).endObject();
+ }
+
+ public static CartesianPoint parsePoint(XContentParser parser, CartesianPoint point, boolean ignoreZvalue)
+ throws IOException, ElasticsearchParseException {
+ float x = Float.NaN;
+ float y = Float.NaN;
+ NumberFormatException numberFormatException = null;
+
+ if(parser.currentToken() == XContentParser.Token.START_OBJECT) {
+ try (XContentSubParser subParser = new XContentSubParser(parser)) {
+ while (subParser.nextToken() != XContentParser.Token.END_OBJECT) {
+ if (subParser.currentToken() == XContentParser.Token.FIELD_NAME) {
+ String field = subParser.currentName();
+ if (field.equals(X_FIELD.getPreferredName())) {
+ subParser.nextToken();
+ switch (subParser.currentToken()) {
+ case VALUE_NUMBER:
+ case VALUE_STRING:
+ try {
+ x = subParser.floatValue(true);
+ } catch (NumberFormatException e) {
+ numberFormatException = e;
+ }
+ break;
+ default:
+ throw new ElasticsearchParseException("[{}] must be a number",
+ X_FIELD.getPreferredName());
+ }
+ } else if (field.equals(Y_FIELD.getPreferredName())) {
+ subParser.nextToken();
+ switch (subParser.currentToken()) {
+ case VALUE_NUMBER:
+ case VALUE_STRING:
+ try {
+ y = subParser.floatValue(true);
+ } catch (NumberFormatException e) {
+ numberFormatException = e;
+ }
+ break;
+ default:
+ throw new ElasticsearchParseException("[{}] must be a number",
+ Y_FIELD.getPreferredName());
+ }
+ } else if (field.equals(Z_FIELD.getPreferredName())) {
+ subParser.nextToken();
+ switch (subParser.currentToken()) {
+ case VALUE_NUMBER:
+ case VALUE_STRING:
+ try {
+ CartesianPoint.assertZValue(ignoreZvalue, subParser.floatValue(true));
+ } catch (NumberFormatException e) {
+ numberFormatException = e;
+ }
+ break;
+ default:
+ throw new ElasticsearchParseException("[{}] must be a number",
+ Z_FIELD.getPreferredName());
+ }
+ } else {
+ throw new ElasticsearchParseException("field must be either [{}] or [{}]",
+ X_FIELD.getPreferredName(),
+ Y_FIELD.getPreferredName());
+ }
+ } else {
+ throw new ElasticsearchParseException("token [{}] not allowed", subParser.currentToken());
+ }
+ }
+ }
+ if (numberFormatException != null) {
+ throw new ElasticsearchParseException("[{}] and [{}] must be valid float values", numberFormatException,
+ X_FIELD.getPreferredName(),
+ Y_FIELD.getPreferredName());
+ } else if (Float.isNaN(x)) {
+ throw new ElasticsearchParseException("field [{}] missing", X_FIELD.getPreferredName());
+ } else if (Float.isNaN(y)) {
+ throw new ElasticsearchParseException("field [{}] missing", Y_FIELD.getPreferredName());
+ } else {
+ return point.reset(x, y);
+ }
+
+ } else if(parser.currentToken() == XContentParser.Token.START_ARRAY) {
+ try (XContentSubParser subParser = new XContentSubParser(parser)) {
+ int element = 0;
+ while (subParser.nextToken() != XContentParser.Token.END_ARRAY) {
+ if (subParser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
+ element++;
+ if (element == 1) {
+ x = subParser.floatValue();
+ } else if (element == 2) {
+ y = subParser.floatValue();
+ } else {
+ throw new ElasticsearchParseException("[{}}] field type does not accept > 2 dimensions",
+ PointFieldMapper.CONTENT_TYPE);
+ }
+ } else {
+ throw new ElasticsearchParseException("numeric value expected");
+ }
+ }
+ }
+ return point.reset(x, y);
+ } else if(parser.currentToken() == XContentParser.Token.VALUE_STRING) {
+ String val = parser.text();
+ return point.resetFromString(val, ignoreZvalue);
+ } else {
+ throw new ElasticsearchParseException("point expected");
+ }
+ }
+
+ public static CartesianPoint parsePoint(Object value, boolean ignoreZValue) throws ElasticsearchParseException {
+ try (XContentParser parser = new MapXContentParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE,
+ Collections.singletonMap("null_value", value), null)) {
+ parser.nextToken(); // start object
+ parser.nextToken(); // field name
+ parser.nextToken(); // field value
+ return parsePoint(parser, new CartesianPoint(), ignoreZValue);
+ } catch (IOException ex) {
+ throw new ElasticsearchParseException("error parsing point", ex);
+ }
+ }
+
+ public static double assertZValue(final boolean ignoreZValue, float zValue) {
+ if (ignoreZValue == false) {
+ throw new ElasticsearchParseException("Exception parsing coordinates: found Z value [{}] but [{}] "
+ + "parameter is [{}]", zValue, IGNORE_Z_VALUE, ignoreZValue);
+ }
+ if (Float.isFinite(zValue) == false) {
+ throw new ElasticsearchParseException("invalid [{}] value [{}]; " +
+ "must be between -3.4028234663852886E38 and 3.4028234663852886E38",
+ Z_FIELD.getPreferredName(),
+ zValue);
+ }
+ return zValue;
+ }
+}
diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java
new file mode 100644
index 00000000000..320e0e019cb
--- /dev/null
+++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapper.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.index.mapper;
+
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.XYDocValuesField;
+import org.apache.lucene.document.XYPointField;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.DocValuesFieldExistsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.common.Explicit;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
+import org.elasticsearch.index.mapper.ArrayValueMapperParser;
+import org.elasticsearch.index.mapper.FieldMapper;
+import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
+import org.elasticsearch.index.mapper.GeoPointFieldMapper;
+import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.mapper.Mapper;
+import org.elasticsearch.index.mapper.MapperParsingException;
+import org.elasticsearch.index.mapper.ParseContext;
+import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.index.query.QueryShardException;
+import org.elasticsearch.xpack.spatial.index.query.ShapeQueryPointProcessor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static org.elasticsearch.index.mapper.TypeParsers.parseField;
+
+
+/**
+ * Field Mapper for point type.
+ *
+ * Uses lucene 8 XYPoint encoding
+ */
+public class PointFieldMapper extends FieldMapper implements ArrayValueMapperParser {
+ public static final String CONTENT_TYPE = "point";
+
+ public static class Names {
+ public static final ParseField IGNORE_MALFORMED = new ParseField("ignore_malformed");
+ public static final ParseField IGNORE_Z_VALUE = new ParseField("ignore_z_value");
+ public static final ParseField NULL_VALUE = new ParseField("null_value");
+ }
+
+ public static class Defaults {
+ public static final Explicit IGNORE_MALFORMED = new Explicit<>(false, false);
+ public static final PointFieldType FIELD_TYPE = new PointFieldType();
+ public static final Explicit IGNORE_Z_VALUE = new Explicit<>(true, false);
+
+ static {
+ FIELD_TYPE.setTokenized(false);
+ FIELD_TYPE.setHasDocValues(true);
+ FIELD_TYPE.setDimensions(2, Integer.BYTES);
+ FIELD_TYPE.freeze();
+ }
+ }
+
+ public static class Builder extends FieldMapper.Builder {
+ protected Boolean ignoreMalformed;
+ private Boolean ignoreZValue;
+
+ public Builder(String name) {
+ super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
+ builder = this;
+ }
+
+ public Builder ignoreMalformed(boolean ignoreMalformed) {
+ this.ignoreMalformed = ignoreMalformed;
+ return builder;
+ }
+
+ protected Explicit ignoreMalformed(BuilderContext context) {
+ if (ignoreMalformed != null) {
+ return new Explicit<>(ignoreMalformed, true);
+ }
+ if (context.indexSettings() != null) {
+ return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
+ }
+ return PointFieldMapper.Defaults.IGNORE_MALFORMED;
+ }
+
+ protected Explicit ignoreZValue(BuilderContext context) {
+ if (ignoreZValue != null) {
+ return new Explicit<>(ignoreZValue, true);
+ }
+ return PointFieldMapper.Defaults.IGNORE_Z_VALUE;
+ }
+
+ public PointFieldMapper.Builder ignoreZValue(final boolean ignoreZValue) {
+ this.ignoreZValue = ignoreZValue;
+ return this;
+ }
+ public PointFieldMapper build(BuilderContext context, String simpleName, MappedFieldType fieldType,
+ MappedFieldType defaultFieldType, Settings indexSettings,
+ MultiFields multiFields, Explicit ignoreMalformed,
+ CopyTo copyTo) {
+ setupFieldType(context);
+ return new PointFieldMapper(simpleName, fieldType, defaultFieldType, indexSettings, multiFields,
+ ignoreMalformed, ignoreZValue(context), copyTo);
+ }
+
+ @Override
+ public PointFieldType fieldType() {
+ return (PointFieldType)fieldType;
+ }
+
+ @Override
+ public PointFieldMapper build(BuilderContext context) {
+ return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
+ multiFieldsBuilder.build(this, context), ignoreMalformed(context), copyTo);
+ }
+
+ @Override
+ protected void setupFieldType(BuilderContext context) {
+ super.setupFieldType(context);
+
+ fieldType().setGeometryQueryBuilder(new ShapeQueryPointProcessor());
+ }
+ }
+
+ public static class TypeParser implements Mapper.TypeParser {
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Mapper.Builder parse(String name, Map node, ParserContext parserContext)
+ throws MapperParsingException {
+ Builder builder = new PointFieldMapper.Builder(name);
+ parseField(builder, name, node, parserContext);
+ Object nullValue = null;
+ for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry entry = iterator.next();
+ String propName = entry.getKey();
+ Object propNode = entry.getValue();
+
+ if (propName.equals(Names.IGNORE_MALFORMED.getPreferredName())) {
+ builder.ignoreMalformed(XContentMapValues.nodeBooleanValue(propNode, name + "." + Names.IGNORE_MALFORMED));
+ iterator.remove();
+ } else if (propName.equals(PointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName())) {
+ builder.ignoreZValue(XContentMapValues.nodeBooleanValue(propNode,
+ name + "." + PointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName()));
+ iterator.remove();
+ } else if (propName.equals(Names.NULL_VALUE.getPreferredName())) {
+ if (propNode == null) {
+ throw new MapperParsingException("Property [null_value] cannot be null.");
+ }
+ nullValue = propNode;
+ iterator.remove();
+ }
+ }
+
+ if (nullValue != null) {
+ boolean ignoreMalformed = builder.ignoreMalformed == null ?
+ Defaults.IGNORE_MALFORMED.value() : builder.ignoreMalformed;
+ boolean ignoreZValue = builder.ignoreZValue == null ?
+ Defaults.IGNORE_Z_VALUE.value() : builder.ignoreZValue;
+ CartesianPoint point = CartesianPoint.parsePoint(nullValue, ignoreZValue);
+ if (ignoreMalformed == false) {
+ if (Float.isFinite(point.getX()) == false) {
+ throw new IllegalArgumentException("illegal x value [" + point.getX() + "]");
+ }
+ if (Float.isFinite(point.getY()) == false) {
+ throw new IllegalArgumentException("illegal y value [" + point.getY() + "]");
+ }
+ }
+ builder.nullValue(point);
+ }
+ return builder;
+ }
+ }
+
+ protected Explicit ignoreMalformed;
+ protected Explicit ignoreZValue;
+
+ public PointFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
+ Settings indexSettings, MultiFields multiFields, Explicit ignoreMalformed,
+ Explicit ignoreZValue, CopyTo copyTo) {
+ super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
+ this.ignoreMalformed = ignoreMalformed;
+ this.ignoreZValue = ignoreZValue;
+ }
+
+ @Override
+ protected void doMerge(Mapper mergeWith) {
+ super.doMerge(mergeWith);
+ PointFieldMapper gpfmMergeWith = (PointFieldMapper) mergeWith;
+ if (gpfmMergeWith.ignoreMalformed.explicit()) {
+ this.ignoreMalformed = gpfmMergeWith.ignoreMalformed;
+ }
+ if (gpfmMergeWith.ignoreZValue.explicit()) {
+ this.ignoreZValue = gpfmMergeWith.ignoreZValue;
+ }
+ }
+
+ @Override
+ protected String contentType() {
+ return CONTENT_TYPE;
+ }
+
+ @Override
+ protected void parseCreateField(ParseContext context, List fields) throws IOException {
+ throw new UnsupportedOperationException("Parsing is implemented in parse(), this method should NEVER be called");
+ }
+
+ public static class PointFieldType extends AbstractSearchableGeometryFieldType {
+ public PointFieldType() {
+ }
+
+ PointFieldType(PointFieldType ref) {
+ super(ref);
+ }
+
+ @Override
+ public String typeName() {
+ return CONTENT_TYPE;
+ }
+
+ @Override
+ public MappedFieldType clone() {
+ return new PointFieldType(this);
+ }
+
+ @Override
+ public Query existsQuery(QueryShardContext context) {
+ if (hasDocValues()) {
+ return new DocValuesFieldExistsQuery(name());
+ } else {
+ return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
+ }
+ }
+
+ @Override
+ public Query termQuery(Object value, QueryShardContext context) {
+ throw new QueryShardException(context, "Spatial fields do not support exact searching, " +
+ "use dedicated spatial queries instead: [" + name() + "]");
+ }
+ }
+
+ protected void parse(ParseContext context, CartesianPoint point) throws IOException {
+
+ if (fieldType().indexOptions() != IndexOptions.NONE) {
+ context.doc().add(new XYPointField(fieldType().name(), point.getX(), point.getY()));
+ }
+ if (fieldType().stored()) {
+ context.doc().add(new StoredField(fieldType().name(), point.toString()));
+ }
+ if (fieldType.hasDocValues()) {
+ context.doc().add(new XYDocValuesField(fieldType().name(), point.getX(), point.getY()));
+ } else if (fieldType().stored() || fieldType().indexOptions() != IndexOptions.NONE) {
+ List fields = new ArrayList<>(1);
+ createFieldNamesField(context, fields);
+ for (IndexableField field : fields) {
+ context.doc().add(field);
+ }
+ }
+ // if the mapping contains multi-fields then throw an error?
+ if (multiFields.iterator().hasNext()) {
+ throw new ElasticsearchParseException("[{}] field type does not accept multi-fields", CONTENT_TYPE);
+ }
+ }
+
+ @Override
+ public void parse(ParseContext context) throws IOException {
+ context.path().add(simpleName());
+
+ try {
+ CartesianPoint sparse = context.parseExternalValue(CartesianPoint.class);
+
+ if (sparse != null) {
+ parse(context, sparse);
+ } else {
+ sparse = new CartesianPoint();
+ XContentParser.Token token = context.parser().currentToken();
+ if (token == XContentParser.Token.START_ARRAY) {
+ token = context.parser().nextToken();
+ if (token == XContentParser.Token.VALUE_NUMBER) {
+ float x = context.parser().floatValue();
+ context.parser().nextToken();
+ float y = context.parser().floatValue();
+ token = context.parser().nextToken();
+ if (token == XContentParser.Token.VALUE_NUMBER) {
+ CartesianPoint.assertZValue(ignoreZValue.value(), context.parser().floatValue());
+ } else if (token != XContentParser.Token.END_ARRAY) {
+ throw new ElasticsearchParseException("[{}] field type does not accept > 3 dimensions", CONTENT_TYPE);
+ }
+ parse(context, sparse.reset(x, y));
+ } else {
+ while (token != XContentParser.Token.END_ARRAY) {
+ parsePointIgnoringMalformed(context, sparse);
+ token = context.parser().nextToken();
+ }
+ }
+ } else if (token == XContentParser.Token.VALUE_NULL) {
+ if (fieldType.nullValue() != null) {
+ parse(context, (CartesianPoint) fieldType.nullValue());
+ }
+ } else {
+ parsePointIgnoringMalformed(context, sparse);
+ }
+ }
+ } catch (Exception ex) {
+ throw new MapperParsingException("failed to parse field [{}] of type [{}]", ex, fieldType().name(), fieldType().typeName());
+ }
+
+ context.path().remove();
+ }
+
+ /**
+ * Parses point represented as an object or an array, ignores malformed points if needed
+ */
+ private void parsePointIgnoringMalformed(ParseContext context, CartesianPoint sparse) throws IOException {
+ try {
+ parse(context, CartesianPoint.parsePoint(context.parser(), sparse, ignoreZValue().value()));
+ } catch (ElasticsearchParseException e) {
+ if (ignoreMalformed.value() == false) {
+ throw e;
+ }
+ context.addIgnoredField(fieldType.name());
+ }
+ }
+
+ @Override
+ protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
+ super.doXContentBody(builder, includeDefaults, params);
+ if (includeDefaults || ignoreMalformed.explicit()) {
+ builder.field(Names.IGNORE_MALFORMED.getPreferredName(), ignoreMalformed.value());
+ }
+ if (includeDefaults || ignoreZValue.explicit()) {
+ builder.field(GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue.value());
+ }
+ if (includeDefaults || fieldType().nullValue() != null) {
+ builder.field(Names.NULL_VALUE.getPreferredName(), fieldType().nullValue());
+ }
+ }
+
+ public Explicit ignoreZValue() {
+ return ignoreZValue;
+ }
+}
diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilder.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilder.java
index 33a183f5260..bc0771a66bb 100644
--- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilder.java
+++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilder.java
@@ -17,13 +17,14 @@ import org.elasticsearch.common.logging.DeprecationLogger;
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.MappedFieldType;
import org.elasticsearch.index.query.AbstractGeometryQueryBuilder;
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
+import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper;
import java.io.IOException;
@@ -48,7 +49,7 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder validContentTypes =
- Collections.unmodifiableList(Arrays.asList(ShapeFieldMapper.CONTENT_TYPE));
+ Collections.unmodifiableList(Arrays.asList(ShapeFieldMapper.CONTENT_TYPE, PointFieldMapper.CONTENT_TYPE));
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
@@ -138,7 +139,7 @@ public class ShapeQueryBuilder extends AbstractGeometryQueryBuilder {
+ QueryShardContext context;
+ MappedFieldType fieldType;
+ String fieldName;
+ ShapeRelation relation;
+
+ ShapeVisitor(QueryShardContext context, String fieldName, ShapeRelation relation) {
+ this.context = context;
+ this.fieldType = context.fieldMapper(fieldName);
+ this.fieldName = fieldName;
+ this.relation = relation;
+ }
+
+ @Override
+ public Query visit(Circle circle) {
+ XYCircle xyCircle = ShapeUtils.toLuceneXYCircle(circle);
+ Query query = XYPointField.newDistanceQuery(fieldName, xyCircle.getX(), xyCircle.getY(), xyCircle.getRadius());
+ if (fieldType.hasDocValues()) {
+ Query dvQuery = XYDocValuesField.newSlowDistanceQuery(fieldName,
+ xyCircle.getX(), xyCircle.getY(), xyCircle.getRadius());
+ query = new IndexOrDocValuesQuery(query, dvQuery);
+ }
+ return query;
+ }
+
+ @Override
+ public Query visit(GeometryCollection> collection) {
+ BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+ visit(bqb, collection);
+ return bqb.build();
+ }
+
+ private void visit(BooleanQuery.Builder bqb, GeometryCollection> collection) {
+ BooleanClause.Occur occur = BooleanClause.Occur.FILTER;
+ for (Geometry shape : collection) {
+ bqb.add(shape.visit(this), occur);
+ }
+ }
+
+ @Override
+ public Query visit(org.elasticsearch.geometry.Line line) {
+ throw new QueryShardException(context, "Field [" + fieldName + "] does not support "
+ + ShapeType.LINESTRING + " queries");
+ }
+
+ @Override
+ // don't think this is called directly
+ public Query visit(LinearRing ring) {
+ throw new QueryShardException(context, "Field [" + fieldName + "] does not support "
+ + ShapeType.LINEARRING + " queries");
+ }
+
+ @Override
+ public Query visit(MultiLine multiLine) {
+ throw new QueryShardException(context, "Field [" + fieldName + "] does not support "
+ + ShapeType.MULTILINESTRING + " queries");
+ }
+
+ @Override
+ public Query visit(MultiPoint multiPoint) {
+ throw new QueryShardException(context, "Field [" + fieldName + "] does not support "
+ + ShapeType.MULTIPOINT + " queries");
+ }
+
+ @Override
+ public Query visit(MultiPolygon multiPolygon) {
+ org.apache.lucene.geo.XYPolygon[] lucenePolygons =
+ new org.apache.lucene.geo.XYPolygon[multiPolygon.size()];
+ for (int i = 0; i < multiPolygon.size(); i++) {
+ lucenePolygons[i] = ShapeUtils.toLuceneXYPolygon(multiPolygon.get(i));
+ }
+ Query query = XYPointField.newPolygonQuery(fieldName, lucenePolygons);
+ if (fieldType.hasDocValues()) {
+ Query dvQuery = XYDocValuesField.newSlowPolygonQuery(fieldName, lucenePolygons);
+ query = new IndexOrDocValuesQuery(query, dvQuery);
+ }
+ return query;
+ }
+
+ @Override
+ public Query visit(Point point) {
+ // not currently supported
+ throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + ShapeType.POINT +
+ " queries");
+ }
+
+ @Override
+ public Query visit(Polygon polygon) {
+ org.apache.lucene.geo.XYPolygon lucenePolygon = ShapeUtils.toLuceneXYPolygon(polygon);
+ Query query = XYPointField.newPolygonQuery(fieldName, lucenePolygon);
+ if (fieldType.hasDocValues()) {
+ Query dvQuery = XYDocValuesField.newSlowPolygonQuery(fieldName, lucenePolygon);
+ query = new IndexOrDocValuesQuery(query, dvQuery);
+ }
+ return query;
+ }
+
+ @Override
+ public Query visit(Rectangle r) {
+ XYRectangle xyRectangle = ShapeUtils.toLuceneXYRectangle(r);
+ Query query = XYPointField.newBoxQuery(fieldName, xyRectangle.minX, xyRectangle.maxX, xyRectangle.minY, xyRectangle.maxY);
+ if (fieldType.hasDocValues()) {
+ Query dvQuery = XYDocValuesField.newSlowBoxQuery(
+ fieldName, xyRectangle.minX, xyRectangle.maxX, xyRectangle.minY, xyRectangle.maxY);
+ query = new IndexOrDocValuesQuery(query, dvQuery);
+ }
+ return query;
+ }
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianFieldMapperTests.java
new file mode 100644
index 00000000000..0f31f481dd3
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/CartesianFieldMapperTests.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.spatial.index.mapper;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.index.mapper.DocumentMapper;
+import org.elasticsearch.index.mapper.DocumentMapperParser;
+import org.elasticsearch.index.mapper.MapperParsingException;
+import org.elasticsearch.index.mapper.ParsedDocument;
+import org.elasticsearch.index.mapper.SourceToParse;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.test.ESSingleNodeTestCase;
+import org.elasticsearch.test.InternalSettingsPlugin;
+import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
+import org.elasticsearch.xpack.spatial.SpatialPlugin;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+/** Base class for testing cartesian field mappers */
+public abstract class CartesianFieldMapperTests extends ESSingleNodeTestCase {
+
+ private static final String FIELD_NAME = "location";
+
+ @Override
+ protected Collection> getPlugins() {
+ return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
+ }
+
+ protected abstract XContentBuilder createDefaultMapping(String fieldName,
+ boolean ignored_malformed,
+ boolean ignoreZValue) throws IOException;
+
+
+ public void testWKT() throws IOException {
+ String mapping = Strings.toString(createDefaultMapping(FIELD_NAME, randomBoolean(), randomBoolean()));
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field(FIELD_NAME, "POINT (2000.1 305.6)")
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField(FIELD_NAME), notNullValue());
+ }
+
+ public void testEmptyName() throws IOException {
+ String mapping = Strings.toString(createDefaultMapping("", randomBoolean(), randomBoolean()));
+
+ DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
+ IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
+ () -> parser.parse("type", new CompressedXContent(mapping))
+ );
+ assertThat(e.getMessage(), containsString("name cannot be empty string"));
+ }
+
+ public void testInvalidPointValuesIgnored() throws IOException {
+ String mapping = Strings.toString(createDefaultMapping(FIELD_NAME, true, randomBoolean()));
+
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "1234.333").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field("lat", "-").field("x", 1.3).endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field("lat", 1.3).field("y", "-").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "-,1.3").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "1.3,-").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field("x", "NaN").field("y", "NaN").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field("lat", 12).field("y", "NaN").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field("x", "NaN").field("y", 10).endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "NaN,NaN").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "10,NaN").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().field(FIELD_NAME, "NaN,12").endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().startObject(FIELD_NAME).nullField("y").field("x", 1).endObject().endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+
+ assertThat(defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject().startObject(FIELD_NAME).nullField("x").nullField("y").endObject().endObject()
+ ), XContentType.JSON)).rootDoc().getField(FIELD_NAME), nullValue());
+ }
+
+ public void testZValue() throws IOException {
+ String mapping = Strings.toString(createDefaultMapping(FIELD_NAME, false, true));
+ DocumentMapper defaultMapper = createIndex("test1").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test1","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field(FIELD_NAME, "POINT (2000.1 305.6 34567.33)")
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField(FIELD_NAME), notNullValue());
+
+ mapping = Strings.toString(createDefaultMapping(FIELD_NAME, false, false));
+ DocumentMapper defaultMapper2 = createIndex("test2").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ MapperParsingException e = expectThrows(MapperParsingException.class,
+ () -> defaultMapper2.parse(new SourceToParse("test2","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field(FIELD_NAME, "POINT (2000.1 305.6 34567.33)")
+ .endObject()),
+ XContentType.JSON))
+ );
+ assertThat(e.getMessage(), containsString("failed to parse field [" + FIELD_NAME + "] of type"));
+ assertThat(e.getRootCause().getMessage(),
+ containsString("found Z value [34567.33] but [ignore_z_value] parameter is [false]"));
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapperTests.java
new file mode 100644
index 00000000000..cd04672e650
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldMapperTests.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.index.mapper;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.index.mapper.DocumentMapper;
+import org.elasticsearch.index.mapper.Mapper;
+import org.elasticsearch.index.mapper.ParsedDocument;
+import org.elasticsearch.index.mapper.SourceToParse;
+import org.hamcrest.CoreMatchers;
+
+import java.io.IOException;
+
+import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
+import static org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper.Names.NULL_VALUE;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class PointFieldMapperTests extends CartesianFieldMapperTests {
+
+ @Override
+ protected XContentBuilder createDefaultMapping(String fieldName,
+ boolean ignored_malformed,
+ boolean ignoreZValue) throws IOException {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject(fieldName).field("type", "point");
+ if (ignored_malformed || randomBoolean()) {
+ xContentBuilder.field(PointFieldMapper.Names.IGNORE_MALFORMED.getPreferredName(), ignored_malformed);
+ }
+ if (ignoreZValue == false || randomBoolean()) {
+ xContentBuilder.field(PointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue);
+ }
+ return xContentBuilder.endObject().endObject().endObject().endObject();
+ }
+
+ public void testValuesStored() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.field("store", true).endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startObject("point").field("x", 2000.1).field("y", 305.6).endObject()
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ }
+
+ public void testArrayValues() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point").field("doc_values", false);
+ String mapping = Strings.toString(xContentBuilder.field("store", true).endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point")
+ .startObject().field("x", 1.2).field("y", 1.3).endObject()
+ .startObject().field("x", 1.4).field("y", 1.5).endObject()
+ .endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ // doc values are enabled by default, but in this test we disable them; we should only have 2 points
+ assertThat(doc.rootDoc().getFields("point"), notNullValue());
+ assertThat(doc.rootDoc().getFields("point").length, equalTo(4));
+ }
+
+ public void testLatLonInOneValue() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test", "type","1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field("point", "1.2,1.3")
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ }
+
+ public void testInOneValueStored() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.field("store", true).endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field("point", "1.2,1.3")
+ .endObject()),
+ XContentType.JSON));
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ }
+
+ public void testLatLonInOneValueArray() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point").field("doc_values", false);
+ String mapping = Strings.toString(xContentBuilder.field("store", true).endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point")
+ .value("1.2,1.3")
+ .value("1.4,1.5")
+ .endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ // doc values are enabled by default, but in this test we disable them; we should only have 2 points
+ assertThat(doc.rootDoc().getFields("point"), notNullValue());
+ assertThat(doc.rootDoc().getFields("point").length, equalTo(4));
+ }
+
+ public void testArray() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point").value(1.3).value(1.2).endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ }
+
+ public void testArrayDynamic() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startArray("dynamic_templates").startObject().startObject("point").field("match", "point*")
+ .startObject("mapping").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.endObject().endObject().endObject().endArray().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point").value(1.3).value(1.2).endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ }
+
+ public void testArrayStored() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.field("store", true).endObject().endObject().endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point").value(1.3).value(1.2).endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("point"), notNullValue());
+ assertThat(doc.rootDoc().getFields("point").length, equalTo(3));
+ }
+
+ public void testArrayArrayStored() throws Exception {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("point").field("type", "point");
+ String mapping = Strings.toString(xContentBuilder.field("store", true)
+ .field("doc_values", false).endObject().endObject()
+ .endObject().endObject());
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .startArray("point")
+ .startArray().value(1.3).value(1.2).endArray()
+ .startArray().value(1.5).value(1.4).endArray()
+ .endArray()
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getFields("point"), notNullValue());
+ assertThat(doc.rootDoc().getFields("point").length, CoreMatchers.equalTo(4));
+ }
+
+ public void testNullValue() throws Exception {
+ String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject("location")
+ .field("type", "point")
+ .field(NULL_VALUE.getPreferredName(), "1,2")
+ .endObject().endObject()
+ .endObject().endObject());
+
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type", new CompressedXContent(mapping));
+ Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
+ assertThat(fieldMapper, instanceOf(PointFieldMapper.class));
+
+ Object nullValue = ((PointFieldMapper) fieldMapper).fieldType().nullValue();
+ assertThat(nullValue, equalTo(new CartesianPoint(1, 2)));
+
+ ParsedDocument doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .nullField("location")
+ .endObject()),
+ XContentType.JSON));
+
+ assertThat(doc.rootDoc().getField("location"), notNullValue());
+ BytesRef defaultValue = doc.rootDoc().getField("location").binaryValue();
+
+ doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field("location", "1, 2")
+ .endObject()),
+ XContentType.JSON));
+ // Shouldn't matter if we specify the value explicitly or use null value
+ assertThat(defaultValue, equalTo(doc.rootDoc().getField("location").binaryValue()));
+
+ doc = defaultMapper.parse(new SourceToParse("test","type", "1",
+ BytesReference.bytes(XContentFactory.jsonBuilder()
+ .startObject()
+ .field("location", "3, 4")
+ .endObject()),
+ XContentType.JSON));
+ // Shouldn't matter if we specify the value explicitly or use null value
+ assertThat(defaultValue, not(equalTo(doc.rootDoc().getField("location").binaryValue())));
+ }
+
+ /**
+ * Test that accept_z_value parameter correctly parses
+ */
+ public void testIgnoreZValue() throws IOException {
+ String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
+ .startObject("properties").startObject("location")
+ .field("type", "point")
+ .field(IGNORE_Z_VALUE.getPreferredName(), "true")
+ .endObject().endObject()
+ .endObject().endObject());
+
+ DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
+ .parse("type1", new CompressedXContent(mapping));
+ Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
+ assertThat(fieldMapper, instanceOf(PointFieldMapper.class));
+
+ boolean ignoreZValue = ((PointFieldMapper)fieldMapper).ignoreZValue().value();
+ assertThat(ignoreZValue, equalTo(true));
+
+ // explicit false accept_z_value test
+ mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
+ .startObject("properties").startObject("location")
+ .field("type", "point")
+ .field(IGNORE_Z_VALUE.getPreferredName(), "false")
+ .endObject().endObject()
+ .endObject().endObject());
+
+ defaultMapper = createIndex("test2").mapperService().documentMapperParser()
+ .parse("type1", new CompressedXContent(mapping));
+ fieldMapper = defaultMapper.mappers().getMapper("location");
+ assertThat(fieldMapper, instanceOf(PointFieldMapper.class));
+
+ ignoreZValue = ((PointFieldMapper)fieldMapper).ignoreZValue().value();
+ assertThat(ignoreZValue, equalTo(false));
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldTypeTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldTypeTests.java
new file mode 100644
index 00000000000..45956d5ddb2
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/PointFieldTypeTests.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.index.mapper;
+
+import org.elasticsearch.index.mapper.FieldTypeTestCase;
+import org.elasticsearch.index.mapper.MappedFieldType;
+
+public class PointFieldTypeTests extends FieldTypeTestCase {
+ @Override
+ protected MappedFieldType createDefaultFieldType() {
+ return new PointFieldMapper.PointFieldType();
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java
index 809f9d62139..3b118160e8c 100644
--- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java
@@ -18,7 +18,6 @@ import org.elasticsearch.index.mapper.DocumentMapperParser;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.plugins.Plugin;
-import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.test.InternalSettingsPlugin;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
import org.elasticsearch.xpack.spatial.SpatialPlugin;
@@ -28,17 +27,31 @@ import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
-import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
/** testing for {@link org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper} */
-public class ShapeFieldMapperTests extends ESSingleNodeTestCase {
+public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
@Override
protected Collection> getPlugins() {
return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
}
+ @Override
+ protected XContentBuilder createDefaultMapping(String fieldName,
+ boolean ignored_malformed,
+ boolean ignoreZValue) throws IOException {
+ XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type")
+ .startObject("properties").startObject(fieldName).field("type", "shape");
+ if (ignored_malformed || randomBoolean()) {
+ xContentBuilder.field("ignore_malformed", ignored_malformed);
+ }
+ if (ignoreZValue == false || randomBoolean()) {
+ xContentBuilder.field(PointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue);
+ }
+ return xContentBuilder.endObject().endObject().endObject().endObject();
+ }
+
public void testDefaultConfiguration() throws IOException {
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location")
@@ -249,21 +262,6 @@ public class ShapeFieldMapperTests extends ESSingleNodeTestCase {
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CW));
}
- public void testEmptyName() throws Exception {
- // after 5.x
- String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
- .startObject("properties").startObject("")
- .field("type", "shape")
- .endObject().endObject()
- .endObject().endObject());
- DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
-
- IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
- () -> parser.parse("type1", new CompressedXContent(mapping))
- );
- assertThat(e.getMessage(), containsString("name cannot be empty string"));
- }
-
public void testSerializeDefaults() throws Exception {
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
{
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverPointTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverPointTests.java
new file mode 100644
index 00000000000..b608d11483a
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverPointTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.index.query;
+
+
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.geometry.Geometry;
+import org.elasticsearch.geometry.ShapeType;
+import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
+
+import java.io.IOException;
+
+
+public class ShapeQueryBuilderOverPointTests extends ShapeQueryBuilderTests {
+
+ @Override
+ protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
+ mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
+ fieldName(), "type=point"))), MapperService.MergeReason.MAPPING_UPDATE);
+ }
+
+ @Override
+ protected ShapeRelation getShapeRelation(ShapeType type) {
+ return ShapeRelation.INTERSECTS;
+ }
+
+ @Override
+ protected Geometry getGeometry() {
+ if (randomBoolean()) {
+ if (randomBoolean()) {
+ return ShapeTestUtils.randomMultiPolygon(false);
+ } else {
+ return ShapeTestUtils.randomPolygon(false);
+ }
+ } else if (randomBoolean()) {
+ // it should be a circle
+ return ShapeTestUtils.randomPolygon(false);
+ } else {
+ return ShapeTestUtils.randomRectangle();
+ }
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverShapeTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverShapeTests.java
new file mode 100644
index 00000000000..71ad0e9bab8
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderOverShapeTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.index.query;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.geometry.Geometry;
+import org.elasticsearch.geometry.ShapeType;
+import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
+
+import java.io.IOException;
+
+public class ShapeQueryBuilderOverShapeTests extends ShapeQueryBuilderTests {
+
+ @Override
+ protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
+ mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
+ fieldName(), "type=shape"))), MapperService.MergeReason.MAPPING_UPDATE);
+ }
+
+ @Override
+ protected ShapeRelation getShapeRelation(ShapeType type) {
+ QueryShardContext context = createShardContext();
+ if (context.indexVersionCreated().onOrAfter(Version.V_7_5_0)) { // CONTAINS is only supported from version 7.5
+ if (type == ShapeType.LINESTRING || type == ShapeType.MULTILINESTRING) {
+ return randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.CONTAINS);
+ } else {
+ return randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS,
+ ShapeRelation.WITHIN, ShapeRelation.CONTAINS);
+ }
+ } else {
+ if (type == ShapeType.LINESTRING || type == ShapeType.MULTILINESTRING) {
+ return randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS);
+ } else {
+ return randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN);
+ }
+ }
+ }
+
+ @Override
+ protected Geometry getGeometry() {
+ return ShapeTestUtils.randomGeometry(false);
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java
index d5e97c0becc..48f44a59498 100644
--- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/ShapeQueryBuilderTests.java
@@ -10,13 +10,10 @@ import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.Version;
-import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
-import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.geo.GeoJson;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
@@ -36,7 +33,6 @@ import org.elasticsearch.index.query.Rewriteable;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.AbstractQueryTestCase;
import org.elasticsearch.xpack.spatial.SpatialPlugin;
-import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
import org.junit.After;
import java.io.IOException;
@@ -49,11 +45,11 @@ import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
-public class ShapeQueryBuilderTests extends AbstractQueryTestCase {
+public abstract class ShapeQueryBuilderTests extends AbstractQueryTestCase {
protected static final String SHAPE_FIELD_NAME = "mapped_shape";
- private static String docType = "_doc";
+ protected static String docType = "_doc";
protected static String indexedShapeId;
protected static String indexedShapeType;
@@ -62,17 +58,15 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase> getPlugins() {
return Collections.singleton(SpatialPlugin.class);
}
- @Override
- protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
- mapperService.merge(docType, new CompressedXContent(Strings.toString(PutMappingRequest.buildFromSimplifiedDef(docType,
- fieldName(), "type=shape"))), MapperService.MergeReason.MAPPING_UPDATE);
- }
-
protected String fieldName() {
return SHAPE_FIELD_NAME;
}
@@ -83,7 +77,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase new ShapeQueryBuilder(null, shape));
assertEquals("fieldName is required", e.getMessage());
}
@@ -168,7 +148,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase builder.relation(null));
assertEquals("No Shape Relation defined", e.getMessage());
@@ -223,7 +203,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase failingQueryBuilder.toQuery(createShardContext()));
- assertThat(e.getMessage(), containsString("failed to find shape field [unmapped]"));
+ assertThat(e.getMessage(), containsString("failed to find shape or point field [unmapped]"));
}
public void testWrongFieldType() {
- Geometry shape = ShapeTestUtils.randomGeometry(false);
+ Geometry shape = getGeometry();
final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder(TEXT_FIELD_NAME, shape);
QueryShardException e = expectThrows(QueryShardException.class, () -> queryBuilder.toQuery(createShardContext()));
- assertThat(e.getMessage(), containsString("Field [mapped_string] is not of type [shape] but of type [text]"));
+ assertThat(e.getMessage(), containsString("Field [mapped_string] is not of type [shape or point] but of type [text]"));
}
public void testSerializationFailsUnlessFetched() throws IOException {
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverPointTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverPointTests.java
new file mode 100644
index 00000000000..cddbc7b8dd7
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverPointTests.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.search;
+
+import org.elasticsearch.action.search.SearchAction;
+import org.elasticsearch.action.search.SearchPhaseExecutionException;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
+import org.elasticsearch.common.geo.builders.LineStringBuilder;
+import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
+import org.elasticsearch.common.geo.builders.MultiPointBuilder;
+import org.elasticsearch.common.geo.builders.PointBuilder;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.geometry.Line;
+import org.elasticsearch.geometry.LinearRing;
+import org.elasticsearch.geometry.MultiLine;
+import org.elasticsearch.geometry.MultiPoint;
+import org.elasticsearch.geometry.Point;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.geometry.ShapeType;
+import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
+import org.hamcrest.CoreMatchers;
+
+public class ShapeQueryOverPointTests extends ShapeQueryTests {
+ @Override
+ protected XContentBuilder createDefaultMapping() throws Exception {
+ XContentBuilder xcb = XContentFactory.jsonBuilder().startObject()
+ .startObject("properties").startObject(defaultFieldName)
+ .field("type", "point")
+ .endObject().endObject().endObject();
+
+ return xcb;
+ }
+
+ public void testProcessRelationSupport() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ Rectangle rectangle = new Rectangle(-35, -25, -25, -35);
+
+ for (ShapeRelation shapeRelation : ShapeRelation.values()) {
+ if (!shapeRelation.equals(ShapeRelation.INTERSECTS)) {
+ SearchPhaseExecutionException e = expectThrows(SearchPhaseExecutionException.class, () ->
+ client().prepareSearch("test")
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, rectangle)
+ .relation(shapeRelation))
+ .get());
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString(shapeRelation
+ + " query relation not supported for Field [" + defaultFieldName + "]"));
+ }
+ }
+ }
+
+ public void testQueryLine() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ Line line = new Line(new double[]{-25, -25}, new double[]{-35, -35});
+
+ try {
+ client().prepareSearch("test")
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, line)).get();
+ } catch (
+ SearchPhaseExecutionException e) {
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString("does not support " + ShapeType.LINESTRING + " queries"));
+ }
+ }
+
+ public void testQueryLinearRing() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ LinearRing linearRing = new LinearRing(new double[]{-25,-35,-25}, new double[]{-25,-35,-25});
+
+ try {
+ // LinearRing extends Line implements Geometry: expose the build process
+ ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder(defaultFieldName, linearRing);
+ SearchRequestBuilder searchRequestBuilder = new SearchRequestBuilder(client(), SearchAction.INSTANCE);
+ searchRequestBuilder.setQuery(queryBuilder);
+ searchRequestBuilder.setIndices("test");
+ searchRequestBuilder.get();
+ } catch (
+ SearchPhaseExecutionException e) {
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString("Field [" + defaultFieldName + "] does not support LINEARRING queries"));
+ }
+ }
+
+ public void testQueryMultiLine() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ CoordinatesBuilder coords1 = new CoordinatesBuilder()
+ .coordinate(-35,-35)
+ .coordinate(-25,-25);
+ CoordinatesBuilder coords2 = new CoordinatesBuilder()
+ .coordinate(-15,-15)
+ .coordinate(-5,-5);
+ LineStringBuilder lsb1 = new LineStringBuilder(coords1);
+ LineStringBuilder lsb2 = new LineStringBuilder(coords2);
+ MultiLineStringBuilder mlb = new MultiLineStringBuilder().linestring(lsb1).linestring(lsb2);
+ MultiLine multiline = (MultiLine) mlb.buildGeometry();
+
+ try {
+ client().prepareSearch("test")
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, multiline)).get();
+ } catch (Exception e) {
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString("does not support " + ShapeType.MULTILINESTRING + " queries"));
+ }
+ }
+
+ public void testQueryMultiPoint() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ MultiPointBuilder mpb = new MultiPointBuilder().coordinate(-35,-25).coordinate(-15,-5);
+ MultiPoint multiPoint = mpb.buildGeometry();
+
+ try {
+ client().prepareSearch("test")
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, multiPoint)).get();
+ } catch (Exception e) {
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString("does not support " + ShapeType.MULTIPOINT + " queries"));
+ }
+ }
+
+ public void testQueryPoint() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate("test").addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ PointBuilder pb = new PointBuilder().coordinate(-35, -25);
+ Point point = pb.buildGeometry();
+
+ try {
+ client().prepareSearch("test")
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, point)).get();
+ } catch (Exception e) {
+ assertThat(e.getCause().getMessage(),
+ CoreMatchers.containsString("does not support " + ShapeType.POINT + " queries"));
+ }
+ }
+
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverShapeTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverShapeTests.java
new file mode 100644
index 00000000000..82c0a41448c
--- /dev/null
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryOverShapeTests.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.spatial.search;
+
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.common.geo.GeoJson;
+import org.elasticsearch.common.geo.ShapeRelation;
+import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
+import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
+import org.elasticsearch.common.geo.builders.MultiPointBuilder;
+import org.elasticsearch.common.geo.builders.PointBuilder;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.geometry.Geometry;
+import org.elasticsearch.geometry.ShapeType;
+import org.elasticsearch.index.query.ExistsQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
+import org.elasticsearch.xpack.spatial.SpatialPlugin;
+import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
+import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
+import org.locationtech.jts.geom.Coordinate;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Locale;
+
+import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+public class ShapeQueryOverShapeTests extends ShapeQueryTests {
+
+ private static String INDEX = "test";
+ private static String IGNORE_MALFORMED_INDEX = INDEX + "_ignore_malformed";
+ private static String FIELD = "shape";
+ private static Geometry queryGeometry = null;
+
+ private int numDocs;
+
+ @Override
+ protected XContentBuilder createDefaultMapping() throws Exception {
+ XContentBuilder xcb = XContentFactory.jsonBuilder().startObject()
+ .startObject("properties").startObject(defaultFieldName)
+ .field("type", "shape")
+ .endObject().endObject().endObject();
+
+ return xcb;
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // create test index
+ assertAcked(client().admin().indices().prepareCreate(INDEX)
+ .addMapping(defaultFieldType, FIELD, "type=shape", "alias", "type=alias,path=" + FIELD).get());
+ // create index that ignores malformed geometry
+ assertAcked(client().admin().indices().prepareCreate(IGNORE_MALFORMED_INDEX)
+ .addMapping(defaultFieldType,FIELD, "type=shape,ignore_malformed=true", "_source", "enabled=false").get());
+ ensureGreen();
+
+ // index random shapes
+ numDocs = randomIntBetween(25, 50);
+ // reset query geometry to make sure we pick one from the indexed shapes
+ queryGeometry = null;
+ Geometry geometry;
+ for (int i = 0; i < numDocs; ++i) {
+ geometry = ShapeTestUtils.randomGeometry(false);
+ if (geometry.type() == ShapeType.CIRCLE) continue;
+ if (queryGeometry == null && geometry.type() != ShapeType.MULTIPOINT) {
+ queryGeometry = geometry;
+ }
+ XContentBuilder geoJson = GeoJson.toXContent(geometry, XContentFactory.jsonBuilder()
+ .startObject().field(FIELD), null).endObject();
+
+ try {
+ client().prepareIndex(INDEX, defaultFieldType).setSource(geoJson).setRefreshPolicy(IMMEDIATE).get();
+ client().prepareIndex(IGNORE_MALFORMED_INDEX, defaultFieldType).setRefreshPolicy(IMMEDIATE).setSource(geoJson).get();
+ } catch (Exception e) {
+ // sometimes GeoTestUtil will create invalid geometry; catch and continue:
+ if (queryGeometry == geometry) {
+ // reset query geometry as it didn't get indexed
+ queryGeometry = null;
+ }
+ --i;
+ continue;
+ }
+ }
+ }
+
+ public void testIndexedShapeReferenceSourceDisabled() throws Exception {
+ EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
+
+ client().prepareIndex(IGNORE_MALFORMED_INDEX, defaultFieldType).setId("Big_Rectangle").setSource(jsonBuilder().startObject()
+ .field(FIELD, shape).endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().prepareSearch(IGNORE_MALFORMED_INDEX)
+ .setQuery(new ShapeQueryBuilder(FIELD, "Big_Rectangle").indexedShapeIndex(IGNORE_MALFORMED_INDEX)).get());
+ assertThat(e.getMessage(), containsString("source disabled"));
+ }
+
+ public void testShapeFetchingPath() throws Exception {
+ String indexName = "shapes_index";
+ String searchIndex = "search_index";
+ createIndex(indexName);
+ client().admin().indices().prepareCreate(searchIndex).addMapping(defaultFieldType,"location", "type=shape").get();
+
+ String location = "\"location\" : {\"type\":\"polygon\", \"coordinates\":[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]}";
+
+ client().prepareIndex(indexName, defaultFieldType).setId("1")
+ .setSource(
+ String.format(
+ Locale.ROOT, "{ %s, \"1\" : { %s, \"2\" : { %s, \"3\" : { %s } }} }", location, location, location, location
+ ), XContentType.JSON)
+ .setRefreshPolicy(IMMEDIATE).get();
+ client().prepareIndex(searchIndex, defaultFieldType).setId("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();
+
+ ShapeQueryBuilder filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("location");
+ SearchResponse result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
+ .setPostFilter(filter).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.location");
+ result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
+ .setPostFilter(filter).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.2.location");
+ result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
+ .setPostFilter(filter).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.2.3.location");
+ result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
+ .setPostFilter(filter).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+
+ // now test the query variant
+ ShapeQueryBuilder query = new ShapeQueryBuilder("location", "1")
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("location");
+ result = client().prepareSearch(searchIndex).setQuery(query).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ query = new ShapeQueryBuilder("location", "1")
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.location");
+ result = client().prepareSearch(searchIndex).setQuery(query).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ query = new ShapeQueryBuilder("location", "1")
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.2.location");
+ result = client().prepareSearch(searchIndex).setQuery(query).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ query = new ShapeQueryBuilder("location", "1")
+ .indexedShapeIndex(indexName)
+ .indexedShapePath("1.2.3.location");
+ result = client().prepareSearch(searchIndex).setQuery(query).get();
+ assertSearchResponse(result);
+ assertHitCount(result, 1);
+ }
+
+ @Override
+ protected Collection> getPlugins() {
+ return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
+ }
+
+ /**
+ * Test that ignore_malformed on GeoShapeFieldMapper does not fail the entire document
+ */
+ public void testIgnoreMalformed() {
+ assertHitCount(client().prepareSearch(IGNORE_MALFORMED_INDEX).setQuery(matchAllQuery()).get(), numDocs);
+ }
+
+ /**
+ * Test that the indexed shape routing can be provided if it is required
+ */
+ public void testIndexShapeRouting() {
+ String source = "{\n" +
+ " \"shape\" : {\n" +
+ " \"type\" : \"bbox\",\n" +
+ " \"coordinates\" : [[" + -Float.MAX_VALUE + "," + Float.MAX_VALUE + "], [" + Float.MAX_VALUE + ", " + -Float.MAX_VALUE
+ + "]]\n" +
+ " }\n" +
+ "}";
+
+ client().prepareIndex(INDEX, defaultFieldType).setId("0").setSource(source, XContentType.JSON).setRouting("ABC").get();
+ client().admin().indices().prepareRefresh(INDEX).get();
+
+ SearchResponse searchResponse = client().prepareSearch(INDEX).setQuery(
+ new ShapeQueryBuilder(FIELD, "0").indexedShapeIndex(INDEX).indexedShapeRouting("ABC")
+ ).get();
+
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo((long)numDocs+1));
+ }
+
+ public void testNullShape() {
+ // index a null shape
+ client().prepareIndex(INDEX, defaultFieldType).setId("aNullshape").setSource("{\"" + FIELD + "\": null}", XContentType.JSON)
+ .setRefreshPolicy(IMMEDIATE).get();
+ client().prepareIndex(IGNORE_MALFORMED_INDEX, defaultFieldType).setId("aNullshape").setSource("{\"" + FIELD + "\": null}",
+ XContentType.JSON).setRefreshPolicy(IMMEDIATE).get();
+ GetResponse result = client().prepareGet(INDEX, defaultFieldType, "aNullshape").get();
+ assertThat(result.getField(FIELD), nullValue());
+ }
+
+ public void testExistsQuery() {
+ ExistsQueryBuilder eqb = QueryBuilders.existsQuery(FIELD);
+ SearchResponse result = client().prepareSearch(INDEX).setQuery(eqb).get();
+ assertSearchResponse(result);
+ assertHitCount(result, numDocs);
+ }
+
+ public void testFieldAlias() {
+ SearchResponse response = client().prepareSearch(INDEX)
+ .setQuery(new ShapeQueryBuilder("alias", queryGeometry).relation(ShapeRelation.INTERSECTS))
+ .get();
+ assertTrue(response.getHits().getTotalHits().value > 0);
+ }
+
+ public void testContainsShapeQuery() {
+
+ client().admin().indices().prepareCreate("test_contains").addMapping(defaultFieldType,"location", "type=shape")
+ .execute().actionGet();
+
+ String doc = "{\"location\" : {\"type\":\"envelope\", \"coordinates\":[ [-100.0, 100.0], [100.0, -100.0]]}}";
+ client().prepareIndex("test_contains", defaultFieldType).setId("1")
+ .setSource(doc, XContentType.JSON).setRefreshPolicy(IMMEDIATE).get();
+
+ // index the mbr of the collection
+ EnvelopeBuilder queryShape = new EnvelopeBuilder(new Coordinate(-50, 50), new Coordinate(50, -50));
+ ShapeQueryBuilder queryBuilder =
+ new ShapeQueryBuilder("location", queryShape.buildGeometry()).relation(ShapeRelation.CONTAINS);
+ SearchResponse response = client().prepareSearch("test_contains").setQuery(queryBuilder).get();
+ assertSearchResponse(response);
+
+ assertThat(response.getHits().getTotalHits().value, equalTo(1L));
+ }
+
+ public void testGeometryCollectionRelations() throws IOException {
+ XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
+ .startObject("properties")
+ .startObject("geometry").field("type", "shape").endObject()
+ .endObject()
+ .endObject();
+
+ createIndex("test_collections", Settings.builder().put("index.number_of_shards", 1).build()
+ , defaultFieldType, mapping);
+
+ EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(-10, 10), new Coordinate(10, -10));
+
+ client().index(new IndexRequest("test_collections")
+ .source(jsonBuilder().startObject().field("geometry", envelopeBuilder).endObject())
+ .setRefreshPolicy(IMMEDIATE)).actionGet();
+
+ {
+ // A geometry collection that is fully within the indexed shape
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder();
+ builder.shape(new PointBuilder(1, 2));
+ builder.shape(new PointBuilder(-2, -1));
+ SearchResponse response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
+ .get();
+ assertEquals(1, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
+ .get();
+ assertEquals(1, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
+ .get();
+ assertEquals(0, response.getHits().getTotalHits().value);
+ }
+ {
+ // A geometry collection (as multi point) that is partially within the indexed shape
+ MultiPointBuilder builder = new MultiPointBuilder();
+ builder.coordinate(1, 2);
+ builder.coordinate(20, 30);
+ SearchResponse response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
+ .get();
+ assertEquals(0, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
+ .get();
+ assertEquals(1, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
+ .get();
+ assertEquals(0, response.getHits().getTotalHits().value);
+ }
+ {
+ // A geometry collection that is disjoint with the indexed shape
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder();
+ MultiPointBuilder innerBuilder = new MultiPointBuilder();
+ innerBuilder.coordinate(-20, -30);
+ innerBuilder.coordinate(20, 30);
+ builder.shape(innerBuilder);
+ SearchResponse response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
+ .get();
+ assertEquals(0, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
+ .get();
+ assertEquals(0, response.getHits().getTotalHits().value);
+ response = client().prepareSearch("test_collections")
+ .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
+ .get();
+ assertEquals(1, response.getHits().getTotalHits().value);
+ }
+ }
+}
diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java
index 2b81d96eb30..9d2f11f0f0e 100644
--- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java
+++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/ShapeQueryTests.java
@@ -8,336 +8,322 @@ package org.elasticsearch.xpack.spatial.search;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.common.geo.GeoJson;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.CircleBuilder;
+import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
-import org.elasticsearch.common.geo.builders.MultiPointBuilder;
+import org.elasticsearch.common.geo.builders.MultiPolygonBuilder;
import org.elasticsearch.common.geo.builders.PointBuilder;
-import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.geometry.Geometry;
-import org.elasticsearch.geometry.ShapeType;
-import org.elasticsearch.index.query.ExistsQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.search.SearchHits;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
import org.elasticsearch.xpack.spatial.SpatialPlugin;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
-import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
import org.locationtech.jts.geom.Coordinate;
-import java.io.IOException;
+
import java.util.Collection;
-import java.util.Locale;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
-import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
-public class ShapeQueryTests extends ESSingleNodeTestCase {
-
- private static String INDEX = "test";
- private static String IGNORE_MALFORMED_INDEX = INDEX + "_ignore_malformed";
- private static String FIELD_TYPE = "geometry";
- private static String FIELD = "shape";
- private static Geometry queryGeometry = null;
-
- private int numDocs;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- // create test index
- assertAcked(client().admin().indices().prepareCreate(INDEX)
- .addMapping(FIELD_TYPE, FIELD, "type=shape", "alias", "type=alias,path=" + FIELD).get());
- // create index that ignores malformed geometry
- assertAcked(client().admin().indices().prepareCreate(IGNORE_MALFORMED_INDEX)
- .addMapping(FIELD_TYPE, FIELD, "type=shape,ignore_malformed=true", "_source", "enabled=false").get());
- ensureGreen();
-
- // index random shapes
- numDocs = randomIntBetween(25, 50);
- // reset query geometry to make sure we pick one from the indexed shapes
- queryGeometry = null;
- Geometry geometry;
- for (int i = 0; i < numDocs; ++i) {
- geometry = ShapeTestUtils.randomGeometry(false);
- if (geometry.type() == ShapeType.CIRCLE) continue;
- if (queryGeometry == null && geometry.type() != ShapeType.MULTIPOINT) {
- queryGeometry = geometry;
- }
- XContentBuilder geoJson = GeoJson.toXContent(geometry, XContentFactory.jsonBuilder()
- .startObject().field(FIELD), null).endObject();
-
- try {
- client().prepareIndex(INDEX, FIELD_TYPE).setSource(geoJson).setRefreshPolicy(IMMEDIATE).get();
- client().prepareIndex(IGNORE_MALFORMED_INDEX, FIELD_TYPE).setRefreshPolicy(IMMEDIATE).setSource(geoJson).get();
- } catch (Exception e) {
- // sometimes GeoTestUtil will create invalid geometry; catch and continue:
- if (queryGeometry == geometry) {
- // reset query geometry as it didn't get indexed
- queryGeometry = null;
- }
- --i;
- continue;
- }
- }
- }
-
- public void testIndexedShapeReferenceSourceDisabled() throws Exception {
- EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
-
- client().prepareIndex(IGNORE_MALFORMED_INDEX, FIELD_TYPE, "Big_Rectangle").setSource(jsonBuilder().startObject()
- .field(FIELD, shape).endObject()).setRefreshPolicy(IMMEDIATE).get();
-
- IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().prepareSearch(IGNORE_MALFORMED_INDEX)
- .setQuery(new ShapeQueryBuilder(FIELD, "Big_Rectangle").indexedShapeIndex(IGNORE_MALFORMED_INDEX)).get());
- assertThat(e.getMessage(), containsString("source disabled"));
- }
-
- public void testShapeFetchingPath() throws Exception {
- String indexName = "shapes_index";
- String searchIndex = "search_index";
- createIndex(indexName);
- client().admin().indices().prepareCreate(searchIndex).addMapping("type", "location", "type=shape").get();
-
- String location = "\"location\" : {\"type\":\"polygon\", \"coordinates\":[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]}";
-
- client().prepareIndex(indexName, "type", "1")
- .setSource(
- String.format(
- Locale.ROOT, "{ %s, \"1\" : { %s, \"2\" : { %s, \"3\" : { %s } }} }", location, location, location, location
- ), XContentType.JSON)
- .setRefreshPolicy(IMMEDIATE).get();
- client().prepareIndex(searchIndex, "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();
-
- ShapeQueryBuilder filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
- .indexedShapeIndex(indexName)
- .indexedShapePath("location");
- SearchResponse result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
- .setPostFilter(filter).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.location");
- result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
- .setPostFilter(filter).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.2.location");
- result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
- .setPostFilter(filter).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- filter = new ShapeQueryBuilder("location", "1").relation(ShapeRelation.INTERSECTS)
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.2.3.location");
- result = client().prepareSearch(searchIndex).setQuery(QueryBuilders.matchAllQuery())
- .setPostFilter(filter).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
-
- // now test the query variant
- ShapeQueryBuilder query = new ShapeQueryBuilder("location", "1")
- .indexedShapeIndex(indexName)
- .indexedShapePath("location");
- result = client().prepareSearch(searchIndex).setQuery(query).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- query = new ShapeQueryBuilder("location", "1")
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.location");
- result = client().prepareSearch(searchIndex).setQuery(query).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- query = new ShapeQueryBuilder("location", "1")
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.2.location");
- result = client().prepareSearch(searchIndex).setQuery(query).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- query = new ShapeQueryBuilder("location", "1")
- .indexedShapeIndex(indexName)
- .indexedShapePath("1.2.3.location");
- result = client().prepareSearch(searchIndex).setQuery(query).get();
- assertSearchResponse(result);
- assertHitCount(result, 1);
- }
+public abstract class ShapeQueryTests extends ESSingleNodeTestCase {
@Override
protected Collection> getPlugins() {
return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
}
- /**
- * Test that ignore_malformed on GeoShapeFieldMapper does not fail the entire document
- */
- public void testIgnoreMalformed() {
- assertHitCount(client().prepareSearch(IGNORE_MALFORMED_INDEX).setQuery(matchAllQuery()).get(), numDocs);
- }
+ protected abstract XContentBuilder createDefaultMapping() throws Exception;
- /**
- * Test that the indexed shape routing can be provided if it is required
- */
- public void testIndexShapeRouting() {
- String source = "{\n" +
- " \"shape\" : {\n" +
- " \"type\" : \"bbox\",\n" +
- " \"coordinates\" : [[" + -Float.MAX_VALUE + "," + Float.MAX_VALUE + "], [" + Float.MAX_VALUE + ", " + -Float.MAX_VALUE
- + "]]\n" +
- " }\n" +
- "}";
+ static String defaultFieldName = "xy";
+ static String defaultIndexName = "test-points";
+ static String defaultFieldType = "doc";
- client().prepareIndex(INDEX, FIELD_TYPE, "0").setSource(source, XContentType.JSON).setRouting("ABC").get();
- client().admin().indices().prepareRefresh(INDEX).get();
+ public void testNullShape() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
- SearchResponse searchResponse = client().prepareSearch(INDEX).setQuery(
- new ShapeQueryBuilder(FIELD, "0").indexedShapeIndex(INDEX).indexedShapeRouting("ABC")
- ).get();
-
- assertThat(searchResponse.getHits().getTotalHits().value, equalTo((long)numDocs+1));
- }
-
- public void testNullShape() {
- // index a null shape
- client().prepareIndex(INDEX, FIELD_TYPE, "aNullshape").setSource("{\"" + FIELD + "\": null}", XContentType.JSON)
+ client().prepareIndex(defaultIndexName, defaultFieldType, "aNullshape")
+ .setSource("{\"geo\": null}", XContentType.JSON)
.setRefreshPolicy(IMMEDIATE).get();
- client().prepareIndex(IGNORE_MALFORMED_INDEX, FIELD_TYPE, "aNullshape").setSource("{\"" + FIELD + "\": null}",
- XContentType.JSON).setRefreshPolicy(IMMEDIATE).get();
- GetResponse result = client().prepareGet(INDEX, FIELD_TYPE, "aNullshape").get();
- assertThat(result.getField(FIELD), nullValue());
- }
+ GetResponse result = client().prepareGet(defaultIndexName, defaultFieldType, "aNullshape").get();
+ assertThat(result.getField("location"), nullValue());
+ };
- public void testExistsQuery() {
- ExistsQueryBuilder eqb = QueryBuilders.existsQuery(FIELD);
- SearchResponse result = client().prepareSearch(INDEX).setQuery(eqb).get();
- assertSearchResponse(result);
- assertHitCount(result, numDocs);
- }
+ public void testIndexPointsFilterRectangle() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
- public void testFieldAlias() {
- SearchResponse response = client().prepareSearch(INDEX)
- .setQuery(new ShapeQueryBuilder("alias", queryGeometry).relation(ShapeRelation.INTERSECTS))
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 1")
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 2")
+ .field(defaultFieldName, "POINT(-45 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ EnvelopeBuilder shape = new EnvelopeBuilder(new Coordinate(-45, 45), new Coordinate(45, -45));
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(shape);
+ Geometry geometry = builder.buildGeometry().get(0);
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, geometry)
+ .relation(ShapeRelation.INTERSECTS))
.get();
- assertTrue(response.getHits().getTotalHits().value > 0);
+
+ assertSearchResponse(searchResponse);
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
+ assertThat(searchResponse.getHits().getHits().length, equalTo(1));
+ assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("1"));
+
+ // default query, without specifying relation (expect intersects)
+ searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, geometry))
+ .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 testContainsShapeQuery() {
+ public void testIndexPointsCircle() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
- client().admin().indices().prepareCreate("test_contains").addMapping("type", "location", "type=shape")
- .execute().actionGet();
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 1")
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
- String doc = "{\"location\" : {\"type\":\"envelope\", \"coordinates\":[ [-100.0, 100.0], [100.0, -100.0]]}}";
- client().prepareIndex("test_contains", "type").setId("1").setSource(doc, XContentType.JSON).setRefreshPolicy(IMMEDIATE).get();
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 2")
+ .field(defaultFieldName, "POINT(-45 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
- // index the mbr of the collection
- EnvelopeBuilder queryShape = new EnvelopeBuilder(new Coordinate(-50, 50), new Coordinate(50, -50));
- ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("location", queryShape.buildGeometry()).relation(ShapeRelation.CONTAINS);
- SearchResponse response = client().prepareSearch("test_contains").setQuery(queryBuilder).get();
- assertSearchResponse(response);
+ CircleBuilder shape = new CircleBuilder().center(new Coordinate(-30, -30)).radius("1");
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(shape);
+ Geometry geometry = builder.buildGeometry().get(0);
- assertThat(response.getHits().getTotalHits().value, equalTo(1L));
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, geometry)
+ .relation(ShapeRelation.INTERSECTS))
+ .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 testGeometryCollectionRelations() throws IOException {
- XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
- .startObject("doc")
- .startObject("properties")
- .startObject("geometry").field("type", "shape").endObject()
+ public void testIndexPointsPolygon() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
+ .startObject()
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
+ .startObject()
+ .field(defaultFieldName, "POINT(-45 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ CoordinatesBuilder cb = new CoordinatesBuilder();
+ cb.coordinate(new Coordinate(-35, -35))
+ .coordinate(new Coordinate(-35, -25))
+ .coordinate(new Coordinate(-25, -25))
+ .coordinate(new Coordinate(-25, -35))
+ .coordinate(new Coordinate(-35, -35));
+ PolygonBuilder shape = new PolygonBuilder(cb);
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(shape);
+ Geometry geometry = builder.buildGeometry();
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, geometry)
+ .relation(ShapeRelation.INTERSECTS))
+ .get();
+
+ assertSearchResponse(searchResponse);
+ SearchHits searchHits = searchResponse.getHits();
+ assertThat(searchHits.getTotalHits().value, equalTo(1L));
+ assertThat(searchHits.getAt(0).getId(), equalTo("1"));
+ }
+
+ public void testIndexPointsMultiPolygon() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 1")
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 2")
+ .field(defaultFieldName, "POINT(-40 -40)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("3").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 3")
+ .field(defaultFieldName, "POINT(-50 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ CoordinatesBuilder encloseDocument1Cb = new CoordinatesBuilder();
+ encloseDocument1Cb.coordinate(new Coordinate(-35, -35))
+ .coordinate(new Coordinate(-35, -25))
+ .coordinate(new Coordinate(-25, -25))
+ .coordinate(new Coordinate(-25, -35))
+ .coordinate(new Coordinate(-35, -35));
+ PolygonBuilder encloseDocument1Shape = new PolygonBuilder(encloseDocument1Cb);
+
+ CoordinatesBuilder encloseDocument2Cb = new CoordinatesBuilder();
+ encloseDocument2Cb.coordinate(new Coordinate(-55, -55))
+ .coordinate(new Coordinate(-55, -45))
+ .coordinate(new Coordinate(-45, -45))
+ .coordinate(new Coordinate(-45, -55))
+ .coordinate(new Coordinate(-55, -55));
+ PolygonBuilder encloseDocument2Shape = new PolygonBuilder(encloseDocument2Cb);
+
+ MultiPolygonBuilder mp = new MultiPolygonBuilder();
+ mp.polygon(encloseDocument1Shape).polygon(encloseDocument2Shape);
+
+ GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(mp);
+ Geometry geometry = builder.buildGeometry();
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, geometry)
+ .relation(ShapeRelation.INTERSECTS))
+ .get();
+
+ assertSearchResponse(searchResponse);
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo(2L));
+ assertThat(searchResponse.getHits().getHits().length, equalTo(2));
+ assertThat(searchResponse.getHits().getAt(0).getId(), not(equalTo("2")));
+ assertThat(searchResponse.getHits().getAt(1).getId(), not(equalTo("2")));
+ }
+
+ public void testIndexPointsRectangle() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 1")
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
+ .startObject()
+ .field("name", "Document 2")
+ .field(defaultFieldName, "POINT(-45 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ Rectangle rectangle = new Rectangle(-50, -40, -45, -55);
+
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, rectangle)
+ .relation(ShapeRelation.INTERSECTS))
+ .get();
+
+ assertSearchResponse(searchResponse);
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
+ assertThat(searchResponse.getHits().getHits().length, equalTo(1));
+ assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("2"));
+ }
+
+ public void testIndexPointsIndexedRectangle() throws Exception {
+ String mapping = Strings.toString(createDefaultMapping());
+ client().admin().indices().prepareCreate(defaultIndexName)
+ .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
+ ensureGreen();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("point1").setSource(jsonBuilder()
+ .startObject()
+ .field(defaultFieldName, "POINT(-30 -30)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ client().prepareIndex(defaultIndexName, defaultFieldType).setId("point2").setSource(jsonBuilder()
+ .startObject()
+ .field(defaultFieldName, "POINT(-45 -50)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
+
+ String indexedShapeIndex = "indexed_query_shapes";
+ String indexedShapePath = "shape";
+ String queryShapesMapping = Strings.toString(XContentFactory.jsonBuilder().startObject()
+ .startObject("properties").startObject(indexedShapePath)
+ .field("type", "shape")
.endObject()
.endObject()
- .endObject();
+ .endObject());
+ client().admin().indices().prepareCreate(indexedShapeIndex)
+ .addMapping(defaultFieldType, queryShapesMapping, XContentType.JSON);
+ ensureGreen();
- createIndex("test_collections", Settings.builder().put("index.number_of_shards", 1).build(), "doc", mapping);
+ client().prepareIndex(indexedShapeIndex, defaultFieldType).setId("shape1").setSource(jsonBuilder()
+ .startObject()
+ .field(indexedShapePath, "BBOX(-50, -40, -45, -55)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
- EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(-10, 10), new Coordinate(10, -10));
+ client().prepareIndex(indexedShapeIndex, defaultFieldType).setId("shape2").setSource(jsonBuilder()
+ .startObject()
+ .field(indexedShapePath, "BBOX(-60, -50, -50, -60)")
+ .endObject()).setRefreshPolicy(IMMEDIATE).get();
- client().index(new IndexRequest("test_collections")
- .source(jsonBuilder().startObject().field("geometry", envelopeBuilder).endObject())
- .setRefreshPolicy(IMMEDIATE)).actionGet();
+ SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, "shape1")
+ .relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexedShapeIndex)
+ .indexedShapePath(indexedShapePath))
+ .get();
- {
- // A geometry collection that is fully within the indexed shape
- GeometryCollectionBuilder builder = new GeometryCollectionBuilder();
- builder.shape(new PointBuilder(1, 2));
- builder.shape(new PointBuilder(-2, -1));
- SearchResponse response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
- .get();
- assertEquals(1, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
- .get();
- assertEquals(1, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
- .get();
- assertEquals(0, response.getHits().getTotalHits().value);
- }
- {
- // A geometry collection (as multi point) that is partially within the indexed shape
- MultiPointBuilder builder = new MultiPointBuilder();
- builder.coordinate(1, 2);
- builder.coordinate(20, 30);
- SearchResponse response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
- .get();
- assertEquals(0, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
- .get();
- assertEquals(1, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
- .get();
- assertEquals(0, response.getHits().getTotalHits().value);
- }
- {
- // A geometry collection that is disjoint with the indexed shape
- GeometryCollectionBuilder builder = new GeometryCollectionBuilder();
- MultiPointBuilder innerBuilder = new MultiPointBuilder();
- innerBuilder.coordinate(-20, -30);
- innerBuilder.coordinate(20, 30);
- builder.shape(innerBuilder);
- SearchResponse response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS))
- .get();
- assertEquals(0, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS))
- .get();
- assertEquals(0, response.getHits().getTotalHits().value);
- response = client().prepareSearch("test_collections")
- .setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.DISJOINT))
- .get();
- assertEquals(1, response.getHits().getTotalHits().value);
- }
+ assertSearchResponse(searchResponse);
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
+ assertThat(searchResponse.getHits().getHits().length, equalTo(1));
+ assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("point2"));
+
+ searchResponse = client().prepareSearch(defaultIndexName)
+ .setQuery(new ShapeQueryBuilder(defaultFieldName, "shape2")
+ .relation(ShapeRelation.INTERSECTS)
+ .indexedShapeIndex(indexedShapeIndex)
+ .indexedShapePath(indexedShapePath))
+ .get();
+ assertSearchResponse(searchResponse);
+ assertThat(searchResponse.getHits().getTotalHits().value, equalTo(0L));
}
public void testDistanceQuery() throws Exception {