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.xcontent.XContentParser;
|
||||
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 java.io.IOException;
|
||||
|
@ -42,7 +42,7 @@ import java.util.List;
|
|||
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
|
||||
*/
|
||||
abstract class GeoJsonParser {
|
||||
protected static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper)
|
||||
protected static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper shapeMapper)
|
||||
throws IOException {
|
||||
GeoShapeType shapeType = null;
|
||||
DistanceUnit.Distance radius = null;
|
||||
|
@ -50,13 +50,13 @@ abstract class GeoJsonParser {
|
|||
GeometryCollectionBuilder geometryCollections = null;
|
||||
|
||||
Orientation orientation = (shapeMapper == null)
|
||||
? BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value()
|
||||
? AbstractGeometryFieldMapper.Defaults.ORIENTATION.value()
|
||||
: shapeMapper.orientation();
|
||||
Explicit<Boolean> coerce = (shapeMapper == null)
|
||||
? BaseGeoShapeFieldMapper.Defaults.COERCE
|
||||
? AbstractGeometryFieldMapper.Defaults.COERCE
|
||||
: shapeMapper.coerce();
|
||||
Explicit<Boolean> ignoreZValue = (shapeMapper == null)
|
||||
? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE
|
||||
? AbstractGeometryFieldMapper.Defaults.IGNORE_Z_VALUE
|
||||
: shapeMapper.ignoreZValue();
|
||||
|
||||
String malformedException = null;
|
||||
|
@ -208,7 +208,7 @@ abstract class GeoJsonParser {
|
|||
* @return Geometry[] geometries of the GeometryCollection
|
||||
* @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 {
|
||||
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
||||
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.logging.Loggers;
|
||||
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 java.io.IOException;
|
||||
|
@ -63,7 +63,7 @@ public class GeoWKTParser {
|
|||
// no instance
|
||||
private GeoWKTParser() {}
|
||||
|
||||
public static ShapeBuilder parse(XContentParser parser, final BaseGeoShapeFieldMapper shapeMapper)
|
||||
public static ShapeBuilder parse(XContentParser parser, final AbstractGeometryFieldMapper shapeMapper)
|
||||
throws IOException, ElasticsearchParseException {
|
||||
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 */
|
||||
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
|
||||
final BaseGeoShapeFieldMapper shapeMapper)
|
||||
final AbstractGeometryFieldMapper shapeMapper)
|
||||
throws IOException, ElasticsearchParseException {
|
||||
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();
|
||||
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
|
||||
StreamTokenizer tokenizer = new StreamTokenizer(reader);
|
||||
tokenizer.resetSyntax();
|
||||
|
@ -258,7 +258,7 @@ public class GeoWKTParser {
|
|||
return null;
|
||||
}
|
||||
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce),
|
||||
BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value());
|
||||
AbstractGeometryFieldMapper.Defaults.ORIENTATION.value());
|
||||
while (nextCloserOrComma(stream).equals(COMMA)) {
|
||||
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.XContentParser;
|
||||
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.util.Collections;
|
||||
|
@ -50,7 +50,7 @@ public interface ShapeParser {
|
|||
* if the parsers current token has been <code>null</code>
|
||||
* @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) {
|
||||
return null;
|
||||
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||
|
|
|
@ -26,17 +26,22 @@ import org.apache.lucene.search.TermQuery;
|
|||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.Explicit;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.geo.ShapeRelation;
|
||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
import org.elasticsearch.geo.geometry.Geometry;
|
||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
|
||||
import org.elasticsearch.index.query.QueryShardContext;
|
||||
import org.elasticsearch.index.query.QueryShardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
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}
|
||||
*/
|
||||
public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
||||
public static final String CONTENT_TYPE = "geo_shape";
|
||||
public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends FieldMapper {
|
||||
|
||||
|
||||
public static class Names {
|
||||
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 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> {
|
||||
protected Boolean coerce;
|
||||
protected Boolean ignoreMalformed;
|
||||
|
@ -152,7 +186,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
throw new IllegalArgumentException("name cannot be empty string");
|
||||
}
|
||||
|
||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
||||
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
|
||||
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 BaseGeoShapeFieldType() {
|
||||
protected Indexer<Parsed, Processed> geometryIndexer;
|
||||
|
||||
protected Parser<Parsed> geometryParser;
|
||||
|
||||
protected QueryProcessor geometryQueryBuilder;
|
||||
|
||||
protected AbstractGeometryFieldType() {
|
||||
setIndexOptions(IndexOptions.DOCS);
|
||||
setTokenized(false);
|
||||
setStored(false);
|
||||
|
@ -229,7 +269,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
setOmitNorms(true);
|
||||
}
|
||||
|
||||
protected BaseGeoShapeFieldType(BaseGeoShapeFieldType ref) {
|
||||
protected AbstractGeometryFieldType(AbstractGeometryFieldType ref) {
|
||||
super(ref);
|
||||
this.orientation = ref.orientation;
|
||||
}
|
||||
|
@ -237,7 +277,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!super.equals(o)) return false;
|
||||
BaseGeoShapeFieldType that = (BaseGeoShapeFieldType) o;
|
||||
AbstractGeometryFieldType that = (AbstractGeometryFieldType) o;
|
||||
return orientation == that.orientation;
|
||||
}
|
||||
|
||||
|
@ -246,16 +286,6 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
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 void setOrientation(Orientation orientation) {
|
||||
|
@ -272,16 +302,40 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
public Query termQuery(Object value, QueryShardContext context) {
|
||||
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> ignoreMalformed;
|
||||
protected Explicit<Boolean> ignoreZValue;
|
||||
|
||||
protected BaseGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
||||
MultiFields multiFields, CopyTo copyTo) {
|
||||
protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
||||
MultiFields multiFields, CopyTo copyTo) {
|
||||
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
|
||||
this.coerce = coerce;
|
||||
this.ignoreMalformed = ignoreMalformed;
|
||||
|
@ -291,7 +345,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
@Override
|
||||
protected void doMerge(Mapper mergeWith) {
|
||||
super.doMerge(mergeWith);
|
||||
BaseGeoShapeFieldMapper gsfm = (BaseGeoShapeFieldMapper)mergeWith;
|
||||
AbstractGeometryFieldMapper gsfm = (AbstractGeometryFieldMapper)mergeWith;
|
||||
if (gsfm.coerce.explicit()) {
|
||||
this.coerce = gsfm.coerce;
|
||||
}
|
||||
|
@ -310,7 +364,7 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
@Override
|
||||
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
||||
builder.field("type", contentType());
|
||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
||||
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
|
||||
if (includeDefaults || ft.orientation() != Defaults.ORIENTATION.value()) {
|
||||
builder.field(Names.ORIENTATION.getPreferredName(), ft.orientation());
|
||||
}
|
||||
|
@ -338,11 +392,35 @@ public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|||
}
|
||||
|
||||
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
|
||||
protected String contentType() {
|
||||
return CONTENT_TYPE;
|
||||
public void parse(ParseContext context) throws IOException {
|
||||
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.index.IndexableField;
|
||||
import org.elasticsearch.common.Explicit;
|
||||
import org.elasticsearch.common.geo.GeometryIndexer;
|
||||
import org.elasticsearch.common.geo.GeometryParser;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
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.MultiPolygon;
|
||||
import org.elasticsearch.geo.geometry.Point;
|
||||
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
@ -62,9 +61,10 @@ import java.util.Arrays;
|
|||
* <p>
|
||||
* "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) {
|
||||
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),
|
||||
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() {
|
||||
super();
|
||||
}
|
||||
|
@ -90,10 +102,17 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
public GeoShapeFieldType clone() {
|
||||
return new GeoShapeFieldType(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final GeometryParser geometryParser;
|
||||
private final GeometryIndexer geometryIndexer;
|
||||
@Override
|
||||
public String typeName() {
|
||||
return CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Indexer<Geometry, Geometry> geometryIndexer() {
|
||||
return new GeoShapeIndexer(orientation == ShapeBuilder.Orientation.RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
||||
|
@ -101,8 +120,6 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
MultiFields multiFields, CopyTo copyTo) {
|
||||
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, ignoreZValue, indexSettings,
|
||||
multiFields, copyTo);
|
||||
geometryParser = new GeometryParser(orientation() == ShapeBuilder.Orientation.RIGHT, coerce().value(), ignoreZValue.value());
|
||||
geometryIndexer = new GeometryIndexer(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,35 +127,9 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
return (GeoShapeFieldType) super.fieldType();
|
||||
}
|
||||
|
||||
/** parsing logic for {@link LatLonShape} indexing */
|
||||
@Override
|
||||
public void parse(ParseContext context) throws IOException {
|
||||
try {
|
||||
|
||||
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");
|
||||
}
|
||||
protected void indexShape(ParseContext context, Geometry luceneShape) {
|
||||
luceneShape.visit(new LuceneGeometryIndexer(context));
|
||||
}
|
||||
|
||||
private class LuceneGeometryIndexer implements GeometryVisitor<Void, RuntimeException> {
|
||||
|
@ -232,4 +223,9 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
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.geo.geometry.Circle;
|
||||
|
@ -52,7 +52,7 @@ import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
|
|||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -60,7 +60,7 @@ public final class GeometryIndexer {
|
|||
|
||||
private final boolean orientation;
|
||||
|
||||
public GeometryIndexer(boolean orientation) {
|
||||
public GeoShapeIndexer(boolean 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.
|
||||
*
|
||||
|
@ -666,10 +671,8 @@ public final class GeometryIndexer {
|
|||
* Array of edges will be ordered asc by the y-coordinate of the
|
||||
* intersections of edges.
|
||||
*
|
||||
* @param dateline
|
||||
* x-coordinate of the dateline
|
||||
* @param edges
|
||||
* set of edges that may intersect with the dateline
|
||||
* @param dateline x-coordinate of the dateline
|
||||
* @param edges set of edges that may intersect with the dateline
|
||||
* @return number of intersecting edges
|
||||
*/
|
||||
protected static int intersections(double dateline, Edge[] edges) {
|
||||
|
@ -697,10 +700,10 @@ public final class GeometryIndexer {
|
|||
|
||||
for (int i = 0; i < edges.length; i++) {
|
||||
if (edges[i].component >= 0) {
|
||||
double[] partitionPoint = new double[3];
|
||||
int length = component(edges[i], -(components.size()+numHoles+1), mainEdges, partitionPoint);
|
||||
double[] partitionPoint = new double[3];
|
||||
int length = component(edges[i], -(components.size() + numHoles + 1), mainEdges, partitionPoint);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -781,16 +784,16 @@ public final class GeometryIndexer {
|
|||
* This method sets the component id of all edges in a ring to a given id and shifts the
|
||||
* coordinates of this component according to the dateline
|
||||
*
|
||||
* @param edge An arbitrary edge of the component
|
||||
* @param id id to apply to the component
|
||||
* @param edge An arbitrary edge of the component
|
||||
* @param id id to apply to the component
|
||||
* @param edges a list of edges to which all edges of the component will be added (could be <code>null</code>)
|
||||
* @return number of edges that belong to this component
|
||||
*/
|
||||
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
|
||||
Edge any = edge;
|
||||
while(any.coordinate.getLon() == +DATELINE || any.coordinate.getLon() == -DATELINE) {
|
||||
if((any = any.next) == edge) {
|
||||
while (any.coordinate.getLon() == +DATELINE || any.coordinate.getLon() == -DATELINE) {
|
||||
if ((any = any.next) == edge) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -847,14 +850,15 @@ public final class GeometryIndexer {
|
|||
prev = current;
|
||||
}
|
||||
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
|
||||
* @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
|
||||
* @return the coordinates parameter
|
||||
*/
|
||||
|
@ -875,8 +879,8 @@ public final class GeometryIndexer {
|
|||
return coordinates;
|
||||
}
|
||||
|
||||
private static List<Polygon> buildPoints(List<List<Point[]>> components) {
|
||||
List<Polygon> result = new ArrayList<>(components.size());
|
||||
private static List<Polygon> buildPoints(List<List<Point[]>> components) {
|
||||
List<Polygon> result = new ArrayList<>(components.size());
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
List<Point[]> component = components.get(i);
|
||||
result.add(buildPolygon(component));
|
||||
|
@ -885,7 +889,7 @@ public final class GeometryIndexer {
|
|||
}
|
||||
|
||||
private static Polygon buildPolygon(List<Point[]> polygon) {
|
||||
List<org.elasticsearch.geo.geometry.LinearRing> holes;
|
||||
List<LinearRing> holes;
|
||||
Point[] shell = polygon.get(0);
|
||||
if (polygon.size() > 1) {
|
||||
holes = new ArrayList<>(polygon.size() - 1);
|
||||
|
@ -899,7 +903,7 @@ public final class GeometryIndexer {
|
|||
x[c] = normalizeLon(coords[c].getLon());
|
||||
y[c] = normalizeLat(coords[c].getLat());
|
||||
}
|
||||
holes.add(new org.elasticsearch.geo.geometry.LinearRing(y, x));
|
||||
holes.add(new LinearRing(y, x));
|
||||
}
|
||||
} else {
|
||||
holes = Collections.emptyList();
|
||||
|
@ -924,11 +928,12 @@ public final class GeometryIndexer {
|
|||
final Point[][] points = new Point[numHoles][];
|
||||
|
||||
for (int i = 0; i < numHoles; i++) {
|
||||
double[] partitionPoint = new double[3];
|
||||
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);
|
||||
double[] partitionPoint = new double[3];
|
||||
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);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
|
@ -46,6 +46,7 @@ import org.elasticsearch.common.unit.DistanceUnit;
|
|||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
import org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor;
|
||||
import org.locationtech.spatial4j.shape.Point;
|
||||
import org.locationtech.spatial4j.shape.Shape;
|
||||
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
|
||||
|
@ -79,7 +80,7 @@ import java.util.Objects;
|
|||
* @deprecated use {@link GeoShapeFieldMapper}
|
||||
*/
|
||||
@Deprecated
|
||||
public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
||||
public class LegacyGeoShapeFieldMapper extends AbstractGeometryFieldMapper<ShapeBuilder<?, ?, ?>, 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 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;
|
||||
|
||||
|
@ -270,6 +272,10 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
protected void setupFieldType(BuilderContext context) {
|
||||
super.setupFieldType(context);
|
||||
|
||||
fieldType().setGeometryIndexer(new LegacyGeoShapeIndexer());
|
||||
fieldType().setGeometryParser(ShapeParser::parse);
|
||||
fieldType().setGeometryQueryBuilder(new LegacyGeoShapeQueryProcessor(fieldType()));
|
||||
|
||||
// field mapper handles this at build time
|
||||
// but prefix tree strategies require a name, so throw a similar exception
|
||||
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 SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
|
||||
|
@ -357,6 +363,11 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
defaultDistanceErrorPct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeName() {
|
||||
return CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
||||
super.checkCompatibility(fieldType, conflicts);
|
||||
|
@ -479,42 +490,26 @@ public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void parse(ParseContext context) throws IOException {
|
||||
try {
|
||||
Shape shape = context.parseExternalValue(Shape.class);
|
||||
if (shape == null) {
|
||||
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
|
||||
if (shapeBuilder == null) {
|
||||
return;
|
||||
protected void indexShape(ParseContext context, Shape shape) {
|
||||
if (fieldType().pointsOnly() == true) {
|
||||
// index configured for pointsOnly
|
||||
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
||||
// MULTIPOINT data: index each point separately
|
||||
@SuppressWarnings("unchecked") List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
||||
for (Shape s : shapes) {
|
||||
doIndexShape(context, s);
|
||||
}
|
||||
shape = shapeBuilder.buildS4J();
|
||||
return;
|
||||
} else if (shape instanceof Point == false) {
|
||||
throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a "
|
||||
+ ((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass())
|
||||
+ " was found");
|
||||
}
|
||||
if (fieldType().pointsOnly() == true) {
|
||||
// index configured for pointsOnly
|
||||
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
||||
// MULTIPOINT data: index each point separately
|
||||
List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
||||
for (Shape s : shapes) {
|
||||
indexShape(context, s);
|
||||
}
|
||||
return;
|
||||
} else if (shape instanceof Point == false) {
|
||||
throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a "
|
||||
+ ((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass())
|
||||
+ " was found");
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
doIndexShape(context, shape);
|
||||
}
|
||||
|
||||
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)));
|
||||
createFieldNamesField(context, 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;
|
||||
|
||||
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.MatchNoDocsQuery;
|
||||
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.ParseField;
|
||||
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.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.parsers.ShapeParser;
|
||||
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.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
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.BaseGeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.spatial4j.shape.Shape;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.elasticsearch.index.mapper.GeoShapeFieldMapper.toLucenePolygon;
|
||||
|
||||
/**
|
||||
* Derived {@link AbstractGeometryQueryBuilder} that builds a lat, lon GeoShape Query
|
||||
*/
|
||||
|
@ -217,12 +182,12 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
|
|||
|
||||
@Override
|
||||
protected List validContentTypes() {
|
||||
return Arrays.asList(BaseGeoShapeFieldMapper.CONTENT_TYPE);
|
||||
return Arrays.asList(GeoShapeFieldMapper.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String queryFieldType() {
|
||||
return BaseGeoShapeFieldMapper.CONTENT_TYPE;
|
||||
return GeoShapeFieldMapper.CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,260 +210,13 @@ public class GeoShapeQueryBuilder extends AbstractGeometryQueryBuilder<GeoShapeQ
|
|||
|
||||
@Override
|
||||
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,
|
||||
"Field [" + fieldName + "] is not of type [" + queryFieldType() + "] but of type [" + fieldType.typeName() + "]");
|
||||
}
|
||||
|
||||
final BaseGeoShapeFieldMapper.BaseGeoShapeFieldType ft = (BaseGeoShapeFieldMapper.BaseGeoShapeFieldType) fieldType;
|
||||
Query query;
|
||||
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());
|
||||
}
|
||||
}
|
||||
final AbstractGeometryFieldMapper.AbstractGeometryFieldType ft = (AbstractGeometryFieldMapper.AbstractGeometryFieldType) fieldType;
|
||||
return new ConstantScoreQuery(ft.geometryQueryBuilder().process(shape, fieldName, strategy, relation, context));
|
||||
}
|
||||
|
||||
@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.index.IndexSettings;
|
||||
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.BooleanFieldMapper;
|
||||
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.FieldNamesFieldMapper;
|
||||
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.IdFieldMapper;
|
||||
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
||||
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(FieldAliasMapper.CONTENT_TYPE, new FieldAliasMapper.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 (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.XContentParser;
|
||||
import org.elasticsearch.geo.utils.GeographyValidator;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
|
@ -66,7 +67,7 @@ abstract class BaseGeoParsingTestCase extends ESTestCase {
|
|||
} else {
|
||||
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.elasticsearch.geo.geometry.GeometryCollection;
|
|||
import org.elasticsearch.geo.geometry.MultiLine;
|
||||
import org.elasticsearch.geo.geometry.MultiPoint;
|
||||
import org.elasticsearch.index.mapper.ContentPath;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
|
@ -1425,7 +1426,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
|||
|
||||
public Geometry parse(XContentParser parser) throws IOException, ParseException {
|
||||
GeometryParser geometryParser = new GeometryParser(true, true, true);
|
||||
GeometryIndexer indexer = new GeometryIndexer(true);
|
||||
GeoShapeIndexer indexer = new GeoShapeIndexer(true);
|
||||
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.index.mapper.ContentPath;
|
||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
||||
|
@ -470,7 +471,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
|||
} else {
|
||||
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
||||
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 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 {
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.elasticsearch.geo.geometry.MultiPolygon;
|
|||
import org.elasticsearch.geo.geometry.Point;
|
||||
import org.elasticsearch.geo.geometry.Polygon;
|
||||
import org.elasticsearch.geo.utils.WellKnownText;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -42,7 +43,7 @@ import java.util.Collections;
|
|||
|
||||
public class GeometryIndexerTests extends ESTestCase {
|
||||
|
||||
GeometryIndexer indexer = new GeometryIndexer(true);
|
||||
GeoShapeIndexer indexer = new GeoShapeIndexer(true);
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
|
|||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
|
@ -778,6 +779,6 @@ public class ShapeBuilderTests extends ESTestCase {
|
|||
}
|
||||
|
||||
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);
|
||||
BooleanFieldMapper boolMapper = boolBuilder.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)
|
||||
: shapeBuilder.build(context);
|
||||
FieldMapper stringMapper = (FieldMapper)stringBuilder.build(context);
|
||||
|
@ -154,13 +154,13 @@ public class ExternalMapper extends FieldMapper {
|
|||
private BinaryFieldMapper binMapper;
|
||||
private BooleanFieldMapper boolMapper;
|
||||
private GeoPointFieldMapper pointMapper;
|
||||
private BaseGeoShapeFieldMapper shapeMapper;
|
||||
private AbstractGeometryFieldMapper shapeMapper;
|
||||
private FieldMapper stringMapper;
|
||||
|
||||
public ExternalMapper(String simpleName, MappedFieldType fieldType,
|
||||
String generatedValue, String mapperName,
|
||||
BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, GeoPointFieldMapper pointMapper,
|
||||
BaseGeoShapeFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
||||
AbstractGeometryFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
||||
MultiFields multiFields, CopyTo copyTo) {
|
||||
super(simpleName, fieldType, new ExternalFieldType(), indexSettings, multiFields, copyTo);
|
||||
this.generatedValue = generatedValue;
|
||||
|
@ -218,7 +218,7 @@ public class ExternalMapper extends FieldMapper {
|
|||
BinaryFieldMapper binMapperUpdate = (BinaryFieldMapper) binMapper.updateFieldType(fullNameToFieldType);
|
||||
BooleanFieldMapper boolMapperUpdate = (BooleanFieldMapper) boolMapper.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);
|
||||
if (update == this
|
||||
&& multiFieldsUpdate == multiFields
|
||||
|
|
|
@ -280,7 +280,8 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
|||
.endObject().endObject());
|
||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||
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