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"] [testenv="basic"]
== Shape queries == Shape queries
Like <<geo-shape,`geo_shape`>> Elasticsearch supports the ability to index Like <<geo-shape,`geo_shape`>> Elasticsearch supports the ability to index
arbitrary two dimension (non Geospatial) geometries making it possible to arbitrary two dimension (non Geospatial) geometries making it possible to
map out virtual worlds, sporting venues, theme parks, and CAD diagrams. The map out virtual worlds, sporting venues, theme parks, and CAD diagrams.
<<shape,`shape`>> field type supports points, lines, polygons, multi-polygons,
envelope, etc. 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: The queries in this group are:
<<query-dsl-shape-query,`shape`>> query:: <<query-dsl-shape-query,`shape`>> query::
Finds documents with shapes that either intersect, are within, or do not Finds documents with:
intersect a specified shape. * `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[] include::shape-query.asciidoc[]

View File

@ -14,6 +14,7 @@ import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.plugins.SearchPlugin; 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.mapper.ShapeFieldMapper;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder; import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
import org.elasticsearch.xpack.spatial.ingest.CircleProcessor; 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() { public Map<String, Mapper.TypeParser> getMappers() {
Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>(); Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>();
mappers.put(ShapeFieldMapper.CONTENT_TYPE, new ShapeFieldMapper.TypeParser()); mappers.put(ShapeFieldMapper.CONTENT_TYPE, new ShapeFieldMapper.TypeParser());
mappers.put(PointFieldMapper.CONTENT_TYPE, new PointFieldMapper.TypeParser());
return Collections.unmodifiableMap(mappers); 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.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry; 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.mapper.MappedFieldType;
import org.elasticsearch.index.query.AbstractGeometryQueryBuilder; import org.elasticsearch.index.query.AbstractGeometryQueryBuilder;
import org.elasticsearch.index.query.GeoShapeQueryBuilder; import org.elasticsearch.index.query.GeoShapeQueryBuilder;
import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException; import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper; import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper;
import java.io.IOException; 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."; "The type should no longer be specified in the [indexed_shape] section.";
protected static final List<String> validContentTypes = 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 * 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() + "]"); + "] 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)); 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.Mapper;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.test.InternalSettingsPlugin; import org.elasticsearch.test.InternalSettingsPlugin;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
import org.elasticsearch.xpack.spatial.SpatialPlugin; import org.elasticsearch.xpack.spatial.SpatialPlugin;
@ -28,17 +27,31 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE; 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.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
/** testing for {@link org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper} */ /** testing for {@link org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper} */
public class ShapeFieldMapperTests extends ESSingleNodeTestCase { public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
@Override @Override
protected Collection<Class<? extends Plugin>> getPlugins() { protected Collection<Class<? extends Plugin>> getPlugins() {
return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class); 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 { public void testDefaultConfiguration() throws IOException {
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1") String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("properties").startObject("location") .startObject("properties").startObject("location")
@ -249,21 +262,6 @@ public class ShapeFieldMapperTests extends ESSingleNodeTestCase {
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CW)); 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 { public void testSerializeDefaults() throws Exception {
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser(); 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.MatchNoDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchException; 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.GetRequest;
import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.geo.GeoJson; import org.elasticsearch.common.geo.GeoJson;
import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.io.stream.BytesStreamOutput; 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.plugins.Plugin;
import org.elasticsearch.test.AbstractQueryTestCase; import org.elasticsearch.test.AbstractQueryTestCase;
import org.elasticsearch.xpack.spatial.SpatialPlugin; import org.elasticsearch.xpack.spatial.SpatialPlugin;
import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
import org.junit.After; import org.junit.After;
import java.io.IOException; 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.anyOf;
import static org.hamcrest.Matchers.equalTo; 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"; 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 indexedShapeId;
protected static String indexedShapeType; protected static String indexedShapeType;
@ -62,17 +58,15 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
protected static String indexedShapeRouting; protected static String indexedShapeRouting;
protected static Geometry indexedShapeToReturn; protected static Geometry indexedShapeToReturn;
protected abstract ShapeRelation getShapeRelation(ShapeType type);
protected abstract Geometry getGeometry();
@Override @Override
protected Collection<Class<? extends Plugin>> getPlugins() { protected Collection<Class<? extends Plugin>> getPlugins() {
return Collections.singleton(SpatialPlugin.class); 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() { protected String fieldName() {
return SHAPE_FIELD_NAME; return SHAPE_FIELD_NAME;
} }
@ -83,7 +77,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
} }
protected ShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) { protected ShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
Geometry shape = ShapeTestUtils.randomGeometry(false); Geometry shape = getGeometry();
ShapeQueryBuilder builder; ShapeQueryBuilder builder;
clearShapeFields(); clearShapeFields();
@ -109,21 +103,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
} }
if (randomBoolean()) { if (randomBoolean()) {
QueryShardContext context = createShardContext(); builder.relation(getShapeRelation(shape.type()));
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));
}
}
} }
if (randomBoolean()) { if (randomBoolean()) {
@ -152,7 +132,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
} }
public void testNoFieldName() { public void testNoFieldName() {
Geometry shape = ShapeTestUtils.randomGeometry(false); Geometry shape = getGeometry();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new ShapeQueryBuilder(null, shape)); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new ShapeQueryBuilder(null, shape));
assertEquals("fieldName is required", e.getMessage()); assertEquals("fieldName is required", e.getMessage());
} }
@ -168,7 +148,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
} }
public void testNoRelation() { public void testNoRelation() {
Geometry shape = ShapeTestUtils.randomGeometry(false); Geometry shape = getGeometry();
ShapeQueryBuilder builder = new ShapeQueryBuilder(fieldName(), shape); ShapeQueryBuilder builder = new ShapeQueryBuilder(fieldName(), shape);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.relation(null)); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.relation(null));
assertEquals("No Shape Relation defined", e.getMessage()); assertEquals("No Shape Relation defined", e.getMessage());
@ -223,7 +203,7 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
} }
public void testIgnoreUnmapped() throws IOException { public void testIgnoreUnmapped() throws IOException {
Geometry shape = ShapeTestUtils.randomGeometry(false); Geometry shape = getGeometry();
final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("unmapped", shape); final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("unmapped", shape);
queryBuilder.ignoreUnmapped(true); queryBuilder.ignoreUnmapped(true);
Query query = queryBuilder.toQuery(createShardContext()); Query query = queryBuilder.toQuery(createShardContext());
@ -233,14 +213,14 @@ public class ShapeQueryBuilderTests extends AbstractQueryTestCase<ShapeQueryBuil
final ShapeQueryBuilder failingQueryBuilder = new ShapeQueryBuilder("unmapped", shape); final ShapeQueryBuilder failingQueryBuilder = new ShapeQueryBuilder("unmapped", shape);
failingQueryBuilder.ignoreUnmapped(false); failingQueryBuilder.ignoreUnmapped(false);
QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(createShardContext())); 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() { public void testWrongFieldType() {
Geometry shape = ShapeTestUtils.randomGeometry(false); Geometry shape = getGeometry();
final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder(TEXT_FIELD_NAME, shape); final ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder(TEXT_FIELD_NAME, shape);
QueryShardException e = expectThrows(QueryShardException.class, () -> queryBuilder.toQuery(createShardContext())); 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 { 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.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchResponse; 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.ShapeRelation;
import org.elasticsearch.common.geo.builders.CircleBuilder; 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.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; 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.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.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.ShapeType; import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.query.ExistsQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.test.ESSingleNodeTestCase;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
import org.elasticsearch.xpack.spatial.SpatialPlugin; import org.elasticsearch.xpack.spatial.SpatialPlugin;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder; import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Coordinate;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Locale;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; 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.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
public class ShapeQueryTests extends ESSingleNodeTestCase { public abstract 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);
}
@Override @Override
protected Collection<Class<? extends Plugin>> getPlugins() { protected Collection<Class<? extends Plugin>> getPlugins() {
return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class); return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
} }
/** protected abstract XContentBuilder createDefaultMapping() throws Exception;
* 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);
}
/** static String defaultFieldName = "xy";
* Test that the indexed shape routing can be provided if it is required static String defaultIndexName = "test-points";
*/ static String defaultFieldType = "doc";
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, FIELD_TYPE, "0").setSource(source, XContentType.JSON).setRouting("ABC").get(); public void testNullShape() throws Exception {
client().admin().indices().prepareRefresh(INDEX).get(); String mapping = Strings.toString(createDefaultMapping());
client().admin().indices().prepareCreate(defaultIndexName)
.addMapping(defaultFieldType, mapping, XContentType.JSON).get();
ensureGreen();
SearchResponse searchResponse = client().prepareSearch(INDEX).setQuery( client().prepareIndex(defaultIndexName, defaultFieldType, "aNullshape")
new ShapeQueryBuilder(FIELD, "0").indexedShapeIndex(INDEX).indexedShapeRouting("ABC") .setSource("{\"geo\": null}", XContentType.JSON)
).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)
.setRefreshPolicy(IMMEDIATE).get(); .setRefreshPolicy(IMMEDIATE).get();
client().prepareIndex(IGNORE_MALFORMED_INDEX, FIELD_TYPE, "aNullshape").setSource("{\"" + FIELD + "\": null}", GetResponse result = client().prepareGet(defaultIndexName, defaultFieldType, "aNullshape").get();
XContentType.JSON).setRefreshPolicy(IMMEDIATE).get(); assertThat(result.getField("location"), nullValue());
GetResponse result = client().prepareGet(INDEX, FIELD_TYPE, "aNullshape").get(); };
assertThat(result.getField(FIELD), nullValue());
}
public void testExistsQuery() { public void testIndexPointsFilterRectangle() throws Exception {
ExistsQueryBuilder eqb = QueryBuilders.existsQuery(FIELD); String mapping = Strings.toString(createDefaultMapping());
SearchResponse result = client().prepareSearch(INDEX).setQuery(eqb).get(); client().admin().indices().prepareCreate(defaultIndexName)
assertSearchResponse(result); .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
assertHitCount(result, numDocs); ensureGreen();
}
public void testFieldAlias() { client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
SearchResponse response = client().prepareSearch(INDEX) .startObject()
.setQuery(new ShapeQueryBuilder("alias", queryGeometry).relation(ShapeRelation.INTERSECTS)) .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(); .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") client().prepareIndex(defaultIndexName, defaultFieldType).setId("1").setSource(jsonBuilder()
.execute().actionGet(); .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(defaultIndexName, defaultFieldType).setId("2").setSource(jsonBuilder()
client().prepareIndex("test_contains", "type").setId("1").setSource(doc, XContentType.JSON).setRefreshPolicy(IMMEDIATE).get(); .startObject()
.field("name", "Document 2")
.field(defaultFieldName, "POINT(-45 -50)")
.endObject()).setRefreshPolicy(IMMEDIATE).get();
// index the mbr of the collection CircleBuilder shape = new CircleBuilder().center(new Coordinate(-30, -30)).radius("1");
EnvelopeBuilder queryShape = new EnvelopeBuilder(new Coordinate(-50, 50), new Coordinate(50, -50)); GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape(shape);
ShapeQueryBuilder queryBuilder = new ShapeQueryBuilder("location", queryShape.buildGeometry()).relation(ShapeRelation.CONTAINS); Geometry geometry = builder.buildGeometry().get(0);
SearchResponse response = client().prepareSearch("test_contains").setQuery(queryBuilder).get();
assertSearchResponse(response);
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 { public void testIndexPointsPolygon() throws Exception {
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject() String mapping = Strings.toString(createDefaultMapping());
.startObject("doc") client().admin().indices().prepareCreate(defaultIndexName)
.startObject("properties") .addMapping(defaultFieldType, mapping, XContentType.JSON).get();
.startObject("geometry").field("type", "shape").endObject() 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()
.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") SearchResponse searchResponse = client().prepareSearch(defaultIndexName)
.source(jsonBuilder().startObject().field("geometry", envelopeBuilder).endObject()) .setQuery(new ShapeQueryBuilder(defaultFieldName, "shape1")
.setRefreshPolicy(IMMEDIATE)).actionGet(); .relation(ShapeRelation.INTERSECTS)
.indexedShapeIndex(indexedShapeIndex)
.indexedShapePath(indexedShapePath))
.get();
{ assertSearchResponse(searchResponse);
// A geometry collection that is fully within the indexed shape assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
GeometryCollectionBuilder builder = new GeometryCollectionBuilder(); assertThat(searchResponse.getHits().getHits().length, equalTo(1));
builder.shape(new PointBuilder(1, 2)); assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("point2"));
builder.shape(new PointBuilder(-2, -1));
SearchResponse response = client().prepareSearch("test_collections") searchResponse = client().prepareSearch(defaultIndexName)
.setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.CONTAINS)) .setQuery(new ShapeQueryBuilder(defaultFieldName, "shape2")
.get(); .relation(ShapeRelation.INTERSECTS)
assertEquals(1, response.getHits().getTotalHits().value); .indexedShapeIndex(indexedShapeIndex)
response = client().prepareSearch("test_collections") .indexedShapePath(indexedShapePath))
.setQuery(new ShapeQueryBuilder("geometry", builder.buildGeometry()).relation(ShapeRelation.INTERSECTS)) .get();
.get(); assertSearchResponse(searchResponse);
assertEquals(1, response.getHits().getTotalHits().value); assertThat(searchResponse.getHits().getTotalHits().value, equalTo(0L));
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);
}
} }
public void testDistanceQuery() throws Exception { public void testDistanceQuery() throws Exception {