this commit leverages the new geo_shape doc values to register a new geo_centroid aggregator that works on geo_shape field.
This commit is contained in:
parent
8df5cff9c1
commit
6ba5148ead
|
@ -30,7 +30,7 @@ import java.util.Map;
|
|||
@FunctionalInterface
|
||||
public interface GeoCentroidAggregatorSupplier extends AggregatorSupplier {
|
||||
|
||||
GeoCentroidAggregator build(String name, SearchContext context, Aggregator parent,
|
||||
MetricsAggregator build(String name, SearchContext context, Aggregator parent,
|
||||
ValuesSource valuesSource,
|
||||
Map<String, Object> metadata) throws IOException;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
|
|||
return GeoEncodingUtils.decodeLongitude((int) (encodedLatLon & 0xFFFFFFFFL));
|
||||
}
|
||||
|
||||
InternalGeoCentroid(String name, GeoPoint centroid, long count, Map<String, Object> metadata) {
|
||||
public InternalGeoCentroid(String name, GeoPoint centroid, long count, Map<String, Object> metadata) {
|
||||
super(name, metadata);
|
||||
assert (centroid == null) == (count == 0);
|
||||
this.centroid = centroid;
|
||||
|
|
|
@ -45,7 +45,8 @@ public class XPackLicenseState {
|
|||
SECURITY_TOKEN_SERVICE(OperationMode.GOLD, false),
|
||||
SECURITY_API_KEY_SERVICE(OperationMode.MISSING, false),
|
||||
SECURITY_AUTHORIZATION_REALM(OperationMode.PLATINUM, true),
|
||||
SECURITY_AUTHORIZATION_ENGINE(OperationMode.PLATINUM, true);
|
||||
SECURITY_AUTHORIZATION_ENGINE(OperationMode.PLATINUM, true),
|
||||
SPATIAL_GEO_CENTROID(OperationMode.GOLD, true);
|
||||
|
||||
final OperationMode minimumOperationMode;
|
||||
final boolean needsActive;
|
||||
|
|
|
@ -18,7 +18,7 @@ dependencies {
|
|||
|
||||
restResources {
|
||||
restApi {
|
||||
includeCore '_common', 'indices', 'index', 'search'
|
||||
includeCore '_common', 'bulk', 'indices', 'index', 'search'
|
||||
}
|
||||
restTests {
|
||||
includeCore 'geo_shape'
|
||||
|
@ -26,6 +26,7 @@ restResources {
|
|||
}
|
||||
|
||||
testClusters.integTest {
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
testDistribution = 'DEFAULT'
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,15 @@ import org.elasticsearch.common.inject.Module;
|
|||
import org.elasticsearch.geo.GeoPlugin;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.ingest.Processor;
|
||||
import org.elasticsearch.license.LicenseUtils;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.plugins.IngestPlugin;
|
||||
import org.elasticsearch.plugins.MapperPlugin;
|
||||
import org.elasticsearch.plugins.SearchPlugin;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoBoundsAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoBoundsAggregatorSupplier;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregatorSupplier;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
|
||||
import org.elasticsearch.xpack.core.XPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.GeoShapeWithDocValuesFieldMapper;
|
||||
|
@ -21,6 +25,7 @@ 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 org.elasticsearch.xpack.spatial.aggregations.metrics.GeoShapeCentroidAggregator;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.metrics.GeoShapeBoundsAggregator;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
@ -42,6 +47,11 @@ public class SpatialPlugin extends GeoPlugin implements MapperPlugin, SearchPlug
|
|||
});
|
||||
}
|
||||
|
||||
// to be overriden by tests
|
||||
protected XPackLicenseState getLicenseState() {
|
||||
return XPackPlugin.getSharedLicenseState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Mapper.TypeParser> getMappers() {
|
||||
Map<String, Mapper.TypeParser> mappers = new HashMap<>(super.getMappers());
|
||||
|
@ -58,7 +68,8 @@ public class SpatialPlugin extends GeoPlugin implements MapperPlugin, SearchPlug
|
|||
|
||||
@Override
|
||||
public List<Consumer<ValuesSourceRegistry.Builder>> getAggregationExtentions() {
|
||||
return org.elasticsearch.common.collect.List.of(SpatialPlugin::registerGeoShapeBoundsAggregator);
|
||||
return org.elasticsearch.common.collect.List.of(this::registerGeoShapeBoundsAggregator,
|
||||
this::registerGeoShapeCentroidAggregator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,10 +77,21 @@ public class SpatialPlugin extends GeoPlugin implements MapperPlugin, SearchPlug
|
|||
return Collections.singletonMap(CircleProcessor.TYPE, new CircleProcessor.Factory());
|
||||
}
|
||||
|
||||
public static void registerGeoShapeBoundsAggregator(ValuesSourceRegistry.Builder builder) {
|
||||
public void registerGeoShapeBoundsAggregator(ValuesSourceRegistry.Builder builder) {
|
||||
builder.register(GeoBoundsAggregationBuilder.NAME, GeoShapeValuesSourceType.instance(),
|
||||
(GeoBoundsAggregatorSupplier) (name, aggregationContext, parent, valuesSource, wrapLongitude, metadata)
|
||||
-> new GeoShapeBoundsAggregator(name, aggregationContext, parent, (GeoShapeValuesSource) valuesSource,
|
||||
wrapLongitude, metadata));
|
||||
}
|
||||
|
||||
public void registerGeoShapeCentroidAggregator(ValuesSourceRegistry.Builder builder) {
|
||||
builder.register(GeoCentroidAggregationBuilder.NAME, GeoShapeValuesSourceType.instance(),
|
||||
(GeoCentroidAggregatorSupplier) (name, aggregationContext, parent, valuesSource, metadata)
|
||||
-> {
|
||||
if (getLicenseState().isAllowed(XPackLicenseState.Feature.SPATIAL_GEO_CENTROID)) {
|
||||
return new GeoShapeCentroidAggregator(name, aggregationContext, parent, (GeoShapeValuesSource) valuesSource, metadata);
|
||||
}
|
||||
throw LicenseUtils.newComplianceException("geo_centroid aggregation on geo_shape fields");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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.aggregations.metrics;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.common.util.ByteArray;
|
||||
import org.elasticsearch.common.util.DoubleArray;
|
||||
import org.elasticsearch.common.util.LongArray;
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregation;
|
||||
import org.elasticsearch.search.aggregations.LeafBucketCollector;
|
||||
import org.elasticsearch.search.aggregations.LeafBucketCollectorBase;
|
||||
import org.elasticsearch.search.aggregations.metrics.CompensatedSum;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid;
|
||||
import org.elasticsearch.search.aggregations.metrics.MetricsAggregator;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.MultiGeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A geo metric aggregator that computes a geo-centroid from a {@code geo_shape} type field
|
||||
*/
|
||||
public final class GeoShapeCentroidAggregator extends MetricsAggregator {
|
||||
private final GeoShapeValuesSource valuesSource;
|
||||
private DoubleArray lonSum, lonCompensations, latSum, latCompensations, weightSum, weightCompensations;
|
||||
private LongArray counts;
|
||||
private ByteArray dimensionalShapeTypes;
|
||||
|
||||
public GeoShapeCentroidAggregator(String name, SearchContext context, Aggregator parent,
|
||||
GeoShapeValuesSource valuesSource, Map<String, Object> metadata) throws IOException {
|
||||
super(name, context, parent, metadata);
|
||||
this.valuesSource = valuesSource;
|
||||
if (valuesSource != null) {
|
||||
final BigArrays bigArrays = context.bigArrays();
|
||||
lonSum = bigArrays.newDoubleArray(1, true);
|
||||
lonCompensations = bigArrays.newDoubleArray(1, true);
|
||||
latSum = bigArrays.newDoubleArray(1, true);
|
||||
latCompensations = bigArrays.newDoubleArray(1, true);
|
||||
weightSum = bigArrays.newDoubleArray(1, true);
|
||||
weightCompensations = bigArrays.newDoubleArray(1, true);
|
||||
counts = bigArrays.newLongArray(1, true);
|
||||
dimensionalShapeTypes = bigArrays.newByteArray(1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
|
||||
if (valuesSource == null) {
|
||||
return LeafBucketCollector.NO_OP_COLLECTOR;
|
||||
}
|
||||
final BigArrays bigArrays = context.bigArrays();
|
||||
final MultiGeoShapeValues values = valuesSource.geoShapeValues(ctx);
|
||||
final CompensatedSum compensatedSumLat = new CompensatedSum(0, 0);
|
||||
final CompensatedSum compensatedSumLon = new CompensatedSum(0, 0);
|
||||
final CompensatedSum compensatedSumWeight = new CompensatedSum(0, 0);
|
||||
|
||||
return new LeafBucketCollectorBase(sub, values) {
|
||||
@Override
|
||||
public void collect(int doc, long bucket) throws IOException {
|
||||
latSum = bigArrays.grow(latSum, bucket + 1);
|
||||
lonSum = bigArrays.grow(lonSum, bucket + 1);
|
||||
weightSum = bigArrays.grow(weightSum, bucket + 1);
|
||||
lonCompensations = bigArrays.grow(lonCompensations, bucket + 1);
|
||||
latCompensations = bigArrays.grow(latCompensations, bucket + 1);
|
||||
weightCompensations = bigArrays.grow(weightCompensations, bucket + 1);
|
||||
counts = bigArrays.grow(counts, bucket + 1);
|
||||
dimensionalShapeTypes = bigArrays.grow(dimensionalShapeTypes, bucket + 1);
|
||||
|
||||
if (values.advanceExact(doc)) {
|
||||
final int valueCount = values.docValueCount();
|
||||
// increment by the number of points for this document
|
||||
counts.increment(bucket, valueCount);
|
||||
// Compute the sum of double values with Kahan summation algorithm which is more
|
||||
// accurate than naive summation.
|
||||
DimensionalShapeType shapeType = DimensionalShapeType.fromOrdinalByte(dimensionalShapeTypes.get(bucket));
|
||||
double sumLat = latSum.get(bucket);
|
||||
double compensationLat = latCompensations.get(bucket);
|
||||
double sumLon = lonSum.get(bucket);
|
||||
double compensationLon = lonCompensations.get(bucket);
|
||||
double sumWeight = weightSum.get(bucket);
|
||||
double compensatedWeight = weightCompensations.get(bucket);
|
||||
|
||||
compensatedSumLat.reset(sumLat, compensationLat);
|
||||
compensatedSumLon.reset(sumLon, compensationLon);
|
||||
compensatedSumWeight.reset(sumWeight, compensatedWeight);
|
||||
|
||||
// update the sum
|
||||
for (int i = 0; i < valueCount; ++i) {
|
||||
MultiGeoShapeValues.GeoShapeValue value = values.nextValue();
|
||||
int compares = shapeType.compareTo(value.dimensionalShapeType());
|
||||
if (compares < 0) {
|
||||
double coordinateWeight = value.weight();
|
||||
compensatedSumLat.reset(coordinateWeight * value.lat(), 0.0);
|
||||
compensatedSumLon.reset(coordinateWeight * value.lon(), 0.0);
|
||||
compensatedSumWeight.reset(coordinateWeight, 0.0);
|
||||
dimensionalShapeTypes.set(bucket, (byte) value.dimensionalShapeType().ordinal());
|
||||
} else if (compares == 0) {
|
||||
double coordinateWeight = value.weight();
|
||||
// weighted latitude
|
||||
compensatedSumLat.add(coordinateWeight * value.lat());
|
||||
// weighted longitude
|
||||
compensatedSumLon.add(coordinateWeight * value.lon());
|
||||
// weight
|
||||
compensatedSumWeight.add(coordinateWeight);
|
||||
}
|
||||
// else (compares > 0)
|
||||
// do not modify centroid calculation since shape is of lower dimension than the running dimension
|
||||
|
||||
}
|
||||
lonSum.set(bucket, compensatedSumLon.value());
|
||||
lonCompensations.set(bucket, compensatedSumLon.delta());
|
||||
latSum.set(bucket, compensatedSumLat.value());
|
||||
latCompensations.set(bucket, compensatedSumLat.delta());
|
||||
weightSum.set(bucket, compensatedSumWeight.value());
|
||||
weightCompensations.set(bucket, compensatedSumWeight.delta());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAggregation buildAggregation(long bucket) {
|
||||
if (valuesSource == null || bucket >= counts.size()) {
|
||||
return buildEmptyAggregation();
|
||||
}
|
||||
final long bucketCount = counts.get(bucket);
|
||||
final double bucketWeight = weightSum.get(bucket);
|
||||
final GeoPoint bucketCentroid = (bucketWeight > 0)
|
||||
? new GeoPoint(latSum.get(bucket) / bucketWeight, lonSum.get(bucket) / bucketWeight)
|
||||
: null;
|
||||
return new InternalGeoCentroid(name, bucketCentroid , bucketCount, metadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalAggregation buildEmptyAggregation() {
|
||||
return new InternalGeoCentroid(name, null, 0L, metadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doClose() {
|
||||
Releasables.close(latSum, latCompensations, lonSum, lonCompensations, counts, weightSum, weightCompensations,
|
||||
dimensionalShapeTypes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.license.License;
|
||||
import org.elasticsearch.license.TestUtils;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
|
||||
/**
|
||||
* This class overrides the {@link SpatialPlugin} in order
|
||||
* to provide the integration test clusters a hook into a real
|
||||
* {@link XPackLicenseState}. In the cases that this is used, the
|
||||
* actual license's operation mode is not important
|
||||
*/
|
||||
public class LocalStateSpatialPlugin extends SpatialPlugin {
|
||||
protected XPackLicenseState getLicenseState() {
|
||||
TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState();
|
||||
License.OperationMode operationMode = License.OperationMode.TRIAL;
|
||||
licenseState.update(operationMode, true, VersionUtils.randomVersion(LuceneTestCase.random()));
|
||||
return licenseState;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.license.License;
|
||||
import org.elasticsearch.license.TestUtils;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregatorSupplier;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class SpatialPluginTests extends ESTestCase {
|
||||
|
||||
public void testGeoCentroidLicenseCheck() {
|
||||
for (License.OperationMode operationMode : License.OperationMode.values()) {
|
||||
SpatialPlugin plugin = getPluginWithOperationMode(operationMode);
|
||||
ValuesSourceRegistry.Builder registryBuilder = new ValuesSourceRegistry.Builder();
|
||||
List<Consumer<ValuesSourceRegistry.Builder>> registrar = plugin.getAggregationExtentions();
|
||||
registrar.forEach(c -> c.accept(registryBuilder));
|
||||
ValuesSourceRegistry registry = registryBuilder.build();
|
||||
GeoCentroidAggregatorSupplier centroidSupplier = (GeoCentroidAggregatorSupplier) registry.getAggregator(
|
||||
GeoShapeValuesSourceType.instance(), GeoCentroidAggregationBuilder.NAME);
|
||||
if (License.OperationMode.TRIAL != operationMode &&
|
||||
License.OperationMode.compare(operationMode, License.OperationMode.GOLD) < 0) {
|
||||
ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class,
|
||||
() -> centroidSupplier.build(null, null, null, null, null));
|
||||
assertThat(exception.getMessage(),
|
||||
equalTo("current license is non-compliant for [geo_centroid aggregation on geo_shape fields]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SpatialPlugin getPluginWithOperationMode(License.OperationMode operationMode) {
|
||||
return new SpatialPlugin() {
|
||||
protected XPackLicenseState getLicenseState() {
|
||||
TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState();
|
||||
licenseState.update(operationMode, true, VersionUtils.randomVersion(random()));
|
||||
return licenseState;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.aggregations.metrics;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.LatLonDocValuesField;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.geo.GeometryTestUtils;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.plugins.SearchPlugin;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.AggregatorTestCase;
|
||||
import org.elasticsearch.search.aggregations.metrics.CompensatedSum;
|
||||
import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid;
|
||||
import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper;
|
||||
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.CentroidCalculator;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.BinaryGeoShapeDocValuesField;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.GeoShapeWithDocValuesFieldMapper;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
import org.elasticsearch.xpack.spatial.util.GeoTestUtils;
|
||||
import org.locationtech.spatial4j.exception.InvalidShapeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class GeoShapeCentroidAggregatorTests extends AggregatorTestCase {
|
||||
|
||||
private static final double GEOHASH_TOLERANCE = 1E-6D;
|
||||
|
||||
@Override
|
||||
protected List<SearchPlugin> getSearchPlugins() {
|
||||
return Collections.singletonList(new LocalStateSpatialPlugin());
|
||||
}
|
||||
|
||||
public void testEmpty() throws Exception {
|
||||
try (Directory dir = newDirectory();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {
|
||||
GeoCentroidAggregationBuilder aggBuilder = new GeoCentroidAggregationBuilder("my_agg")
|
||||
.field("field");
|
||||
|
||||
MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType();
|
||||
fieldType.setHasDocValues(true);
|
||||
fieldType.setName("field");
|
||||
try (IndexReader reader = w.getReader()) {
|
||||
IndexSearcher searcher = new IndexSearcher(reader);
|
||||
InternalGeoCentroid result = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
assertNull(result.centroid());
|
||||
assertFalse(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testUnmapped() throws Exception {
|
||||
try (Directory dir = newDirectory();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {
|
||||
GeoCentroidAggregationBuilder aggBuilder = new GeoCentroidAggregationBuilder("my_agg")
|
||||
.field("another_field");
|
||||
|
||||
Document document = new Document();
|
||||
document.add(new LatLonDocValuesField("field", 10, 10));
|
||||
w.addDocument(document);
|
||||
try (IndexReader reader = w.getReader()) {
|
||||
IndexSearcher searcher = new IndexSearcher(reader);
|
||||
|
||||
MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType();
|
||||
fieldType.setHasDocValues(true);
|
||||
fieldType.setName("another_field");
|
||||
InternalGeoCentroid result = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
assertNull(result.centroid());
|
||||
|
||||
fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType();
|
||||
fieldType.setHasDocValues(true);
|
||||
fieldType.setName("field");
|
||||
result = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
assertNull(result.centroid());
|
||||
assertFalse(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testUnmappedWithMissing() throws Exception {
|
||||
try (Directory dir = newDirectory();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {
|
||||
GeoCentroidAggregationBuilder aggBuilder = new GeoCentroidAggregationBuilder("my_agg")
|
||||
.field("another_field")
|
||||
.missing("POINT(6.475031 53.69437)");
|
||||
|
||||
double normalizedLat = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(53.69437));
|
||||
double normalizedLon = GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(6.475031));
|
||||
GeoPoint expectedCentroid = new GeoPoint(normalizedLat, normalizedLon);
|
||||
Document document = new Document();
|
||||
document.add(new LatLonDocValuesField("field", 10, 10));
|
||||
w.addDocument(document);
|
||||
try (IndexReader reader = w.getReader()) {
|
||||
IndexSearcher searcher = new IndexSearcher(reader);
|
||||
|
||||
MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType();
|
||||
fieldType.setHasDocValues(true);
|
||||
fieldType.setName("another_field");
|
||||
InternalGeoCentroid result = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
assertThat(result.centroid(), equalTo(expectedCentroid));
|
||||
assertTrue(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSingleValuedField() throws Exception {
|
||||
int numDocs = scaledRandomIntBetween(64, 256);
|
||||
List<Geometry> geometries = new ArrayList<>();
|
||||
DimensionalShapeType targetShapeType = DimensionalShapeType.POINT;
|
||||
GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
Function<Boolean, Geometry> geometryGenerator = ESTestCase.randomFrom(
|
||||
GeometryTestUtils::randomLine,
|
||||
GeometryTestUtils::randomPoint,
|
||||
GeometryTestUtils::randomPolygon,
|
||||
GeometryTestUtils::randomMultiLine,
|
||||
GeometryTestUtils::randomMultiPoint,
|
||||
GeometryTestUtils::randomMultiPolygon
|
||||
);
|
||||
Geometry geometry = geometryGenerator.apply(false);
|
||||
try {
|
||||
geometries.add(indexer.prepareForIndexing(geometry));
|
||||
} catch (InvalidShapeException e) {
|
||||
// do not include geometry
|
||||
}
|
||||
// find dimensional-shape-type of geometry
|
||||
CentroidCalculator centroidCalculator = new CentroidCalculator(geometry);
|
||||
DimensionalShapeType geometryShapeType = centroidCalculator.getDimensionalShapeType();
|
||||
targetShapeType = targetShapeType.compareTo(geometryShapeType) >= 0 ? targetShapeType : geometryShapeType;
|
||||
}
|
||||
try (Directory dir = newDirectory();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir)) {
|
||||
CompensatedSum compensatedSumLon = new CompensatedSum(0, 0);
|
||||
CompensatedSum compensatedSumLat = new CompensatedSum(0, 0);
|
||||
CompensatedSum compensatedSumWeight = new CompensatedSum(0, 0);
|
||||
for (Geometry geometry : geometries) {
|
||||
Document document = new Document();
|
||||
CentroidCalculator calculator = new CentroidCalculator(geometry);
|
||||
document.add(new BinaryGeoShapeDocValuesField("field", GeoTestUtils.toDecodedTriangles(geometry), calculator));
|
||||
w.addDocument(document);
|
||||
if (targetShapeType.compareTo(calculator.getDimensionalShapeType()) == 0) {
|
||||
double weight = calculator.sumWeight();
|
||||
compensatedSumLat.add(weight * calculator.getY());
|
||||
compensatedSumLon.add(weight * calculator.getX());
|
||||
compensatedSumWeight.add(weight);
|
||||
}
|
||||
}
|
||||
GeoPoint expectedCentroid = new GeoPoint(compensatedSumLat.value() / compensatedSumWeight.value(),
|
||||
compensatedSumLon.value() / compensatedSumWeight.value());
|
||||
assertCentroid(w, expectedCentroid);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertCentroid(RandomIndexWriter w, GeoPoint expectedCentroid) throws IOException {
|
||||
MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType();
|
||||
fieldType.setHasDocValues(true);
|
||||
fieldType.setName("field");
|
||||
GeoCentroidAggregationBuilder aggBuilder = new GeoCentroidAggregationBuilder("my_agg")
|
||||
.field("field");
|
||||
try (IndexReader reader = w.getReader()) {
|
||||
IndexSearcher searcher = new IndexSearcher(reader);
|
||||
InternalGeoCentroid result = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
|
||||
assertEquals("my_agg", result.getName());
|
||||
GeoPoint centroid = result.centroid();
|
||||
assertNotNull(centroid);
|
||||
assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE);
|
||||
assertTrue(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AggregationBuilder createAggBuilderForTypeTest(MappedFieldType fieldType, String fieldName) {
|
||||
return new GeoCentroidAggregationBuilder("foo").field(fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValuesSourceType> getSupportedValuesSourceTypes() {
|
||||
return Arrays.asList(CoreValuesSourceType.GEOPOINT, GeoShapeValuesSourceType.instance());
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ import org.elasticsearch.plugins.Plugin;
|
|||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.elasticsearch.test.InternalSettingsPlugin;
|
||||
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
@ -37,7 +37,7 @@ public abstract class CartesianFieldMapperTests extends ESSingleNodeTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
return pluginList(InternalSettingsPlugin.class, LocalStateSpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
}
|
||||
|
||||
protected abstract XContentBuilder createDefaultMapping(String fieldName,
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.elasticsearch.plugins.Plugin;
|
|||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.elasticsearch.test.InternalSettingsPlugin;
|
||||
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
@ -61,7 +61,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends ESSingleNodeTestCase
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
return pluginList(InternalSettingsPlugin.class, LocalStateCompositeXPackPlugin.class, LocalStateSpatialPlugin.class);
|
||||
}
|
||||
|
||||
public void testDefaultConfiguration() throws IOException {
|
||||
|
|
|
@ -17,13 +17,8 @@ import org.elasticsearch.index.mapper.DocumentMapper;
|
|||
import org.elasticsearch.index.mapper.DocumentMapperParser;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.InternalSettingsPlugin;
|
||||
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
|
||||
|
@ -32,10 +27,6 @@ import static org.hamcrest.Matchers.instanceOf;
|
|||
|
||||
/** testing for {@link org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper} */
|
||||
public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return pluginList(InternalSettingsPlugin.class, SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XContentBuilder createDefaultMapping(String fieldName,
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.elasticsearch.index.query.QueryShardException;
|
|||
import org.elasticsearch.index.query.Rewriteable;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.AbstractQueryTestCase;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
import org.junit.After;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -64,7 +64,7 @@ public abstract class ShapeQueryBuilderTests extends AbstractQueryTestCase<Shape
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return Arrays.asList(SpatialPlugin.class);
|
||||
return Arrays.asList(LocalStateSpatialPlugin.class);
|
||||
}
|
||||
|
||||
protected String fieldName() {
|
||||
|
|
|
@ -22,15 +22,11 @@ import org.elasticsearch.geometry.Geometry;
|
|||
import org.elasticsearch.geometry.ShapeType;
|
||||
import org.elasticsearch.index.query.ExistsQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
|
||||
import org.elasticsearch.xpack.spatial.util.ShapeTestUtils;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
|
||||
|
@ -196,11 +192,6 @@ public class ShapeQueryOverShapeTests extends ShapeQueryTests {
|
|||
assertHitCount(result, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ignore_malformed on GeoShapeFieldMapper does not fail the entire document
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.elasticsearch.plugins.Plugin;
|
|||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.index.query.ShapeQueryBuilder;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
|
||||
|
@ -45,7 +45,7 @@ public abstract class ShapeQueryTests extends ESSingleNodeTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return pluginList(SpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
return pluginList(LocalStateSpatialPlugin.class, LocalStateCompositeXPackPlugin.class);
|
||||
}
|
||||
|
||||
protected abstract XContentBuilder createDefaultMapping() throws Exception;
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds;
|
|||
import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper;
|
||||
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.xpack.spatial.SpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.CentroidCalculator;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.BinaryGeoShapeDocValuesField;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.GeoShapeWithDocValuesFieldMapper;
|
||||
|
@ -48,7 +48,7 @@ public class GeoShapeBoundsAggregatorTests extends AggregatorTestCase {
|
|||
|
||||
@Override
|
||||
protected List<SearchPlugin> getSearchPlugins() {
|
||||
return Collections.singletonList(new SpatialPlugin());
|
||||
return Collections.singletonList(new LocalStateSpatialPlugin());
|
||||
}
|
||||
|
||||
public void testEmpty() throws Exception {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
"Test geo_centroid aggregation on geo_shape field":
|
||||
- do:
|
||||
indices.create:
|
||||
index: locations
|
||||
body:
|
||||
mappings:
|
||||
properties:
|
||||
location:
|
||||
type: geo_shape
|
||||
|
||||
- do:
|
||||
bulk:
|
||||
refresh: true
|
||||
body:
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 1
|
||||
- '{"location": "POINT(4.912350 52.374081)", "city": "Amsterdam", "name": "NEMO Science Museum"}'
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 2
|
||||
- '{"location": "POINT(4.901618 52.369219)", "city": "Amsterdam", "name": "Museum Het Rembrandthuis"}'
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 3
|
||||
- '{"location": "POINT(4.914722 52.371667)", "city": "Amsterdam", "name": "Nederlands Scheepvaartmuseum"}'
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 4
|
||||
- '{"location": "POINT(4.405200 51.222900)", "city": "Antwerp", "name": "Letterenhuis"}'
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 5
|
||||
- '{"location": "POINT(2.336389 48.861111)", "city": "Paris", "name": "Musée du Louvre"}'
|
||||
- index:
|
||||
_index: locations
|
||||
_id: 6
|
||||
- '{"location": "POINT(2.327000 48.860000)", "city": "Paris", "name": "Musée dOrsay"}'
|
||||
|
||||
- do:
|
||||
search:
|
||||
rest_total_hits_as_int: true
|
||||
index: locations
|
||||
size: 0
|
||||
body:
|
||||
aggs:
|
||||
centroid:
|
||||
geo_centroid:
|
||||
field: location
|
||||
- match: {hits.total: 6 }
|
||||
- match: { aggregations.centroid.location.lat: 51.00982965203002 }
|
||||
- match: { aggregations.centroid.location.lon: 3.9662131341174245 }
|
||||
- match: { aggregations.centroid.count: 6 }
|
Loading…
Reference in New Issue