Support distance units in GeoHashGrid aggregation precision (#26291)
Currently the `precision` parameter must be a precision level in the range of [1,12]. In #5042 it was suggested also supporting distance units like "1km" to automatically approcimate the needed precision level. This change adds this support to the Rest API by making use of GeoUtils#geoHashLevelsForPrecision. Plain integer values without a unit are still treated as precision levels like before. Distance values that are too small to be represented by a precision level of 12 (values approx. less than 0.056m) are rejected. Closes #5042
This commit is contained in:
parent
4ff12c9a0b
commit
5dae277bb2
|
@ -24,11 +24,13 @@ import org.apache.lucene.index.SortedNumericDocValues;
|
|||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.geo.GeoHashUtils;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
import org.elasticsearch.index.fielddata.AbstractSortingNumericDocValues;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
||||
|
@ -57,7 +59,29 @@ public class GeoGridAggregationBuilder extends ValuesSourceAggregationBuilder<Va
|
|||
static {
|
||||
PARSER = new ObjectParser<>(GeoGridAggregationBuilder.NAME);
|
||||
ValuesSourceParserHelper.declareGeoFields(PARSER, false, false);
|
||||
PARSER.declareInt(GeoGridAggregationBuilder::precision, GeoHashGridParams.FIELD_PRECISION);
|
||||
PARSER.declareField((parser, builder, context) -> {
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token.equals(XContentParser.Token.VALUE_NUMBER)) {
|
||||
builder.precision(XContentMapValues.nodeIntegerValue(parser.intValue()));
|
||||
} else {
|
||||
String precision = parser.text();
|
||||
try {
|
||||
// we want to treat simple integer strings as precision levels, not distances
|
||||
builder.precision(XContentMapValues.nodeIntegerValue(Integer.parseInt(precision)));
|
||||
} catch (NumberFormatException e) {
|
||||
// try to parse as a distance value
|
||||
try {
|
||||
builder.precision(GeoUtils.geoHashLevelsForPrecision(precision));
|
||||
} catch (NumberFormatException e2) {
|
||||
// can happen when distance unit is unknown, in this case we simply want to know the reason
|
||||
throw e2;
|
||||
} catch (IllegalArgumentException e3) {
|
||||
// this happens when distance too small, so precision > 12. We'd like to see the original string
|
||||
throw new IllegalArgumentException("precision too high [" + precision + "]", e3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, GeoHashGridParams.FIELD_PRECISION, org.elasticsearch.common.xcontent.ObjectParser.ValueType.INT);
|
||||
PARSER.declareInt(GeoGridAggregationBuilder::size, GeoHashGridParams.FIELD_SIZE);
|
||||
PARSER.declareInt(GeoGridAggregationBuilder::shardSize, GeoHashGridParams.FIELD_SHARD_SIZE);
|
||||
}
|
||||
|
|
|
@ -19,11 +19,14 @@
|
|||
package org.elasticsearch.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
public class GeoHashGridParserTests extends ESTestCase {
|
||||
public void testParseValidFromInts() throws Exception {
|
||||
|
@ -46,6 +49,46 @@ public class GeoHashGridParserTests extends ESTestCase {
|
|||
assertNotNull(GeoGridAggregationBuilder.parse("geohash_grid", stParser));
|
||||
}
|
||||
|
||||
public void testParseDistanceUnitPrecision() throws Exception {
|
||||
double distance = randomDoubleBetween(10.0, 100.00, true);
|
||||
DistanceUnit unit = randomFrom(DistanceUnit.values());
|
||||
if (unit.equals(DistanceUnit.MILLIMETERS)) {
|
||||
distance = 5600 + randomDouble(); // 5.6cm is approx. smallest distance represented by precision 12
|
||||
}
|
||||
String distanceString = distance + unit.toString();
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"field\":\"my_loc\", \"precision\": \"" + distanceString + "\", \"size\": \"500\", \"shard_size\": \"550\"}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
// can create a factory
|
||||
GeoGridAggregationBuilder builder = GeoGridAggregationBuilder.parse("geohash_grid", stParser);
|
||||
assertNotNull(builder);
|
||||
assertThat(builder.precision(), greaterThanOrEqualTo(0));
|
||||
assertThat(builder.precision(), lessThanOrEqualTo(12));
|
||||
}
|
||||
|
||||
public void testParseInvalidUnitPrecision() throws Exception {
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"field\":\"my_loc\", \"precision\": \"10kg\", \"size\": \"500\", \"shard_size\": \"550\"}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
ParsingException ex = expectThrows(ParsingException.class, () -> GeoGridAggregationBuilder.parse("geohash_grid", stParser));
|
||||
assertEquals("[geohash_grid] failed to parse field [precision]", ex.getMessage());
|
||||
assertThat(ex.getCause(), instanceOf(NumberFormatException.class));
|
||||
assertEquals("For input string: \"10kg\"", ex.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseDistanceUnitPrecisionTooSmall() throws Exception {
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"field\":\"my_loc\", \"precision\": \"1cm\", \"size\": \"500\", \"shard_size\": \"550\"}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
ParsingException ex = expectThrows(ParsingException.class, () -> GeoGridAggregationBuilder.parse("geohash_grid", stParser));
|
||||
assertEquals("[geohash_grid] failed to parse field [precision]", ex.getMessage());
|
||||
assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class));
|
||||
assertEquals("precision too high [1cm]", ex.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseErrorOnBooleanPrecision() throws Exception {
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent, "{\"field\":\"my_loc\", \"precision\":false}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
|
|
|
@ -149,6 +149,15 @@ field:: Mandatory. The name of the field indexed with GeoPoints.
|
|||
|
||||
precision:: Optional. The string length of the geohashes used to define
|
||||
cells/buckets in the results. Defaults to 5.
|
||||
The precision can either be defined in terms of the integer
|
||||
precision levels mentioned above. Values outside of [1,12] will
|
||||
be rejected.
|
||||
Alternatively, the precision level can be approximated from a
|
||||
distance measure like "1km", "10m". The precision level is
|
||||
calculate such that cells will not exceed the specified
|
||||
size (diagonal) of the required precision. When this would lead
|
||||
to precision levels higher than the supported 12 levels,
|
||||
(e.g. for distances <5.6cm) the value is rejected.
|
||||
|
||||
size:: Optional. The maximum number of geohash buckets to return
|
||||
(defaults to 10,000). When results are trimmed, buckets are
|
||||
|
|
Loading…
Reference in New Issue