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:
parent
8f2c1cda2e
commit
9b64149ad2
|
@ -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() {
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,7 +255,11 @@ public abstract class AbstractGeometryFieldMapper extends FieldMapper {
|
|||
|
||||
@Override
|
||||
public Query existsQuery(QueryShardContext context) {
|
||||
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
|
||||
if (hasDocValues()) {
|
||||
return new DocValuesFieldExistsQuery(name());
|
||||
} else {
|
||||
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,81 +65,94 @@ 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();
|
||||
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() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (nullValue != null) {
|
||||
boolean ignoreZValue = builder.ignoreZValue().value();
|
||||
boolean ignoreMalformed = builder.ignoreMalformed().value();
|
||||
GeoPoint point = GeoUtils.parseGeoPoint(nullValue, ignoreZValue);
|
||||
if (ignoreMalformed == false) {
|
||||
if (point.lat() > 90.0 || point.lat() < -90.0) {
|
||||
throw new IllegalArgumentException("illegal latitude value [" + point.lat() + "]");
|
||||
}
|
||||
if (point.lon() > 180.0 || point.lon() < -180) {
|
||||
throw new IllegalArgumentException("illegal longitude value [" + point.lon() + "]");
|
||||
}
|
||||
} else {
|
||||
GeoUtils.normalizePoint(point);
|
||||
if (point.lon() > 180.0 || point.lon() < -180) {
|
||||
throw new IllegalArgumentException("illegal longitude value [" + point.lon() + "]");
|
||||
}
|
||||
builder.nullValue(point);
|
||||
} else {
|
||||
GeoUtils.normalizePoint(point);
|
||||
}
|
||||
return builder;
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@Override
|
||||
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());
|
||||
@Override
|
||||
public boolean isNormalizable(double coord) {
|
||||
return Double.isNaN(coord) == false && Double.isInfinite(coord) == false;
|
||||
}
|
||||
|
||||
try {
|
||||
GeoPoint sparse = context.parseExternalValue(GeoPoint.class);
|
||||
@Override
|
||||
public void resetCoords(double x, double y) {
|
||||
this.reset(y, x);
|
||||
}
|
||||
|
||||
if (sparse != null) {
|
||||
parse(context, sparse);
|
||||
@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 {
|
||||
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);
|
||||
}
|
||||
ParsedGeoPoint o = (ParsedGeoPoint)other;
|
||||
oLat = o.lat();
|
||||
oLon = o.lon();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", ex, fieldType().name(), fieldType().typeName());
|
||||
if (Double.compare(oLat, lat) != 0) return false;
|
||||
if (Double.compare(oLon, lon) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
context.path().remove();
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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();
|
||||
}
|
||||
context.addIgnoredField(fieldType.name());
|
||||
return geoPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
@Override
|
||||
public Class<List<ParsedGeoPoint>> processedClass() {
|
||||
return (Class<List<ParsedGeoPoint>>)(Object)List.class;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNormalizable(double coord) {
|
||||
return Double.isNaN(coord) == false && Double.isInfinite(coord) == false;
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,46 +92,49 @@ public class GeoShapeWithDocValuesFieldMapper extends GeoShapeFieldMapper {
|
|||
return new Explicit<>(fieldType.hasDocValues(), false);
|
||||
}
|
||||
|
||||
protected void setupFieldType(BuilderContext context) {
|
||||
super.setupFieldType(context);
|
||||
@Override
|
||||
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));
|
||||
}
|
||||
|
||||
GeoShapeWithDocValuesFieldType fieldType = (GeoShapeWithDocValuesFieldType)fieldType();
|
||||
boolean orientation = fieldType.orientation() == ShapeBuilder.Orientation.RIGHT;
|
||||
@Override
|
||||
protected void setGeometryIndexer(GeoShapeWithDocValuesFieldType 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()) {
|
||||
@Override
|
||||
public List<IndexableField> indexShape(ParseContext context, Geometry shape) {
|
||||
List<IndexableField> fields = super.indexShape(context, shape);
|
||||
if (fieldType().hasDocValues()) {
|
||||
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();
|
||||
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());
|
||||
}
|
||||
BinaryGeoShapeDocValuesField docValuesField =
|
||||
(BinaryGeoShapeDocValuesField) context.doc().getByKey(name);
|
||||
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));
|
||||
@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 = ((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());
|
||||
}
|
||||
BinaryGeoShapeDocValuesField docValuesField =
|
||||
(BinaryGeoShapeDocValuesField) context.doc().getByKey(name);
|
||||
if (docValuesField == null) {
|
||||
docValuesField = new BinaryGeoShapeDocValuesField(name, triangles, calculator);
|
||||
context.doc().addWithKey(name, docValuesField);
|
||||
|
||||
} else {
|
||||
docValuesField.add(triangles, calculator);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class GeoShapeWithDocValuesFieldType extends GeoShapeFieldMapper.GeoShapeFieldType {
|
||||
public GeoShapeWithDocValuesFieldType() {
|
||||
super();
|
||||
|
@ -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();
|
||||
|
|
|
@ -8,129 +8,128 @@ 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();
|
||||
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() + "]");
|
||||
}
|
||||
if (Float.isFinite(point.getY()) == false) {
|
||||
throw new IllegalArgumentException("illegal y value [" + point.getY() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (nullValue != null) {
|
||||
boolean ignoreMalformed = builder.ignoreMalformed().value();
|
||||
boolean ignoreZValue = builder.ignoreZValue().value();
|
||||
CartesianPoint point = CartesianPoint.parsePoint(nullValue, ignoreZValue);
|
||||
if (ignoreMalformed == false) {
|
||||
if (Float.isFinite(point.getX()) == false) {
|
||||
throw new IllegalArgumentException("illegal x value [" + point.getX() + "]");
|
||||
}
|
||||
if (Float.isFinite(point.getY()) == false) {
|
||||
throw new IllegalArgumentException("illegal y value [" + point.getY() + "]");
|
||||
}
|
||||
}
|
||||
builder.nullValue(point);
|
||||
}
|
||||
return builder;
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,14 +137,17 @@ 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) {
|
||||
PointFieldType(PointFieldType ref) {
|
||||
super(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 (Float.isFinite(getY()) == false) {
|
||||
throw new IllegalArgumentException("illegal y value [" + getY() + "] 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(ParseContext context) throws IOException {
|
||||
context.path().add(simpleName());
|
||||
@Override
|
||||
public void normalize(String fieldName) {
|
||||
// noop
|
||||
}
|
||||
|
||||
try {
|
||||
CartesianPoint sparse = context.parseExternalValue(CartesianPoint.class);
|
||||
@Override
|
||||
public boolean isNormalizable(double coord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparse != null) {
|
||||
parse(context, sparse);
|
||||
@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 {
|
||||
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);
|
||||
}
|
||||
ParsedCartesianPoint o = (ParsedCartesianPoint)other;
|
||||
oX = o.getX();
|
||||
oY = o.getY();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", ex, fieldType().name(), fieldType().typeName());
|
||||
if (Double.compare(oX, x) != 0) return false;
|
||||
if (Double.compare(oY, y) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
context.path().remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses point represented as an object or an array, ignores malformed points if needed
|
||||
*/
|
||||
private void parsePointIgnoringMalformed(ParseContext context, CartesianPoint sparse) throws IOException {
|
||||
try {
|
||||
parse(context, CartesianPoint.parsePoint(context.parser(), sparse, ignoreZValue().value()));
|
||||
} catch (ElasticsearchParseException e) {
|
||||
if (ignoreMalformed.value() == false) {
|
||||
throw e;
|
||||
}
|
||||
context.addIgnoredField(fieldType.name());
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue