Geo: refactor geo mapper and query builder (#44884)
Refactors out the indexing and query generation logic out of the mapper and query builder into a separate unit-testable classes.
This commit is contained in:
parent
1561ab5420
commit
cfc8d17bb4
|
@ -29,7 +29,7 @@ import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
||||||
import org.elasticsearch.common.unit.DistanceUnit;
|
import org.elasticsearch.common.unit.DistanceUnit;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentSubParser;
|
import org.elasticsearch.common.xcontent.XContentSubParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -42,7 +42,7 @@ import java.util.List;
|
||||||
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
|
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
|
||||||
*/
|
*/
|
||||||
abstract class GeoJsonParser {
|
abstract class GeoJsonParser {
|
||||||
protected static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper)
|
protected static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper shapeMapper)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
GeoShapeType shapeType = null;
|
GeoShapeType shapeType = null;
|
||||||
DistanceUnit.Distance radius = null;
|
DistanceUnit.Distance radius = null;
|
||||||
|
@ -50,13 +50,13 @@ abstract class GeoJsonParser {
|
||||||
GeometryCollectionBuilder geometryCollections = null;
|
GeometryCollectionBuilder geometryCollections = null;
|
||||||
|
|
||||||
Orientation orientation = (shapeMapper == null)
|
Orientation orientation = (shapeMapper == null)
|
||||||
? BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value()
|
? AbstractGeometryFieldMapper.Defaults.ORIENTATION.value()
|
||||||
: shapeMapper.orientation();
|
: shapeMapper.orientation();
|
||||||
Explicit<Boolean> coerce = (shapeMapper == null)
|
Explicit<Boolean> coerce = (shapeMapper == null)
|
||||||
? BaseGeoShapeFieldMapper.Defaults.COERCE
|
? AbstractGeometryFieldMapper.Defaults.COERCE
|
||||||
: shapeMapper.coerce();
|
: shapeMapper.coerce();
|
||||||
Explicit<Boolean> ignoreZValue = (shapeMapper == null)
|
Explicit<Boolean> ignoreZValue = (shapeMapper == null)
|
||||||
? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE
|
? AbstractGeometryFieldMapper.Defaults.IGNORE_Z_VALUE
|
||||||
: shapeMapper.ignoreZValue();
|
: shapeMapper.ignoreZValue();
|
||||||
|
|
||||||
String malformedException = null;
|
String malformedException = null;
|
||||||
|
@ -208,7 +208,7 @@ abstract class GeoJsonParser {
|
||||||
* @return Geometry[] geometries of the GeometryCollection
|
* @return Geometry[] geometries of the GeometryCollection
|
||||||
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
||||||
*/
|
*/
|
||||||
static GeometryCollectionBuilder parseGeometries(XContentParser parser, BaseGeoShapeFieldMapper mapper) throws
|
static GeometryCollectionBuilder parseGeometries(XContentParser parser, AbstractGeometryFieldMapper mapper) throws
|
||||||
IOException {
|
IOException {
|
||||||
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
||||||
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
|
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -63,7 +63,7 @@ public class GeoWKTParser {
|
||||||
// no instance
|
// no instance
|
||||||
private GeoWKTParser() {}
|
private GeoWKTParser() {}
|
||||||
|
|
||||||
public static ShapeBuilder parse(XContentParser parser, final BaseGeoShapeFieldMapper shapeMapper)
|
public static ShapeBuilder parse(XContentParser parser, final AbstractGeometryFieldMapper shapeMapper)
|
||||||
throws IOException, ElasticsearchParseException {
|
throws IOException, ElasticsearchParseException {
|
||||||
return parseExpectedType(parser, null, shapeMapper);
|
return parseExpectedType(parser, null, shapeMapper);
|
||||||
}
|
}
|
||||||
|
@ -75,12 +75,12 @@ public class GeoWKTParser {
|
||||||
|
|
||||||
/** throws an exception if the parsed geometry type does not match the expected shape type */
|
/** throws an exception if the parsed geometry type does not match the expected shape type */
|
||||||
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
|
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
|
||||||
final BaseGeoShapeFieldMapper shapeMapper)
|
final AbstractGeometryFieldMapper shapeMapper)
|
||||||
throws IOException, ElasticsearchParseException {
|
throws IOException, ElasticsearchParseException {
|
||||||
try (StringReader reader = new StringReader(parser.text())) {
|
try (StringReader reader = new StringReader(parser.text())) {
|
||||||
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE :
|
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? AbstractGeometryFieldMapper.Defaults.IGNORE_Z_VALUE :
|
||||||
shapeMapper.ignoreZValue();
|
shapeMapper.ignoreZValue();
|
||||||
Explicit<Boolean> coerce = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
|
Explicit<Boolean> coerce = (shapeMapper == null) ? AbstractGeometryFieldMapper.Defaults.COERCE : shapeMapper.coerce();
|
||||||
// setup the tokenizer; configured to read words w/o numbers
|
// setup the tokenizer; configured to read words w/o numbers
|
||||||
StreamTokenizer tokenizer = new StreamTokenizer(reader);
|
StreamTokenizer tokenizer = new StreamTokenizer(reader);
|
||||||
tokenizer.resetSyntax();
|
tokenizer.resetSyntax();
|
||||||
|
@ -258,7 +258,7 @@ public class GeoWKTParser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce),
|
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce),
|
||||||
BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value());
|
AbstractGeometryFieldMapper.Defaults.ORIENTATION.value());
|
||||||
while (nextCloserOrComma(stream).equals(COMMA)) {
|
while (nextCloserOrComma(stream).equals(COMMA)) {
|
||||||
builder.hole(parseLinearRing(stream, ignoreZValue, coerce));
|
builder.hole(parseLinearRing(stream, ignoreZValue, coerce));
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.XContent;
|
import org.elasticsearch.common.xcontent.XContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.support.MapXContentParser;
|
import org.elasticsearch.common.xcontent.support.MapXContentParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -50,7 +50,7 @@ public interface ShapeParser {
|
||||||
* if the parsers current token has been <code>null</code>
|
* if the parsers current token has been <code>null</code>
|
||||||
* @throws IOException if the input could not be read
|
* @throws IOException if the input could not be read
|
||||||
*/
|
*/
|
||||||
static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper) throws IOException {
|
static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper shapeMapper) throws IOException {
|
||||||
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
||||||
return null;
|
return null;
|
||||||
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||||
|
|
|
@ -26,17 +26,22 @@ import org.apache.lucene.search.TermQuery;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.Explicit;
|
import org.elasticsearch.common.Explicit;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||||
|
import org.elasticsearch.geo.geometry.Geometry;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
|
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
|
||||||
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 java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -47,8 +52,8 @@ import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_MA
|
||||||
/**
|
/**
|
||||||
* Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper}
|
* Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper}
|
||||||
*/
|
*/
|
||||||
public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends FieldMapper {
|
||||||
public static final String CONTENT_TYPE = "geo_shape";
|
|
||||||
|
|
||||||
public static class Names {
|
public static class Names {
|
||||||
public static final ParseField ORIENTATION = new ParseField("orientation");
|
public static final ParseField ORIENTATION = new ParseField("orientation");
|
||||||
|
@ -62,7 +67,36 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
|
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class Builder<T extends Builder, Y extends BaseGeoShapeFieldMapper>
|
|
||||||
|
/**
|
||||||
|
* Interface representing an preprocessor in geo-shape indexing pipeline
|
||||||
|
*/
|
||||||
|
public interface Indexer<Parsed, Processed> {
|
||||||
|
|
||||||
|
Processed prepareForIndexing(Parsed geometry);
|
||||||
|
|
||||||
|
Class<Processed> processedClass();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interface representing parser in geo shape indexing pipeline
|
||||||
|
*/
|
||||||
|
public interface Parser<Parsed> {
|
||||||
|
|
||||||
|
Parsed parse(XContentParser parser, AbstractGeometryFieldMapper mapper) throws IOException, ParseException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interface representing a query builder that generates a query from the given shape
|
||||||
|
*/
|
||||||
|
public interface QueryProcessor {
|
||||||
|
|
||||||
|
Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, QueryShardContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper>
|
||||||
extends FieldMapper.Builder<T, Y> {
|
extends FieldMapper.Builder<T, Y> {
|
||||||
protected Boolean coerce;
|
protected Boolean coerce;
|
||||||
protected Boolean ignoreMalformed;
|
protected Boolean ignoreMalformed;
|
||||||
|
@ -152,7 +186,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
throw new IllegalArgumentException("name cannot be empty string");
|
throw new IllegalArgumentException("name cannot be empty string");
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
|
||||||
ft.setOrientation(orientation().value());
|
ft.setOrientation(orientation().value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,10 +252,16 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class BaseGeoShapeFieldType extends MappedFieldType {
|
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
|
||||||
protected Orientation orientation = Defaults.ORIENTATION.value();
|
protected Orientation orientation = Defaults.ORIENTATION.value();
|
||||||
|
|
||||||
protected BaseGeoShapeFieldType() {
|
protected Indexer<Parsed, Processed> geometryIndexer;
|
||||||
|
|
||||||
|
protected Parser<Parsed> geometryParser;
|
||||||
|
|
||||||
|
protected QueryProcessor geometryQueryBuilder;
|
||||||
|
|
||||||
|
protected AbstractGeometryFieldType() {
|
||||||
setIndexOptions(IndexOptions.DOCS);
|
setIndexOptions(IndexOptions.DOCS);
|
||||||
setTokenized(false);
|
setTokenized(false);
|
||||||
setStored(false);
|
setStored(false);
|
||||||
|
@ -229,7 +269,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
setOmitNorms(true);
|
setOmitNorms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BaseGeoShapeFieldType(BaseGeoShapeFieldType ref) {
|
protected AbstractGeometryFieldType(AbstractGeometryFieldType ref) {
|
||||||
super(ref);
|
super(ref);
|
||||||
this.orientation = ref.orientation;
|
this.orientation = ref.orientation;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +277,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!super.equals(o)) return false;
|
if (!super.equals(o)) return false;
|
||||||
BaseGeoShapeFieldType that = (BaseGeoShapeFieldType) o;
|
AbstractGeometryFieldType that = (AbstractGeometryFieldType) o;
|
||||||
return orientation == that.orientation;
|
return orientation == that.orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,16 +286,6 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
return Objects.hash(super.hashCode(), orientation);
|
return Objects.hash(super.hashCode(), orientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String typeName() {
|
|
||||||
return CONTENT_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
|
||||||
super.checkCompatibility(fieldType, conflicts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Orientation orientation() { return this.orientation; }
|
public Orientation orientation() { return this.orientation; }
|
||||||
|
|
||||||
public void setOrientation(Orientation orientation) {
|
public void setOrientation(Orientation orientation) {
|
||||||
|
@ -272,13 +302,37 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
public Query termQuery(Object value, QueryShardContext context) {
|
public Query termQuery(Object value, QueryShardContext context) {
|
||||||
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead");
|
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGeometryIndexer(Indexer<Parsed, Processed> geometryIndexer) {
|
||||||
|
this.geometryIndexer = geometryIndexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Indexer<Parsed, Processed> geometryIndexer() {
|
||||||
|
return geometryIndexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGeometryParser(Parser<Parsed> geometryParser) {
|
||||||
|
this.geometryParser = geometryParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Parser<Parsed> geometryParser() {
|
||||||
|
return geometryParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
|
||||||
|
this.geometryQueryBuilder = geometryQueryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryProcessor geometryQueryBuilder() {
|
||||||
|
return geometryQueryBuilder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Explicit<Boolean> coerce;
|
protected Explicit<Boolean> coerce;
|
||||||
protected Explicit<Boolean> ignoreMalformed;
|
protected Explicit<Boolean> ignoreMalformed;
|
||||||
protected Explicit<Boolean> ignoreZValue;
|
protected Explicit<Boolean> ignoreZValue;
|
||||||
|
|
||||||
protected BaseGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
||||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
||||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
MultiFields multiFields, CopyTo copyTo) {
|
||||||
|
@ -291,7 +345,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
@Override
|
@Override
|
||||||
protected void doMerge(Mapper mergeWith) {
|
protected void doMerge(Mapper mergeWith) {
|
||||||
super.doMerge(mergeWith);
|
super.doMerge(mergeWith);
|
||||||
BaseGeoShapeFieldMapper gsfm = (BaseGeoShapeFieldMapper)mergeWith;
|
AbstractGeometryFieldMapper gsfm = (AbstractGeometryFieldMapper)mergeWith;
|
||||||
if (gsfm.coerce.explicit()) {
|
if (gsfm.coerce.explicit()) {
|
||||||
this.coerce = gsfm.coerce;
|
this.coerce = gsfm.coerce;
|
||||||
}
|
}
|
||||||
|
@ -310,7 +364,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
@Override
|
@Override
|
||||||
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
||||||
builder.field("type", contentType());
|
builder.field("type", contentType());
|
||||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
|
||||||
if (includeDefaults || ft.orientation() != Defaults.ORIENTATION.value()) {
|
if (includeDefaults || ft.orientation() != Defaults.ORIENTATION.value()) {
|
||||||
builder.field(Names.ORIENTATION.getPreferredName(), ft.orientation());
|
builder.field(Names.ORIENTATION.getPreferredName(), ft.orientation());
|
||||||
}
|
}
|
||||||
|
@ -338,11 +392,35 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Orientation orientation() {
|
public Orientation orientation() {
|
||||||
return ((BaseGeoShapeFieldType)fieldType).orientation();
|
return ((AbstractGeometryFieldType)fieldType).orientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void indexShape(ParseContext context, Processed shape);
|
||||||
|
|
||||||
|
/** parsing logic for geometry indexing */
|
||||||
@Override
|
@Override
|
||||||
protected String contentType() {
|
public void parse(ParseContext context) throws IOException {
|
||||||
return CONTENT_TYPE;
|
AbstractGeometryFieldType fieldType = (AbstractGeometryFieldType)fieldType();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") Indexer<Parsed, Processed> geometryIndexer = fieldType.geometryIndexer();
|
||||||
|
@SuppressWarnings("unchecked") Parser<Parsed> geometryParser = fieldType.geometryParser();
|
||||||
|
try {
|
||||||
|
Processed shape = context.parseExternalValue(geometryIndexer.processedClass());
|
||||||
|
if (shape == null) {
|
||||||
|
Parsed geometry = geometryParser.parse(context.parser(), this);
|
||||||
|
if (geometry == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
shape = geometryIndexer.prepareForIndexing(geometry);
|
||||||
|
}
|
||||||
|
indexShape(context, shape);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (ignoreMalformed.value() == false) {
|
||||||
|
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
||||||
|
fieldType().typeName());
|
||||||
|
}
|
||||||
|
context.addIgnoredField(fieldType().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,7 +24,6 @@ import org.apache.lucene.geo.Line;
|
||||||
import org.apache.lucene.geo.Polygon;
|
import org.apache.lucene.geo.Polygon;
|
||||||
import org.apache.lucene.index.IndexableField;
|
import org.apache.lucene.index.IndexableField;
|
||||||
import org.elasticsearch.common.Explicit;
|
import org.elasticsearch.common.Explicit;
|
||||||
import org.elasticsearch.common.geo.GeometryIndexer;
|
|
||||||
import org.elasticsearch.common.geo.GeometryParser;
|
import org.elasticsearch.common.geo.GeometryParser;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -37,8 +36,8 @@ import org.elasticsearch.geo.geometry.MultiLine;
|
||||||
import org.elasticsearch.geo.geometry.MultiPoint;
|
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||||
import org.elasticsearch.geo.geometry.MultiPolygon;
|
import org.elasticsearch.geo.geometry.MultiPolygon;
|
||||||
import org.elasticsearch.geo.geometry.Point;
|
import org.elasticsearch.geo.geometry.Point;
|
||||||
|
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -62,9 +61,10 @@ import java.util.Arrays;
|
||||||
* <p>
|
* <p>
|
||||||
* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))
|
* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))
|
||||||
*/
|
*/
|
||||||
public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
public class GeoShapeFieldMapper extends AbstractGeometryFieldMapper<Geometry, Geometry> {
|
||||||
|
public static final String CONTENT_TYPE = "geo_shape";
|
||||||
|
|
||||||
public static class Builder extends BaseGeoShapeFieldMapper.Builder<BaseGeoShapeFieldMapper.Builder, GeoShapeFieldMapper> {
|
public static class Builder extends AbstractGeometryFieldMapper.Builder<AbstractGeometryFieldMapper.Builder, GeoShapeFieldMapper> {
|
||||||
public Builder(String name) {
|
public Builder(String name) {
|
||||||
super (name, new GeoShapeFieldType(), new GeoShapeFieldType());
|
super (name, new GeoShapeFieldType(), new GeoShapeFieldType());
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,21 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
return new GeoShapeFieldMapper(name, fieldType, defaultFieldType, ignoreMalformed(context), coerce(context),
|
return new GeoShapeFieldMapper(name, fieldType, defaultFieldType, ignoreMalformed(context), coerce(context),
|
||||||
ignoreZValue(), context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
|
ignoreZValue(), context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupFieldType(BuilderContext context) {
|
||||||
|
super.setupFieldType(context);
|
||||||
|
|
||||||
|
GeometryParser geometryParser = new GeometryParser(orientation == ShapeBuilder.Orientation.RIGHT, coerce(context).value(),
|
||||||
|
ignoreZValue().value());
|
||||||
|
|
||||||
|
((GeoShapeFieldType)fieldType()).setGeometryIndexer(new GeoShapeIndexer(orientation == ShapeBuilder.Orientation.RIGHT));
|
||||||
|
((GeoShapeFieldType)fieldType()).setGeometryParser( (parser, mapper) -> geometryParser.parse(parser));
|
||||||
|
((GeoShapeFieldType)fieldType()).setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class GeoShapeFieldType extends BaseGeoShapeFieldType {
|
public static final class GeoShapeFieldType extends AbstractGeometryFieldType<Geometry, Geometry> {
|
||||||
public GeoShapeFieldType() {
|
public GeoShapeFieldType() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -90,10 +102,17 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
public GeoShapeFieldType clone() {
|
public GeoShapeFieldType clone() {
|
||||||
return new GeoShapeFieldType(this);
|
return new GeoShapeFieldType(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeName() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final GeometryParser geometryParser;
|
@Override
|
||||||
private final GeometryIndexer geometryIndexer;
|
protected Indexer<Geometry, Geometry> geometryIndexer() {
|
||||||
|
return new GeoShapeIndexer(orientation == ShapeBuilder.Orientation.RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
||||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
||||||
|
@ -101,8 +120,6 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
MultiFields multiFields, CopyTo copyTo) {
|
||||||
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, ignoreZValue, indexSettings,
|
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, ignoreZValue, indexSettings,
|
||||||
multiFields, copyTo);
|
multiFields, copyTo);
|
||||||
geometryParser = new GeometryParser(orientation() == ShapeBuilder.Orientation.RIGHT, coerce().value(), ignoreZValue.value());
|
|
||||||
geometryIndexer = new GeometryIndexer(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -110,35 +127,9 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
return (GeoShapeFieldType) super.fieldType();
|
return (GeoShapeFieldType) super.fieldType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** parsing logic for {@link LatLonShape} indexing */
|
|
||||||
@Override
|
@Override
|
||||||
public void parse(ParseContext context) throws IOException {
|
protected void indexShape(ParseContext context, Geometry luceneShape) {
|
||||||
try {
|
luceneShape.visit(new LuceneGeometryIndexer(context));
|
||||||
|
|
||||||
Object shape = context.parseExternalValue(Object.class);
|
|
||||||
if (shape == null) {
|
|
||||||
Geometry geometry = geometryParser.parse(context.parser());
|
|
||||||
if (geometry == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
shape = geometryIndexer.prepareForIndexing(geometry);
|
|
||||||
}
|
|
||||||
indexShape(context, shape);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (ignoreMalformed.value() == false) {
|
|
||||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
|
||||||
fieldType().typeName());
|
|
||||||
}
|
|
||||||
context.addIgnoredField(fieldType().name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void indexShape(ParseContext context, Object luceneShape) {
|
|
||||||
if (luceneShape instanceof Geometry) {
|
|
||||||
((Geometry) luceneShape).visit(new LuceneGeometryIndexer(context));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("invalid shape type found [" + luceneShape.getClass() + "] while indexing shape");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LuceneGeometryIndexer implements GeometryVisitor<Void, RuntimeException> {
|
private class LuceneGeometryIndexer implements GeometryVisitor<Void, RuntimeException> {
|
||||||
|
@ -232,4 +223,9 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
context.doc().add(f);
|
context.doc().add(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String contentType() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.elasticsearch.common.geo;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.geo.geometry.Circle;
|
import org.elasticsearch.geo.geometry.Circle;
|
||||||
|
@ -52,7 +52,7 @@ import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
|
||||||
/**
|
/**
|
||||||
* Utility class that converts geometries into Lucene-compatible form
|
* Utility class that converts geometries into Lucene-compatible form
|
||||||
*/
|
*/
|
||||||
public final class GeometryIndexer {
|
public final class GeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<Geometry, Geometry> {
|
||||||
|
|
||||||
private static final double DATELINE = 180;
|
private static final double DATELINE = 180;
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ public final class GeometryIndexer {
|
||||||
|
|
||||||
private final boolean orientation;
|
private final boolean orientation;
|
||||||
|
|
||||||
public GeometryIndexer(boolean orientation) {
|
public GeoShapeIndexer(boolean orientation) {
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +176,11 @@ public final class GeometryIndexer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Geometry> processedClass() {
|
||||||
|
return Geometry.class;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the intersection of a line segment and a vertical dateline.
|
* Calculate the intersection of a line segment and a vertical dateline.
|
||||||
*
|
*
|
||||||
|
@ -666,10 +671,8 @@ public final class GeometryIndexer {
|
||||||
* Array of edges will be ordered asc by the y-coordinate of the
|
* Array of edges will be ordered asc by the y-coordinate of the
|
||||||
* intersections of edges.
|
* intersections of edges.
|
||||||
*
|
*
|
||||||
* @param dateline
|
* @param dateline x-coordinate of the dateline
|
||||||
* x-coordinate of the dateline
|
* @param edges set of edges that may intersect with the dateline
|
||||||
* @param edges
|
|
||||||
* set of edges that may intersect with the dateline
|
|
||||||
* @return number of intersecting edges
|
* @return number of intersecting edges
|
||||||
*/
|
*/
|
||||||
protected static int intersections(double dateline, Edge[] edges) {
|
protected static int intersections(double dateline, Edge[] edges) {
|
||||||
|
@ -698,9 +701,9 @@ public final class GeometryIndexer {
|
||||||
for (int i = 0; i < edges.length; i++) {
|
for (int i = 0; i < edges.length; i++) {
|
||||||
if (edges[i].component >= 0) {
|
if (edges[i].component >= 0) {
|
||||||
double[] partitionPoint = new double[3];
|
double[] partitionPoint = new double[3];
|
||||||
int length = component(edges[i], -(components.size()+numHoles+1), mainEdges, partitionPoint);
|
int length = component(edges[i], -(components.size() + numHoles + 1), mainEdges, partitionPoint);
|
||||||
List<Point[]> component = new ArrayList<>();
|
List<Point[]> component = new ArrayList<>();
|
||||||
component.add(coordinates(edges[i], new Point[length+1], partitionPoint));
|
component.add(coordinates(edges[i], new Point[length + 1], partitionPoint));
|
||||||
components.add(component);
|
components.add(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -789,8 +792,8 @@ public final class GeometryIndexer {
|
||||||
private static int component(final Edge edge, final int id, final ArrayList<Edge> edges, double[] partitionPoint) {
|
private static int component(final Edge edge, final int id, final ArrayList<Edge> edges, double[] partitionPoint) {
|
||||||
// find a coordinate that is not part of the dateline
|
// find a coordinate that is not part of the dateline
|
||||||
Edge any = edge;
|
Edge any = edge;
|
||||||
while(any.coordinate.getLon() == +DATELINE || any.coordinate.getLon() == -DATELINE) {
|
while (any.coordinate.getLon() == +DATELINE || any.coordinate.getLon() == -DATELINE) {
|
||||||
if((any = any.next) == edge) {
|
if ((any = any.next) == edge) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -847,13 +850,14 @@ public final class GeometryIndexer {
|
||||||
prev = current;
|
prev = current;
|
||||||
}
|
}
|
||||||
length++;
|
length++;
|
||||||
} while(connectedComponents == 0 && (current = current.next) != edge);
|
} while (connectedComponents == 0 && (current = current.next) != edge);
|
||||||
|
|
||||||
return (splitIndex != 1) ? length-splitIndex: length;
|
return (splitIndex != 1) ? length - splitIndex : length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute all coordinates of a component
|
* Compute all coordinates of a component
|
||||||
|
*
|
||||||
* @param component an arbitrary edge of the component
|
* @param component an arbitrary edge of the component
|
||||||
* @param coordinates Array of coordinates to write the result to
|
* @param coordinates Array of coordinates to write the result to
|
||||||
* @return the coordinates parameter
|
* @return the coordinates parameter
|
||||||
|
@ -885,7 +889,7 @@ public final class GeometryIndexer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Polygon buildPolygon(List<Point[]> polygon) {
|
private static Polygon buildPolygon(List<Point[]> polygon) {
|
||||||
List<org.elasticsearch.geo.geometry.LinearRing> holes;
|
List<LinearRing> holes;
|
||||||
Point[] shell = polygon.get(0);
|
Point[] shell = polygon.get(0);
|
||||||
if (polygon.size() > 1) {
|
if (polygon.size() > 1) {
|
||||||
holes = new ArrayList<>(polygon.size() - 1);
|
holes = new ArrayList<>(polygon.size() - 1);
|
||||||
|
@ -899,7 +903,7 @@ public final class GeometryIndexer {
|
||||||
x[c] = normalizeLon(coords[c].getLon());
|
x[c] = normalizeLon(coords[c].getLon());
|
||||||
y[c] = normalizeLat(coords[c].getLat());
|
y[c] = normalizeLat(coords[c].getLat());
|
||||||
}
|
}
|
||||||
holes.add(new org.elasticsearch.geo.geometry.LinearRing(y, x));
|
holes.add(new LinearRing(y, x));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
holes = Collections.emptyList();
|
holes = Collections.emptyList();
|
||||||
|
@ -925,10 +929,11 @@ public final class GeometryIndexer {
|
||||||
|
|
||||||
for (int i = 0; i < numHoles; i++) {
|
for (int i = 0; i < numHoles; i++) {
|
||||||
double[] partitionPoint = new double[3];
|
double[] partitionPoint = new double[3];
|
||||||
int length = component(holes[i], -(i+1), null, partitionPoint); // mark as visited by inverting the sign
|
int length = component(holes[i], -(i + 1), null, partitionPoint); // mark as visited by inverting the sign
|
||||||
points[i] = coordinates(holes[i], new Point[length+1], partitionPoint);
|
points[i] = coordinates(holes[i], new Point[length + 1], partitionPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -46,6 +46,7 @@ import org.elasticsearch.common.unit.DistanceUnit;
|
||||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||||
|
import org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor;
|
||||||
import org.locationtech.spatial4j.shape.Point;
|
import org.locationtech.spatial4j.shape.Point;
|
||||||
import org.locationtech.spatial4j.shape.Shape;
|
import org.locationtech.spatial4j.shape.Shape;
|
||||||
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
|
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
|
||||||
|
@ -79,7 +80,7 @@ import java.util.Objects;
|
||||||
* @deprecated use {@link GeoShapeFieldMapper}
|
* @deprecated use {@link GeoShapeFieldMapper}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
public class LegacyGeoShapeFieldMapper extends AbstractGeometryFieldMapper<ShapeBuilder<?, ?, ?>, Shape> {
|
||||||
|
|
||||||
public static final String CONTENT_TYPE = "geo_shape";
|
public static final String CONTENT_TYPE = "geo_shape";
|
||||||
|
|
||||||
|
@ -183,7 +184,8 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
private static final Logger logger = LogManager.getLogger(LegacyGeoShapeFieldMapper.class);
|
private static final Logger logger = LogManager.getLogger(LegacyGeoShapeFieldMapper.class);
|
||||||
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(logger);
|
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(logger);
|
||||||
|
|
||||||
public static class Builder extends BaseGeoShapeFieldMapper.Builder<BaseGeoShapeFieldMapper.Builder, LegacyGeoShapeFieldMapper> {
|
public static class Builder extends AbstractGeometryFieldMapper.Builder<AbstractGeometryFieldMapper.Builder,
|
||||||
|
LegacyGeoShapeFieldMapper> {
|
||||||
|
|
||||||
DeprecatedParameters deprecatedParameters;
|
DeprecatedParameters deprecatedParameters;
|
||||||
|
|
||||||
|
@ -270,6 +272,10 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
protected void setupFieldType(BuilderContext context) {
|
protected void setupFieldType(BuilderContext context) {
|
||||||
super.setupFieldType(context);
|
super.setupFieldType(context);
|
||||||
|
|
||||||
|
fieldType().setGeometryIndexer(new LegacyGeoShapeIndexer());
|
||||||
|
fieldType().setGeometryParser(ShapeParser::parse);
|
||||||
|
fieldType().setGeometryQueryBuilder(new LegacyGeoShapeQueryProcessor(fieldType()));
|
||||||
|
|
||||||
// field mapper handles this at build time
|
// field mapper handles this at build time
|
||||||
// but prefix tree strategies require a name, so throw a similar exception
|
// but prefix tree strategies require a name, so throw a similar exception
|
||||||
if (fieldType().name().isEmpty()) {
|
if (fieldType().name().isEmpty()) {
|
||||||
|
@ -299,7 +305,7 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class GeoShapeFieldType extends BaseGeoShapeFieldType {
|
public static final class GeoShapeFieldType extends AbstractGeometryFieldType {
|
||||||
|
|
||||||
private String tree = DeprecatedParameters.Defaults.TREE;
|
private String tree = DeprecatedParameters.Defaults.TREE;
|
||||||
private SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
|
private SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
|
||||||
|
@ -357,6 +363,11 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
defaultDistanceErrorPct);
|
defaultDistanceErrorPct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeName() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
||||||
super.checkCompatibility(fieldType, conflicts);
|
super.checkCompatibility(fieldType, conflicts);
|
||||||
|
@ -479,23 +490,14 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parse(ParseContext context) throws IOException {
|
protected void indexShape(ParseContext context, Shape shape) {
|
||||||
try {
|
|
||||||
Shape shape = context.parseExternalValue(Shape.class);
|
|
||||||
if (shape == null) {
|
|
||||||
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
|
|
||||||
if (shapeBuilder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
shape = shapeBuilder.buildS4J();
|
|
||||||
}
|
|
||||||
if (fieldType().pointsOnly() == true) {
|
if (fieldType().pointsOnly() == true) {
|
||||||
// index configured for pointsOnly
|
// index configured for pointsOnly
|
||||||
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
||||||
// MULTIPOINT data: index each point separately
|
// MULTIPOINT data: index each point separately
|
||||||
List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
@SuppressWarnings("unchecked") List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
||||||
for (Shape s : shapes) {
|
for (Shape s : shapes) {
|
||||||
indexShape(context, s);
|
doIndexShape(context, s);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (shape instanceof Point == false) {
|
} else if (shape instanceof Point == false) {
|
||||||
|
@ -504,17 +506,10 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
+ " was found");
|
+ " was found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
indexShape(context, shape);
|
doIndexShape(context, shape);
|
||||||
} catch (Exception e) {
|
|
||||||
if (ignoreMalformed.value() == false) {
|
|
||||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
|
||||||
fieldType().typeName());
|
|
||||||
}
|
|
||||||
context.addIgnoredField(fieldType.name());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexShape(ParseContext context, Shape shape) {
|
private void doIndexShape(ParseContext context, Shape shape) {
|
||||||
List<IndexableField> fields = new ArrayList<>(Arrays.asList(fieldType().defaultPrefixTreeStrategy().createIndexableFields(shape)));
|
List<IndexableField> fields = new ArrayList<>(Arrays.asList(fieldType().defaultPrefixTreeStrategy().createIndexableFields(shape)));
|
||||||
createFieldNamesField(context, fields);
|
createFieldNamesField(context, fields);
|
||||||
for (IndexableField field : fields) {
|
for (IndexableField field : fields) {
|
||||||
|
@ -574,4 +569,9 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String contentType() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
|
import org.locationtech.spatial4j.shape.Shape;
|
||||||
|
|
||||||
|
public class LegacyGeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<ShapeBuilder<?, ?, ?>, Shape> {
|
||||||
|
@Override
|
||||||
|
public Shape prepareForIndexing(ShapeBuilder<?, ?, ?> shapeBuilder) {
|
||||||
|
return shapeBuilder.buildS4J();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Shape> processedClass() {
|
||||||
|
return Shape.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,33 +20,13 @@
|
||||||
package org.elasticsearch.index.query;
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.lucene.document.LatLonShape;
|
|
||||||
import org.apache.lucene.geo.Line;
|
|
||||||
import org.apache.lucene.geo.Polygon;
|
|
||||||
import org.apache.lucene.search.BooleanClause;
|
|
||||||
import org.apache.lucene.search.BooleanQuery;
|
|
||||||
import org.apache.lucene.search.ConstantScoreQuery;
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
import org.apache.lucene.search.MatchNoDocsQuery;
|
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.query.SpatialArgs;
|
|
||||||
import org.apache.lucene.spatial.query.SpatialOperation;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.geo.GeoShapeType;
|
|
||||||
import org.elasticsearch.common.geo.GeometryIndexer;
|
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
|
||||||
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.MultiPolygonBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
@ -54,32 +34,17 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
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.geo.geometry.Circle;
|
|
||||||
import org.elasticsearch.geo.geometry.Geometry;
|
import org.elasticsearch.geo.geometry.Geometry;
|
||||||
import org.elasticsearch.geo.geometry.GeometryCollection;
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
import org.elasticsearch.geo.geometry.GeometryVisitor;
|
|
||||||
import org.elasticsearch.geo.geometry.LinearRing;
|
|
||||||
import org.elasticsearch.geo.geometry.MultiLine;
|
|
||||||
import org.elasticsearch.geo.geometry.MultiPoint;
|
|
||||||
import org.elasticsearch.geo.geometry.MultiPolygon;
|
|
||||||
import org.elasticsearch.geo.geometry.Point;
|
|
||||||
import org.elasticsearch.geo.geometry.Rectangle;
|
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
|
||||||
import org.locationtech.spatial4j.shape.Shape;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static org.elasticsearch.index.mapper.GeoShapeFieldMapper.toLucenePolygon;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derived {@link AbstractGeometryQueryBuilder} that builds a lat, lon GeoShape Query
|
* Derived {@link AbstractGeometryQueryBuilder} that builds a lat, lon GeoShape Query
|
||||||
*/
|
*/
|
||||||
|
@ -217,12 +182,12 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List validContentTypes() {
|
protected List validContentTypes() {
|
||||||
return Arrays.asList(BaseGeoShapeFieldMapper.CONTENT_TYPE);
|
return Arrays.asList(GeoShapeFieldMapper.CONTENT_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String queryFieldType() {
|
public String queryFieldType() {
|
||||||
return BaseGeoShapeFieldMapper.CONTENT_TYPE;
|
return GeoShapeFieldMapper.CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -245,260 +210,13 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
|
public Query buildShapeQuery(QueryShardContext context, MappedFieldType fieldType) {
|
||||||
if (fieldType.typeName().equals(BaseGeoShapeFieldMapper.CONTENT_TYPE) == false) {
|
if (fieldType.typeName().equals(GeoShapeFieldMapper.CONTENT_TYPE) == false) {
|
||||||
throw new QueryShardException(context,
|
throw new QueryShardException(context,
|
||||||
"Field [" + fieldName + "] is not of type [" + queryFieldType() + "] but of type [" + fieldType.typeName() + "]");
|
"Field [" + fieldName + "] is not of type [" + queryFieldType() + "] but of type [" + fieldType.typeName() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
final BaseGeoShapeFieldMapper.BaseGeoShapeFieldType ft = (BaseGeoShapeFieldMapper.BaseGeoShapeFieldType) fieldType;
|
final AbstractGeometryFieldMapper.AbstractGeometryFieldType ft = (AbstractGeometryFieldMapper.AbstractGeometryFieldType) fieldType;
|
||||||
Query query;
|
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context));
|
||||||
if (strategy != null || ft instanceof LegacyGeoShapeFieldMapper.GeoShapeFieldType) {
|
|
||||||
LegacyGeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (LegacyGeoShapeFieldMapper.GeoShapeFieldType) ft;
|
|
||||||
SpatialStrategy spatialStrategy = shapeFieldType.strategy();
|
|
||||||
if (this.strategy != null) {
|
|
||||||
spatialStrategy = this.strategy;
|
|
||||||
}
|
|
||||||
PrefixTreeStrategy prefixTreeStrategy = shapeFieldType.resolvePrefixTreeStrategy(spatialStrategy);
|
|
||||||
if (prefixTreeStrategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
|
|
||||||
// this strategy doesn't support disjoint anymore: but it did
|
|
||||||
// before, including creating lucene fieldcache (!)
|
|
||||||
// in this case, execute disjoint as exists && !intersects
|
|
||||||
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
|
||||||
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
|
||||||
Query intersects = prefixTreeStrategy.makeQuery(getArgs(shape, ShapeRelation.INTERSECTS));
|
|
||||||
bool.add(exists, BooleanClause.Occur.MUST);
|
|
||||||
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
|
||||||
query = new ConstantScoreQuery(bool.build());
|
|
||||||
} else {
|
|
||||||
query = new ConstantScoreQuery(prefixTreeStrategy.makeQuery(getArgs(shape, relation)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query = new ConstantScoreQuery(getVectorQuery(context, shape));
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SpatialArgs getArgs(Geometry shape, ShapeRelation relation) {
|
|
||||||
switch (relation) {
|
|
||||||
case DISJOINT:
|
|
||||||
return new SpatialArgs(SpatialOperation.IsDisjointTo, buildS4J(shape));
|
|
||||||
case INTERSECTS:
|
|
||||||
return new SpatialArgs(SpatialOperation.Intersects, buildS4J(shape));
|
|
||||||
case WITHIN:
|
|
||||||
return new SpatialArgs(SpatialOperation.IsWithin, buildS4J(shape));
|
|
||||||
case CONTAINS:
|
|
||||||
return new SpatialArgs(SpatialOperation.Contains, buildS4J(shape));
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("invalid relation [" + relation + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds JTS shape from a geometry
|
|
||||||
*
|
|
||||||
* This method is needed to handle legacy indices and will be removed when we no longer need to build JTS shapes
|
|
||||||
*/
|
|
||||||
private static Shape buildS4J(Geometry geometry) {
|
|
||||||
return geometryToShapeBuilder(geometry).buildS4J();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Query getVectorQuery(QueryShardContext context, Geometry queryShape) {
|
|
||||||
// CONTAINS queries are not yet supported by VECTOR strategy
|
|
||||||
if (relation == ShapeRelation.CONTAINS) {
|
|
||||||
throw new QueryShardException(context,
|
|
||||||
ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]");
|
|
||||||
}
|
|
||||||
// wrap geoQuery as a ConstantScoreQuery
|
|
||||||
return getVectorQueryFromShape(context, queryShape);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Query getVectorQueryFromShape(QueryShardContext context, Geometry queryShape) {
|
|
||||||
// TODO: Move this to QueryShardContext
|
|
||||||
GeometryIndexer geometryIndexer = new GeometryIndexer(true);
|
|
||||||
|
|
||||||
Geometry processedShape = geometryIndexer.prepareForIndexing(queryShape);
|
|
||||||
|
|
||||||
if (processedShape == null) {
|
|
||||||
return new MatchNoDocsQuery();
|
|
||||||
}
|
|
||||||
return queryShape.visit(new ShapeVisitor(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ShapeBuilder<?, ?, ?> geometryToShapeBuilder(Geometry geometry) {
|
|
||||||
ShapeBuilder<?, ?, ?> shapeBuilder = geometry.visit(new GeometryVisitor<ShapeBuilder<?, ?, ?>, RuntimeException>() {
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(Circle circle) {
|
|
||||||
throw new UnsupportedOperationException("circle is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(GeometryCollection<?> collection) {
|
|
||||||
GeometryCollectionBuilder shapes = new GeometryCollectionBuilder();
|
|
||||||
for (Geometry geometry : collection) {
|
|
||||||
shapes.shape(geometry.visit(this));
|
|
||||||
}
|
|
||||||
return shapes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Line line) {
|
|
||||||
List<Coordinate> coordinates = new ArrayList<>();
|
|
||||||
for (int i = 0; i < line.length(); i++) {
|
|
||||||
coordinates.add(new Coordinate(line.getLon(i), line.getLat(i), line.getAlt(i)));
|
|
||||||
}
|
|
||||||
return new LineStringBuilder(coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(LinearRing ring) {
|
|
||||||
throw new UnsupportedOperationException("circle is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(MultiLine multiLine) {
|
|
||||||
MultiLineStringBuilder lines = new MultiLineStringBuilder();
|
|
||||||
for (int i = 0; i < multiLine.size(); i++) {
|
|
||||||
lines.linestring((LineStringBuilder) visit(multiLine.get(i)));
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(MultiPoint multiPoint) {
|
|
||||||
List<Coordinate> coordinates = new ArrayList<>();
|
|
||||||
for (int i = 0; i < multiPoint.size(); i++) {
|
|
||||||
Point p = multiPoint.get(i);
|
|
||||||
coordinates.add(new Coordinate(p.getLon(), p.getLat(), p.getAlt()));
|
|
||||||
}
|
|
||||||
return new MultiPointBuilder(coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(MultiPolygon multiPolygon) {
|
|
||||||
MultiPolygonBuilder polygons = new MultiPolygonBuilder();
|
|
||||||
for (int i = 0; i < multiPolygon.size(); i++) {
|
|
||||||
polygons.polygon((PolygonBuilder) visit(multiPolygon.get(i)));
|
|
||||||
}
|
|
||||||
return polygons;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(Point point) {
|
|
||||||
return new PointBuilder(point.getLon(), point.getLat());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Polygon polygon) {
|
|
||||||
PolygonBuilder polygonBuilder =
|
|
||||||
new PolygonBuilder((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getPolygon()),
|
|
||||||
ShapeBuilder.Orientation.RIGHT, false);
|
|
||||||
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
|
|
||||||
polygonBuilder.hole((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getHole(i)));
|
|
||||||
}
|
|
||||||
return polygonBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ShapeBuilder<?, ?, ?> visit(Rectangle rectangle) {
|
|
||||||
return new EnvelopeBuilder(new Coordinate(rectangle.getMinLon(), rectangle.getMaxLat()),
|
|
||||||
new Coordinate(rectangle.getMaxLon(), rectangle.getMinLat()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return shapeBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ShapeVisitor implements GeometryVisitor<Query, RuntimeException> {
|
|
||||||
QueryShardContext context;
|
|
||||||
MappedFieldType fieldType;
|
|
||||||
|
|
||||||
ShapeVisitor(QueryShardContext context) {
|
|
||||||
this.context = context;
|
|
||||||
this.fieldType = context.fieldMapper(fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(Circle circle) {
|
|
||||||
throw new QueryShardException(context, "Field [" + fieldName + "] found and unknown shape Circle");
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
for (Geometry shape : collection) {
|
|
||||||
if (shape instanceof MultiPoint) {
|
|
||||||
// Flatten multipoints
|
|
||||||
visit(bqb, (GeometryCollection<?>) shape);
|
|
||||||
} else {
|
|
||||||
bqb.add(shape.visit(this), BooleanClause.Occur.SHOULD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(org.elasticsearch.geo.geometry.Line line) {
|
|
||||||
validateIsGeoShapeFieldType();
|
|
||||||
return LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), new Line(line.getLats(), line.getLons()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(LinearRing ring) {
|
|
||||||
throw new QueryShardException(context, "Field [" + fieldName + "] found and unsupported shape LinearRing");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(MultiLine multiLine) {
|
|
||||||
validateIsGeoShapeFieldType();
|
|
||||||
Line[] lines = new Line[multiLine.size()];
|
|
||||||
for (int i=0; i<multiLine.size(); i++) {
|
|
||||||
lines[i] = new Line(multiLine.get(i).getLats(), multiLine.get(i).getLons());
|
|
||||||
}
|
|
||||||
return LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(MultiPoint multiPoint) {
|
|
||||||
throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + GeoShapeType.MULTIPOINT +
|
|
||||||
" queries");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(MultiPolygon multiPolygon) {
|
|
||||||
Polygon[] polygons = new Polygon[multiPolygon.size()];
|
|
||||||
for (int i=0; i<multiPolygon.size(); i++) {
|
|
||||||
polygons[i] = toLucenePolygon(multiPolygon.get(i));
|
|
||||||
}
|
|
||||||
return LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), polygons);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(Point point) {
|
|
||||||
validateIsGeoShapeFieldType();
|
|
||||||
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(),
|
|
||||||
point.getLat(), point.getLat(), point.getLon(), point.getLon());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(org.elasticsearch.geo.geometry.Polygon polygon) {
|
|
||||||
return LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), toLucenePolygon(polygon));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query visit(org.elasticsearch.geo.geometry.Rectangle r) {
|
|
||||||
return LatLonShape.newBoxQuery(fieldName(), relation.getLuceneRelation(),
|
|
||||||
r.getMinLat(), r.getMaxLat(), r.getMinLon(), r.getMaxLon());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateIsGeoShapeFieldType() {
|
|
||||||
if (fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType == false) {
|
|
||||||
throw new QueryShardException(context, "Expected " + GeoShapeFieldMapper.CONTENT_TYPE
|
|
||||||
+ " field type for Field [" + fieldName + "] but found " + fieldType.typeName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
|
import org.apache.lucene.search.BooleanClause;
|
||||||
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialArgs;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||||
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
|
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
||||||
|
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.MultiPolygonBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
|
import org.elasticsearch.geo.geometry.Circle;
|
||||||
|
import org.elasticsearch.geo.geometry.Geometry;
|
||||||
|
import org.elasticsearch.geo.geometry.GeometryCollection;
|
||||||
|
import org.elasticsearch.geo.geometry.GeometryVisitor;
|
||||||
|
import org.elasticsearch.geo.geometry.LinearRing;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiLine;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiPolygon;
|
||||||
|
import org.elasticsearch.geo.geometry.Point;
|
||||||
|
import org.elasticsearch.geo.geometry.Rectangle;
|
||||||
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||||
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
import org.locationtech.spatial4j.shape.Shape;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LegacyGeoShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryProcessor {
|
||||||
|
|
||||||
|
private AbstractGeometryFieldMapper.AbstractGeometryFieldType ft;
|
||||||
|
|
||||||
|
public LegacyGeoShapeQueryProcessor(AbstractGeometryFieldMapper.AbstractGeometryFieldType ft) {
|
||||||
|
this.ft = ft;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, QueryShardContext context) {
|
||||||
|
LegacyGeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (LegacyGeoShapeFieldMapper.GeoShapeFieldType) ft;
|
||||||
|
SpatialStrategy spatialStrategy = shapeFieldType.strategy();
|
||||||
|
if (strategy != null) {
|
||||||
|
spatialStrategy = strategy;
|
||||||
|
}
|
||||||
|
PrefixTreeStrategy prefixTreeStrategy = shapeFieldType.resolvePrefixTreeStrategy(spatialStrategy);
|
||||||
|
if (prefixTreeStrategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
|
||||||
|
// this strategy doesn't support disjoint anymore: but it did
|
||||||
|
// before, including creating lucene fieldcache (!)
|
||||||
|
// in this case, execute disjoint as exists && !intersects
|
||||||
|
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
||||||
|
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
||||||
|
Query intersects = prefixTreeStrategy.makeQuery(getArgs(shape, ShapeRelation.INTERSECTS));
|
||||||
|
bool.add(exists, BooleanClause.Occur.MUST);
|
||||||
|
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
||||||
|
return bool.build();
|
||||||
|
} else {
|
||||||
|
return prefixTreeStrategy.makeQuery(getArgs(shape, relation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpatialArgs getArgs(Geometry shape, ShapeRelation relation) {
|
||||||
|
switch (relation) {
|
||||||
|
case DISJOINT:
|
||||||
|
return new SpatialArgs(SpatialOperation.IsDisjointTo, buildS4J(shape));
|
||||||
|
case INTERSECTS:
|
||||||
|
return new SpatialArgs(SpatialOperation.Intersects, buildS4J(shape));
|
||||||
|
case WITHIN:
|
||||||
|
return new SpatialArgs(SpatialOperation.IsWithin, buildS4J(shape));
|
||||||
|
case CONTAINS:
|
||||||
|
return new SpatialArgs(SpatialOperation.Contains, buildS4J(shape));
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("invalid relation [" + relation + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds JTS shape from a geometry
|
||||||
|
* <p>
|
||||||
|
* This method is needed to handle legacy indices and will be removed when we no longer need to build JTS shapes
|
||||||
|
*/
|
||||||
|
private static Shape buildS4J(Geometry geometry) {
|
||||||
|
return geometryToShapeBuilder(geometry).buildS4J();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ShapeBuilder<?, ?, ?> geometryToShapeBuilder(Geometry geometry) {
|
||||||
|
ShapeBuilder<?, ?, ?> shapeBuilder = geometry.visit(new GeometryVisitor<ShapeBuilder<?, ?, ?>, RuntimeException>() {
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(Circle circle) {
|
||||||
|
throw new UnsupportedOperationException("circle is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(GeometryCollection<?> collection) {
|
||||||
|
GeometryCollectionBuilder shapes = new GeometryCollectionBuilder();
|
||||||
|
for (Geometry geometry : collection) {
|
||||||
|
shapes.shape(geometry.visit(this));
|
||||||
|
}
|
||||||
|
return shapes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Line line) {
|
||||||
|
List<Coordinate> coordinates = new ArrayList<>();
|
||||||
|
for (int i = 0; i < line.length(); i++) {
|
||||||
|
coordinates.add(new Coordinate(line.getLon(i), line.getLat(i), line.getAlt(i)));
|
||||||
|
}
|
||||||
|
return new LineStringBuilder(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(LinearRing ring) {
|
||||||
|
throw new UnsupportedOperationException("circle is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(MultiLine multiLine) {
|
||||||
|
MultiLineStringBuilder lines = new MultiLineStringBuilder();
|
||||||
|
for (int i = 0; i < multiLine.size(); i++) {
|
||||||
|
lines.linestring((LineStringBuilder) visit(multiLine.get(i)));
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(MultiPoint multiPoint) {
|
||||||
|
List<Coordinate> coordinates = new ArrayList<>();
|
||||||
|
for (int i = 0; i < multiPoint.size(); i++) {
|
||||||
|
Point p = multiPoint.get(i);
|
||||||
|
coordinates.add(new Coordinate(p.getLon(), p.getLat(), p.getAlt()));
|
||||||
|
}
|
||||||
|
return new MultiPointBuilder(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(MultiPolygon multiPolygon) {
|
||||||
|
MultiPolygonBuilder polygons = new MultiPolygonBuilder();
|
||||||
|
for (int i = 0; i < multiPolygon.size(); i++) {
|
||||||
|
polygons.polygon((PolygonBuilder) visit(multiPolygon.get(i)));
|
||||||
|
}
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(Point point) {
|
||||||
|
return new PointBuilder(point.getLon(), point.getLat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Polygon polygon) {
|
||||||
|
PolygonBuilder polygonBuilder =
|
||||||
|
new PolygonBuilder((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getPolygon()),
|
||||||
|
ShapeBuilder.Orientation.RIGHT, false);
|
||||||
|
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
|
||||||
|
polygonBuilder.hole((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getHole(i)));
|
||||||
|
}
|
||||||
|
return polygonBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShapeBuilder<?, ?, ?> visit(Rectangle rectangle) {
|
||||||
|
return new EnvelopeBuilder(new Coordinate(rectangle.getMinLon(), rectangle.getMaxLat()),
|
||||||
|
new Coordinate(rectangle.getMaxLon(), rectangle.getMinLat()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return shapeBuilder;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
|
import org.apache.lucene.document.LatLonShape;
|
||||||
|
import org.apache.lucene.geo.Line;
|
||||||
|
import org.apache.lucene.geo.Polygon;
|
||||||
|
import org.apache.lucene.search.BooleanClause;
|
||||||
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
import org.apache.lucene.search.MatchNoDocsQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.elasticsearch.common.geo.GeoShapeType;
|
||||||
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
|
import org.elasticsearch.geo.geometry.Circle;
|
||||||
|
import org.elasticsearch.geo.geometry.Geometry;
|
||||||
|
import org.elasticsearch.geo.geometry.GeometryCollection;
|
||||||
|
import org.elasticsearch.geo.geometry.GeometryVisitor;
|
||||||
|
import org.elasticsearch.geo.geometry.LinearRing;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiLine;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||||
|
import org.elasticsearch.geo.geometry.MultiPolygon;
|
||||||
|
import org.elasticsearch.geo.geometry.Point;
|
||||||
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
|
|
||||||
|
import static org.elasticsearch.index.mapper.GeoShapeFieldMapper.toLucenePolygon;
|
||||||
|
|
||||||
|
public class VectorGeoShapeQueryProcessor implements AbstractGeometryFieldMapper.QueryProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, QueryShardContext context) {
|
||||||
|
// CONTAINS queries are not yet supported by VECTOR strategy
|
||||||
|
if (relation == ShapeRelation.CONTAINS) {
|
||||||
|
throw new QueryShardException(context,
|
||||||
|
ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]");
|
||||||
|
}
|
||||||
|
// wrap geoQuery as a ConstantScoreQuery
|
||||||
|
return getVectorQueryFromShape(shape, fieldName, relation, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Query getVectorQueryFromShape(Geometry queryShape, String fieldName, ShapeRelation relation, QueryShardContext context) {
|
||||||
|
GeoShapeIndexer geometryIndexer = new GeoShapeIndexer(true);
|
||||||
|
|
||||||
|
Geometry processedShape = geometryIndexer.prepareForIndexing(queryShape);
|
||||||
|
|
||||||
|
if (processedShape == null) {
|
||||||
|
return new MatchNoDocsQuery();
|
||||||
|
}
|
||||||
|
return queryShape.visit(new ShapeVisitor(context, fieldName, relation));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throw new QueryShardException(context, "Field [" + fieldName + "] found and unknown shape Circle");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
for (Geometry shape : collection) {
|
||||||
|
if (shape instanceof MultiPoint) {
|
||||||
|
// Flatten multipoints
|
||||||
|
visit(bqb, (GeometryCollection<?>) shape);
|
||||||
|
} else {
|
||||||
|
bqb.add(shape.visit(this), BooleanClause.Occur.SHOULD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(org.elasticsearch.geo.geometry.Line line) {
|
||||||
|
validateIsGeoShapeFieldType();
|
||||||
|
return LatLonShape.newLineQuery(fieldName, relation.getLuceneRelation(), new Line(line.getLats(), line.getLons()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(LinearRing ring) {
|
||||||
|
throw new QueryShardException(context, "Field [" + fieldName + "] found and unsupported shape LinearRing");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(MultiLine multiLine) {
|
||||||
|
validateIsGeoShapeFieldType();
|
||||||
|
Line[] lines = new Line[multiLine.size()];
|
||||||
|
for (int i = 0; i < multiLine.size(); i++) {
|
||||||
|
lines[i] = new Line(multiLine.get(i).getLats(), multiLine.get(i).getLons());
|
||||||
|
}
|
||||||
|
return LatLonShape.newLineQuery(fieldName, relation.getLuceneRelation(), lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(MultiPoint multiPoint) {
|
||||||
|
throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + GeoShapeType.MULTIPOINT +
|
||||||
|
" queries");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(MultiPolygon multiPolygon) {
|
||||||
|
Polygon[] polygons = new Polygon[multiPolygon.size()];
|
||||||
|
for (int i = 0; i < multiPolygon.size(); i++) {
|
||||||
|
polygons[i] = toLucenePolygon(multiPolygon.get(i));
|
||||||
|
}
|
||||||
|
return LatLonShape.newPolygonQuery(fieldName, relation.getLuceneRelation(), polygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(Point point) {
|
||||||
|
validateIsGeoShapeFieldType();
|
||||||
|
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(),
|
||||||
|
point.getLat(), point.getLat(), point.getLon(), point.getLon());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(org.elasticsearch.geo.geometry.Polygon polygon) {
|
||||||
|
return LatLonShape.newPolygonQuery(fieldName, relation.getLuceneRelation(), toLucenePolygon(polygon));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query visit(org.elasticsearch.geo.geometry.Rectangle r) {
|
||||||
|
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(),
|
||||||
|
r.getMinLat(), r.getMaxLat(), r.getMinLon(), r.getMaxLon());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIsGeoShapeFieldType() {
|
||||||
|
if (fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType == false) {
|
||||||
|
throw new QueryShardException(context, "Expected " + GeoShapeFieldMapper.CONTENT_TYPE
|
||||||
|
+ " field type for Field [" + fieldName + "] but found " + fieldType.typeName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.index.engine.EngineFactory;
|
import org.elasticsearch.index.engine.EngineFactory;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.BinaryFieldMapper;
|
import org.elasticsearch.index.mapper.BinaryFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.BooleanFieldMapper;
|
import org.elasticsearch.index.mapper.BooleanFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.CompletionFieldMapper;
|
import org.elasticsearch.index.mapper.CompletionFieldMapper;
|
||||||
|
@ -39,6 +39,7 @@ import org.elasticsearch.index.mapper.DateFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.FieldAliasMapper;
|
import org.elasticsearch.index.mapper.FieldAliasMapper;
|
||||||
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
|
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IdFieldMapper;
|
import org.elasticsearch.index.mapper.IdFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IndexFieldMapper;
|
import org.elasticsearch.index.mapper.IndexFieldMapper;
|
||||||
|
@ -134,7 +135,7 @@ public class IndicesModule extends AbstractModule {
|
||||||
mappers.put(CompletionFieldMapper.CONTENT_TYPE, new CompletionFieldMapper.TypeParser());
|
mappers.put(CompletionFieldMapper.CONTENT_TYPE, new CompletionFieldMapper.TypeParser());
|
||||||
mappers.put(FieldAliasMapper.CONTENT_TYPE, new FieldAliasMapper.TypeParser());
|
mappers.put(FieldAliasMapper.CONTENT_TYPE, new FieldAliasMapper.TypeParser());
|
||||||
mappers.put(GeoPointFieldMapper.CONTENT_TYPE, new GeoPointFieldMapper.TypeParser());
|
mappers.put(GeoPointFieldMapper.CONTENT_TYPE, new GeoPointFieldMapper.TypeParser());
|
||||||
mappers.put(BaseGeoShapeFieldMapper.CONTENT_TYPE, new BaseGeoShapeFieldMapper.TypeParser());
|
mappers.put(GeoShapeFieldMapper.CONTENT_TYPE, new AbstractGeometryFieldMapper.TypeParser());
|
||||||
|
|
||||||
for (MapperPlugin mapperPlugin : mapperPlugins) {
|
for (MapperPlugin mapperPlugin : mapperPlugins) {
|
||||||
for (Map.Entry<String, Mapper.TypeParser> entry : mapperPlugin.getMappers().entrySet()) {
|
for (Map.Entry<String, Mapper.TypeParser> entry : mapperPlugin.getMappers().entrySet()) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.elasticsearch.common.geo.parsers.ShapeParser;
|
||||||
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.geo.utils.GeographyValidator;
|
import org.elasticsearch.geo.utils.GeographyValidator;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
|
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
|
||||||
import org.locationtech.jts.geom.Geometry;
|
import org.locationtech.jts.geom.Geometry;
|
||||||
|
@ -66,7 +67,7 @@ abstract class BaseGeoParsingTestCase extends ESTestCase {
|
||||||
} else {
|
} else {
|
||||||
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
||||||
org.elasticsearch.geo.geometry.Geometry shape = geometryParser.parse(parser);
|
org.elasticsearch.geo.geometry.Geometry shape = geometryParser.parse(parser);
|
||||||
shape = new GeometryIndexer(true).prepareForIndexing(shape);
|
shape = new GeoShapeIndexer(true).prepareForIndexing(shape);
|
||||||
ElasticsearchGeoAssertions.assertEquals(expected, shape);
|
ElasticsearchGeoAssertions.assertEquals(expected, shape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.elasticsearch.geo.geometry.GeometryCollection;
|
||||||
import org.elasticsearch.geo.geometry.MultiLine;
|
import org.elasticsearch.geo.geometry.MultiLine;
|
||||||
import org.elasticsearch.geo.geometry.MultiPoint;
|
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||||
import org.elasticsearch.index.mapper.ContentPath;
|
import org.elasticsearch.index.mapper.ContentPath;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.Mapper;
|
import org.elasticsearch.index.mapper.Mapper;
|
||||||
import org.elasticsearch.test.VersionUtils;
|
import org.elasticsearch.test.VersionUtils;
|
||||||
|
@ -1425,7 +1426,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
|
|
||||||
public Geometry parse(XContentParser parser) throws IOException, ParseException {
|
public Geometry parse(XContentParser parser) throws IOException, ParseException {
|
||||||
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
||||||
GeometryIndexer indexer = new GeometryIndexer(true);
|
GeoShapeIndexer indexer = new GeoShapeIndexer(true);
|
||||||
return indexer.prepareForIndexing(geometryParser.parse(parser));
|
return indexer.prepareForIndexing(geometryParser.parse(parser));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.elasticsearch.geo.geometry.MultiLine;
|
||||||
import org.elasticsearch.geo.geometry.MultiPoint;
|
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||||
import org.elasticsearch.index.mapper.ContentPath;
|
import org.elasticsearch.index.mapper.ContentPath;
|
||||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.Mapper;
|
import org.elasticsearch.index.mapper.Mapper;
|
||||||
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
||||||
|
@ -470,7 +471,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
} else {
|
} else {
|
||||||
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
||||||
assertExpected(gcb.buildS4J(), gcb, true);
|
assertExpected(gcb.buildS4J(), gcb, true);
|
||||||
assertExpected(new GeometryIndexer(true).prepareForIndexing(gcb.buildGeometry()), gcb, false);
|
assertExpected(new GeoShapeIndexer(true).prepareForIndexing(gcb.buildGeometry()), gcb, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.elasticsearch.geo.geometry.ShapeType;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import static org.elasticsearch.geo.GeometryTestUtils.randomGeometry;
|
import static org.elasticsearch.geo.GeometryTestUtils.randomGeometry;
|
||||||
import static org.elasticsearch.index.query.GeoShapeQueryBuilder.geometryToShapeBuilder;
|
import static org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor.geometryToShapeBuilder;
|
||||||
|
|
||||||
public class GeometryIOTests extends ESTestCase {
|
public class GeometryIOTests extends ESTestCase {
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.elasticsearch.geo.geometry.MultiPolygon;
|
||||||
import org.elasticsearch.geo.geometry.Point;
|
import org.elasticsearch.geo.geometry.Point;
|
||||||
import org.elasticsearch.geo.geometry.Polygon;
|
import org.elasticsearch.geo.geometry.Polygon;
|
||||||
import org.elasticsearch.geo.utils.WellKnownText;
|
import org.elasticsearch.geo.utils.WellKnownText;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -42,7 +43,7 @@ import java.util.Collections;
|
||||||
|
|
||||||
public class GeometryIndexerTests extends ESTestCase {
|
public class GeometryIndexerTests extends ESTestCase {
|
||||||
|
|
||||||
GeometryIndexer indexer = new GeometryIndexer(true);
|
GeoShapeIndexer indexer = new GeoShapeIndexer(true);
|
||||||
private static final WellKnownText WKT = new WellKnownText(true, geometry -> {
|
private static final WellKnownText WKT = new WellKnownText(true, geometry -> {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -208,13 +209,13 @@ public class GeometryIndexerTests extends ESTestCase {
|
||||||
|
|
||||||
private Geometry actual(String wkt, boolean rightOrientation) throws IOException, ParseException {
|
private Geometry actual(String wkt, boolean rightOrientation) throws IOException, ParseException {
|
||||||
Geometry shape = parseGeometry(wkt, rightOrientation);
|
Geometry shape = parseGeometry(wkt, rightOrientation);
|
||||||
return new GeometryIndexer(true).prepareForIndexing(shape);
|
return new GeoShapeIndexer(true).prepareForIndexing(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Geometry actual(XContentBuilder geoJson, boolean rightOrientation) throws IOException, ParseException {
|
private Geometry actual(XContentBuilder geoJson, boolean rightOrientation) throws IOException, ParseException {
|
||||||
Geometry shape = parseGeometry(geoJson, rightOrientation);
|
Geometry shape = parseGeometry(geoJson, rightOrientation);
|
||||||
return new GeometryIndexer(true).prepareForIndexing(shape);
|
return new GeoShapeIndexer(true).prepareForIndexing(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Geometry parseGeometry(String wkt, boolean rightOrientation) throws IOException, ParseException {
|
private Geometry parseGeometry(String wkt, boolean rightOrientation) throws IOException, ParseException {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
import org.locationtech.jts.geom.LineString;
|
import org.locationtech.jts.geom.LineString;
|
||||||
|
@ -778,6 +779,6 @@ public class ShapeBuilderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object buildGeometry(ShapeBuilder<?, ?, ?> builder) {
|
public Object buildGeometry(ShapeBuilder<?, ?, ?> builder) {
|
||||||
return new GeometryIndexer(true).prepareForIndexing(builder.buildGeometry());
|
return new GeoShapeIndexer(true).prepareForIndexing(builder.buildGeometry());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class ExternalMapper extends FieldMapper {
|
||||||
BinaryFieldMapper binMapper = binBuilder.build(context);
|
BinaryFieldMapper binMapper = binBuilder.build(context);
|
||||||
BooleanFieldMapper boolMapper = boolBuilder.build(context);
|
BooleanFieldMapper boolMapper = boolBuilder.build(context);
|
||||||
GeoPointFieldMapper pointMapper = latLonPointBuilder.build(context);
|
GeoPointFieldMapper pointMapper = latLonPointBuilder.build(context);
|
||||||
BaseGeoShapeFieldMapper shapeMapper = (context.indexCreatedVersion().before(Version.V_6_6_0))
|
AbstractGeometryFieldMapper shapeMapper = (context.indexCreatedVersion().before(Version.V_6_6_0))
|
||||||
? legacyShapeBuilder.build(context)
|
? legacyShapeBuilder.build(context)
|
||||||
: shapeBuilder.build(context);
|
: shapeBuilder.build(context);
|
||||||
FieldMapper stringMapper = (FieldMapper)stringBuilder.build(context);
|
FieldMapper stringMapper = (FieldMapper)stringBuilder.build(context);
|
||||||
|
@ -154,13 +154,13 @@ public class ExternalMapper extends FieldMapper {
|
||||||
private BinaryFieldMapper binMapper;
|
private BinaryFieldMapper binMapper;
|
||||||
private BooleanFieldMapper boolMapper;
|
private BooleanFieldMapper boolMapper;
|
||||||
private GeoPointFieldMapper pointMapper;
|
private GeoPointFieldMapper pointMapper;
|
||||||
private BaseGeoShapeFieldMapper shapeMapper;
|
private AbstractGeometryFieldMapper shapeMapper;
|
||||||
private FieldMapper stringMapper;
|
private FieldMapper stringMapper;
|
||||||
|
|
||||||
public ExternalMapper(String simpleName, MappedFieldType fieldType,
|
public ExternalMapper(String simpleName, MappedFieldType fieldType,
|
||||||
String generatedValue, String mapperName,
|
String generatedValue, String mapperName,
|
||||||
BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, GeoPointFieldMapper pointMapper,
|
BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, GeoPointFieldMapper pointMapper,
|
||||||
BaseGeoShapeFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
AbstractGeometryFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
MultiFields multiFields, CopyTo copyTo) {
|
||||||
super(simpleName, fieldType, new ExternalFieldType(), indexSettings, multiFields, copyTo);
|
super(simpleName, fieldType, new ExternalFieldType(), indexSettings, multiFields, copyTo);
|
||||||
this.generatedValue = generatedValue;
|
this.generatedValue = generatedValue;
|
||||||
|
@ -218,7 +218,7 @@ public class ExternalMapper extends FieldMapper {
|
||||||
BinaryFieldMapper binMapperUpdate = (BinaryFieldMapper) binMapper.updateFieldType(fullNameToFieldType);
|
BinaryFieldMapper binMapperUpdate = (BinaryFieldMapper) binMapper.updateFieldType(fullNameToFieldType);
|
||||||
BooleanFieldMapper boolMapperUpdate = (BooleanFieldMapper) boolMapper.updateFieldType(fullNameToFieldType);
|
BooleanFieldMapper boolMapperUpdate = (BooleanFieldMapper) boolMapper.updateFieldType(fullNameToFieldType);
|
||||||
GeoPointFieldMapper pointMapperUpdate = (GeoPointFieldMapper) pointMapper.updateFieldType(fullNameToFieldType);
|
GeoPointFieldMapper pointMapperUpdate = (GeoPointFieldMapper) pointMapper.updateFieldType(fullNameToFieldType);
|
||||||
BaseGeoShapeFieldMapper shapeMapperUpdate = (BaseGeoShapeFieldMapper) shapeMapper.updateFieldType(fullNameToFieldType);
|
AbstractGeometryFieldMapper shapeMapperUpdate = (AbstractGeometryFieldMapper) shapeMapper.updateFieldType(fullNameToFieldType);
|
||||||
TextFieldMapper stringMapperUpdate = (TextFieldMapper) stringMapper.updateFieldType(fullNameToFieldType);
|
TextFieldMapper stringMapperUpdate = (TextFieldMapper) stringMapper.updateFieldType(fullNameToFieldType);
|
||||||
if (update == this
|
if (update == this
|
||||||
&& multiFieldsUpdate == multiFields
|
&& multiFieldsUpdate == multiFields
|
||||||
|
|
|
@ -280,7 +280,8 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
assertTrue(serialized, serialized.contains("\"orientation\":\"" + BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value() + "\""));
|
assertTrue(serialized, serialized.contains("\"orientation\":\"" +
|
||||||
|
AbstractGeometryFieldMapper.Defaults.ORIENTATION.value() + "\""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue