Skip to content

Commit

Permalink
Add support for metrics aggregations to mvt end point (elastic#78614)
Browse files Browse the repository at this point in the history
It adds support for several aggregations.
  • Loading branch information
iverase authored Oct 5, 2021
1 parent 0ca3d12 commit 920b3b5
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 49 deletions.
7 changes: 7 additions & 0 deletions docs/reference/search/search-vector-tile-api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,17 @@ If `false`, the response does not include the total number of hits matching the
aggregation types:
+
* <<search-aggregations-metrics-avg-aggregation,`avg`>>
* <<search-aggregations-metrics-boxplot-aggregation,`boxplot`>>
* <<search-aggregations-metrics-cardinality-aggregation,`cardinality`>>
* <<search-aggregations-metrics-extendedstats-aggregation,`extended stats`>>
* <<search-aggregations-metrics-max-aggregation,`max`>>
* <<search-aggregations-metrics-median-absolute-deviation-aggregation,`median absolute deviation`>>
* <<search-aggregations-metrics-min-aggregation,`min`>>
* <<search-aggregations-metrics-percentile-aggregation,`percentile`>>
* <<search-aggregations-metrics-percentile-rank-aggregation,`percentile-rank`>>
* <<search-aggregations-metrics-stats-aggregation,`stats`>>
* <<search-aggregations-metrics-sum-aggregation,`sum`>>
* <<search-aggregations-metrics-valuecount-aggregation,`value count`>>
+
The aggregation names can't start with `_mvt_`. The `_mvt_` prefix is reserved
for internal aggregations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ public void testWithFields() throws Exception {
assertLayer(tile, META_LAYER, 4096, 1, 13);
}

public void testMinAgg() throws Exception {
public void testSingleValueAgg() throws Exception {
final Request mvtRequest = new Request(getHttpMethod(), INDEX_POLYGON + "/_mvt/location/" + z + "/" + x + "/" + y);
mvtRequest.setJsonEntity(
"{\n"
Expand All @@ -676,6 +676,35 @@ public void testMinAgg() throws Exception {
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.minVal.max", 1.0);
}

public void testMultiValueAgg() throws Exception {
final Request mvtRequest = new Request(getHttpMethod(), INDEX_POLYGON + "/_mvt/location/" + z + "/" + x + "/" + y);
mvtRequest.setJsonEntity(
"{\n"
+ " \"aggs\": {\n"
+ " \"percentilesAgg\": {\n"
+ " \"percentiles\": {\n"
+ " \"field\": \"value1\",\n"
+ " \"percents\": [95, 99, 99.9]\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}"
);
final VectorTile.Tile tile = execute(mvtRequest);
assertThat(tile.getLayersCount(), Matchers.equalTo(3));
assertLayer(tile, HITS_LAYER, 4096, 1, 2);
assertLayer(tile, AGGS_LAYER, 4096, 256 * 256, 4);
assertLayer(tile, META_LAYER, 4096, 1, 28);
// check pipeline aggregation values
final VectorTile.Tile.Layer metaLayer = getLayer(tile, META_LAYER);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.95.0.min", 1.0);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.95.0.max", 1.0);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.0.min", 1.0);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.0.max", 1.0);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.9.min", 1.0);
assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.9.max", 1.0);
}

public void testOverlappingMultipolygon() throws Exception {
// Overlapping multipolygon are accepted by Elasticsearch but is invalid for JTS. This
// causes and error in the mvt library that gets logged using slf4j
Expand Down Expand Up @@ -720,7 +749,7 @@ private void assertSintTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Feature
return;
}
}
fail("Could not find tag [" + tag + " ]");
fail("Could not find tag [" + tag + "]");
}

private void assertDoubleTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Feature feature, String tag, double value) {
Expand All @@ -732,7 +761,7 @@ private void assertDoubleTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Featur
return;
}
}
fail("Could not find tag [" + tag + " ]");
fail("Could not find tag [" + tag + "]");
}

private void assertStringTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Feature feature, String tag, String value) {
Expand All @@ -744,7 +773,7 @@ private void assertStringTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Featur
return;
}
}
fail("Could not find tag [" + tag + " ]");
fail("Could not find tag [" + tag + "]");
}

private VectorTile.Tile execute(Request mvtRequest) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.AggregatorFactories;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
Expand All @@ -46,13 +44,14 @@
import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds;
import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid;
import org.elasticsearch.search.aggregations.pipeline.StatsBucketPipelineAggregationBuilder;
import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder.MetricsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
import org.elasticsearch.search.profile.SearchProfileResults;
import org.elasticsearch.search.sort.SortBuilder;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.elasticsearch.rest.RestRequest.Method.GET;
Expand Down Expand Up @@ -209,27 +208,33 @@ private static SearchRequestBuilder searchRequestBuilder(RestCancellableNodeClie
searchRequestBuilder.addAggregation(tileAggBuilder);
searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(COUNT_TAG, GRID_FIELD + "." + COUNT_TAG));
if (request.getGridType() == VectorTileRequest.GRID_TYPE.CENTROID) {
GeoCentroidAggregationBuilder centroidAggregationBuilder = new GeoCentroidAggregationBuilder(CENTROID_AGG_NAME);
centroidAggregationBuilder.field(request.getField());
tileAggBuilder.subAggregation(centroidAggregationBuilder);
tileAggBuilder.subAggregation(new GeoCentroidAggregationBuilder(CENTROID_AGG_NAME).field(request.getField()));
}
final AggregatorFactories.Builder otherAggBuilder = request.getAggBuilder();
if (otherAggBuilder != null) {
final Collection<AggregationBuilder> aggregations = otherAggBuilder.getAggregatorFactories();
for (AggregationBuilder aggregation : aggregations) {
if (aggregation.getName().startsWith(INTERNAL_AGG_PREFIX)) {
throw new IllegalArgumentException(
"Invalid aggregation name ["
+ aggregation.getName()
+ "]. Aggregation names cannot start with prefix '"
+ INTERNAL_AGG_PREFIX
+ "'"
);
final List<MetricsAggregationBuilder<?, ?>> aggregations = request.getAggBuilder();
for (MetricsAggregationBuilder<?, ?> aggregation : aggregations) {
if (aggregation.getName().startsWith(INTERNAL_AGG_PREFIX)) {
throw new IllegalArgumentException(
"Invalid aggregation name ["
+ aggregation.getName()
+ "]. Aggregation names cannot start with prefix '"
+ INTERNAL_AGG_PREFIX
+ "'"
);
}
tileAggBuilder.subAggregation(aggregation);
final Set<String> metricNames = aggregation.metricNames();
for (String metric : metricNames) {
final String bucketPath;
// handle the case where the metric contains a dot
if (metric.contains(".")) {
bucketPath = GRID_FIELD + ">" + aggregation.getName() + "[" + metric + "]";
} else {
bucketPath = GRID_FIELD + ">" + aggregation.getName() + "." + metric;
}
tileAggBuilder.subAggregation(aggregation);
// we add the metric (.value) to the path in order to support aggregation names with '.'
final String bucketPath = GRID_FIELD + ">" + aggregation.getName() + ".value";
searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(aggregation.getName(), bucketPath));
// we only add the metric name to multi-value metric aggregations
final String aggName = metricNames.size() == 1 ? aggregation.getName() : aggregation.getName() + "." + metric;
searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(aggName, bucketPath));

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@
import org.elasticsearch.search.aggregations.AggregatorFactories;
import org.elasticsearch.search.aggregations.PipelineAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder.MetricsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
import org.elasticsearch.search.sort.ScriptSortBuilder;
Expand Down Expand Up @@ -85,7 +81,7 @@ protected static class Defaults {
public static final List<FieldAndFormat> FETCH = emptyList();
public static final Map<String, Object> RUNTIME_MAPPINGS = emptyMap();
public static final QueryBuilder QUERY = null;
public static final AggregatorFactories.Builder AGGS = null;
public static final List<MetricsAggregationBuilder<?, ?>> AGGS = emptyList();
public static final int GRID_PRECISION = 8;
public static final GRID_TYPE GRID_TYPE = VectorTileRequest.GRID_TYPE.GRID;
public static final int EXTENT = 4096;
Expand Down Expand Up @@ -211,7 +207,7 @@ static VectorTileRequest parseRestRequest(RestRequest restRequest) throws IOExce
private GRID_TYPE gridType = Defaults.GRID_TYPE;
private int size = Defaults.SIZE;
private int extent = Defaults.EXTENT;
private AggregatorFactories.Builder aggBuilder = Defaults.AGGS;
private List<MetricsAggregationBuilder<?, ?>> aggs = Defaults.AGGS;
private List<FieldAndFormat> fields = Defaults.FETCH;
private List<SortBuilder<?>> sortBuilders;
private boolean exact_bounds = Defaults.EXACT_BOUNDS;
Expand Down Expand Up @@ -328,31 +324,27 @@ private void setSize(int size) {
this.size = size;
}

public AggregatorFactories.Builder getAggBuilder() {
return aggBuilder;
public List<MetricsAggregationBuilder<?, ?>> getAggBuilder() {
return aggs;
}

private void setAggBuilder(AggregatorFactories.Builder aggBuilder) {
List<MetricsAggregationBuilder<?, ?>> aggs = new ArrayList<>(aggBuilder.count());
for (AggregationBuilder aggregation : aggBuilder.getAggregatorFactories()) {
final String type = aggregation.getType();
switch (type) {
case MinAggregationBuilder.NAME:
case MaxAggregationBuilder.NAME:
case AvgAggregationBuilder.NAME:
case SumAggregationBuilder.NAME:
case CardinalityAggregationBuilder.NAME:
break;
default:
// top term and percentile should be supported
throw new IllegalArgumentException("Unsupported aggregation of type [" + type + "]");
if (aggregation instanceof MetricsAggregationBuilder<?, ?>) {
aggs.add((MetricsAggregationBuilder<?, ?>) aggregation);
} else {
throw new IllegalArgumentException(
"Unsupported aggregation of type [" + aggregation.getType() + "]." + "Only metric aggregations are supported."
);
}
}
for (PipelineAggregationBuilder aggregation : aggBuilder.getPipelineAggregatorFactories()) {
// should not have pipeline aggregations
final String type = aggregation.getType();
throw new IllegalArgumentException("Unsupported pipeline aggregation of type [" + type + "]");
}
this.aggBuilder = aggBuilder;
this.aggs = aggs;
}

public List<SortBuilder<?>> getSortBuilders() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ public void testFieldAgg() throws IOException {
aggregationBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
}, (vectorTileRequest) -> {
assertThat(vectorTileRequest.getAggBuilder().getAggregatorFactories(), Matchers.iterableWithSize(1));
assertThat(vectorTileRequest.getAggBuilder().getAggregatorFactories().contains(aggregationBuilder), Matchers.equalTo(true));
assertThat(vectorTileRequest.getAggBuilder(), Matchers.iterableWithSize(1));
assertThat(vectorTileRequest.getAggBuilder().contains(aggregationBuilder), Matchers.equalTo(true));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,109 @@ setup:
test.cardinality_value:
cardinality:
field: value

---
"value count agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.value_count_value:
value_count:
field: value

---
"median absolute deviation agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.median_absolute_deviation_value:
median_absolute_deviation:
field: value

---
"stats agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.stats_values:
stats:
field: value

---
"extended stats agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.extended_stats_values:
extended_stats:
field: value

---
"percentile agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.percentiles_values:
percentiles:
field: value

---
"percentile rank agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.percentile_ranks_values:
percentile_ranks:
field: value
values: [5, 10]

---
"boxplot deviation agg":
- do:
search_mvt:
index: locations
field: location
x: 0
y: 0
zoom: 0
body:
aggs:
test.boxplot_values:
boxplot:
field: value

0 comments on commit 920b3b5

Please sign in to comment.