geotile_grid implementation (#37842)
Implements `geotile_grid` aggregation This patch refactors previous implementation https://github.com/elastic/elasticsearch/pull/30240 This code uses the same base classes as `geohash_grid` agg, but uses a different hashing algorithm to allow zoom consistency. Each grid bucket is aligned to Web Mercator tiles.
This commit is contained in:
parent
6c1e9fad47
commit
f3cde06a1d
|
@ -95,6 +95,8 @@ import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
|
|||
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilters;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoHashGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoTileGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.ParsedGlobal;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.AutoDateHistogramAggregationBuilder;
|
||||
|
@ -1760,6 +1762,7 @@ public class RestHighLevelClient implements Closeable {
|
|||
map.put(FilterAggregationBuilder.NAME, (p, c) -> ParsedFilter.fromXContent(p, (String) c));
|
||||
map.put(InternalSampler.PARSER_NAME, (p, c) -> ParsedSampler.fromXContent(p, (String) c));
|
||||
map.put(GeoHashGridAggregationBuilder.NAME, (p, c) -> ParsedGeoHashGrid.fromXContent(p, (String) c));
|
||||
map.put(GeoTileGridAggregationBuilder.NAME, (p, c) -> ParsedGeoTileGrid.fromXContent(p, (String) c));
|
||||
map.put(RangeAggregationBuilder.NAME, (p, c) -> ParsedRange.fromXContent(p, (String) c));
|
||||
map.put(DateRangeAggregationBuilder.NAME, (p, c) -> ParsedDateRange.fromXContent(p, (String) c));
|
||||
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
[[search-aggregations-bucket-geotilegrid-aggregation]]
|
||||
=== GeoTile Grid Aggregation
|
||||
|
||||
A multi-bucket aggregation that works on `geo_point` fields and groups points into
|
||||
buckets that represent cells in a grid. The resulting grid can be sparse and only
|
||||
contains cells that have matching data. Each cell corresponds to a
|
||||
https://en.wikipedia.org/wiki/Tiled_web_map[map tile] as used by many online map
|
||||
sites. Each cell is labeled using a "{zoom}/{x}/{y}" format, where zoom is equal
|
||||
to the user-specified precision.
|
||||
|
||||
* High precision keys have a larger range for x and y, and represent tiles that
|
||||
cover only a small area.
|
||||
* Low precision keys have a smaller range for x and y, and represent tiles that
|
||||
each cover a large area.
|
||||
|
||||
See https://wiki.openstreetmap.org/wiki/Zoom_levels[Zoom level documentation]
|
||||
on how precision (zoom) correlates to size on the ground. Precision for this
|
||||
aggregation can be between 0 and 29, inclusive.
|
||||
|
||||
WARNING: The highest-precision geotile of length 29 produces cells that cover
|
||||
less than a 10cm by 10cm of land and so high-precision requests can be very
|
||||
costly in terms of RAM and result sizes. Please see the example below on how
|
||||
to first filter the aggregation to a smaller geographic area before requesting
|
||||
high-levels of detail.
|
||||
|
||||
The specified field must be of type `geo_point` (which can only be set
|
||||
explicitly in the mappings) and it can also hold an array of `geo_point`
|
||||
fields, in which case all points will be taken into account during aggregation.
|
||||
|
||||
|
||||
==== Simple low-precision request
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
PUT /museums
|
||||
{
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "geo_point"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
POST /museums/_bulk?refresh
|
||||
{"index":{"_id":1}}
|
||||
{"location": "52.374081,4.912350", "name": "NEMO Science Museum"}
|
||||
{"index":{"_id":2}}
|
||||
{"location": "52.369219,4.901618", "name": "Museum Het Rembrandthuis"}
|
||||
{"index":{"_id":3}}
|
||||
{"location": "52.371667,4.914722", "name": "Nederlands Scheepvaartmuseum"}
|
||||
{"index":{"_id":4}}
|
||||
{"location": "51.222900,4.405200", "name": "Letterenhuis"}
|
||||
{"index":{"_id":5}}
|
||||
{"location": "48.861111,2.336389", "name": "Musée du Louvre"}
|
||||
{"index":{"_id":6}}
|
||||
{"location": "48.860000,2.327000", "name": "Musée d'Orsay"}
|
||||
|
||||
POST /museums/_search?size=0
|
||||
{
|
||||
"aggregations" : {
|
||||
"large-grid" : {
|
||||
"geotile_grid" : {
|
||||
"field" : "location",
|
||||
"precision" : 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
Response:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
...
|
||||
"aggregations": {
|
||||
"large-grid": {
|
||||
"buckets": [
|
||||
{
|
||||
"key" : "8/131/84",
|
||||
"doc_count" : 3
|
||||
},
|
||||
{
|
||||
"key" : "8/129/88",
|
||||
"doc_count" : 2
|
||||
},
|
||||
{
|
||||
"key" : "8/131/85",
|
||||
"doc_count" : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
// TESTRESPONSE[s/\.\.\./"took": $body.took,"_shards": $body._shards,"hits":$body.hits,"timed_out":false,/]
|
||||
|
||||
==== High-precision requests
|
||||
|
||||
When requesting detailed buckets (typically for displaying a "zoomed in" map)
|
||||
a filter like <<query-dsl-geo-bounding-box-query,geo_bounding_box>> should be
|
||||
applied to narrow the subject area otherwise potentially millions of buckets
|
||||
will be created and returned.
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
POST /museums/_search?size=0
|
||||
{
|
||||
"aggregations" : {
|
||||
"zoomed-in" : {
|
||||
"filter" : {
|
||||
"geo_bounding_box" : {
|
||||
"location" : {
|
||||
"top_left" : "52.4, 4.9",
|
||||
"bottom_right" : "52.3, 5.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aggregations":{
|
||||
"zoom1":{
|
||||
"geotile_grid" : {
|
||||
"field": "location",
|
||||
"precision": 22
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
// TEST[continued]
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
...
|
||||
"aggregations" : {
|
||||
"zoomed-in" : {
|
||||
"doc_count" : 3,
|
||||
"zoom1" : {
|
||||
"buckets" : [
|
||||
{
|
||||
"key" : "22/2154412/1378379",
|
||||
"doc_count" : 1
|
||||
},
|
||||
{
|
||||
"key" : "22/2154385/1378332",
|
||||
"doc_count" : 1
|
||||
},
|
||||
{
|
||||
"key" : "22/2154259/1378425",
|
||||
"doc_count" : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
// TESTRESPONSE[s/\.\.\./"took": $body.took,"_shards": $body._shards,"hits":$body.hits,"timed_out":false,/]
|
||||
|
||||
|
||||
==== Options
|
||||
|
||||
[horizontal]
|
||||
field:: Mandatory. The name of the field indexed with GeoPoints.
|
||||
|
||||
precision:: Optional. The integer zoom of the key used to define
|
||||
cells/buckets in the results. Defaults to 7.
|
||||
Values outside of [0,29] will be rejected.
|
||||
|
||||
size:: Optional. The maximum number of geohash buckets to return
|
||||
(defaults to 10,000). When results are trimmed, buckets are
|
||||
prioritised based on the volumes of documents they contain.
|
||||
|
||||
shard_size:: Optional. To allow for more accurate counting of the top cells
|
||||
returned in the final result the aggregation defaults to
|
||||
returning `max(10,(size x number-of-shards))` buckets from each
|
||||
shard. If this heuristic is undesirable, the number considered
|
||||
from each shard can be over-ridden using this parameter.
|
|
@ -0,0 +1,65 @@
|
|||
setup:
|
||||
- skip:
|
||||
version: " - 6.99.99"
|
||||
reason: "added in 7.0.0"
|
||||
- do:
|
||||
indices.create:
|
||||
include_type_name: false
|
||||
index: test_1
|
||||
body:
|
||||
settings:
|
||||
number_of_replicas: 0
|
||||
mappings:
|
||||
properties:
|
||||
location:
|
||||
type: geo_point
|
||||
|
||||
---
|
||||
"Basic test":
|
||||
- do:
|
||||
bulk:
|
||||
refresh: true
|
||||
body:
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 1
|
||||
- location: "52.374081,4.912350"
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 2
|
||||
- location: "52.369219,4.901618"
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 3
|
||||
- location: "52.371667,4.914722"
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 4
|
||||
- location: "51.222900,4.405200"
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 5
|
||||
- location: "48.861111,2.336389"
|
||||
- index:
|
||||
_index: test_1
|
||||
_id: 6
|
||||
- location: "48.860000,2.327000"
|
||||
|
||||
- do:
|
||||
search:
|
||||
rest_total_hits_as_int: true
|
||||
body:
|
||||
aggregations:
|
||||
grid:
|
||||
geotile_grid:
|
||||
field: location
|
||||
precision: 8
|
||||
|
||||
|
||||
- match: { hits.total: 6 }
|
||||
- match: { aggregations.grid.buckets.0.key: "8/131/84" }
|
||||
- match: { aggregations.grid.buckets.0.doc_count: 3 }
|
||||
- match: { aggregations.grid.buckets.1.key: "8/129/88" }
|
||||
- match: { aggregations.grid.buckets.1.doc_count: 2 }
|
||||
- match: { aggregations.grid.buckets.2.key: "8/131/85" }
|
||||
- match: { aggregations.grid.buckets.2.doc_count: 1 }
|
|
@ -110,6 +110,8 @@ import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
|
|||
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilters;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.InternalGeoHashGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.InternalGeoTileGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.InternalGlobal;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.AutoDateHistogramAggregationBuilder;
|
||||
|
@ -422,6 +424,8 @@ public class SearchModule {
|
|||
GeoDistanceAggregationBuilder::parse).addResultReader(InternalGeoDistance::new));
|
||||
registerAggregation(new AggregationSpec(GeoHashGridAggregationBuilder.NAME, GeoHashGridAggregationBuilder::new,
|
||||
GeoHashGridAggregationBuilder::parse).addResultReader(InternalGeoHashGrid::new));
|
||||
registerAggregation(new AggregationSpec(GeoTileGridAggregationBuilder.NAME, GeoTileGridAggregationBuilder::new,
|
||||
GeoTileGridAggregationBuilder::parse).addResultReader(InternalGeoTileGrid::new));
|
||||
registerAggregation(new AggregationSpec(NestedAggregationBuilder.NAME, NestedAggregationBuilder::new,
|
||||
NestedAggregationBuilder::parse).addResultReader(InternalNested::new));
|
||||
registerAggregation(new AggregationSpec(ReverseNestedAggregationBuilder.NAME, ReverseNestedAggregationBuilder::new,
|
||||
|
|
|
@ -30,6 +30,8 @@ import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBui
|
|||
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator.KeyedFilter;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.InternalGeoHashGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.InternalGeoTileGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.Global;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
|
||||
|
@ -250,6 +252,13 @@ public class AggregationBuilders {
|
|||
return new GeoHashGridAggregationBuilder(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link InternalGeoTileGrid} aggregation with the given name.
|
||||
*/
|
||||
public static GeoTileGridAggregationBuilder geotileGrid(String name) {
|
||||
return new GeoTileGridAggregationBuilder(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SignificantTerms} aggregation with the given name.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactory;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeoTileGridAggregationBuilder extends GeoGridAggregationBuilder {
|
||||
public static final String NAME = "geotile_grid";
|
||||
private static final int DEFAULT_PRECISION = 7;
|
||||
private static final int DEFAULT_MAX_NUM_CELLS = 10000;
|
||||
|
||||
private static final ObjectParser<GeoGridAggregationBuilder, Void> PARSER = createParser(NAME, GeoTileUtils::parsePrecision);
|
||||
|
||||
public GeoTileGridAggregationBuilder(String name) {
|
||||
super(name);
|
||||
precision(DEFAULT_PRECISION);
|
||||
size(DEFAULT_MAX_NUM_CELLS);
|
||||
shardSize = -1;
|
||||
}
|
||||
|
||||
public GeoTileGridAggregationBuilder(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoGridAggregationBuilder precision(int precision) {
|
||||
this.precision = GeoTileUtils.checkPrecisionRange(precision);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValuesSourceAggregatorFactory<ValuesSource.GeoPoint, ?> createFactory(
|
||||
String name, ValuesSourceConfig<ValuesSource.GeoPoint> config, int precision, int requiredSize, int shardSize,
|
||||
SearchContext context, AggregatorFactory<?> parent, AggregatorFactories.Builder subFactoriesBuilder,
|
||||
Map<String, Object> metaData
|
||||
) throws IOException {
|
||||
return new GeoTileGridAggregatorFactory(name, config, precision, requiredSize, shardSize, context, parent,
|
||||
subFactoriesBuilder, metaData);
|
||||
}
|
||||
|
||||
private GeoTileGridAggregationBuilder(GeoTileGridAggregationBuilder clone, AggregatorFactories.Builder factoriesBuilder,
|
||||
Map<String, Object> metaData) {
|
||||
super(clone, factoriesBuilder, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AggregationBuilder shallowCopy(AggregatorFactories.Builder factoriesBuilder, Map<String, Object> metaData) {
|
||||
return new GeoTileGridAggregationBuilder(this, factoriesBuilder, metaData);
|
||||
}
|
||||
|
||||
public static GeoGridAggregationBuilder parse(String aggregationName, XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, new GeoTileGridAggregationBuilder(aggregationName), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Aggregates data expressed as geotile longs (for efficiency's sake) but formats results as geotile strings.
|
||||
*/
|
||||
public class GeoTileGridAggregator extends GeoGridAggregator<InternalGeoTileGrid> {
|
||||
|
||||
GeoTileGridAggregator(String name, AggregatorFactories factories, CellIdSource valuesSource,
|
||||
int requiredSize, int shardSize, SearchContext aggregationContext, Aggregator parent,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
||||
super(name, factories, valuesSource, requiredSize, shardSize, aggregationContext, parent, pipelineAggregators, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
InternalGeoTileGrid buildAggregation(String name, int requiredSize, List<InternalGeoGridBucket> buckets,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||
return new InternalGeoTileGrid(name, requiredSize, buckets, pipelineAggregators, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalGeoTileGrid buildEmptyAggregation() {
|
||||
return new InternalGeoTileGrid(name, requiredSize, Collections.emptyList(), pipelineAggregators(), metaData());
|
||||
}
|
||||
|
||||
InternalGeoGridBucket newEmptyBucket() {
|
||||
return new InternalGeoTileGridBucket(0, 0, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactory;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregation;
|
||||
import org.elasticsearch.search.aggregations.NonCollectingAggregator;
|
||||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource.GeoPoint;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeoTileGridAggregatorFactory extends ValuesSourceAggregatorFactory<ValuesSource.GeoPoint, GeoTileGridAggregatorFactory> {
|
||||
|
||||
private final int precision;
|
||||
private final int requiredSize;
|
||||
private final int shardSize;
|
||||
|
||||
GeoTileGridAggregatorFactory(String name, ValuesSourceConfig<GeoPoint> config, int precision, int requiredSize,
|
||||
int shardSize, SearchContext context, AggregatorFactory<?> parent,
|
||||
AggregatorFactories.Builder subFactoriesBuilder, Map<String, Object> metaData
|
||||
) throws IOException {
|
||||
super(name, config, context, parent, subFactoriesBuilder, metaData);
|
||||
this.precision = precision;
|
||||
this.requiredSize = requiredSize;
|
||||
this.shardSize = shardSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Aggregator createUnmapped(Aggregator parent, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData)
|
||||
throws IOException {
|
||||
final InternalAggregation aggregation = new InternalGeoTileGrid(name, requiredSize,
|
||||
Collections.emptyList(), pipelineAggregators, metaData);
|
||||
return new NonCollectingAggregator(name, context, parent, pipelineAggregators, metaData) {
|
||||
@Override
|
||||
public InternalAggregation buildEmptyAggregation() {
|
||||
return aggregation;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Aggregator doCreateInternal(final ValuesSource.GeoPoint valuesSource, Aggregator parent, boolean collectsFromSingleBucket,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
||||
if (collectsFromSingleBucket == false) {
|
||||
return asMultiBucketAggregator(this, context, parent);
|
||||
}
|
||||
CellIdSource cellIdSource = new CellIdSource(valuesSource, precision, GeoTileUtils::longEncode);
|
||||
return new GeoTileGridAggregator(name, factories, cellIdSource, requiredSize, shardSize, context, parent,
|
||||
pipelineAggregators, metaData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.common.geo.GeoUtils.normalizeLat;
|
||||
import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
|
||||
|
||||
/**
|
||||
* Implements geotile key hashing, same as used by many map tile implementations.
|
||||
* The string key is formatted as "zoom/x/y"
|
||||
* The hash value (long) contains all three of those values compacted into a single 64bit value:
|
||||
* bits 58..63 -- zoom (0..29)
|
||||
* bits 29..57 -- X tile index (0..2^zoom)
|
||||
* bits 0..28 -- Y tile index (0..2^zoom)
|
||||
*/
|
||||
final class GeoTileUtils {
|
||||
|
||||
private GeoTileUtils() {}
|
||||
|
||||
/**
|
||||
* Largest number of tiles (precision) to use.
|
||||
* This value cannot be more than (64-5)/2 = 29, because 5 bits are used for zoom level itself (0-31)
|
||||
* If zoom is not stored inside hash, it would be possible to use up to 32.
|
||||
* Note that changing this value will make serialization binary-incompatible between versions.
|
||||
* Another consideration is that index optimizes lat/lng storage, loosing some precision.
|
||||
* E.g. hash lng=140.74779717298918D lat=45.61884022447444D == "18/233561/93659", but shown as "18/233561/93658"
|
||||
*/
|
||||
static final int MAX_ZOOM = 29;
|
||||
|
||||
/**
|
||||
* Bit position of the zoom value within hash - zoom is stored in the most significant 6 bits of a long number.
|
||||
*/
|
||||
private static final int ZOOM_SHIFT = MAX_ZOOM * 2;
|
||||
|
||||
/**
|
||||
* Bit mask to extract just the lowest 29 bits of a long
|
||||
*/
|
||||
private static final long X_Y_VALUE_MASK = (1L << MAX_ZOOM) - 1;
|
||||
|
||||
/**
|
||||
* Parse an integer precision (zoom level). The {@link ValueType#INT} allows it to be a number or a string.
|
||||
*
|
||||
* The precision is expressed as a zoom level between 0 and {@link #MAX_ZOOM} (inclusive).
|
||||
*
|
||||
* @param parser {@link XContentParser} to parse the value from
|
||||
* @return int representing precision
|
||||
*/
|
||||
static int parsePrecision(XContentParser parser) throws IOException, ElasticsearchParseException {
|
||||
final Object node = parser.currentToken().equals(XContentParser.Token.VALUE_NUMBER)
|
||||
? Integer.valueOf(parser.intValue())
|
||||
: parser.text();
|
||||
return XContentMapValues.nodeIntegerValue(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the precision value is within the allowed range, and return it if ok, or throw.
|
||||
*/
|
||||
static int checkPrecisionRange(int precision) {
|
||||
if (precision < 0 || precision > MAX_ZOOM) {
|
||||
throw new IllegalArgumentException("Invalid geotile_grid precision of " +
|
||||
precision + ". Must be between 0 and " + MAX_ZOOM + ".");
|
||||
}
|
||||
return precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode lon/lat to the geotile based long format.
|
||||
* The resulting hash contains interleaved tile X and Y coordinates.
|
||||
* The precision itself is also encoded as a few high bits.
|
||||
*/
|
||||
static long longEncode(double longitude, double latitude, int precision) {
|
||||
// Mathematics for this code was adapted from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Java
|
||||
|
||||
// Number of tiles for the current zoom level along the X and Y axis
|
||||
final long tiles = 1 << checkPrecisionRange(precision);
|
||||
|
||||
long xTile = (long) Math.floor((normalizeLon(longitude) + 180) / 360 * tiles);
|
||||
|
||||
double latSin = Math.sin(Math.toRadians(normalizeLat(latitude)));
|
||||
long yTile = (long) Math.floor((0.5 - (Math.log((1 + latSin) / (1 - latSin)) / (4 * Math.PI))) * tiles);
|
||||
|
||||
// Edge values may generate invalid values, and need to be clipped.
|
||||
// For example, polar regions (above/below lat 85.05112878) get normalized.
|
||||
if (xTile < 0) {
|
||||
xTile = 0;
|
||||
}
|
||||
if (xTile >= tiles) {
|
||||
xTile = tiles - 1;
|
||||
}
|
||||
if (yTile < 0) {
|
||||
yTile = 0;
|
||||
}
|
||||
if (yTile >= tiles) {
|
||||
yTile = tiles - 1;
|
||||
}
|
||||
|
||||
// Zoom value is placed in front of all the bits used for the geotile
|
||||
// e.g. when max zoom is 29, the largest index would use 58 bits (57th..0th),
|
||||
// leaving 5 bits unused for zoom. See MAX_ZOOM comment above.
|
||||
return ((long) precision << ZOOM_SHIFT) | (xTile << MAX_ZOOM) | yTile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse geotile hash as zoom, x, y integers.
|
||||
*/
|
||||
private static int[] parseHash(long hash) {
|
||||
final int zoom = (int) (hash >>> ZOOM_SHIFT);
|
||||
final int xTile = (int) ((hash >>> MAX_ZOOM) & X_Y_VALUE_MASK);
|
||||
final int yTile = (int) (hash & X_Y_VALUE_MASK);
|
||||
return new int[]{zoom, xTile, yTile};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode to a geotile string from the geotile based long format
|
||||
*/
|
||||
static String stringEncode(long hash) {
|
||||
int[] res = parseHash(hash);
|
||||
validateZXY(res[0], res[1], res[2]);
|
||||
return "" + res[0] + "/" + res[1] + "/" + res[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode long hash as a GeoPoint (center of the tile)
|
||||
*/
|
||||
static GeoPoint hashToGeoPoint(long hash) {
|
||||
int[] res = parseHash(hash);
|
||||
return zxyToGeoPoint(res[0], res[1], res[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string bucket key in "zoom/x/y" format to a GeoPoint (center of the tile)
|
||||
*/
|
||||
static GeoPoint keyToGeoPoint(String hashAsString) {
|
||||
final String[] parts = hashAsString.split("/", 4);
|
||||
if (parts.length != 3) {
|
||||
throw new IllegalArgumentException("Invalid geotile_grid hash string of " +
|
||||
hashAsString + ". Must be three integers in a form \"zoom/x/y\".");
|
||||
}
|
||||
|
||||
try {
|
||||
return zxyToGeoPoint(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid geotile_grid hash string of " +
|
||||
hashAsString + ". Must be three integers in a form \"zoom/x/y\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates Zoom, X, and Y values, and returns the total number of allowed tiles along the x/y axis.
|
||||
*/
|
||||
private static int validateZXY(int zoom, int xTile, int yTile) {
|
||||
final int tiles = 1 << checkPrecisionRange(zoom);
|
||||
if (xTile < 0 || yTile < 0 || xTile >= tiles || yTile >= tiles) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
Locale.ROOT, "Zoom/X/Y combination is not valid: %d/%d/%d", zoom, xTile, yTile));
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts zoom/x/y integers into a GeoPoint.
|
||||
*/
|
||||
private static GeoPoint zxyToGeoPoint(int zoom, int xTile, int yTile) {
|
||||
final int tiles = validateZXY(zoom, xTile, yTile);
|
||||
final double n = Math.PI - (2.0 * Math.PI * (yTile + 0.5)) / tiles;
|
||||
final double lat = Math.toDegrees(Math.atan(Math.sinh(n)));
|
||||
final double lon = ((xTile + 0.5) / tiles * 360.0) - 180;
|
||||
return new GeoPoint(lat, lon);
|
||||
}
|
||||
}
|
|
@ -33,8 +33,6 @@ import java.util.Map;
|
|||
*/
|
||||
public class InternalGeoHashGrid extends InternalGeoGrid<InternalGeoHashGridBucket> {
|
||||
|
||||
private static final String NAME = "geohash_grid";
|
||||
|
||||
InternalGeoHashGrid(String name, int requiredSize, List<InternalGeoGridBucket> buckets,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||
super(name, requiredSize, buckets, pipelineAggregators, metaData);
|
||||
|
@ -66,6 +64,6 @@ public class InternalGeoHashGrid extends InternalGeoGrid<InternalGeoHashGridBuck
|
|||
|
||||
@Override
|
||||
public String getWriteableName() {
|
||||
return NAME;
|
||||
return GeoHashGridAggregationBuilder.NAME;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregations;
|
||||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a grid of cells where each cell's location is determined by a geohash.
|
||||
* All geohashes in a grid are of the same precision and held internally as a single long
|
||||
* for efficiency's sake.
|
||||
*/
|
||||
public class InternalGeoTileGrid extends InternalGeoGrid<InternalGeoTileGridBucket> {
|
||||
|
||||
InternalGeoTileGrid(String name, int requiredSize, List<InternalGeoGridBucket> buckets,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||
super(name, requiredSize, buckets, pipelineAggregators, metaData);
|
||||
}
|
||||
|
||||
public InternalGeoTileGrid(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalGeoGrid create(List<InternalGeoGridBucket> buckets) {
|
||||
return new InternalGeoTileGrid(name, requiredSize, buckets, pipelineAggregators(), metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalGeoGridBucket createBucket(InternalAggregations aggregations, InternalGeoGridBucket prototype) {
|
||||
return new InternalGeoTileGridBucket(prototype.hashAsLong, prototype.docCount, aggregations);
|
||||
}
|
||||
|
||||
@Override
|
||||
InternalGeoGrid create(String name, int requiredSize, List buckets, List list, Map metaData) {
|
||||
return new InternalGeoTileGrid(name, requiredSize, buckets, list, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
Reader getBucketReader() {
|
||||
return InternalGeoTileGridBucket::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteableName() {
|
||||
return GeoTileGridAggregationBuilder.NAME;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregations;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InternalGeoTileGridBucket extends InternalGeoGridBucket<InternalGeoTileGridBucket> {
|
||||
InternalGeoTileGridBucket(long hashAsLong, long docCount, InternalAggregations aggregations) {
|
||||
super(hashAsLong, docCount, aggregations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from a stream.
|
||||
*/
|
||||
public InternalGeoTileGridBucket(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
InternalGeoTileGridBucket buildBucket(InternalGeoGridBucket bucket, long hashAsLong, long docCount,
|
||||
InternalAggregations aggregations) {
|
||||
return new InternalGeoTileGridBucket(hashAsLong, docCount, aggregations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyAsString() {
|
||||
return GeoTileUtils.stringEncode(hashAsLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint getKey() {
|
||||
return GeoTileUtils.hashToGeoPoint(hashAsLong);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ParsedGeoTileGrid extends ParsedGeoGrid {
|
||||
|
||||
private static ObjectParser<ParsedGeoGrid, Void> PARSER = createParser(ParsedGeoTileGrid::new,
|
||||
ParsedGeoTileGridBucket::fromXContent, ParsedGeoTileGridBucket::fromXContent);
|
||||
|
||||
public static ParsedGeoGrid fromXContent(XContentParser parser, String name) throws IOException {
|
||||
ParsedGeoGrid aggregation = PARSER.parse(parser, null);
|
||||
aggregation.setName(name);
|
||||
return aggregation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return GeoTileGridAggregationBuilder.NAME;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ParsedGeoTileGridBucket extends ParsedGeoGridBucket {
|
||||
|
||||
@Override
|
||||
public GeoPoint getKey() {
|
||||
return GeoTileUtils.keyToGeoPoint(hashAsString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyAsString() {
|
||||
return hashAsString;
|
||||
}
|
||||
|
||||
static ParsedGeoTileGridBucket fromXContent(XContentParser parser) throws IOException {
|
||||
return parseXContent(parser, false, ParsedGeoTileGridBucket::new, (p, bucket) -> bucket.hashAsString = p.text());
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ import org.elasticsearch.search.aggregations.bucket.composite.InternalCompositeT
|
|||
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilterTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.filter.InternalFiltersTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.InternalGlobalTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.InternalAutoDateHistogramTests;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.InternalDateHistogramTests;
|
||||
|
@ -140,6 +141,7 @@ public class AggregationsTests extends ESTestCase {
|
|||
aggsTests.add(new InternalFilterTests());
|
||||
aggsTests.add(new InternalSamplerTests());
|
||||
aggsTests.add(new GeoHashGridTests());
|
||||
aggsTests.add(new GeoTileGridTests());
|
||||
aggsTests.add(new InternalRangeTests());
|
||||
aggsTests.add(new InternalDateRangeTests());
|
||||
aggsTests.add(new InternalGeoDistanceTests());
|
||||
|
|
|
@ -39,6 +39,7 @@ import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHist
|
|||
import static org.elasticsearch.search.aggregations.AggregationBuilders.dateRange;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.filter;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.geohashGrid;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.geotileGrid;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.global;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.histogram;
|
||||
import static org.elasticsearch.search.aggregations.AggregationBuilders.ipRange;
|
||||
|
@ -306,5 +307,20 @@ public class ShardReduceIT extends ESIntegTestCase {
|
|||
assertThat(histo.getBuckets().size(), equalTo(4));
|
||||
}
|
||||
|
||||
public void testGeoTileGrid() throws Exception {
|
||||
SearchResponse response = client().prepareSearch("idx")
|
||||
.setQuery(QueryBuilders.matchAllQuery())
|
||||
.addAggregation(geotileGrid("grid").field("location")
|
||||
.subAggregation(dateHistogram("histo").field("date").dateHistogramInterval(DateHistogramInterval.DAY)
|
||||
.minDocCount(0)))
|
||||
.get();
|
||||
|
||||
assertSearchResponse(response);
|
||||
|
||||
GeoGrid grid = response.getAggregations().get("grid");
|
||||
Histogram histo = grid.getBuckets().iterator().next().getAggregations().get("histo");
|
||||
assertThat(histo.getBuckets().size(), equalTo(4));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.elasticsearch.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.apache.lucene.document.LatLonDocValuesField;
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
|
@ -89,6 +90,13 @@ public abstract class GeoGridAggregatorTestCase<T extends InternalGeoGridBucket>
|
|||
double lat = (180d * randomDouble()) - 90d;
|
||||
double lng = (360d * randomDouble()) - 180d;
|
||||
|
||||
// Precision-adjust longitude/latitude to avoid wrong bucket placement
|
||||
// Internally, lat/lng get converted to 32 bit integers, loosing some precision.
|
||||
// This does not affect geohashing because geohash uses the same algorithm,
|
||||
// but it does affect other bucketing algos, thus we need to do the same steps here.
|
||||
lng = GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lng));
|
||||
lat = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat));
|
||||
|
||||
points.add(new LatLonDocValuesField(FIELD_NAME, lat, lng));
|
||||
String hash = hashAsString(lng, lat, precision);
|
||||
if (distinctHashesPerDoc.contains(hash) == false) {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
public class GeoTileGridAggregatorTests extends GeoGridAggregatorTestCase<InternalGeoTileGridBucket> {
|
||||
|
||||
@Override
|
||||
protected int randomPrecision() {
|
||||
return randomIntBetween(0, GeoTileUtils.MAX_ZOOM);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String hashAsString(double lng, double lat, int precision) {
|
||||
return GeoTileUtils.stringEncode(GeoTileUtils.longEncode(lng, lat, precision));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GeoGridAggregationBuilder createBuilder(String name) {
|
||||
return new GeoTileGridAggregationBuilder(name);
|
||||
}
|
||||
|
||||
public void testPrecision() {
|
||||
final GeoGridAggregationBuilder builder = createBuilder("_name");
|
||||
|
||||
expectThrows(IllegalArgumentException.class, () -> builder.precision(-1));
|
||||
expectThrows(IllegalArgumentException.class, () -> builder.precision(30));
|
||||
|
||||
int precision = randomIntBetween(0, 29);
|
||||
builder.precision(precision);
|
||||
assertEquals(precision, builder.precision());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParseException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
public class GeoTileGridParserTests extends ESTestCase {
|
||||
public void testParseValidFromInts() throws Exception {
|
||||
int precision = randomIntBetween(0, GeoTileUtils.MAX_ZOOM);
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"field\":\"my_loc\", \"precision\":" + precision + ", \"size\": 500, \"shard_size\": 550}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
// can create a factory
|
||||
assertNotNull(GeoTileGridAggregationBuilder.parse("geotile_grid", stParser));
|
||||
}
|
||||
|
||||
public void testParseValidFromStrings() throws Exception {
|
||||
int precision = randomIntBetween(0, GeoTileUtils.MAX_ZOOM);
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"field\":\"my_loc\", \"precision\":\"" + precision + "\", \"size\": \"500\", \"shard_size\": \"550\"}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
// can create a factory
|
||||
assertNotNull(GeoTileGridAggregationBuilder.parse("geotile_grid", stParser));
|
||||
}
|
||||
|
||||
public void testParseErrorOnBooleanPrecision() throws Exception {
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent, "{\"field\":\"my_loc\", \"precision\":false}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
XContentParseException e = expectThrows(XContentParseException.class,
|
||||
() -> GeoTileGridAggregationBuilder.parse("geotile_grid", stParser));
|
||||
assertThat(ExceptionsHelper.detailedMessage(e),
|
||||
containsString("[geotile_grid] precision doesn't support values of type: VALUE_BOOLEAN"));
|
||||
}
|
||||
|
||||
public void testParseErrorOnPrecisionOutOfRange() throws Exception {
|
||||
XContentParser stParser = createParser(JsonXContent.jsonXContent, "{\"field\":\"my_loc\", \"precision\":\"30\"}");
|
||||
XContentParser.Token token = stParser.nextToken();
|
||||
assertSame(XContentParser.Token.START_OBJECT, token);
|
||||
try {
|
||||
GeoTileGridAggregationBuilder.parse("geotile_grid", stParser);
|
||||
fail();
|
||||
} catch (XContentParseException ex) {
|
||||
assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class));
|
||||
assertEquals("Invalid geotile_grid precision of 30. Must be between 0 and 29.", ex.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregations;
|
||||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeoTileGridTests extends GeoGridTestCase<InternalGeoTileGridBucket, InternalGeoTileGrid> {
|
||||
|
||||
@Override
|
||||
protected InternalGeoTileGrid createInternalGeoGrid(String name, int size, List<InternalGeoGridBucket> buckets,
|
||||
List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) {
|
||||
return new InternalGeoTileGrid(name, size, buckets, pipelineAggregators, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Writeable.Reader<InternalGeoTileGrid> instanceReader() {
|
||||
return InternalGeoTileGrid::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InternalGeoTileGridBucket createInternalGeoGridBucket(Long key, long docCount, InternalAggregations aggregations) {
|
||||
return new InternalGeoTileGridBucket(key, docCount, aggregations);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long longEncode(double lng, double lat, int precision) {
|
||||
return GeoTileUtils.longEncode(lng, lat, precision);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int randomPrecision() {
|
||||
// precision values below 8 can lead to parsing errors
|
||||
return randomIntBetween(8, GeoTileUtils.MAX_ZOOM);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.MAX_ZOOM;
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.checkPrecisionRange;
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.hashToGeoPoint;
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.keyToGeoPoint;
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.longEncode;
|
||||
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.stringEncode;
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class GeoTileUtilsTests extends ESTestCase {
|
||||
|
||||
private static final double GEOTILE_TOLERANCE = 1E-5D;
|
||||
|
||||
/**
|
||||
* Precision validation should throw an error if its outside of the valid range.
|
||||
*/
|
||||
public void testCheckPrecisionRange() {
|
||||
for (int i = 0; i <= 29; i++) {
|
||||
assertEquals(i, checkPrecisionRange(i));
|
||||
}
|
||||
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> checkPrecisionRange(-1));
|
||||
assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of -1. Must be between 0 and 29."));
|
||||
ex = expectThrows(IllegalArgumentException.class, () -> checkPrecisionRange(30));
|
||||
assertThat(ex.getMessage(), containsString("Invalid geotile_grid precision of 30. Must be between 0 and 29."));
|
||||
}
|
||||
|
||||
/**
|
||||
* A few hardcoded lat/lng/zoom hashing expectations
|
||||
*/
|
||||
public void testLongEncode() {
|
||||
assertEquals(0x0000000000000000L, longEncode(0, 0, 0));
|
||||
assertEquals(0x3C00095540001CA5L, longEncode(30, 70, 15));
|
||||
assertEquals(0x77FFFF4580000000L, longEncode(179.999, 89.999, 29));
|
||||
assertEquals(0x740000BA7FFFFFFFL, longEncode(-179.999, -89.999, 29));
|
||||
assertEquals(0x0800000040000001L, longEncode(1, 1, 2));
|
||||
assertEquals(0x0C00000060000000L, longEncode(-20, 100, 3));
|
||||
assertEquals(0x71127D27C8ACA67AL, longEncode(13, -15, 28));
|
||||
assertEquals(0x4C0077776003A9ACL, longEncode(-12, 15, 19));
|
||||
assertEquals(0x140000024000000EL, longEncode(-328.231870,16.064082, 5));
|
||||
assertEquals(0x6436F96B60000000L, longEncode(-590.769588,89.549167, 25));
|
||||
assertEquals(0x6411BD6BA0A98359L, longEncode(999.787079,51.830093, 25));
|
||||
assertEquals(0x751BD6BBCA983596L, longEncode(999.787079,51.830093, 29));
|
||||
assertEquals(0x77CF880A20000000L, longEncode(-557.039740,-632.103969, 29));
|
||||
assertEquals(0x7624FA4FA0000000L, longEncode(13,88, 29));
|
||||
assertEquals(0x7624FA4FBFFFFFFFL, longEncode(13,-88, 29));
|
||||
assertEquals(0x0400000020000000L, longEncode(13,89, 1));
|
||||
assertEquals(0x0400000020000001L, longEncode(13,-89, 1));
|
||||
assertEquals(0x0400000020000000L, longEncode(13,95, 1));
|
||||
assertEquals(0x0400000020000001L, longEncode(13,-95, 1));
|
||||
|
||||
expectThrows(IllegalArgumentException.class, () -> longEncode(0, 0, -1));
|
||||
expectThrows(IllegalArgumentException.class, () -> longEncode(-1, 0, MAX_ZOOM + 1));
|
||||
}
|
||||
|
||||
private void assertGeoPointEquals(GeoPoint gp, final double longitude, final double latitude) {
|
||||
assertThat(gp.lon(), closeTo(longitude, GEOTILE_TOLERANCE));
|
||||
assertThat(gp.lat(), closeTo(latitude, GEOTILE_TOLERANCE));
|
||||
}
|
||||
|
||||
public void testHashToGeoPoint() {
|
||||
assertGeoPointEquals(keyToGeoPoint("0/0/0"), 0.0, 0.0);
|
||||
assertGeoPointEquals(keyToGeoPoint("1/0/0"), -90.0, 66.51326044311186);
|
||||
assertGeoPointEquals(keyToGeoPoint("1/1/0"), 90.0, 66.51326044311186);
|
||||
assertGeoPointEquals(keyToGeoPoint("1/0/1"), -90.0, -66.51326044311186);
|
||||
assertGeoPointEquals(keyToGeoPoint("1/1/1"), 90.0, -66.51326044311186);
|
||||
assertGeoPointEquals(keyToGeoPoint("29/536870000/10"), 179.99938879162073, 85.05112817241982);
|
||||
assertGeoPointEquals(keyToGeoPoint("29/10/536870000"), -179.99999295920134, -85.0510760525731);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
expectThrows(NullPointerException.class, () -> keyToGeoPoint(null));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint(""));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("a"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/0/0/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/-1/-1"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/-1/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/0/-1"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("a/0/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/a/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("0/0/a"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint("-1/0/0"));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint((MAX_ZOOM + 1) + "/0/0"));
|
||||
|
||||
for (int z = 0; z <= MAX_ZOOM; z++) {
|
||||
final int zoom = z;
|
||||
final int max_index = (int) Math.pow(2, zoom);
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint(zoom + "/0/" + max_index));
|
||||
expectThrows(IllegalArgumentException.class, () -> keyToGeoPoint(zoom + "/" + max_index + "/0"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that hash produces the expected key, and that the key could be converted to hash via a GeoPoint
|
||||
*/
|
||||
private void assertStrCodec(long hash, String key, int zoom) {
|
||||
assertEquals(key, stringEncode(hash));
|
||||
final GeoPoint gp = keyToGeoPoint(key);
|
||||
assertEquals(hash, longEncode(gp.lon(), gp.lat(), zoom));
|
||||
}
|
||||
|
||||
/**
|
||||
* A few hardcoded lat/lng/zoom hashing expectations
|
||||
*/
|
||||
public void testStringEncode() {
|
||||
assertStrCodec(0x0000000000000000L, "0/0/0", 0);
|
||||
assertStrCodec(0x3C00095540001CA5L, "15/19114/7333", 15);
|
||||
assertStrCodec(0x77FFFF4580000000L, "29/536869420/0", 29);
|
||||
assertStrCodec(0x740000BA7FFFFFFFL, "29/1491/536870911", 29);
|
||||
assertStrCodec(0x0800000040000001L, "2/2/1", 2);
|
||||
assertStrCodec(0x0C00000060000000L, "3/3/0", 3);
|
||||
assertStrCodec(0x71127D27C8ACA67AL, "28/143911230/145532538", 28);
|
||||
assertStrCodec(0x4C0077776003A9ACL, "19/244667/240044", 19);
|
||||
assertStrCodec(0x140000024000000EL, "5/18/14", 5);
|
||||
assertStrCodec(0x6436F96B60000000L, "25/28822363/0", 25);
|
||||
assertStrCodec(0x6411BD6BA0A98359L, "25/9300829/11109209", 25);
|
||||
assertStrCodec(0x751BD6BBCA983596L, "29/148813278/177747350", 29);
|
||||
assertStrCodec(0x77CF880A20000000L, "29/511459409/0", 29);
|
||||
assertStrCodec(0x7624FA4FA0000000L, "29/287822461/0", 29);
|
||||
assertStrCodec(0x7624FA4FBFFFFFFFL, "29/287822461/536870911", 29);
|
||||
assertStrCodec(0x0400000020000000L, "1/1/0", 1);
|
||||
assertStrCodec(0x0400000020000001L, "1/1/1", 1);
|
||||
|
||||
expectThrows(IllegalArgumentException.class, () -> stringEncode(-1L));
|
||||
expectThrows(IllegalArgumentException.class, () -> stringEncode(0x7800000000000000L)); // z=30
|
||||
expectThrows(IllegalArgumentException.class, () -> stringEncode(0x0000000000000001L)); // z=0,x=0,y=1
|
||||
expectThrows(IllegalArgumentException.class, () -> stringEncode(0x0000000020000000L)); // z=0,x=1,y=0
|
||||
|
||||
for (int zoom = 0; zoom < 5; zoom++) {
|
||||
int maxTile = 1 << zoom;
|
||||
for (int x = 0; x < maxTile; x++) {
|
||||
for (int y = 0; y < maxTile; y++) {
|
||||
String expectedTileIndex = zoom + "/" + x + "/" + y;
|
||||
GeoPoint point = keyToGeoPoint(expectedTileIndex);
|
||||
String actualTileIndex = stringEncode(longEncode(point.lon(), point.lat(), zoom));
|
||||
assertEquals(expectedTileIndex, actualTileIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that for all points at all supported precision levels that the long encoding of a geotile
|
||||
* is compatible with its String based counterpart
|
||||
*/
|
||||
public void testGeoTileAsLongRoutines() {
|
||||
for (double lat = -90; lat <= 90; lat++) {
|
||||
for (double lng = -180; lng <= 180; lng++) {
|
||||
for (int p = 0; p <= 29; p++) {
|
||||
long hash = longEncode(lng, lat, p);
|
||||
if (p > 0) {
|
||||
assertNotEquals(0, hash);
|
||||
}
|
||||
|
||||
// GeoPoint would be in the center of the bucket, thus must produce the same hash
|
||||
GeoPoint point = hashToGeoPoint(hash);
|
||||
long hashAsLong2 = longEncode(point.lon(), point.lat(), p);
|
||||
assertEquals(hash, hashAsLong2);
|
||||
|
||||
// Same point should be generated from the string key
|
||||
assertEquals(point, keyToGeoPoint(stringEncode(hash)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the polar regions are handled properly.
|
||||
* Mercator projection does not show anything above 85 or below -85,
|
||||
* so ensure they are clipped correctly.
|
||||
*/
|
||||
public void testSingularityAtPoles() {
|
||||
double minLat = -85.05112878;
|
||||
double maxLat = 85.05112878;
|
||||
double lon = randomIntBetween(-180, 180);
|
||||
double lat = randomBoolean()
|
||||
? randomDoubleBetween(-90, minLat, true)
|
||||
: randomDoubleBetween(maxLat, 90, true);
|
||||
double clippedLat = Math.min(Math.max(lat, minLat), maxLat);
|
||||
int zoom = randomIntBetween(0, MAX_ZOOM);
|
||||
String tileIndex = stringEncode(longEncode(lon, lat, zoom));
|
||||
String clippedTileIndex = stringEncode(longEncode(lon, clippedLat, zoom));
|
||||
assertEquals(tileIndex, clippedTileIndex);
|
||||
}
|
||||
}
|
|
@ -51,6 +51,8 @@ import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
|
|||
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilters;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoHashGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoTileGrid;
|
||||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.global.ParsedGlobal;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.AutoDateHistogramAggregationBuilder;
|
||||
|
@ -212,6 +214,7 @@ public abstract class InternalAggregationTestCase<T extends InternalAggregation>
|
|||
map.put(FilterAggregationBuilder.NAME, (p, c) -> ParsedFilter.fromXContent(p, (String) c));
|
||||
map.put(InternalSampler.PARSER_NAME, (p, c) -> ParsedSampler.fromXContent(p, (String) c));
|
||||
map.put(GeoHashGridAggregationBuilder.NAME, (p, c) -> ParsedGeoHashGrid.fromXContent(p, (String) c));
|
||||
map.put(GeoTileGridAggregationBuilder.NAME, (p, c) -> ParsedGeoTileGrid.fromXContent(p, (String) c));
|
||||
map.put(RangeAggregationBuilder.NAME, (p, c) -> ParsedRange.fromXContent(p, (String) c));
|
||||
map.put(DateRangeAggregationBuilder.NAME, (p, c) -> ParsedDateRange.fromXContent(p, (String) c));
|
||||
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
|
||||
|
|
Loading…
Reference in New Issue