[Geo] Refactor Point Field Mappers (#56060) (#56540)

This commit refactors the following:
  * GeoPointFieldMapper and PointFieldMapper to
    AbstractPointGeometryFieldMapper derived from AbstractGeometryFieldMapper.
  * .setupFieldType moved up to AbstractGeometryFieldMapper
  * lucene indexing moved up to AbstractGeometryFieldMapper.parse
  * new addStoredFields, addDocValuesFields abstract methods for implementing
    stored field and doc values field indexing in the concrete field mappers

This refactor is the next phase for setting up a framework for extending
spatial field mapper functionality in x-pack.
This commit is contained in:
Nick Knize 2020-05-11 17:11:36 -05:00 committed by GitHub
parent 8f2c1cda2e
commit 9b64149ad2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 895 additions and 523 deletions

View File

@ -41,12 +41,12 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.IGNORE_Z_VALUE;
public final class GeoPoint implements ToXContentFragment {
public class GeoPoint implements ToXContentFragment {
private double lat;
private double lon;
protected double lat;
protected double lon;
public GeoPoint() {
}

View File

@ -373,12 +373,25 @@ public class GeoUtils {
* Array: two or more elements, the first element is longitude, the second is latitude, the rest is ignored if ignoreZValue is true
*/
public static GeoPoint parseGeoPoint(Object value, final boolean ignoreZValue) throws ElasticsearchParseException {
return parseGeoPoint(value, new GeoPoint(), ignoreZValue);
}
/**
* Parses the value as a geopoint. The following types of values are supported:
* <p>
* Object: has to contain either lat and lon or geohash fields
* <p>
* String: expected to be in "latitude, longitude" format or a geohash
* <p>
* Array: two or more elements, the first element is longitude, the second is latitude, the rest is ignored if ignoreZValue is true
*/
public static GeoPoint parseGeoPoint(Object value, GeoPoint point, final boolean ignoreZValue) throws ElasticsearchParseException {
try (XContentParser parser = new MapXContentParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE,
Collections.singletonMap("null_value", value), null)) {
parser.nextToken(); // start object
parser.nextToken(); // field name
parser.nextToken(); // field value
return parseGeoPoint(parser, new GeoPoint(), ignoreZValue);
return parseGeoPoint(parser, point, ignoreZValue);
} catch (IOException ex) {
throw new ElasticsearchParseException("error parsing geopoint", ex);
}

View File

@ -22,7 +22,6 @@ package org.elasticsearch.common.geo;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry;
import java.io.IOException;
import java.text.ParseException;
@ -30,16 +29,16 @@ import java.text.ParseException;
/**
* Geometry serializer/deserializer
*/
public interface GeometryFormat {
public interface GeometryFormat<ParsedFormat> {
/**
* Parser JSON representation of a geometry
*/
Geometry fromXContent(XContentParser parser) throws IOException, ParseException;
ParsedFormat fromXContent(XContentParser parser) throws IOException, ParseException;
/**
* Serializes the geometry into its JSON representation
*/
XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException;
XContentBuilder toXContent(ParsedFormat geometry, XContentBuilder builder, ToXContent.Params params) throws IOException;
}

View File

@ -55,9 +55,9 @@ public final class GeometryParser {
/**
* Returns a geometry format object that can parse and then serialize the object back to the same format.
*/
public GeometryFormat geometryFormat(XContentParser parser) {
public GeometryFormat<Geometry> geometryFormat(XContentParser parser) {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return new GeometryFormat() {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException {
return null;
@ -74,7 +74,7 @@ public final class GeometryParser {
}
};
} else if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return new GeometryFormat() {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException {
return geoJsonParser.fromXContent(parser);
@ -90,7 +90,7 @@ public final class GeometryParser {
}
};
} else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
return new GeometryFormat() {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException, ParseException {
return wellKnownTextParser.fromWKT(parser.text());

View File

@ -428,6 +428,10 @@ public abstract class ShapeBuilder<T extends Shape, G extends org.elasticsearch.
return in.readBoolean() ? Orientation.RIGHT : Orientation.LEFT;
}
public boolean getAsBoolean() {
return this == Orientation.RIGHT;
}
public static Orientation fromString(String orientation) {
orientation = orientation.toLowerCase(Locale.ROOT);
switch (orientation) {

View File

@ -26,6 +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.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import java.io.IOException;
@ -43,14 +44,21 @@ public interface ShapeParser {
/**
* Create a new {@link ShapeBuilder} from {@link XContent}
* @param parser parser to read the GeoShape from
* @param shapeMapper document field mapper reference required for spatial parameters relevant
* @param geometryMapper document field mapper reference required for spatial parameters relevant
* to the shape construction process (e.g., orientation)
* todo: refactor to place build specific parameters in the SpatialContext
* @return {@link ShapeBuilder} read from the parser or null
* if the parsers current token has been <code>null</code>
* @throws IOException if the input could not be read
*/
static ShapeBuilder parse(XContentParser parser, AbstractShapeGeometryFieldMapper shapeMapper) throws IOException {
static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper geometryMapper) throws IOException {
AbstractShapeGeometryFieldMapper shapeMapper = null;
if (geometryMapper != null) {
if (geometryMapper instanceof AbstractShapeGeometryFieldMapper == false) {
throw new IllegalArgumentException("geometry must be a shape type");
}
shapeMapper = (AbstractShapeGeometryFieldMapper) geometryMapper;
}
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return null;
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {

View File

@ -19,7 +19,9 @@
package org.elasticsearch.index.mapper;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.common.Explicit;
@ -29,20 +31,24 @@ import org.elasticsearch.common.geo.SpatialStrategy;
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.geometry.Geometry;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Base field mapper class for all spatial field types
*/
public abstract class AbstractGeometryFieldMapper extends FieldMapper {
public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends FieldMapper {
public static class Names {
public static final ParseField IGNORE_MALFORMED = new ParseField("ignore_malformed");
@ -54,7 +60,23 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
}
public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper>
/**
* Interface representing an preprocessor in geometry indexing pipeline
*/
public interface Indexer<Parsed, Processed> {
Processed prepareForIndexing(Parsed geometry);
Class<Processed> processedClass();
List<IndexableField> indexShape(ParseContext context, Processed shape);
}
/**
* interface representing parser in geometry indexing pipeline
*/
public interface Parser<Parsed> {
Parsed parse(XContentParser parser, AbstractGeometryFieldMapper mapper) throws IOException, ParseException;
}
public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper, FT extends AbstractGeometryFieldType>
extends FieldMapper.Builder<T, Y> {
protected Boolean ignoreMalformed;
protected Boolean ignoreZValue;
@ -89,7 +111,7 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
if (ignoreMalformed != null) {
return new Explicit<>(ignoreMalformed, true);
}
return AbstractShapeGeometryFieldMapper.Defaults.IGNORE_MALFORMED;
return Defaults.IGNORE_MALFORMED;
}
protected Explicit<Boolean> ignoreZValue(BuilderContext context) {
@ -103,7 +125,7 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
if (ignoreZValue != null) {
return new Explicit<>(ignoreZValue, true);
}
return AbstractShapeGeometryFieldMapper.Defaults.IGNORE_Z_VALUE;
return Defaults.IGNORE_Z_VALUE;
}
public Builder ignoreZValue(final boolean ignoreZValue) {
@ -120,7 +142,20 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
if (name().isEmpty()) {
throw new IllegalArgumentException("name cannot be empty string");
}
setGeometryParser();
setGeometryIndexer(fieldType());
setGeometryQueryBuilder(fieldType());
}
@Override
public FT fieldType() {
return (FT)fieldType;
}
protected abstract void setGeometryParser();
protected abstract void setGeometryIndexer(FT fieldType);
protected abstract void setGeometryQueryBuilder(FT fieldType);
}
public abstract static class TypeParser<T extends Builder> implements Mapper.TypeParser {
@ -164,7 +199,9 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
}
}
public abstract static class AbstractGeometryFieldType extends MappedFieldType {
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
protected Indexer<Parsed, Processed> geometryIndexer;
protected Parser<Parsed> geometryParser;
protected QueryProcessor geometryQueryBuilder;
protected AbstractGeometryFieldType() {
@ -183,6 +220,22 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
this.geometryQueryBuilder = geometryQueryBuilder;
}
public void setGeometryIndexer(Indexer<Parsed, Processed> geometryIndexer) {
this.geometryIndexer = geometryIndexer;
}
protected Indexer<Parsed, Processed> geometryIndexer() {
return geometryIndexer;
}
public void setGeometryParser(Parser geometryParser) {
this.geometryParser = geometryParser;
}
protected Parser<Parsed> geometryParser() {
return geometryParser;
}
public QueryProcessor geometryQueryBuilder() {
return geometryQueryBuilder;
}
@ -202,8 +255,12 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
@Override
public Query existsQuery(QueryShardContext context) {
if (hasDocValues()) {
return new DocValuesFieldExistsQuery(name());
} else {
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
}
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
@ -237,11 +294,74 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
}
}
@Override
public AbstractGeometryFieldType fieldType() {
return (AbstractGeometryFieldType)fieldType;
}
@Override
protected void parseCreateField(ParseContext context) throws IOException {
throw new UnsupportedOperationException("Parsing is implemented in parse(), this method should NEVER be called");
}
protected abstract void addStoredFields(ParseContext context, Processed geometry);
protected abstract void addDocValuesFields(String name, Processed geometry, List<IndexableField> fields, ParseContext context);
protected abstract void addMultiFields(ParseContext context, Processed geometry) throws IOException;
/** parsing logic for geometry indexing */
@Override
public void parse(ParseContext context) throws IOException {
AbstractGeometryFieldMapper.AbstractGeometryFieldType fieldType = 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);
}
List<IndexableField> fields = new ArrayList<>();
if (fieldType.indexOptions() != IndexOptions.NONE || fieldType.hasDocValues()) {
fields.addAll(geometryIndexer.indexShape(context, shape));
}
// indexed:
List<IndexableField> indexedFields = new ArrayList<>();
if (fieldType.indexOptions() != IndexOptions.NONE) {
indexedFields.addAll(fields);
}
// stored:
if (fieldType.stored()) {
addStoredFields(context, shape);
}
// docValues:
if (fieldType().hasDocValues()) {
addDocValuesFields(fieldType.name(), shape, fields, context);
} else if (fieldType.stored() || fieldType.indexOptions() != IndexOptions.NONE) {
createFieldNamesField(context);
}
// add the indexed fields to the doc:
for (IndexableField field : indexedFields) {
context.doc().add(field);
}
// add multifields (e.g., used for completion suggester)
addMultiFields(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());
}
}
@Override
public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);

View File

@ -0,0 +1,262 @@
/*
* 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.ElasticsearchParseException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeometryFormat;
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 java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.mapper.TypeParsers.parseField;
/** Base class for for spatial fields that only support indexing points */
public abstract class AbstractPointGeometryFieldMapper<Parsed, Processed> extends AbstractGeometryFieldMapper<Parsed, Processed> {
public static class Names extends AbstractGeometryFieldMapper.Names {
public static final ParseField NULL_VALUE = new ParseField("null_value");
}
public abstract static class Builder<T extends Builder, Y extends AbstractPointGeometryFieldMapper,
FT extends AbstractPointGeometryFieldType> extends AbstractGeometryFieldMapper.Builder<T, Y, FT> {
public Builder(String name, MappedFieldType fieldType, MappedFieldType defaultFieldType) {
super(name, fieldType, defaultFieldType);
}
public abstract Y build(BuilderContext context, String simpleName, MappedFieldType fieldType,
MappedFieldType defaultFieldType, Settings indexSettings,
MultiFields multiFields, Explicit<Boolean> ignoreMalformed, Explicit<Boolean> ignoreZValue,
CopyTo copyTo);
@Override
public Y build(BuilderContext context) {
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
multiFieldsBuilder.build(this, context), ignoreMalformed(context),
ignoreZValue(context), copyTo);
}
@Override
public FT fieldType() {
return (FT)fieldType;
}
}
public abstract static class TypeParser<Processed, T extends Builder> extends AbstractGeometryFieldMapper.TypeParser<Builder> {
protected abstract Processed parseNullValue(Object nullValue, boolean ignoreZValue, boolean ignoreMalformed);
@Override
@SuppressWarnings("rawtypes")
public T parse(String name, Map<String, Object> node, Map<String, Object> params, ParserContext parserContext) {
T builder = (T)(super.parse(name, node, params, parserContext));
parseField(builder, name, node, parserContext);
Object nullValue = null;
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String propName = entry.getKey();
Object propNode = entry.getValue();
if (Names.NULL_VALUE.match(propName, LoggingDeprecationHandler.INSTANCE)) {
if (propNode == null) {
throw new MapperParsingException("Property [null_value] cannot be null.");
}
nullValue = propNode;
iterator.remove();
}
}
if (nullValue != null) {
builder.nullValue(parseNullValue(nullValue, (Boolean)builder.ignoreZValue().value(),
(Boolean)builder.ignoreMalformed().value()));
}
return builder;
}
}
public abstract static class AbstractPointGeometryFieldType<Parsed, Processed>
extends AbstractGeometryFieldType<Parsed, Processed> {
protected AbstractPointGeometryFieldType() {
super();
setHasDocValues(true);
setDimensions(2, Integer.BYTES);
}
protected AbstractPointGeometryFieldType(AbstractPointGeometryFieldType ref) {
super(ref);
}
}
protected AbstractPointGeometryFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, ignoreMalformed, ignoreZValue, multiFields, copyTo);
}
@Override
protected void doMerge(Mapper mergeWith) {
super.doMerge(mergeWith);
AbstractPointGeometryFieldMapper gpfm = (AbstractPointGeometryFieldMapper)mergeWith;
if (gpfm.fieldType().nullValue() != null) {
this.fieldType().setNullValue(gpfm.fieldType().nullValue());
}
}
@Override
@SuppressWarnings("rawtypes")
public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);
if (includeDefaults || fieldType().nullValue() != null) {
builder.field(Names.NULL_VALUE.getPreferredName(), fieldType().nullValue());
}
}
protected abstract ParsedPoint newParsedPoint();
/** represents a Point that has been parsed by {@link PointParser} */
public interface ParsedPoint {
void validate(String fieldName);
void normalize(String fieldName);
void resetCoords(double x, double y);
default boolean isNormalizable(double coord) {
return Double.isNaN(coord) == false && Double.isInfinite(coord) == false;
}
}
protected void parsePointIgnoringMalformed(XContentParser parser, ParsedPoint point) throws IOException {
try {
if (ignoreMalformed.value() == false) {
point.validate(name());
} else {
point.normalize(name());
}
} catch (ElasticsearchParseException e) {
if (ignoreMalformed.value() == false) {
throw e;
}
}
}
/** A parser implementation that can parse the various point formats */
public static class PointParser<P extends ParsedPoint> implements Parser<List<P>> {
@Override
public List<P> parse(XContentParser parser, AbstractGeometryFieldMapper mapper) throws IOException, ParseException {
return geometryFormat(parser, (AbstractPointGeometryFieldMapper)mapper).fromXContent(parser);
}
public GeometryFormat<List<P>> geometryFormat(XContentParser parser, AbstractPointGeometryFieldMapper mapper) {
if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
return new GeometryFormat<List<P>>() {
@Override
public List<P> fromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken();
P point = (P)(mapper.newParsedPoint());
ArrayList<P> points = new ArrayList();
if (token == XContentParser.Token.VALUE_NUMBER) {
double x = parser.doubleValue();
parser.nextToken();
double y = parser.doubleValue();
token = parser.nextToken();
if (token == XContentParser.Token.VALUE_NUMBER) {
GeoPoint.assertZValue((Boolean)(mapper.ignoreZValue().value()), parser.doubleValue());
} else if (token != XContentParser.Token.END_ARRAY) {
throw new ElasticsearchParseException("[{}] field type does not accept > 3 dimensions",
mapper.contentType());
}
point.resetCoords(x, y);
if ((Boolean)(mapper.ignoreMalformed().value()) == false) {
point.validate(mapper.name());
} else {
point.normalize(mapper.name());
}
points.add(point);
} else {
while (token != XContentParser.Token.END_ARRAY) {
mapper.parsePointIgnoringMalformed(parser, point);
points.add(point);
point = (P)(mapper.newParsedPoint());
token = parser.nextToken();
}
}
return points;
}
@Override
public XContentBuilder toXContent(List<P> points, XContentBuilder builder, Params params) throws IOException {
return null;
}
};
} else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return new GeometryFormat<List<P>>() {
@Override
public List<P> fromXContent(XContentParser parser) throws IOException, ParseException {
P point = null;
ArrayList<P> points = null;
if (mapper.fieldType().nullValue() != null) {
point = (P)(mapper.fieldType().nullValue());
if ((Boolean)(mapper.ignoreMalformed().value()) == false) {
point.validate(mapper.name());
} else {
point.normalize(mapper.name());
}
points = new ArrayList<>();
points.add(point);
}
return points;
}
@Override
public XContentBuilder toXContent(List<P> points, XContentBuilder builder, Params params) throws IOException {
return null;
}
};
} else {
return new GeometryFormat<List<P>>() {
@Override
public List<P> fromXContent(XContentParser parser) throws IOException, ParseException {
P point = (P)mapper.newParsedPoint();
mapper.parsePointIgnoringMalformed(parser, point);
ArrayList<P> points = new ArrayList();
points.add(point);
return points;
}
@Override
public XContentBuilder toXContent(List<P> points, XContentBuilder builder, Params params) throws IOException {
return null;
}
};
}
}
}
}

View File

@ -18,7 +18,6 @@
*/
package org.elasticsearch.index.mapper;
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.Version;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
@ -27,21 +26,18 @@ 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.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
import java.io.IOException;
import java.text.ParseException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper}
*/
public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extends AbstractGeometryFieldMapper {
public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extends AbstractGeometryFieldMapper<Parsed, Processed> {
public static class Names extends AbstractGeometryFieldMapper.Names {
public static final ParseField ORIENTATION = new ParseField("orientation");
@ -53,29 +49,8 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
public static final Explicit<Boolean> COERCE = new Explicit<>(false, false);
}
/**
* Interface representing an preprocessor in geo-shape indexing pipeline
*/
public interface Indexer<Parsed, Processed> {
Processed prepareForIndexing(Parsed geometry);
Class<Processed> processedClass();
List<IndexableField> indexShape(ParseContext context, Processed shape);
}
/**
* interface representing parser in geo shape indexing pipeline
*/
public interface Parser<Parsed> {
Parsed parse(XContentParser parser, AbstractShapeGeometryFieldMapper mapper) throws IOException, ParseException;
}
public abstract static class Builder<T extends Builder, Y extends AbstractShapeGeometryFieldMapper>
extends AbstractGeometryFieldMapper.Builder<T, Y> {
public abstract static class Builder<T extends Builder, Y extends AbstractShapeGeometryFieldMapper,
FT extends AbstractShapeGeometryFieldType> extends AbstractGeometryFieldMapper.Builder<T, Y, FT> {
protected Boolean coerce;
protected Orientation orientation;
@ -106,6 +81,13 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
return Defaults.COERCE;
}
protected Explicit<Boolean> coerce() {
if (coerce != null) {
return new Explicit<>(coerce, true);
}
return Defaults.COERCE;
}
public Builder orientation(Orientation orientation) {
this.orientation = orientation;
return this;
@ -124,12 +106,13 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
AbstractShapeGeometryFieldType ft = (AbstractShapeGeometryFieldType)fieldType();
protected void setGeometryParser() {
AbstractShapeGeometryFieldType ft = fieldType();
ft.setOrientation(orientation().value());
setGeometryParser(fieldType());
}
protected abstract void setGeometryParser(FT fieldType);
}
protected static final String DEPRECATED_PARAMETERS_KEY = "deprecated_parameters";
@ -184,13 +167,9 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
}
}
public abstract static class AbstractShapeGeometryFieldType<Parsed, Processed> extends AbstractGeometryFieldType {
public abstract static class AbstractShapeGeometryFieldType<Parsed, Processed> extends AbstractGeometryFieldType<Parsed, Processed> {
protected Orientation orientation = Defaults.ORIENTATION.value();
protected Indexer<Parsed, Processed> geometryIndexer;
protected Parser<Parsed> geometryParser;
protected AbstractShapeGeometryFieldType() {
super();
}
@ -218,22 +197,6 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
checkIfFrozen();
this.orientation = orientation;
}
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;
}
}
protected Explicit<Boolean> coerce;
@ -279,34 +242,4 @@ public abstract class AbstractShapeGeometryFieldMapper<Parsed, Processed> extend
public Orientation orientation() {
return ((AbstractShapeGeometryFieldType)fieldType).orientation();
}
/** parsing logic for geometry indexing */
@Override
public void parse(ParseContext context) throws IOException {
AbstractShapeGeometryFieldType fieldType = (AbstractShapeGeometryFieldType)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);
}
List<IndexableField> fields = geometryIndexer.indexShape(context, shape);
context.doc().addAll(fields);
createFieldNamesField(context);
} 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());
}
}
}

