Add new point field. (#53804) (#54879)

This commit adds a new point field that is able to index arbitrary pair of values (x/y)
in the cartesian space. It only supports filtering using shape queries at the moment.
This commit is contained in:
Ignacio Vera 2020-04-07 15:28:50 +02:00 committed by GitHub
parent 4d36917e52
commit 076c199484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2326 additions and 338 deletions

View File

@ -0,0 +1,99 @@
[[point]]
[role="xpack"]
[testenv="basic"]
=== Point datatype
++++
<titleabbrev>Point</titleabbrev>
++++
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
<<query-dsl-shape-query,shape Query>>.
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]
<<ignore-malformed,`ignore_malformed`>>::
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.
<<null-value,`null_value`>>::
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.

View File

@ -3,16 +3,21 @@
[testenv="basic"]
== Shape queries
Like <<geo-shape,`geo_shape`>> 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
<<shape,`shape`>> 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:
<<point,`point`>> fields which support x/y pairs, and
<<shape,`shape`>> fields, which support points, lines, circles, polygons, multi-polygons, etc.
The queries in this group are:
<<query-dsl-shape-query,`shape`>> 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[]

View File

@ -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<String, Mapper.TypeParser> getMappers() {
Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>();
mappers.put(ShapeFieldMapper.CONTENT_TYPE, new ShapeFieldMapper.TypeParser());
mappers.put(PointFieldMapper.CONTENT_TYPE, new PointFieldMapper.TypeParser());
return Collections.unmodifiableMap(mappers);
}

View File

@ -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;
}
}

View File

@ -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<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
public static final PointFieldType FIELD_TYPE = new PointFieldType();
public static final Explicit<Boolean> 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<Builder, PointFieldMapper> {
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<Boolean> 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<Boolean> 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<Boolean> 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<String, Object> node, ParserContext parserContext)
throws MapperParsingException {
Builder builder = new PointFieldMapper.Builder(name);
parseField(builder, name, node, parserContext);
Object nullValue = null;
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> 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<Boolean> ignoreMalformed;
protected Explicit<Boolean> ignoreZValue;
public PointFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> 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<IndexableField> 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<IndexableField> 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<Boolean> ignoreZValue() {
return ignoreZValue;
}
}

View File

@ -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<ShapeQueryBu
"The type should no longer be specified in the [indexed_shape] section.";
protected static final List<String> 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<ShapeQueryBu
+ "] but of type [" + fieldType.typeName() + "]");
}
final AbstractGeometryFieldMapper.AbstractGeometryFieldType ft = (AbstractGeometryFieldMapper.AbstractGeometryFieldType) fieldType;
final AbstractSearchableGeometryFieldType ft = (AbstractSearchableGeometryFieldType) fieldType;
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, ft.name(), relation, context));
}

View File

@ -0,0 +1,174 @@
/*
* 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.apache.lucene.document.XYDocValuesField;
import org.apache.lucene.document.XYPointField;
import org.apache.lucene.geo.XYCircle;
import org.apache.lucene.geo.XYRectangle;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.geometry.Circle;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
import org.elasticsearch.geometry.GeometryVisitor;
import org.elasticsearch.geometry.LinearRing;
import org.elasticsearch.geometry.MultiLine;
import org.elasticsearch.geometry.MultiPoint;
import org.elasticsearch.geometry.MultiPolygon;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.ShapeType;
import org.elasticsearch.index.mapper.AbstractSearchableGeometryFieldType;
import org.elasticsearch.index.mapper.MappedFieldType;
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.ShapeUtils;
public class ShapeQueryPointProcessor implements AbstractSearchableGeometryFieldType.QueryProcessor {
@Override
public Query process(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
validateIsPointFieldType(fieldName, context);
// only the intersects relation is supported for indexed cartesian point types
if (relation != ShapeRelation.INTERSECTS) {
throw new QueryShardException(context,
relation+ " query relation not supported for Field [" + fieldName + "].");
}
// wrap XYPoint query as a ConstantScoreQuery
return getVectorQueryFromShape(shape, fieldName, relation, context);
}
private void validateIsPointFieldType(String fieldName, QueryShardContext context) {
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType instanceof PointFieldMapper.PointFieldType == false) {
throw new QueryShardException(context, "Expected " + PointFieldMapper.CONTENT_TYPE
+ " field type for Field [" + fieldName + "] but found " + fieldType.typeName());
}
}
protected Query getVectorQueryFromShape(
Geometry queryShape, String fieldName, ShapeRelation relation, QueryShardContext context) {
ShapeVisitor shapeVisitor = new ShapeVisitor(context, fieldName, relation);
return queryShape.visit(shapeVisitor);
}
private class ShapeVisitor implements GeometryVisitor<Query, RuntimeException> {
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;
}
}
}

View File

@ -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<Class<? extends Plugin>> 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]"));
}
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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<Class<? extends Plugin>> 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();
{

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<ShapeQueryBuilder> {
public abstract class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuilder> {
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<ShapeQueryBuil
protected static String indexedShapeRouting;
protected static Geometry indexedShapeToReturn;
protected abstract ShapeRelation getShapeRelation(ShapeType type);
protected abstract Geometry getGeometry();
@Override
protected Collection<Class<? extends Plugin>> 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<ShapeQueryBuil
}
protected ShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
Geometry shape = ShapeTestUtils.randomGeometry(false);
Geometry shape = getGeometry();
ShapeQueryBuilder builder;
clearShapeFields();
@ -109,21 +103,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
if (randomBoolean()) {
QueryShardContext context = createShardContext();
if (context.indexVersionCreated().onOrAfter(Version.V_7_5_0)) { // CONTAINS is only supported from version 7.5
if (shape.type() == ShapeType.LINESTRING || shape.type() == ShapeType.MULTILINESTRING) {
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.CONTAINS));
} else {
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS,
ShapeRelation.WITHIN, ShapeRelation.CONTAINS));
}
} else {
if (shape.type() == ShapeType.LINESTRING || shape.type() == ShapeType.MULTILINESTRING) {
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS));
} else {
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN));
}
}
builder.relation(getShapeRelation(shape.type()));
}
if (randomBoolean()) {
@ -152,7 +132,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
public void testNoFieldName() {
Geometry shape = ShapeTestUtils.randomGeometry(false);
Geometry shape = getGeometry();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new ShapeQueryBuilder(null, shape));
assertEquals("fieldName is required", e.getMessage());
}
@ -168,7 +148,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
public void testNoRelation() {
Geometry shape = ShapeTestUtils.randomGeometry(false);
Geometry shape = getGeometry();
ShapeQueryBuilder builder = new ShapeQueryBuilder(fieldName(), shape);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.relation(null));
assertEquals("No Shape Relation defined", e.getMessage());
@ -223,7 +203,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
}
public void testIgnoreUnmapped() throws IOException {
Geometry shape = ShapeTestUtils.randomGeometry(false);
Geometry shape = getGeometry();
final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("unmapped", shape);
queryBuilder.ignoreUnmapped(true);
Query query = queryBuilder.toQuery(createShardContext());
@ -233,14 +213,14 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
final ShapeQueryBuilder failingQueryBuilder = new ShapeQueryBuilder("unmapped", shape);
failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> 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 {

View File

@ -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"));
}
}
}

View File

@ -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<Class<? extends Plugin>> 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);
}
}
}

View File

@ -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<Class<? extends Plugin>> 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 {