Skip to content

Commit

Permalink
Add geo_shape mapper supporting doc-values in Spatial Plugin
Browse files Browse the repository at this point in the history
After elastic#53562, the `geo_shape` field mapper is registered within
a module. This opens the door for introducing a new `geo_shape`
field mapper into the Spatial Plugin that has doc-values support.

This is very much an extension of server's GeoShapeFieldMapper,
but with the addition of the doc values implementation.
  • Loading branch information
talevy committed Apr 10, 2020
1 parent 1328e4b commit bce21fb
Show file tree
Hide file tree
Showing 37 changed files with 3,835 additions and 26 deletions.
6 changes: 6 additions & 0 deletions distribution/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
excludePlatforms = []
}
from(buildModules) {
// geo registers the geo_shape mapper that is overridden by
// the geo_shape mapper registered in the x-pack-spatial plugin
if (oss == false) {
exclude "**/geo/**"
}

for (String excludePlatform : excludePlatforms) {
exclude "**/platform/${excludePlatform}-x86_64/**"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

package org.elasticsearch.geo;

import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.plugins.MapperPlugin;
Expand All @@ -32,6 +31,6 @@ public class GeoPlugin extends Plugin implements MapperPlugin {

@Override
public Map<String, Mapper.TypeParser> getMappers() {
return Collections.singletonMap(GeoShapeFieldMapper.CONTENT_TYPE, new AbstractGeometryFieldMapper.TypeParser());
return Collections.singletonMap(GeoShapeFieldMapper.CONTENT_TYPE, new GeoShapeFieldMapper.TypeParser());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ public long readLong() throws IOException {
}
}

public void position(int newPosition) throws IOException {
buffer.position(newPosition);
}

public int position() throws IOException {
return buffer.position();
}

@Override
public void reset() throws IOException {
buffer.reset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ protected void setupFieldType(BuilderContext context) {

protected static final String DEPRECATED_PARAMETERS_KEY = "deprecated_parameters";

public static class TypeParser implements Mapper.TypeParser {
public abstract static class TypeParser implements Mapper.TypeParser {
protected abstract Builder newBuilder(String name, Map<String, Object> params);

protected boolean parseXContentParameters(String name, Map.Entry<String, Object> entry, Map<String, Object> params)
throws MapperParsingException {
if (DeprecatedParameters.parse(name, entry.getKey(), entry.getValue(),
Expand All @@ -195,13 +197,6 @@ protected boolean parseXContentParameters(String name, Map.Entry<String, Object>
return false;
}

protected Builder newBuilder(String name, Map<String, Object> params) {
if (params.containsKey(DEPRECATED_PARAMETERS_KEY)) {
return new LegacyGeoShapeFieldMapper.Builder(name, (DeprecatedParameters)params.get(DEPRECATED_PARAMETERS_KEY));
}
return new GeoShapeFieldMapper.Builder(name);
}

@Override
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
Map<String, Object> params = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.VectorGeoShapeQueryProcessor;

import java.util.Map;

/**
* FieldMapper for indexing {@link LatLonShape}s.
* <p>
Expand Down Expand Up @@ -96,6 +98,18 @@ public String typeName() {
}
}

public static final class TypeParser extends AbstractGeometryFieldMapper.TypeParser {

@Override
protected AbstractGeometryFieldMapper.Builder newBuilder(String name, Map<String, Object> params) {
if (params.containsKey(DEPRECATED_PARAMETERS_KEY)) {
return new LegacyGeoShapeFieldMapper.Builder(name,
(LegacyGeoShapeFieldMapper.DeprecatedParameters)params.get(DEPRECATED_PARAMETERS_KEY));
}
return new GeoShapeFieldMapper.Builder(name);
}
}

public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
Explicit<Boolean> ignoreZValue, Settings indexSettings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
/**
* Utility class that converts geometries into Lucene-compatible form for indexing in a geo_shape field.
*/
public final class GeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<Geometry, Geometry> {
public class GeoShapeIndexer implements AbstractGeometryFieldMapper.Indexer<Geometry, Geometry> {

private final boolean orientation;
private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public String toString() {
};
}

static SortedBinaryDocValues replaceMissing(final SortedBinaryDocValues values, final BytesRef missing) {
public static SortedBinaryDocValues replaceMissing(final SortedBinaryDocValues values, final BytesRef missing) {
return new SortedBinaryDocValues() {

private int count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package org.elasticsearch.test;

import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.plugins.MapperPlugin;
Expand All @@ -40,7 +39,7 @@ public class TestGeoShapeFieldMapperPlugin extends Plugin implements MapperPlugi
@Override
public Map<String, Mapper.TypeParser> getMappers() {
Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>();
mappers.put(GeoShapeFieldMapper.CONTENT_TYPE, new AbstractGeometryFieldMapper.TypeParser());
mappers.put(GeoShapeFieldMapper.CONTENT_TYPE, new GeoShapeFieldMapper.TypeParser());
return Collections.unmodifiableMap(mappers);
}
}
2 changes: 1 addition & 1 deletion x-pack/plugin/spatial/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ esplugin {
dependencies {
compileOnly project(path: xpackModule('core'), configuration: 'default')
testCompile project(path: xpackModule('core'), configuration: 'testArtifacts')
testCompile project(path: ':modules:geo', configuration: 'runtime')
compile project(path: ':modules:geo', configuration: 'default')
}

licenseHeaders {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,30 @@

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.geo.GeoPlugin;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.IngestPlugin;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
import org.elasticsearch.xpack.spatial.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.PointFieldMapper;
import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper;
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
import org.elasticsearch.xpack.spatial.ingest.CircleProcessor;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.singletonList;

public class SpatialPlugin extends Plugin implements ActionPlugin, MapperPlugin, SearchPlugin, IngestPlugin {

public SpatialPlugin(Settings settings) {
}
public class SpatialPlugin extends GeoPlugin implements ActionPlugin, MapperPlugin, SearchPlugin, IngestPlugin {

@Override
public List<ActionPlugin.ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
Expand All @@ -44,9 +41,10 @@ public SpatialPlugin(Settings settings) {

@Override
public Map<String, Mapper.TypeParser> getMappers() {
Map<String, Mapper.TypeParser> mappers = new LinkedHashMap<>();
Map<String, Mapper.TypeParser> mappers = new HashMap<>(super.getMappers());
mappers.put(ShapeFieldMapper.CONTENT_TYPE, new ShapeFieldMapper.TypeParser());
mappers.put(PointFieldMapper.CONTENT_TYPE, new PointFieldMapper.TypeParser());
mappers.put(GeoShapeFieldMapper.CONTENT_TYPE, new GeoShapeFieldMapper.TypeParser());
return Collections.unmodifiableMap(mappers);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.spatial.index.mapper;

import org.apache.lucene.util.Accountable;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;

import java.util.Collection;
import java.util.Collections;

public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoShapeFieldData {

@Override
public final SortedBinaryDocValues getBytesValues() {
throw new UnsupportedOperationException("scripts and term aggs are not supported by geo_shape doc values");
}

@Override
public final ScriptDocValues.BytesRefs getScriptValues() {
throw new UnsupportedOperationException("scripts are not supported by geo_shape doc values");
}

public static LeafGeoShapeFieldData empty(final int maxDoc) {
return new AbstractAtomicGeoShapeShapeFieldData() {

@Override
public long ramBytesUsed() {
return 0;
}

@Override
public Collection<Accountable> getChildResources() {
return Collections.emptyList();
}

@Override
public void close() {
}

@Override
public MultiGeoShapeValues getGeoShapeValues() {
return MultiGeoShapeValues.EMPTY;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.spatial.index.mapper;

import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.SortField;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.sort.BucketedSort;
import org.elasticsearch.search.sort.SortOrder;

public abstract class AbstractLatLonShapeDVIndexFieldData extends DocValuesIndexFieldData implements IndexGeoShapeFieldData {
AbstractLatLonShapeDVIndexFieldData(Index index, String fieldName) {
super(index, fieldName);
}

@Override
public SortField sortField(@Nullable Object missingValue, MultiValueMode sortMode, XFieldComparatorSource.Nested nested,
boolean reverse) {
throw new IllegalArgumentException("can't sort on geo_shape field without using specific sorting feature, like geo_distance");
}

public static class LatLonShapeDVIndexFieldData extends AbstractLatLonShapeDVIndexFieldData {
public LatLonShapeDVIndexFieldData(Index index, String fieldName) {
super(index, fieldName);
}

@Override
public LeafGeoShapeFieldData load(LeafReaderContext context) {
LeafReader reader = context.reader();
FieldInfo info = reader.getFieldInfos().fieldInfo(fieldName);
if (info != null) {
checkCompatible(info);
}
return new LatLonShapeDVAtomicShapeFieldData(reader, fieldName);
}

@Override
public LeafGeoShapeFieldData loadDirect(LeafReaderContext context) throws Exception {
return load(context);
}

@Override
public BucketedSort newBucketedSort(BigArrays bigArrays, Object missingValue, MultiValueMode sortMode,
IndexFieldData.XFieldComparatorSource.Nested nested, SortOrder sortOrder, DocValueFormat format,
int bucketSize, BucketedSort.ExtraData extra) {
throw new IllegalArgumentException("can't sort on geo_shape field without using specific sorting feature, like geo_distance");
}

/** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */
static void checkCompatible(FieldInfo fieldInfo) {
// dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment.
if (fieldInfo.getDocValuesType() != DocValuesType.NONE
&& fieldInfo.getDocValuesType() != DocValuesType.BINARY) {
throw new IllegalArgumentException("field=\"" + fieldInfo.name + "\" was indexed with docValuesType="
+ fieldInfo.getDocValuesType() + " but this type has docValuesType="
+ DocValuesType.BINARY + ", is the field really a geo-shape field?");
}
}
}

public static class Builder implements IndexFieldData.Builder {
@Override
public IndexFieldData<?> build(IndexSettings indexSettings, MappedFieldType fieldType, IndexFieldDataCache cache,
CircuitBreakerService breakerService, MapperService mapperService) {
// ignore breaker
return new LatLonShapeDVIndexFieldData(indexSettings.getIndex(), fieldType.name());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.spatial.index.mapper;

import org.apache.lucene.document.ShapeField;
import org.apache.lucene.store.ByteBuffersDataOutput;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.index.mapper.CustomDocValuesField;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BinaryGeoShapeDocValuesField extends CustomDocValuesField {

private final List<ShapeField.DecodedTriangle> triangles;
private final CentroidCalculator centroidCalculator;

public BinaryGeoShapeDocValuesField(String name, ShapeField.DecodedTriangle[] triangles, CentroidCalculator centroidCalculator) {
super(name);
this.triangles = new ArrayList<>(triangles.length);
this.centroidCalculator = centroidCalculator;
this.triangles.addAll(Arrays.asList(triangles));
}

public void add(ShapeField.DecodedTriangle[] triangles, CentroidCalculator centroidCalculator) {
this.triangles.addAll(Arrays.asList(triangles));
this.centroidCalculator.addFrom(centroidCalculator);
}

@Override
public BytesRef binaryValue() {
try {
final TriangleTreeWriter writer = new TriangleTreeWriter(triangles, GeoShapeCoordinateEncoder.INSTANCE, centroidCalculator);
ByteBuffersDataOutput output = new ByteBuffersDataOutput();
writer.writeTo(output);
return new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size()));
} catch (IOException e) {
throw new ElasticsearchException("failed to encode shape", e);
}
}
}
Loading

0 comments on commit bce21fb

Please sign in to comment.