View File

@ -21,44 +21,35 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.document.LatLonDocValuesField;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
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.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointDVIndexFieldData;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.VectorGeoPointShapeQueryProcessor;
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import java.io.IOException;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.mapper.TypeParsers.parseField;
/**
* Field Mapper for geo_point types.
*
* Uses lucene 6 LatLonPoint encoding
*/
public class GeoPointFieldMapper extends AbstractGeometryFieldMapper implements ArrayValueMapperParser {
public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<List<? extends GeoPoint>, List<? extends GeoPoint>>
implements ArrayValueMapperParser {
public static final String CONTENT_TYPE = "geo_point";
public static class Names extends AbstractGeometryFieldMapper.Names {
public static final ParseField NULL_VALUE = new ParseField("null_value");
}
public static class Builder extends AbstractGeometryFieldMapper.Builder<Builder, GeoPointFieldMapper> {
public static class Builder extends AbstractPointGeometryFieldMapper.Builder<Builder, GeoPointFieldMapper, GeoPointFieldType> {
public Builder(String name) {
super(name, new GeoPointFieldType(), new GeoPointFieldType());
builder = this;
@ -74,56 +65,31 @@ public class GeoPointFieldMapper extends AbstractGeometryFieldMapper implements
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
protected void setGeometryParser() {
PointParser<ParsedGeoPoint> pointParser = new PointParser<>();
fieldType().setGeometryParser((parser, mapper) -> pointParser.parse(parser, mapper));
}
GeoPointFieldType fieldType = (GeoPointFieldType)fieldType();
@Override
protected void setGeometryIndexer(GeoPointFieldType fieldType) {
fieldType.setGeometryIndexer(new GeoPointIndexer(fieldType));
}
@Override
protected void setGeometryQueryBuilder(GeoPointFieldType fieldType) {
fieldType.setGeometryQueryBuilder(new VectorGeoPointShapeQueryProcessor());
}
@Override
public GeoPointFieldMapper build(BuilderContext context) {
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
multiFieldsBuilder.build(this, context), ignoreMalformed(context),
ignoreZValue(context), copyTo);
}
@Override
public GeoPointFieldType fieldType() {
return (GeoPointFieldType)fieldType;
}
}
public static class TypeParser extends AbstractGeometryFieldMapper.TypeParser<Builder> {
public static class TypeParser extends AbstractPointGeometryFieldMapper.TypeParser<ParsedGeoPoint, Builder> {
@Override
protected Builder newBuilder(String name, Map<String, Object> params) {
return new GeoPointFieldMapper.Builder(name);
}
@Override
public Builder parse(String name, Map<String, Object> node, Map<String, Object> params, ParserContext parserContext) {
Builder builder = super.parse(name, node, params, parserContext);
parseField(builder, name, node, parserContext);
Object nullValue = null;
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String propName = entry.getKey();
Object propNode = entry.getValue();
if (Names.NULL_VALUE.match(propName, LoggingDeprecationHandler.INSTANCE)) {
if (propNode == null) {
throw new MapperParsingException("Property [null_value] cannot be null.");
}
nullValue = propNode;
iterator.remove();
}
}
if (nullValue != null) {
boolean ignoreZValue = builder.ignoreZValue().value();
boolean ignoreMalformed = builder.ignoreMalformed().value();
GeoPoint point = GeoUtils.parseGeoPoint(nullValue, ignoreZValue);
protected ParsedGeoPoint parseNullValue(Object nullValue, boolean ignoreZValue, boolean ignoreMalformed) {
ParsedGeoPoint point = new ParsedGeoPoint();
GeoUtils.parseGeoPoint(nullValue, point, ignoreZValue);
if (ignoreMalformed == false) {
if (point.lat() > 90.0 || point.lat() < -90.0) {
throw new IllegalArgumentException("illegal latitude value [" + point.lat() + "]");
@ -134,21 +100,59 @@ public class GeoPointFieldMapper extends AbstractGeometryFieldMapper implements
} else {
GeoUtils.normalizePoint(point);
}
builder.nullValue(point);
return point;
}
return builder;
}
/**
* Parses geopoint represented as an object or an array, ignores malformed geopoints if needed
*/
@Override
protected void parsePointIgnoringMalformed(XContentParser parser, ParsedPoint point) throws IOException {
GeoUtils.parseGeoPoint(parser, (GeoPoint)point, ignoreZValue().value());
super.parsePointIgnoringMalformed(parser, point);
}
public GeoPointFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, ignoreMalformed, ignoreZValue, multiFields, copyTo);
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, ignoreMalformed, ignoreZValue, copyTo);
}
@Override
protected void doMerge(Mapper mergeWith) {
super.doMerge(mergeWith);
protected void addStoredFields(ParseContext context, List<? extends GeoPoint> points) {
for (GeoPoint point : points) {
context.doc().add(new StoredField(fieldType.name(), point.toString()));
}
}
@Override
protected void addMultiFields(ParseContext context, List<? extends GeoPoint> points) throws IOException {
// @todo phase out geohash (which is currently used in the CompletionSuggester)
if (points.isEmpty()) {
return;
}
StringBuilder s = new StringBuilder();
if (points.size() > 1) {
s.append('[');
}
s.append(points.get(0).geohash());
for (int i = 1; i < points.size(); ++i) {
s.append(',');
s.append(points.get(i).geohash());
}
if (points.size() > 1) {
s.append(']');
}
multiFields.parse(this, context.createExternalValueContext(s));
}
@Override
protected void addDocValuesFields(String name, List<? extends GeoPoint> points, List<IndexableField> fields, ParseContext context) {
for (GeoPoint point : points) {
context.doc().add(new LatLonDocValuesField(fieldType.name(), point.lat(), point.lon()));
}
}
@Override
@ -156,11 +160,19 @@ public class GeoPointFieldMapper extends AbstractGeometryFieldMapper implements
return CONTENT_TYPE;
}
public static class GeoPointFieldType extends AbstractGeometryFieldType {
@Override
public GeoPointFieldType fieldType() {
return (GeoPointFieldType)fieldType;
}
@Override
protected ParsedPoint newParsedPoint() {
return new ParsedGeoPoint();
}
public static class GeoPointFieldType extends AbstractPointGeometryFieldType<List<ParsedGeoPoint>, List<ParsedGeoPoint>> {
public GeoPointFieldType() {
super();
setHasDocValues(true);
setDimensions(2, Integer.BYTES);
}
GeoPointFieldType(GeoPointFieldType ref) {
@ -187,119 +199,93 @@ public class GeoPointFieldMapper extends AbstractGeometryFieldMapper implements
public ValuesSourceType getValuesSourceType() {
return CoreValuesSourceType.GEOPOINT;
}
}
protected static class ParsedGeoPoint extends GeoPoint implements ParsedPoint {
@Override
public void validate(String fieldName) {
if (lat() > 90.0 || lat() < -90.0) {
throw new IllegalArgumentException("illegal latitude value [" + lat() + "] for " + fieldName);
}
if (lon() > 180.0 || lon() < -180) {
throw new IllegalArgumentException("illegal longitude value [" + lon() + "] for " + fieldName);
}
}
@Override
public Query existsQuery(QueryShardContext context) {
if (hasDocValues()) {
return new DocValuesFieldExistsQuery(name());
} else {
return super.existsQuery(context);
}
}
}
protected void parse(ParseContext context, GeoPoint point) throws IOException {
if (ignoreMalformed.value() == false) {
if (point.lat() > 90.0 || point.lat() < -90.0) {
throw new IllegalArgumentException("illegal latitude value [" + point.lat() + "] for " + name());
}
if (point.lon() > 180.0 || point.lon() < -180) {
throw new IllegalArgumentException("illegal longitude value [" + point.lon() + "] for " + name());
}
} else {
if (isNormalizable(point.lat()) && isNormalizable(point.lon())) {
GeoUtils.normalizePoint(point);
public void normalize(String name) {
if (isNormalizable(lat()) && isNormalizable(lon())) {
GeoUtils.normalizePoint(this);
} else {
throw new ElasticsearchParseException("cannot normalize the point - not a number");
}
}
if (fieldType().indexOptions() != IndexOptions.NONE) {
context.doc().add(new LatLonPoint(fieldType().name(), point.lat(), point.lon()));
}
if (fieldType().stored()) {
context.doc().add(new StoredField(fieldType().name(), point.toString()));
}
if (fieldType.hasDocValues()) {
context.doc().add(new LatLonDocValuesField(fieldType().name(), point.lat(), point.lon()));
} else if (fieldType().stored() || fieldType().indexOptions() != IndexOptions.NONE) {
createFieldNamesField(context);
}
// if the mapping contains multifields then use the geohash string
if (multiFields.iterator().hasNext()) {
multiFields.parse(this, context.createExternalValueContext(point.geohash()));
}
}
@Override
public void parse(ParseContext context) throws IOException {
context.path().add(simpleName());
try {
GeoPoint sparse = context.parseExternalValue(GeoPoint.class);
if (sparse != null) {
parse(context, sparse);
} else {
sparse = new GeoPoint();
XContentParser.Token token = context.parser().currentToken();
if (token == XContentParser.Token.START_ARRAY) {
token = context.parser().nextToken();
if (token == XContentParser.Token.VALUE_NUMBER) {
double lon = context.parser().doubleValue();
context.parser().nextToken();
double lat = context.parser().doubleValue();
token = context.parser().nextToken();
if (token == XContentParser.Token.VALUE_NUMBER) {
GeoPoint.assertZValue(ignoreZValue.value(), context.parser().doubleValue());
} else if (token != XContentParser.Token.END_ARRAY) {
throw new ElasticsearchParseException("[{}] field type does not accept > 3 dimensions", CONTENT_TYPE);
}
parse(context, sparse.reset(lat, lon));
} else {
while (token != XContentParser.Token.END_ARRAY) {
parseGeoPointIgnoringMalformed(context, sparse);
token = context.parser().nextToken();
}
}
} else if (token == XContentParser.Token.VALUE_NULL) {
if (fieldType.nullValue() != null) {
parse(context, (GeoPoint) fieldType.nullValue());
}
} else {
parseGeoPointIgnoringMalformed(context, sparse);
}
}
} catch (Exception ex) {
throw new MapperParsingException("failed to parse field [{}] of type [{}]", ex, fieldType().name(), fieldType().typeName());
}
context.path().remove();
}
/**
* Parses geopoint represented as an object or an array, ignores malformed geopoints if needed
*/
private void parseGeoPointIgnoringMalformed(ParseContext context, GeoPoint sparse) throws IOException {
try {
parse(context, GeoUtils.parseGeoPoint(context.parser(), sparse, ignoreZValue.value()));
} catch (ElasticsearchParseException e) {
if (ignoreMalformed.value() == false) {
throw e;
}
context.addIgnoredField(fieldType.name());
}
}
@Override
public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);
if (includeDefaults || fieldType().nullValue() != null) {
builder.field(Names.NULL_VALUE.getPreferredName(), fieldType().nullValue());
}
}
private boolean isNormalizable(double coord) {
public boolean isNormalizable(double coord) {
return Double.isNaN(coord) == false && Double.isInfinite(coord) == false;
}
@Override
public void resetCoords(double x, double y) {
this.reset(y, x);
}
@Override
public boolean equals(Object other) {
double oLat;
double oLon;
if (other instanceof GeoPoint) {
GeoPoint o = (GeoPoint)other;
oLat = o.lat();
oLon = o.lon();
} else if (other instanceof ParsedGeoPoint == false) {
return false;
} else {
ParsedGeoPoint o = (ParsedGeoPoint)other;
oLat = o.lat();
oLon = o.lon();
}
if (Double.compare(oLat, lat) != 0) return false;
if (Double.compare(oLon, lon) != 0) return false;
return true;
}
@Override
public int hashCode() {
return super.hashCode();
}
}
protected static class GeoPointIndexer implements Indexer<List<ParsedGeoPoint>, List<ParsedGeoPoint>> {
protected final GeoPointFieldType fieldType;
GeoPointIndexer(GeoPointFieldType fieldType) {
this.fieldType = fieldType;
}
@Override
public List<ParsedGeoPoint> prepareForIndexing(List<ParsedGeoPoint> geoPoints) {
if (geoPoints == null || geoPoints.isEmpty()) {
return Collections.emptyList();
}
return geoPoints;
}
@Override
public Class<List<ParsedGeoPoint>> processedClass() {
return (Class<List<ParsedGeoPoint>>)(Object)List.class;
}
@Override
public List<IndexableField> indexShape(ParseContext context, List<ParsedGeoPoint> points) {
ArrayList<IndexableField> fields = new ArrayList<>(points.size());
for (GeoPoint point : points) {
fields.add(new LatLonPoint(fieldType.name(), point.lat(), point.lon()));
}
return fields;
}
}
}

View File

@ -26,6 +26,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
import java.util.List;
import java.util.Map;
/**
@ -52,7 +53,7 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geomet
public static final String CONTENT_TYPE = "geo_shape";
public static class Builder extends AbstractShapeGeometryFieldMapper.Builder<AbstractShapeGeometryFieldMapper.Builder,
GeoShapeFieldMapper> {
GeoShapeFieldMapper, GeoShapeFieldType> {
public Builder(String name) {
super (name, new GeoShapeFieldType(), new GeoShapeFieldType());
}
@ -65,16 +66,20 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geomet
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
protected void setGeometryParser(GeoShapeFieldType ft) {
// @todo check coerce
GeometryParser geometryParser = new GeometryParser(ft.orientation.getAsBoolean(), coerce().value(),
ignoreZValue().value());
ft.setGeometryParser( (parser, mapper) -> geometryParser.parse(parser));
}
GeoShapeFieldType fieldType = (GeoShapeFieldType)fieldType();
boolean orientation = fieldType.orientation == ShapeBuilder.Orientation.RIGHT;
@Override
protected void setGeometryIndexer(GeoShapeFieldType fieldType) {
fieldType.setGeometryIndexer(new GeoShapeIndexer(fieldType.orientation.getAsBoolean(), fieldType.name()));
}
GeometryParser geometryParser = new GeometryParser(orientation, coerce(context).value(), ignoreZValue().value());
fieldType.setGeometryIndexer(new GeoShapeIndexer(orientation, fieldType.name()));
fieldType.setGeometryParser( (parser, mapper) -> geometryParser.parse(parser));
@Override
protected void setGeometryQueryBuilder(GeoShapeFieldType fieldType) {
fieldType.setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
}
}
@ -120,6 +125,23 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geomet
multiFields, copyTo);
}
@Override
protected void addStoredFields(ParseContext context, Geometry geometry) {
// noop: we currently do not store geo_shapes
// @todo store as geojson string?
}
@Override
@SuppressWarnings("rawtypes")
protected void addDocValuesFields(String name, Geometry geometry, List fields, ParseContext context) {
// we will throw a mapping exception before we get here
}
@Override
protected void addMultiFields(ParseContext context, Geometry geometry) {
// noop (completion suggester currently not compatible with geo_shape)
}
@Override
protected void doMerge(Mapper mergeWith) {
if (mergeWith instanceof LegacyGeoShapeFieldMapper) {

View File

@ -49,7 +49,7 @@ import static org.elasticsearch.common.geo.GeoUtils.normalizePoint;
/**
* Utility class that converts geometries into Lucene-compatible form for indexing in a geo_shape field.
*/
public class GeoShapeIndexer implements AbstractShapeGeometryFieldMapper.Indexer<Geometry, Geometry> {
public class GeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<Geometry, Geometry> {
private final boolean orientation;
private final String name;

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.mapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
@ -179,7 +180,7 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(logger);
public static class Builder extends AbstractShapeGeometryFieldMapper.Builder<AbstractShapeGeometryFieldMapper.Builder,
LegacyGeoShapeFieldMapper> {
LegacyGeoShapeFieldMapper, LegacyGeoShapeFieldMapper.GeoShapeFieldType> {
DeprecatedParameters deprecatedParameters;
@ -193,8 +194,18 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
}
@Override
public GeoShapeFieldType fieldType() {
return (GeoShapeFieldType)fieldType;
protected void setGeometryParser(GeoShapeFieldType fieldType) {
fieldType().setGeometryParser(ShapeParser::parse);
}
@Override
public void setGeometryIndexer(LegacyGeoShapeFieldMapper.GeoShapeFieldType fieldType) {
fieldType().setGeometryIndexer(new LegacyGeoShapeIndexer(fieldType));
}
@Override
protected void setGeometryQueryBuilder(GeoShapeFieldType fieldType) {
fieldType().setGeometryQueryBuilder(new LegacyGeoShapeQueryProcessor(fieldType()));
}
private void setupFieldTypeDeprecatedParameters(BuilderContext context) {
@ -266,16 +277,6 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
fieldType().setGeometryIndexer(new LegacyGeoShapeIndexer(fieldType()));
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()) {
throw new IllegalArgumentException("name cannot be empty string");
}
// setup the deprecated parameters and the prefix tree configuration
setupFieldTypeDeprecatedParameters(context);
setupPrefixTrees();
@ -299,7 +300,7 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
}
}
public static final class GeoShapeFieldType extends AbstractShapeGeometryFieldType {
public static final class GeoShapeFieldType extends AbstractShapeGeometryFieldType<ShapeBuilder<?, ?, ?>, Shape> {
private String tree = DeprecatedParameters.Defaults.TREE;
private SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
@ -483,6 +484,21 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
return (GeoShapeFieldType) super.fieldType();
}
@Override
protected void addStoredFields(ParseContext context, Shape geometry) {
// noop: we do not store geo_shapes; and will not store legacy geo_shape types
}
@Override
protected void addDocValuesFields(String name, Shape geometry, List<IndexableField> fields, ParseContext context) {
// doc values are not supported
}
@Override
protected void addMultiFields(ParseContext context, Shape geometry) {
// noop (completion suggester currently not compatible with geo_shape)
}
@Override
public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);

View File

@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LegacyGeoShapeIndexer implements AbstractShapeGeometryFieldMapper.Indexer<ShapeBuilder<?, ?, ?>, Shape> {
public class LegacyGeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<ShapeBuilder<?, ?, ?>, Shape> {
private LegacyGeoShapeFieldMapper.GeoShapeFieldType fieldType;

View File

@ -34,6 +34,7 @@ import org.elasticsearch.index.query.QueryShardContext;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
@ -180,8 +181,9 @@ public class ExternalMapper extends FieldMapper {
// Let's add a Dummy Point
Double lat = 42.0;
Double lng = 51.0;
GeoPoint point = new GeoPoint(lat, lng);
pointMapper.parse(context.createExternalValueContext(point));
ArrayList<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(lat, lng));
pointMapper.parse(context.createExternalValueContext(points));
// Let's add a Dummy Shape
if (shapeMapper instanceof GeoShapeFieldMapper) {

View File

@ -41,9 +41,9 @@ import java.util.Collection;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.geometry.utils.Geohash.stringEncode;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_MALFORMED;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.NULL_VALUE;
import static org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.Names.IGNORE_MALFORMED;
import static org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.NULL_VALUE;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
@ -407,7 +407,7 @@ public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
XContentType.JSON));
assertThat(doc.rootDoc().getField("location"), notNullValue());
BytesRef defaultValue = doc.rootDoc().getField("location").binaryValue();
BytesRef defaultValue = doc.rootDoc().getBinaryValue("location");
doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
BytesReference.bytes(XContentFactory.jsonBuilder()
@ -416,7 +416,7 @@ public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
.endObject()),
XContentType.JSON));
// Shouldn't matter if we specify the value explicitly or use null value
assertThat(defaultValue, equalTo(doc.rootDoc().getField("location").binaryValue()));
assertThat(defaultValue, equalTo(doc.rootDoc().getBinaryValue("location")));
doc = defaultMapper.parse(new SourceToParse("test", "type", "1",
BytesReference.bytes(XContentFactory.jsonBuilder()
@ -425,7 +425,7 @@ public class GeoPointFieldMapperTests extends ESSingleNodeTestCase {
.endObject()),
XContentType.JSON));
// Shouldn't matter if we specify the value explicitly or use null value
assertThat(defaultValue, not(equalTo(doc.rootDoc().getField("location").binaryValue())));
assertThat(defaultValue, not(equalTo(doc.rootDoc().getBinaryValue("location"))));
}
/**

View File

@ -34,7 +34,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

View File

@ -45,7 +45,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

View File

@ -25,19 +25,19 @@ import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractGeometryFieldMapper.Names.IGNORE_Z_VALUE;
/**
* Represents a point in the cartesian space.
*/
public final class CartesianPoint implements ToXContentFragment {
public class CartesianPoint implements ToXContentFragment {
private static final ParseField X_FIELD = new ParseField("x");
private static final ParseField Y_FIELD = new ParseField("y");
private static final ParseField Z_FIELD = new ParseField("z");
private float x;
private float y;
protected float x;
protected float y;
public CartesianPoint() {
}
@ -267,12 +267,16 @@ public final class CartesianPoint implements ToXContentFragment {
}
public static CartesianPoint parsePoint(Object value, boolean ignoreZValue) throws ElasticsearchParseException {
return parsePoint(value, new CartesianPoint(), ignoreZValue);
}
public static CartesianPoint parsePoint(Object value, CartesianPoint point, boolean ignoreZValue) throws ElasticsearchParseException {
try (XContentParser parser = new MapXContentParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE,
Collections.singletonMap("null_value", value), null)) {
parser.nextToken(); // start object
parser.nextToken(); // field name
parser.nextToken(); // field value
return parsePoint(parser, new CartesianPoint(), ignoreZValue);
return parsePoint(parser, point, ignoreZValue);
} catch (IOException ex) {
throw new ElasticsearchParseException("error parsing point", ex);
}

View File

@ -9,10 +9,6 @@ package org.elasticsearch.xpack.spatial.index.mapper;
import org.apache.lucene.document.LatLonShape;
import org.apache.lucene.document.ShapeField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.Explicit;
@ -23,7 +19,6 @@ import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeIndexer;
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
@ -31,7 +26,6 @@ import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.TypeParsers;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import org.elasticsearch.xpack.spatial.index.fielddata.AbstractLatLonShapeDVIndexFieldData;
@ -67,13 +61,11 @@ import java.util.Map;
*/
public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
public static final String CONTENT_TYPE = "geo_shape";
private Explicit<Boolean> docValues;
@SuppressWarnings("rawtypes")
public static class Builder extends AbstractShapeGeometryFieldMapper.Builder<AbstractShapeGeometryFieldMapper.Builder,
GeoShapeWithDocValuesFieldMapper> {
GeoShapeWithDocValuesFieldMapper, GeoShapeWithDocValuesFieldType> {
public Builder(String name) {
super (name, new GeoShapeWithDocValuesFieldType(), new GeoShapeWithDocValuesFieldType());
}
@ -100,25 +92,34 @@ public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
return new Explicit<>(fieldType.hasDocValues(), false);
}
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
GeoShapeWithDocValuesFieldType fieldType = (GeoShapeWithDocValuesFieldType)fieldType();
boolean orientation = fieldType.orientation() == ShapeBuilder.Orientation.RIGHT;
GeometryParser geometryParser = new GeometryParser(orientation, coerce(context).value(), ignoreZValue().value());
fieldType.setGeometryIndexer(new GeoShapeIndexer(orientation, fieldType.name()) {
@Override
public List<IndexableField> indexShape(ParseContext context, Geometry shape) {
List<IndexableField> fields = super.indexShape(context, shape);
if (fieldType().hasDocValues()) {
protected void setGeometryParser(GeoShapeWithDocValuesFieldType ft) {
// @todo check coerce
GeometryParser geometryParser = new GeometryParser(ft.orientation().getAsBoolean(), coerce().value(),
ignoreZValue().value());
ft.setGeometryParser( (parser, mapper) -> geometryParser.parse(parser));
}
@Override
protected void setGeometryIndexer(GeoShapeWithDocValuesFieldType fieldType) {
fieldType.setGeometryIndexer(new GeoShapeIndexer(fieldType.orientation().getAsBoolean(), fieldType.name()));
}
@Override
protected void setGeometryQueryBuilder(GeoShapeWithDocValuesFieldType fieldType) {
fieldType.setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
}
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
protected void addDocValuesFields(String name, Geometry shape, List fields, ParseContext context) {
CentroidCalculator calculator = new CentroidCalculator(shape);
final byte[] scratch = new byte[7 * Integer.BYTES];
// doc values are generated from the indexed fields.
ShapeField.DecodedTriangle[] triangles = new ShapeField.DecodedTriangle[fields.size()];
for (int i = 0; i < fields.size(); i++) {
BytesRef bytesRef = fields.get(i).binaryValue();
BytesRef bytesRef = ((List<IndexableField>)fields).get(i).binaryValue();
assert bytesRef.length == 7 * Integer.BYTES;
System.arraycopy(bytesRef.bytes, bytesRef.offset, scratch, 0, 7 * Integer.BYTES);
ShapeField.decodeTriangle(scratch, triangles[i] = new ShapeField.DecodedTriangle());
@ -128,17 +129,11 @@ public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
if (docValuesField == null) {
docValuesField = new BinaryGeoShapeDocValuesField(name, triangles, calculator);
context.doc().addWithKey(name, docValuesField);
} else {
docValuesField.add(triangles, calculator);
}
}
return fields;
}
});
fieldType.setGeometryParser( (parser, mapper) -> geometryParser.parse(parser));
fieldType.setGeometryQueryBuilder(new VectorGeoShapeQueryProcessor());
}
}
public static final class GeoShapeWithDocValuesFieldType extends GeoShapeFieldMapper.GeoShapeFieldType {
public GeoShapeWithDocValuesFieldType() {
@ -154,15 +149,6 @@ public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
return new AbstractLatLonShapeDVIndexFieldData.Builder();
}
@Override
public Query existsQuery(QueryShardContext context) {
if (hasDocValues()) {
return new DocValuesFieldExistsQuery(name());
} else {
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
}
}
@Override
public ValuesSourceType getValuesSourceType() {
return GeoShapeValuesSourceType.instance();

View File

@ -8,106 +8,81 @@ package org.elasticsearch.xpack.spatial.index.mapper;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.XYDocValuesField;
import org.apache.lucene.document.XYPointField;
import org.apache.lucene.index.IndexOptions;
import org.elasticsearch.ElasticsearchParseException;
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.ParseField;
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.index.mapper.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper;
import org.elasticsearch.index.mapper.ArrayValueMapperParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.xpack.spatial.common.CartesianPoint;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryPointProcessor;
import java.io.IOException;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.mapper.TypeParsers.parseField;
/**
* Field Mapper for point type.
*
* Uses lucene 8 XYPoint encoding
*/
public class PointFieldMapper extends AbstractGeometryFieldMapper implements ArrayValueMapperParser {
public class PointFieldMapper extends AbstractPointGeometryFieldMapper<List<? extends CartesianPoint>, List<? extends CartesianPoint>>
implements ArrayValueMapperParser {
public static final String CONTENT_TYPE = "point";
public static class Names extends AbstractGeometryFieldMapper.Names {
public static final ParseField NULL_VALUE = new ParseField("null_value");
}
public static class Builder extends AbstractGeometryFieldMapper.Builder<Builder, PointFieldMapper> {
public static class Builder extends AbstractPointGeometryFieldMapper.Builder<Builder, PointFieldMapper, PointFieldType> {
public Builder(String name) {
super(name, new PointFieldType(), new PointFieldType());
builder = this;
}
@Override
public PointFieldMapper build(BuilderContext context, String simpleName, MappedFieldType fieldType,
MappedFieldType defaultFieldType, Settings indexSettings,
MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
CopyTo copyTo) {
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
setupFieldType(context);
return new PointFieldMapper(simpleName, fieldType, defaultFieldType, indexSettings, multiFields,
ignoreMalformed, ignoreZValue(context), copyTo);
}
@Override
public PointFieldType fieldType() {
return (PointFieldType)fieldType;
protected void setGeometryParser() {
PointParser<ParsedCartesianPoint> pointParser = new PointParser<>();
fieldType().setGeometryParser((parser, mapper) -> pointParser.parse(parser, mapper));
}
@Override
public PointFieldMapper build(BuilderContext context) {
return build(context, name, fieldType, defaultFieldType, context.indexSettings(),
multiFieldsBuilder.build(this, context), ignoreMalformed(context), copyTo);
@SuppressWarnings("unchecked")
protected void setGeometryIndexer(PointFieldType fieldType) {
fieldType.setGeometryIndexer(new PointIndexer(fieldType));
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
fieldType().setGeometryQueryBuilder(new ShapeQueryPointProcessor());
protected void setGeometryQueryBuilder(PointFieldType fieldType) {
fieldType.setGeometryQueryBuilder(new ShapeQueryPointProcessor());
}
}
public static class TypeParser extends AbstractGeometryFieldMapper.TypeParser<Builder> {
@Override
protected ParsedPoint newParsedPoint() {
return new ParsedCartesianPoint();
}
public static class TypeParser extends AbstractPointGeometryFieldMapper.TypeParser<CartesianPoint, Builder> {
@Override
protected Builder newBuilder(String name, Map<String, Object> params) {
return new PointFieldMapper.Builder(name);
}
@Override
@SuppressWarnings("rawtypes")
public Builder parse(String name, Map<String, Object> node, Map<String, Object> params, ParserContext parserContext) {
Builder builder = super.parse(name, node, params, parserContext);
parseField(builder, name, node, parserContext);
Object nullValue = null;
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String propName = entry.getKey();
Object propNode = entry.getValue();
if (Names.NULL_VALUE.match(propName, LoggingDeprecationHandler.INSTANCE)) {
if (propNode == null) {
throw new MapperParsingException("Property [null_value] cannot be null.");
}
nullValue = propNode;
iterator.remove();
}
}
if (nullValue != null) {
boolean ignoreMalformed = builder.ignoreMalformed().value();
boolean ignoreZValue = builder.ignoreZValue().value();
CartesianPoint point = CartesianPoint.parsePoint(nullValue, ignoreZValue);
protected CartesianPoint parseNullValue(Object nullValue, boolean ignoreZValue, boolean ignoreMalformed) {
ParsedCartesianPoint point = new ParsedCartesianPoint();
CartesianPoint.parsePoint(nullValue, point, ignoreZValue);
if (ignoreMalformed == false) {
if (Float.isFinite(point.getX()) == false) {
throw new IllegalArgumentException("illegal x value [" + point.getX() + "]");
@ -116,21 +91,45 @@ public class PointFieldMapper extends AbstractGeometryFieldMapper implements Arr
throw new IllegalArgumentException("illegal y value [" + point.getY() + "]");
}
}
builder.nullValue(point);
return point;
}
return builder;
}
/**
* Parses geopoint represented as an object or an array, ignores malformed geopoints if needed
*/
@Override
protected void parsePointIgnoringMalformed(XContentParser parser, ParsedPoint point) throws IOException {
super.parsePointIgnoringMalformed(parser, point);
CartesianPoint.parsePoint(parser, (CartesianPoint)point, ignoreZValue().value());
}
public PointFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> ignoreZValue, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, ignoreMalformed, ignoreZValue, multiFields, copyTo);
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, ignoreMalformed, ignoreZValue, copyTo);
}
@Override
protected void doMerge(Mapper mergeWith) {
super.doMerge(mergeWith);
@SuppressWarnings("unchecked")
protected void addStoredFields(ParseContext context, List<? extends CartesianPoint> points) {
for (CartesianPoint point : points) {
context.doc().add(new StoredField(fieldType().name(), point.toString()));
}
}
@Override
@SuppressWarnings("unchecked")
protected void addDocValuesFields(String name, List<? extends CartesianPoint> points, List<IndexableField> fields,
ParseContext context) {
for (CartesianPoint point : points) {
context.doc().add(new XYDocValuesField(fieldType().name(), point.getX(), point.getY()));
}
}
@Override
protected void addMultiFields(ParseContext context, List<? extends CartesianPoint> points) {
// noop
}
@Override
@ -138,11 +137,14 @@ public class PointFieldMapper extends AbstractGeometryFieldMapper implements Arr
return CONTENT_TYPE;
}
public static class PointFieldType extends AbstractGeometryFieldType {
@Override
public PointFieldType fieldType() {
return (PointFieldType)fieldType;
}
public static class PointFieldType extends AbstractPointGeometryFieldType<List<ParsedCartesianPoint>, List<ParsedCartesianPoint>> {
public PointFieldType() {
super();
setHasDocValues(true);
setDimensions(2, Integer.BYTES);
}
PointFieldType(PointFieldType ref) {
@ -160,90 +162,87 @@ public class PointFieldMapper extends AbstractGeometryFieldMapper implements Arr
}
}
protected void parse(ParseContext context, CartesianPoint point) throws IOException {
if (fieldType().indexOptions() != IndexOptions.NONE) {
context.doc().add(new XYPointField(fieldType().name(), point.getX(), point.getY()));
protected static class ParsedCartesianPoint extends CartesianPoint implements ParsedPoint {
@Override
public void validate(String fieldName) {
if (Float.isFinite(getX()) == false) {
throw new IllegalArgumentException("illegal x value [" + getX() + "] for " + fieldName);
}
if (fieldType().stored()) {
context.doc().add(new StoredField(fieldType().name(), point.toString()));
}
if (fieldType.hasDocValues()) {
context.doc().add(new XYDocValuesField(fieldType().name(), point.getX(), point.getY()));
} else if (fieldType().stored() || fieldType().indexOptions() != IndexOptions.NONE) {
createFieldNamesField(context);
}
// if the mapping contains multi-fields then throw an error?
if (multiFields.iterator().hasNext()) {
throw new ElasticsearchParseException("[{}] field type does not accept multi-fields", CONTENT_TYPE);
if (Float.isFinite(getY()) == false) {
throw new IllegalArgumentException("illegal y value [" + getY() + "] for " + fieldName);
}
}
@Override
public void parse(ParseContext context) throws IOException {
context.path().add(simpleName());
try {
CartesianPoint sparse = context.parseExternalValue(CartesianPoint.class);
if (sparse != null) {
parse(context, sparse);
} else {
sparse = new CartesianPoint();
XContentParser.Token token = context.parser().currentToken();
if (token == XContentParser.Token.START_ARRAY) {
token = context.parser().nextToken();
if (token == XContentParser.Token.VALUE_NUMBER) {
float x = context.parser().floatValue();
context.parser().nextToken();
float y = context.parser().floatValue();
token = context.parser().nextToken();
if (token == XContentParser.Token.VALUE_NUMBER) {
CartesianPoint.assertZValue(ignoreZValue.value(), context.parser().floatValue());
} else if (token != XContentParser.Token.END_ARRAY) {
throw new ElasticsearchParseException("[{}] field type does not accept > 3 dimensions", CONTENT_TYPE);
}
parse(context, sparse.reset(x, y));
} else {
while (token != XContentParser.Token.END_ARRAY) {
parsePointIgnoringMalformed(context, sparse);
token = context.parser().nextToken();
}
}
} else if (token == XContentParser.Token.VALUE_NULL) {
if (fieldType.nullValue() != null) {
parse(context, (CartesianPoint) fieldType.nullValue());
}
} else {
parsePointIgnoringMalformed(context, sparse);
}
}
} catch (Exception ex) {
throw new MapperParsingException("failed to parse field [{}] of type [{}]", ex, fieldType().name(), fieldType().typeName());
}
context.path().remove();
}
/**
* Parses point represented as an object or an array, ignores malformed points if needed
*/
private void parsePointIgnoringMalformed(ParseContext context, CartesianPoint sparse) throws IOException {
try {
parse(context, CartesianPoint.parsePoint(context.parser(), sparse, ignoreZValue().value()));
} catch (ElasticsearchParseException e) {
if (ignoreMalformed.value() == false) {
throw e;
}
context.addIgnoredField(fieldType.name());
}
public void normalize(String fieldName) {
// noop
}
@Override
public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
super.doXContentBody(builder, includeDefaults, params);
if (includeDefaults || fieldType().nullValue() != null) {
builder.field(Names.NULL_VALUE.getPreferredName(), fieldType().nullValue());
public boolean isNormalizable(double coord) {
return false;
}
@Override
public void resetCoords(double x, double y) {
this.reset((float)x, (float)y);
}
@Override
public boolean equals(Object other) {
double oX;
double oY;
if (other instanceof CartesianPoint) {
CartesianPoint o = (CartesianPoint)other;
oX = o.getX();
oY = o.getY();
} else if (other instanceof ParsedCartesianPoint == false) {
return false;
} else {
ParsedCartesianPoint o = (ParsedCartesianPoint)other;
oX = o.getX();
oY = o.getY();
}
if (Double.compare(oX, x) != 0) return false;
if (Double.compare(oY, y) != 0) return false;
return true;
}
@Override
public int hashCode() {
return super.hashCode();
}
}
protected static class PointIndexer implements Indexer<List<ParsedCartesianPoint>, List<ParsedCartesianPoint>> {
protected final PointFieldType fieldType;
PointIndexer(PointFieldType fieldType) {
this.fieldType = fieldType;
}
@Override
public List<ParsedCartesianPoint> prepareForIndexing(List<ParsedCartesianPoint> points) {
if (points == null || points.isEmpty()) {
return Collections.emptyList();
}
return points;
}
@Override
@SuppressWarnings("unchecked")
public Class<List<ParsedCartesianPoint>> processedClass() {
return (Class<List<ParsedCartesianPoint>>)(Object)List.class;
}
@Override
public List<IndexableField> indexShape(ParseContext context, List<ParsedCartesianPoint> points) {
ArrayList<IndexableField> fields = new ArrayList<>(1);
for (ParsedCartesianPoint point : points) {
fields.add(new XYPointField(fieldType.name(), point.getX(), point.getY()));
}
return fields;
}
}
}

View File

@ -14,8 +14,10 @@ import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryProcessor;
import java.util.List;
import java.util.Map;
/**
@ -43,7 +45,7 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry,
@SuppressWarnings({"unchecked", "rawtypes"})
public static class Builder extends AbstractShapeGeometryFieldMapper.Builder<AbstractShapeGeometryFieldMapper.Builder,
ShapeFieldMapper> {
ShapeFieldMapper, ShapeFieldType> {
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
@ -58,21 +60,20 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry,
}
@Override
public ShapeFieldType fieldType() {
return (ShapeFieldType)fieldType;
protected void setGeometryParser(ShapeFieldType fieldType) {
GeometryParser geometryParser = new GeometryParser(fieldType.orientation().getAsBoolean(),
coerce().value(), ignoreZValue().value());
fieldType().setGeometryParser((parser, mapper) -> geometryParser.parse(parser));
}
@SuppressWarnings("unchecked")
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
protected void setGeometryIndexer(ShapeFieldType fieldType) {
fieldType.setGeometryIndexer(new ShapeIndexer(fieldType.name()));
}
GeometryParser geometryParser = new GeometryParser(orientation == Orientation.RIGHT,
coerce(context).value(), ignoreZValue().value());
fieldType().setGeometryIndexer(new ShapeIndexer(fieldType().name()));
fieldType().setGeometryParser((parser, mapper) -> geometryParser.parse(parser));
fieldType().setGeometryQueryBuilder(new ShapeQueryProcessor());
@Override
protected void setGeometryQueryBuilder(ShapeFieldType fieldType) {
fieldType.setGeometryQueryBuilder(new ShapeQueryProcessor());
}
}
@ -124,6 +125,23 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry,
multiFields, copyTo);
}
@Override
protected void addStoredFields(ParseContext context, Geometry geometry) {
// noop: we currently do not store geo_shapes
// @todo store as geojson string?
}
@Override
@SuppressWarnings("rawtypes")
protected void addDocValuesFields(String name, Geometry geometry, List fields, ParseContext context) {
// we should throw a mapping exception before we get here
}
@Override
protected void addMultiFields(ParseContext context, Geometry geometry) {
// noop (completion suggester currently not compatible with geo_shape)
}
@Override
protected String contentType() {
return CONTENT_TYPE;

View File

@ -81,7 +81,7 @@ public final class CircleProcessor extends AbstractProcessor {
parser.nextToken(); // START_OBJECT
parser.nextToken(); // "shape" field key
parser.nextToken(); // shape value
GeometryFormat geometryFormat = PARSER.geometryFormat(parser);
GeometryFormat<Geometry> geometryFormat = PARSER.geometryFormat(parser);
Geometry geometry = geometryFormat.fromXContent(parser);
if (ShapeType.CIRCLE.equals(geometry.type())) {
Circle circle = (Circle) geometry;

View File

@ -47,7 +47,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

View File

@ -21,8 +21,8 @@ import org.hamcrest.CoreMatchers;
import java.io.IOException;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper.Names.NULL_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.NULL_VALUE;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
@ -239,7 +239,7 @@ public class PointFieldMapperTests extends CartesianFieldMapperTests {
XContentType.JSON));
assertThat(doc.rootDoc().getField("location"), notNullValue());
BytesRef defaultValue = doc.rootDoc().getField("location").binaryValue();
BytesRef defaultValue = doc.rootDoc().getBinaryValue("location");
doc = defaultMapper.parse(new SourceToParse("test","type", "1",
BytesReference.bytes(XContentFactory.jsonBuilder()
@ -248,7 +248,7 @@ public class PointFieldMapperTests extends CartesianFieldMapperTests {
.endObject()),
XContentType.JSON));
// Shouldn't matter if we specify the value explicitly or use null value
assertThat(defaultValue, equalTo(doc.rootDoc().getField("location").binaryValue()));
assertThat(defaultValue, equalTo(doc.rootDoc().getBinaryValue("location")));
doc = defaultMapper.parse(new SourceToParse("test","type", "1",
BytesReference.bytes(XContentFactory.jsonBuilder()
@ -257,7 +257,7 @@ public class PointFieldMapperTests extends CartesianFieldMapperTests {
.endObject()),
XContentType.JSON));
// Shouldn't matter if we specify the value explicitly or use null value
assertThat(defaultValue, not(equalTo(doc.rootDoc().getField("location").binaryValue())));
assertThat(defaultValue, not(equalTo(doc.rootDoc().getBinaryValue("location"))));
}
/**

View File

@ -21,7 +21,7 @@ import org.elasticsearch.index.mapper.MapperService;
import java.io.IOException;
import java.util.Collections;
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
import static org.elasticsearch.index.mapper.AbstractPointGeometryFieldMapper.Names.IGNORE_Z_VALUE;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;