diff --git a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java
index cecbf227a3975..b6b06e5088162 100644
--- a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java
+++ b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java
@@ -322,7 +322,6 @@ private GeoRelation relateTriangle(int aX, int aY, boolean ab, int bX, int bY, b
int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY);
int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY);
-
// 1. check bounding boxes are disjoint, where north and east boundaries are not considered as crossing
if (tMaxX <= minX || tMinX > maxX || tMinY > maxY || tMaxY <= minY) {
return GeoRelation.QUERY_DISJOINT;
diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java
index ea973fa91fa5f..e5bb77b4b20ca 100644
--- a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java
+++ b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java
@@ -316,6 +316,5 @@ public double minX() {
public double maxX() {
return Math.max(negRight, posRight);
}
-
}
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java
index e06df02518e83..b47ae3029bffd 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java
@@ -31,9 +31,11 @@
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.DocValueFormat;
+import org.elasticsearch.search.aggregations.bucket.geogrid.BoundedGeoTileGridTiler;
import org.elasticsearch.search.aggregations.bucket.geogrid.CellIdSource;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoGridTiler;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridTiler;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
import org.elasticsearch.search.aggregations.support.ValueType;
import org.elasticsearch.search.aggregations.support.ValuesSource;
@@ -138,7 +140,15 @@ protected CompositeValuesSourceConfig innerBuild(QueryShardContext queryShardCon
ValuesSource.Geo geoValue = (ValuesSource.Geo) orig;
// is specified in the builder.
final MappedFieldType fieldType = config.fieldContext() != null ? config.fieldContext().fieldType() : null;
- CellIdSource cellIdSource = new CellIdSource(geoValue, precision, geoBoundingBox, GeoGridTiler.GeoTileGridTiler.INSTANCE);
+
+ final GeoGridTiler tiler;
+ if (geoBoundingBox.isUnbounded()) {
+ tiler = new GeoTileGridTiler();
+ } else {
+ tiler = new BoundedGeoTileGridTiler(geoBoundingBox);
+ }
+
+ CellIdSource cellIdSource = new CellIdSource(geoValue, precision, tiler);
return new CompositeValuesSourceConfig(name, fieldType, cellIdSource, DocValueFormat.GEOTILE, order(),
missingBucket(), script() != null);
} else {
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoHashGridTiler.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoHashGridTiler.java
new file mode 100644
index 0000000000000..0636de647a22c
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoHashGridTiler.java
@@ -0,0 +1,108 @@
+/*
+ * 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.GeoBoundingBox;
+import org.elasticsearch.common.geo.GeoRelation;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.geometry.utils.Geohash;
+import org.elasticsearch.index.fielddata.MultiGeoValues;
+
+public class BoundedGeoHashGridTiler extends GeoHashGridTiler {
+ private final double boundsTop;
+ private final double boundsBottom;
+ private final double boundsWestLeft;
+ private final double boundsWestRight;
+ private final double boundsEastLeft;
+ private final double boundsEastRight;
+ private final boolean crossesDateline;
+
+ BoundedGeoHashGridTiler(GeoBoundingBox geoBoundingBox) {
+ // split geoBoundingBox into west and east boxes
+ boundsTop = geoBoundingBox.top();
+ boundsBottom = geoBoundingBox.bottom();
+ if (geoBoundingBox.right() < geoBoundingBox.left()) {
+ boundsWestLeft = -180;
+ boundsWestRight = geoBoundingBox.right();
+ boundsEastLeft = geoBoundingBox.left();
+ boundsEastRight = 180;
+ crossesDateline = true;
+ } else { // only set east bounds
+ boundsEastLeft = geoBoundingBox.left();
+ boundsEastRight = geoBoundingBox.right();
+ boundsWestLeft = 0;
+ boundsWestRight = 0;
+ crossesDateline = false;
+ }
+ }
+
+ @Override
+ public int advancePointValue(long[] values, double x, double y, int precision, int valuesIdx) {
+ long hash = encode(x, y, precision);
+ if (cellIntersectsGeoBoundingBox(Geohash.toBoundingBox(Geohash.stringEncode(hash)))) {
+ values[valuesIdx] = hash;
+ return valuesIdx + 1;
+ }
+ return valuesIdx;
+ }
+
+ boolean cellIntersectsGeoBoundingBox(Rectangle rectangle) {
+ return (boundsTop >= rectangle.getMinY() && boundsBottom <= rectangle.getMaxY()
+ && (boundsEastLeft <= rectangle.getMaxX() && boundsEastRight >= rectangle.getMinX()
+ || (crossesDateline && boundsWestLeft <= rectangle.getMaxX() && boundsWestRight >= rectangle.getMinX())));
+ }
+
+ @Override
+ protected int setValue(CellValues docValues, MultiGeoValues.GeoValue geoValue, MultiGeoValues.BoundingBox bounds, int precision) {
+ String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
+ GeoRelation relation = relateTile(geoValue, hash);
+ if (relation != GeoRelation.QUERY_DISJOINT) {
+ docValues.resizeCell(1);
+ docValues.add(0, Geohash.longEncode(hash));
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ protected GeoRelation relateTile(MultiGeoValues.GeoValue geoValue, String hash) {
+ Rectangle rectangle = Geohash.toBoundingBox(hash);
+ if (cellIntersectsGeoBoundingBox(rectangle)) {
+ return geoValue.relate(rectangle);
+ } else {
+ return GeoRelation.QUERY_DISJOINT;
+ }
+ }
+
+ @Override
+ protected int setValuesForFullyContainedTile(String hash, CellValues values,
+ int valuesIndex, int targetPrecision) {
+ String[] hashes = Geohash.getSubGeohashes(hash);
+ for (int i = 0; i < hashes.length; i++) {
+ if (hashes[i].length() == targetPrecision ) {
+ if (cellIntersectsGeoBoundingBox(Geohash.toBoundingBox(hashes[i]))) {
+ values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
+ }
+ } else {
+ valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, targetPrecision);
+ }
+ }
+ return valuesIndex;
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoPointCellValues.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoPointCellValues.java
deleted file mode 100644
index 493a9aa729678..0000000000000
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoPointCellValues.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.GeoBoundingBox;
-import org.elasticsearch.index.fielddata.MultiGeoValues;
-
-/**
- * Class representing {@link CellValues} whose values are filtered
- * according to whether they are within the specified {@link GeoBoundingBox}.
- *
- * The specified bounding box is assumed to be bounded.
- */
-class BoundedGeoPointCellValues extends CellValues {
-
- private final GeoBoundingBox geoBoundingBox;
-
- protected BoundedGeoPointCellValues(MultiGeoValues geoValues, int precision, GeoGridTiler tiler, GeoBoundingBox geoBoundingBox) {
- super(geoValues, precision, tiler);
- this.geoBoundingBox = geoBoundingBox;
- }
-
-
- @Override
- int advanceValue(MultiGeoValues.GeoValue target, int valuesIdx) {
- if (geoBoundingBox.pointInBounds(target.lon(), target.lat())) {
- values[valuesIdx] = tiler.encode(target.lon(), target.lat(), precision);
- return valuesIdx + 1;
- }
- return valuesIdx;
- }
-}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java
new file mode 100644
index 0000000000000..89b6b69c41ba7
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java
@@ -0,0 +1,107 @@
+/*
+ * 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.GeoBoundingBox;
+import org.elasticsearch.common.geo.GeoRelation;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.index.fielddata.MultiGeoValues;
+
+public class BoundedGeoTileGridTiler extends GeoTileGridTiler {
+ private final double boundsTop;
+ private final double boundsBottom;
+ private final double boundsWestLeft;
+ private final double boundsWestRight;
+ private final double boundsEastLeft;
+ private final double boundsEastRight;
+ private final boolean crossesDateline;
+
+ public BoundedGeoTileGridTiler(GeoBoundingBox geoBoundingBox) {
+ // split geoBoundingBox into west and east boxes
+ boundsTop = geoBoundingBox.top();
+ boundsBottom = geoBoundingBox.bottom();
+ if (geoBoundingBox.right() < geoBoundingBox.left()) {
+ boundsWestLeft = -180;
+ boundsWestRight = geoBoundingBox.right();
+ boundsEastLeft = geoBoundingBox.left();
+ boundsEastRight = 180;
+ crossesDateline = true;
+ } else { // only set east bounds
+ boundsEastLeft = geoBoundingBox.left();
+ boundsEastRight = geoBoundingBox.right();
+ boundsWestLeft = 0;
+ boundsWestRight = 0;
+ crossesDateline = false;
+ }
+ }
+
+ public int advancePointValue(long[] values, double x, double y, int precision, int valuesIdx) {
+ long hash = encode(x, y, precision);
+ if (cellIntersectsGeoBoundingBox(GeoTileUtils.toBoundingBox(hash))) {
+ values[valuesIdx] = hash;
+ return valuesIdx + 1;
+ }
+ return valuesIdx;
+ }
+
+ boolean cellIntersectsGeoBoundingBox(Rectangle rectangle) {
+ return (boundsTop >= rectangle.getMinY() && boundsBottom <= rectangle.getMaxY()
+ && (boundsEastLeft <= rectangle.getMaxX() && boundsEastRight >= rectangle.getMinX()
+ || (crossesDateline && boundsWestLeft <= rectangle.getMaxX() && boundsWestRight >= rectangle.getMinX())));
+ }
+
+ @Override
+ public GeoRelation relateTile(MultiGeoValues.GeoValue geoValue, int xTile, int yTile, int precision) {
+ Rectangle rectangle = GeoTileUtils.toBoundingBox(xTile, yTile, precision);
+ if (cellIntersectsGeoBoundingBox(rectangle)) {
+ return geoValue.relate(rectangle);
+ }
+ return GeoRelation.QUERY_DISJOINT;
+ }
+
+ @Override
+ protected int setValue(CellValues docValues, MultiGeoValues.GeoValue geoValue, int xTile, int yTile, int precision) {
+ if (cellIntersectsGeoBoundingBox(GeoTileUtils.toBoundingBox(xTile, yTile, precision))) {
+ docValues.resizeCell(1);
+ docValues.add(0, GeoTileUtils.longEncodeTiles(precision, xTile, yTile));
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, CellValues values, int valuesIndex,
+ int targetPrecision) {
+ zTile++;
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ int nextX = 2 * xTile + i;
+ int nextY = 2 * yTile + j;
+ if (zTile == targetPrecision) {
+ if (cellIntersectsGeoBoundingBox(GeoTileUtils.toBoundingBox(nextX, nextY, zTile))) {
+ values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
+ }
+ } else {
+ valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision);
+ }
+ }
+ }
+ return valuesIndex;
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java
index 8161aef98746c..8267bc69f8c52 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java
@@ -21,7 +21,6 @@
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.elasticsearch.index.fielddata.MultiGeoValues;
-import org.elasticsearch.common.geo.GeoBoundingBox;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
@@ -36,13 +35,11 @@ public class CellIdSource extends ValuesSource.Numeric {
private final ValuesSource.Geo valuesSource;
private final int precision;
private final GeoGridTiler encoder;
- private final GeoBoundingBox geoBoundingBox;
- public CellIdSource(ValuesSource.Geo valuesSource, int precision, GeoBoundingBox geoBoundingBox, GeoGridTiler encoder) {
+ public CellIdSource(ValuesSource.Geo valuesSource, int precision, GeoGridTiler encoder) {
this.valuesSource = valuesSource;
//different GeoPoints could map to the same or different hashing cells.
this.precision = precision;
- this.geoBoundingBox = geoBoundingBox;
this.encoder = encoder;
}
@@ -65,19 +62,10 @@ public SortedNumericDocValues longValues(LeafReaderContext ctx) {
ValuesSourceType vs = geoValues.valuesSourceType();
if (CoreValuesSourceType.GEOPOINT == vs) {
// docValues are geo points
- if (geoBoundingBox.isUnbounded()) {
- return new UnboundedGeoPointCellValues(geoValues, precision, encoder);
- } else {
- return new BoundedGeoPointCellValues(geoValues, precision, encoder, geoBoundingBox);
- }
+ return new GeoPointCellValues(geoValues, precision, encoder);
} else if (CoreValuesSourceType.GEOSHAPE == vs || CoreValuesSourceType.GEO == vs) {
// docValues are geo shapes
- if (geoBoundingBox.isUnbounded()) {
- return new GeoShapeCellValues(geoValues, precision, encoder);
- } else {
- // TODO(talevy): support unbounded
- throw new IllegalArgumentException("bounded geogrid is not supported on geo_shape fields");
- }
+ return new GeoShapeCellValues(geoValues, precision, encoder);
} else {
throw new IllegalArgumentException("unsupported geo type");
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellValues.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellValues.java
index b53d02e81a2ef..6ebad166f513d 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellValues.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellValues.java
@@ -56,6 +56,19 @@ public boolean advanceExact(int docId) throws IOException {
}
}
+ // for testing
+ protected long[] getValues() {
+ return values;
+ }
+
+ protected void add(int idx, long value) {
+ values[idx] = value;
+ }
+
+ void resizeCell(int newSize) {
+ resize(newSize);
+ }
+
/**
* Sets the appropriate long-encoded value for target
* in values
.
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTiler.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTiler.java
index ebf7ad21d208c..1f98f9bdf3031 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTiler.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTiler.java
@@ -19,9 +19,6 @@
package org.elasticsearch.search.aggregations.bucket.geogrid;
-import org.elasticsearch.common.geo.GeoRelation;
-import org.elasticsearch.geometry.Rectangle;
-import org.elasticsearch.geometry.utils.Geohash;
import org.elasticsearch.index.fielddata.MultiGeoValues;
/**
@@ -39,241 +36,31 @@ public interface GeoGridTiler {
/**
*
- * @param docValues the array of long-encoded bucket keys to fill
- * @param geoValue the input shape
- * @param precision the tile zoom-level
+ * @param docValues the array of long-encoded bucket keys to fill
+ * @param geoValue the input shape
+ * @param precision the tile zoom-level
*
* @return the number of tiles the geoValue intersects
*/
- int setValues(GeoShapeCellValues docValues, MultiGeoValues.GeoValue geoValue, int precision);
+ int setValues(CellValues docValues, MultiGeoValues.GeoValue geoValue, int precision);
- class GeoHashGridTiler implements GeoGridTiler {
- public static final GeoHashGridTiler INSTANCE = new GeoHashGridTiler();
- private GeoHashGridTiler() {}
-
- @Override
- public long encode(double x, double y, int precision) {
- return Geohash.longEncode(x, y, precision);
- }
-
- @Override
- public int setValues(GeoShapeCellValues values, MultiGeoValues.GeoValue geoValue, int precision) {
- if (precision == 1) {
- values.resizeCell(1);
- values.add(0, Geohash.longEncode(0, 0, 0));
- }
-
- MultiGeoValues.BoundingBox bounds = geoValue.boundingBox();
- assert bounds.minX() <= bounds.maxX();
- long numLonCells = (long) ((bounds.maxX() - bounds.minX()) / Geohash.lonWidthInDegrees(precision));
- long numLatCells = (long) ((bounds.maxY() - bounds.minY()) / Geohash.latHeightInDegrees(precision));
- long count = (numLonCells + 1) * (numLatCells + 1);
- if (count == 1) {
- String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
- values.resizeCell(1);
- values.add(0, Geohash.longEncode(hash));
- return 1;
- } else if (count <= precision) {
- return setValuesByBruteForceScan(values, geoValue, precision, bounds);
- } else {
- return setValuesByRasterization("", values, 0, precision, geoValue);
- }
- }
-
- protected int setValuesByBruteForceScan(GeoShapeCellValues values, MultiGeoValues.GeoValue geoValue,
- int precision, MultiGeoValues.BoundingBox bounds) {
- // TODO: This way to discover cells inside of a bounding box seems not to work as expected. I can
- // see that eventually we will be visiting twice the same cell which should not happen.
- int idx = 0;
- String min = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
- String max = Geohash.stringEncode(bounds.maxX(), bounds.maxY(), precision);
- String minNeighborBelow = Geohash.getNeighbor(min, precision, 0, -1);
- double minY = Geohash.decodeLatitude((minNeighborBelow == null) ? min : minNeighborBelow);
- double minX = Geohash.decodeLongitude(min);
- double maxY = Geohash.decodeLatitude(max);
- double maxX = Geohash.decodeLongitude(max);
- for (double i = minX; i <= maxX; i += Geohash.lonWidthInDegrees(precision)) {
- for (double j = minY; j <= maxY; j += Geohash.latHeightInDegrees(precision)) {
- Rectangle rectangle = Geohash.toBoundingBox(Geohash.stringEncode(i, j, precision));
- GeoRelation relation = geoValue.relate(rectangle);
- if (relation != GeoRelation.QUERY_DISJOINT) {
- values.resizeCell(idx + 1);
- values.add(idx++, encode(i, j, precision));
- }
- }
- }
- return idx;
- }
-
- protected int setValuesByRasterization(String hash, GeoShapeCellValues values, int valuesIndex,
- int targetPrecision, MultiGeoValues.GeoValue geoValue) {
- String[] hashes = Geohash.getSubGeohashes(hash);
- for (int i = 0; i < hashes.length; i++) {
- Rectangle rectangle = Geohash.toBoundingBox(hashes[i]);
- GeoRelation relation = geoValue.relate(rectangle);
- if (relation == GeoRelation.QUERY_CROSSES) {
- if (hashes[i].length() == targetPrecision) {
- values.resizeCell(valuesIndex + 1);
- values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
- } else {
- valuesIndex =
- setValuesByRasterization(hashes[i], values, valuesIndex, targetPrecision, geoValue);
- }
- } else if (relation == GeoRelation.QUERY_INSIDE) {
- if (hashes[i].length() == targetPrecision) {
- values.resizeCell(valuesIndex + 1);
- values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
- } else {
- values.resizeCell(valuesIndex + (int) Math.pow(32, targetPrecision - hash.length()) + 1);
- valuesIndex = setValuesForFullyContainedTile(hashes[i],values, valuesIndex, targetPrecision);
- }
- }
- }
- return valuesIndex;
- }
-
- private int setValuesForFullyContainedTile(String hash, GeoShapeCellValues values,
- int valuesIndex, int targetPrecision) {
- String[] hashes = Geohash.getSubGeohashes(hash);
- for (int i = 0; i < hashes.length; i++) {
- if (hashes[i].length() == targetPrecision) {
- values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
- } else {
- valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, targetPrecision);
- }
- }
- return valuesIndex;
- }
- }
-
- class GeoTileGridTiler implements GeoGridTiler {
- public static final GeoTileGridTiler INSTANCE = new GeoTileGridTiler();
-
- private GeoTileGridTiler() {}
-
- @Override
- public long encode(double x, double y, int precision) {
- return GeoTileUtils.longEncode(x, y, precision);
- }
-
- /**
- * Sets the values of the long[] underlying {@link GeoShapeCellValues}.
- *
- * If the shape resides between GeoTileUtils.NORMALIZED_LATITUDE_MASK
and 90 or
- * between GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK
and -90 degree latitudes, then
- * the shape is not accounted for since geo-tiles are only defined within those bounds.
- *
- * @param values the bucket values
- * @param geoValue the input shape
- * @param precision the tile zoom-level
- *
- * @return the number of tiles set by the shape
- */
- @Override
- public int setValues(GeoShapeCellValues values, MultiGeoValues.GeoValue geoValue, int precision) {
- MultiGeoValues.BoundingBox bounds = geoValue.boundingBox();
- assert bounds.minX() <= bounds.maxX();
-
- if (precision == 0) {
- values.resizeCell(1);
- values.add(0, GeoTileUtils.longEncodeTiles(0, 0, 0));
- return 1;
- }
-
- // geo tiles are not defined at the extreme latitudes due to them
- // tiling the world as a square.
- if ((bounds.top > GeoTileUtils.NORMALIZED_LATITUDE_MASK && bounds.bottom > GeoTileUtils.NORMALIZED_LATITUDE_MASK)
- || (bounds.top < GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK
- && bounds.bottom < GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK)) {
- return 0;
- }
-
-
- final double tiles = 1 << precision;
- int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
- int minYTile = GeoTileUtils.getYTile(bounds.maxY(), (long) tiles);
- int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
- int maxYTile = GeoTileUtils.getYTile(bounds.minY(), (long) tiles);
- int count = (maxXTile - minXTile + 1) * (maxYTile - minYTile + 1);
- if (count == 1) {
- values.resizeCell(1);
- values.add(0, GeoTileUtils.longEncodeTiles(precision, minXTile, minYTile));
- return 1;
- } else if (count <= precision) {
- return setValuesByBruteForceScan(values, geoValue, precision, minXTile, minYTile, maxXTile, maxYTile);
- } else {
- return setValuesByRasterization(0, 0, 0, values, 0, precision, geoValue);
- }
- }
-
- /**
- *
- * @param values the bucket values as longs
- * @param geoValue the shape value
- * @param precision the target precision to split the shape up into
- * @return the number of buckets the geoValue is found in
- */
- protected int setValuesByBruteForceScan(GeoShapeCellValues values, MultiGeoValues.GeoValue geoValue,
- int precision, int minXTile, int minYTile, int maxXTile, int maxYTile) {
- int idx = 0;
- for (int i = minXTile; i <= maxXTile; i++) {
- for (int j = minYTile; j <= maxYTile; j++) {
- Rectangle rectangle = GeoTileUtils.toBoundingBox(i, j, precision);
- if (geoValue.relate(rectangle) != GeoRelation.QUERY_DISJOINT) {
- values.resizeCell(idx + 1);
- values.add(idx++, GeoTileUtils.longEncodeTiles(precision, i, j));
- }
- }
- }
- return idx;
- }
-
- protected int setValuesByRasterization(int xTile, int yTile, int zTile, GeoShapeCellValues values,
- int valuesIndex, int targetPrecision, MultiGeoValues.GeoValue geoValue) {
- zTile++;
- for (int i = 0; i < 2; i++) {
- for (int j = 0; j < 2; j++) {
- int nextX = 2 * xTile + i;
- int nextY = 2 * yTile + j;
- Rectangle rectangle = GeoTileUtils.toBoundingBox(nextX, nextY, zTile);
- GeoRelation relation = geoValue.relate(rectangle);
- if (GeoRelation.QUERY_INSIDE == relation) {
- if (zTile == targetPrecision) {
- values.resizeCell(valuesIndex + 1);
- values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
- } else {
- values.resizeCell(valuesIndex + 1 << ( 2 * (targetPrecision - zTile)) + 1);
- valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision);
- }
- } else if (GeoRelation.QUERY_CROSSES == relation) {
- if (zTile == targetPrecision) {
- values.resizeCell(valuesIndex + 1);
- values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
- } else {
- valuesIndex = setValuesByRasterization(nextX, nextY, zTile, values, valuesIndex, targetPrecision, geoValue);
- }
- }
- }
- }
- return valuesIndex;
- }
-
- private int setValuesForFullyContainedTile(int xTile, int yTile, int zTile,
- GeoShapeCellValues values, int valuesIndex, int targetPrecision) {
- zTile++;
- for (int i = 0; i < 2; i++) {
- for (int j = 0; j < 2; j++) {
- int nextX = 2 * xTile + i;
- int nextY = 2 * yTile + j;
- if (zTile == targetPrecision) {
- values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
- } else {
- valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision);
- }
- }
- }
- return valuesIndex;
- }
+ /**
+ * This sets the long-encoded value of the geo-point into the associated doc-values
+ * array. This is to be overridden by the {@link BoundedGeoTileGridTiler} and
+ * {@link BoundedGeoHashGridTiler} to check whether the point's tile intersects
+ * the appropriate bounds.
+ *
+ * @param values the doc-values array
+ * @param x the longitude of the point
+ * @param y the latitude of the point
+ * @param precision the zoom-level
+ * @param valuesIdx the index into the doc-values array at the time of advancement
+ *
+ * @return the next index into the array
+ */
+ default int advancePointValue(long[] values, double x, double y, int precision, int valuesIdx) {
+ values[valuesIdx] = encode(x, y, precision);
+ return valuesIdx + 1;
}
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorFactory.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorFactory.java
index 153602493c3ba..0999732dc31d9 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorFactory.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorFactory.java
@@ -80,7 +80,13 @@ protected Aggregator doCreateInternal(final ValuesSource.Geo valuesSource,
if (collectsFromSingleBucket == false) {
return asMultiBucketAggregator(this, searchContext, parent);
}
- CellIdSource cellIdSource = new CellIdSource(valuesSource, precision, geoBoundingBox, GeoGridTiler.GeoHashGridTiler.INSTANCE);
+ final GeoGridTiler tiler;
+ if (geoBoundingBox.isUnbounded()) {
+ tiler = new GeoHashGridTiler();
+ } else {
+ tiler = new BoundedGeoHashGridTiler(geoBoundingBox);
+ }
+ CellIdSource cellIdSource = new CellIdSource(valuesSource, precision, tiler);
return new GeoHashGridAggregator(name, factories, cellIdSource, requiredSize, shardSize,
searchContext, parent, pipelineAggregators, metaData);
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridTiler.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridTiler.java
new file mode 100644
index 0000000000000..7e60e7752d58c
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridTiler.java
@@ -0,0 +1,133 @@
+/*
+ * 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.GeoRelation;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.geometry.utils.Geohash;
+import org.elasticsearch.index.fielddata.MultiGeoValues;
+
+public class GeoHashGridTiler implements GeoGridTiler {
+
+ @Override
+ public long encode(double x, double y, int precision) {
+ return Geohash.longEncode(x, y, precision);
+ }
+
+ @Override
+ public int setValues(CellValues values, MultiGeoValues.GeoValue geoValue, int precision) {
+ if (precision == 1) {
+ values.resizeCell(1);
+ values.add(0, Geohash.longEncode(0, 0, 0));
+ }
+
+ MultiGeoValues.BoundingBox bounds = geoValue.boundingBox();
+ assert bounds.minX() <= bounds.maxX();
+ long numLonCells = (long) ((bounds.maxX() - bounds.minX()) / Geohash.lonWidthInDegrees(precision));
+ long numLatCells = (long) ((bounds.maxY() - bounds.minY()) / Geohash.latHeightInDegrees(precision));
+ long count = (numLonCells + 1) * (numLatCells + 1);
+ if (count == 1) {
+ return setValue(values, geoValue, bounds, precision);
+ } else if (count <= precision) {
+ return setValuesByBruteForceScan(values, geoValue, precision, bounds);
+ } else {
+ return setValuesByRasterization("", values, 0, precision, geoValue);
+ }
+ }
+
+ /**
+ * Sets a singular doc-value for the {@link MultiGeoValues.GeoValue}. To be overriden by {@link BoundedGeoHashGridTiler}
+ * to account for {@link org.elasticsearch.common.geo.GeoBoundingBox} conditions
+ */
+ protected int setValue(CellValues docValues, MultiGeoValues.GeoValue geoValue, MultiGeoValues.BoundingBox bounds, int precision) {
+ String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
+ docValues.resizeCell(1);
+ docValues.add(0, Geohash.longEncode(hash));
+ return 1;
+ }
+
+ protected GeoRelation relateTile(MultiGeoValues.GeoValue geoValue, String hash) {
+ Rectangle rectangle = Geohash.toBoundingBox(hash);
+ return geoValue.relate(rectangle);
+ }
+
+ protected int setValuesByBruteForceScan(CellValues values, MultiGeoValues.GeoValue geoValue, int precision,
+ MultiGeoValues.BoundingBox bounds) {
+ // TODO: This way to discover cells inside of a bounding box seems not to work as expected. I can
+ // see that eventually we will be visiting twice the same cell which should not happen.
+ int idx = 0;
+ String min = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
+ String max = Geohash.stringEncode(bounds.maxX(), bounds.maxY(), precision);
+ String minNeighborBelow = Geohash.getNeighbor(min, precision, 0, -1);
+ double minY = Geohash.decodeLatitude((minNeighborBelow == null) ? min : minNeighborBelow);
+ double minX = Geohash.decodeLongitude(min);
+ double maxY = Geohash.decodeLatitude(max);
+ double maxX = Geohash.decodeLongitude(max);
+ for (double i = minX; i <= maxX; i += Geohash.lonWidthInDegrees(precision)) {
+ for (double j = minY; j <= maxY; j += Geohash.latHeightInDegrees(precision)) {
+ String hash = Geohash.stringEncode(i, j, precision);
+ GeoRelation relation = relateTile(geoValue, hash);
+ if (relation != GeoRelation.QUERY_DISJOINT) {
+ values.resizeCell(idx + 1);
+ values.add(idx++, encode(i, j, precision));
+ }
+ }
+ }
+ return idx;
+ }
+
+ protected int setValuesByRasterization(String hash, CellValues values, int valuesIndex, int targetPrecision,
+ MultiGeoValues.GeoValue geoValue) {
+ String[] hashes = Geohash.getSubGeohashes(hash);
+ for (int i = 0; i < hashes.length; i++) {
+ GeoRelation relation = relateTile(geoValue, hashes[i]);
+ if (relation == GeoRelation.QUERY_CROSSES) {
+ if (hashes[i].length() == targetPrecision) {
+ values.resizeCell(valuesIndex + 1);
+ values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
+ } else {
+ valuesIndex =
+ setValuesByRasterization(hashes[i], values, valuesIndex, targetPrecision, geoValue);
+ }
+ } else if (relation == GeoRelation.QUERY_INSIDE) {
+ if (hashes[i].length() == targetPrecision) {
+ values.resizeCell(valuesIndex + 1);
+ values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
+ } else {
+ values.resizeCell(valuesIndex + (int) Math.pow(32, targetPrecision - hash.length()) + 1);
+ valuesIndex = setValuesForFullyContainedTile(hashes[i],values, valuesIndex, targetPrecision);
+ }
+ }
+ }
+ return valuesIndex;
+ }
+
+ protected int setValuesForFullyContainedTile(String hash, CellValues values,
+ int valuesIndex, int targetPrecision) {
+ String[] hashes = Geohash.getSubGeohashes(hash);
+ for (int i = 0; i < hashes.length; i++) {
+ if (hashes[i].length() == targetPrecision) {
+ values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
+ } else {
+ valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, targetPrecision);
+ }
+ }
+ return valuesIndex;
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/UnboundedGeoPointCellValues.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoPointCellValues.java
similarity index 71%
rename from server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/UnboundedGeoPointCellValues.java
rename to server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoPointCellValues.java
index 3ce9cac197158..57e7311eee38c 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/UnboundedGeoPointCellValues.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoPointCellValues.java
@@ -18,22 +18,19 @@
*/
package org.elasticsearch.search.aggregations.bucket.geogrid;
-import org.elasticsearch.common.geo.GeoBoundingBox;
import org.elasticsearch.index.fielddata.MultiGeoValues;
/**
- * Class representing {@link CellValues} that are unbounded by any
- * {@link GeoBoundingBox}.
+ * Class representing geo_point {@link CellValues}
*/
-class UnboundedGeoPointCellValues extends CellValues {
+class GeoPointCellValues extends CellValues {
- protected UnboundedGeoPointCellValues(MultiGeoValues geoValues, int precision, GeoGridTiler tiler) {
+ protected GeoPointCellValues(MultiGeoValues geoValues, int precision, GeoGridTiler tiler) {
super(geoValues, precision, tiler);
}
@Override
int advanceValue(MultiGeoValues.GeoValue target, int valuesIdx) {
- values[valuesIdx] = tiler.encode(target.lon(), target.lat(), precision);
- return valuesIdx + 1;
+ return tiler.advancePointValue(values, target.lon(), target.lat(), precision, valuesIdx);
}
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoShapeCellValues.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoShapeCellValues.java
index 807b2808b9a45..22ed207a41338 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoShapeCellValues.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoShapeCellValues.java
@@ -18,49 +18,18 @@
*/
package org.elasticsearch.search.aggregations.bucket.geogrid;
-import org.elasticsearch.index.fielddata.AbstractSortingNumericDocValues;
import org.elasticsearch.index.fielddata.MultiGeoValues;
-import org.elasticsearch.search.aggregations.support.ValuesSourceType;
-
-import java.io.IOException;
/** Sorted numeric doc values for geo shapes */
-class GeoShapeCellValues extends AbstractSortingNumericDocValues {
- private MultiGeoValues geoValues;
- private int precision;
- private GeoGridTiler tiler;
+class GeoShapeCellValues extends CellValues {
protected GeoShapeCellValues(MultiGeoValues geoValues, int precision, GeoGridTiler tiler) {
- this.geoValues = geoValues;
- this.precision = precision;
- this.tiler = tiler;
- }
-
- protected void resizeCell(int newSize) {
- resize(newSize);
- }
-
- protected void add(int idx, long value) {
- values[idx] = value;
- }
-
- // for testing
- protected long[] getValues() {
- return values;
+ super(geoValues, precision, tiler);
}
@Override
- public boolean advanceExact(int docId) throws IOException {
- if (geoValues.advanceExact(docId)) {
- ValuesSourceType vs = geoValues.valuesSourceType();
- MultiGeoValues.GeoValue target = geoValues.nextValue();
- // TODO(talevy): determine reasonable circuit-breaker here
- resize(0);
- tiler.setValues(this, target, precision);
- sort();
- return true;
- } else {
- return false;
- }
+ int advanceValue(MultiGeoValues.GeoValue target, int valuesIdx) {
+ // TODO(talevy): determine reasonable circuit-breaker here
+ return tiler.setValues(this, target, precision);
}
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorFactory.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorFactory.java
index 54b29b01a8f20..680639e49a70d 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorFactory.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorFactory.java
@@ -80,7 +80,13 @@ protected Aggregator doCreateInternal(final ValuesSource.Geo valuesSource,
if (collectsFromSingleBucket == false) {
return asMultiBucketAggregator(this, searchContext, parent);
}
- CellIdSource cellIdSource = new CellIdSource(valuesSource, precision, geoBoundingBox, GeoGridTiler.GeoTileGridTiler.INSTANCE);
+ final GeoGridTiler tiler;
+ if (geoBoundingBox.isUnbounded()) {
+ tiler = new GeoTileGridTiler();
+ } else {
+ tiler = new BoundedGeoTileGridTiler(geoBoundingBox);
+ }
+ CellIdSource cellIdSource = new CellIdSource(valuesSource, precision, tiler);
return new GeoTileGridAggregator(name, factories, cellIdSource, requiredSize, shardSize,
searchContext, parent, pipelineAggregators, metaData);
}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridTiler.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridTiler.java
new file mode 100644
index 0000000000000..869c14c24e252
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridTiler.java
@@ -0,0 +1,166 @@
+/*
+ * 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.GeoRelation;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.index.fielddata.MultiGeoValues;
+
+public class GeoTileGridTiler implements GeoGridTiler {
+
+ @Override
+ public long encode(double x, double y, int precision) {
+ return GeoTileUtils.longEncode(x, y, precision);
+ }
+
+ public int advancePointValue(long[] values, double x, double y, int precision, int valuesIdx) {
+ values[valuesIdx] = encode(x, y, precision);
+ return valuesIdx + 1;
+ }
+
+ /**
+ * Sets the values of the long[] underlying {@link CellValues}.
+ *
+ * If the shape resides between GeoTileUtils.NORMALIZED_LATITUDE_MASK
and 90 or
+ * between GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK
and -90 degree latitudes, then
+ * the shape is not accounted for since geo-tiles are only defined within those bounds.
+ *
+ * @param values the bucket values
+ * @param geoValue the input shape
+ * @param precision the tile zoom-level
+ *
+ * @return the number of tiles set by the shape
+ */
+ @Override
+ public int setValues(CellValues values, MultiGeoValues.GeoValue geoValue, int precision) {
+ MultiGeoValues.BoundingBox bounds = geoValue.boundingBox();
+ assert bounds.minX() <= bounds.maxX();
+
+ if (precision == 0) {
+ values.resizeCell(1);
+ values.add(0, GeoTileUtils.longEncodeTiles(0, 0, 0));
+ return 1;
+ }
+
+ // geo tiles are not defined at the extreme latitudes due to them
+ // tiling the world as a square.
+ if ((bounds.top > GeoTileUtils.NORMALIZED_LATITUDE_MASK && bounds.bottom > GeoTileUtils.NORMALIZED_LATITUDE_MASK)
+ || (bounds.top < GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK
+ && bounds.bottom < GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK)) {
+ return 0;
+ }
+
+ final double tiles = 1 << precision;
+ int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
+ int minYTile = GeoTileUtils.getYTile(bounds.maxY(), (long) tiles);
+ int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
+ int maxYTile = GeoTileUtils.getYTile(bounds.minY(), (long) tiles);
+ int count = (maxXTile - minXTile + 1) * (maxYTile - minYTile + 1);
+ if (count == 1) {
+ return setValue(values, geoValue, minXTile, minYTile, precision);
+ } else if (count <= precision) {
+ return setValuesByBruteForceScan(values, geoValue, precision, minXTile, minYTile, maxXTile, maxYTile);
+ } else {
+ return setValuesByRasterization(0, 0, 0, values, 0, precision, geoValue);
+ }
+ }
+
+ protected GeoRelation relateTile(MultiGeoValues.GeoValue geoValue, int xTile, int yTile, int precision) {
+ Rectangle rectangle = GeoTileUtils.toBoundingBox(xTile, yTile, precision);
+ return geoValue.relate(rectangle);
+ }
+
+ /**
+ * Sets a singular doc-value for the {@link MultiGeoValues.GeoValue}. To be overriden by {@link BoundedGeoTileGridTiler}
+ * to account for {@link org.elasticsearch.common.geo.GeoBoundingBox} conditions
+ */
+ protected int setValue(CellValues docValues, MultiGeoValues.GeoValue geoValue, int xTile, int yTile, int precision) {
+ docValues.resizeCell(1);
+ docValues.add(0, GeoTileUtils.longEncodeTiles(precision, xTile, yTile));
+ return 1;
+ }
+
+ /**
+ *
+ * @param values the bucket values as longs
+ * @param geoValue the shape value
+ * @param precision the target precision to split the shape up into
+ * @return the number of buckets the geoValue is found in
+ */
+ protected int setValuesByBruteForceScan(CellValues values, MultiGeoValues.GeoValue geoValue,
+ int precision, int minXTile, int minYTile, int maxXTile, int maxYTile) {
+ int idx = 0;
+ for (int i = minXTile; i <= maxXTile; i++) {
+ for (int j = minYTile; j <= maxYTile; j++) {
+ GeoRelation relation = relateTile(geoValue, i, j, precision);
+ if (relation != GeoRelation.QUERY_DISJOINT) {
+ values.resizeCell(idx + 1);
+ values.add(idx++, GeoTileUtils.longEncodeTiles(precision, i, j));
+ }
+ }
+ }
+ return idx;
+ }
+
+ protected int setValuesByRasterization(int xTile, int yTile, int zTile, CellValues values, int valuesIndex,
+ int targetPrecision, MultiGeoValues.GeoValue geoValue) {
+ zTile++;
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ int nextX = 2 * xTile + i;
+ int nextY = 2 * yTile + j;
+ GeoRelation relation = relateTile(geoValue, nextX, nextY, zTile);
+ if (GeoRelation.QUERY_INSIDE == relation) {
+ if (zTile == targetPrecision) {
+ values.resizeCell(valuesIndex + 1);
+ values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
+ } else {
+ values.resizeCell(valuesIndex + 1 << ( 2 * (targetPrecision - zTile)) + 1);
+ valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision);
+ }
+ } else if (GeoRelation.QUERY_CROSSES == relation) {
+ if (zTile == targetPrecision) {
+ values.resizeCell(valuesIndex + 1);
+ values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
+ } else {
+ valuesIndex = setValuesByRasterization(nextX, nextY, zTile, values, valuesIndex, targetPrecision, geoValue);
+ }
+ }
+ }
+ }
+ return valuesIndex;
+ }
+
+ protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, CellValues values, int valuesIndex,
+ int targetPrecision) {
+ zTile++;
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ int nextX = 2 * xTile + i;
+ int nextY = 2 * yTile + j;
+ if (zTile == targetPrecision) {
+ values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY));
+ } else {
+ valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision);
+ }
+ }
+ }
+ return valuesIndex;
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileUtils.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileUtils.java
index c97bb88135109..d872a8c7ce640 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileUtils.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileUtils.java
@@ -44,23 +44,10 @@
*/
public final class GeoTileUtils {
- /**
- * The geo-tile map is clipped at 85.05112878 to 90 and -85.05112878 to -90
- */
- public static final double LATITUDE_MASK = 85.0511287798066;
-
- /**
- * Since shapes are encoded, their boundaries are to be compared to against the encoded/decoded values of LATITUDE_MASK
- */
- static final double NORMALIZED_LATITUDE_MASK = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(LATITUDE_MASK));
- static final double NORMALIZED_NEGATIVE_LATITUDE_MASK =
- GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(-LATITUDE_MASK));
+ private GeoTileUtils() {}
private static final double PI_DIV_2 = Math.PI / 2;
-
- 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)
@@ -71,6 +58,18 @@ private GeoTileUtils() {}
*/
public static final int MAX_ZOOM = 29;
+ /**
+ * The geo-tile map is clipped at 85.05112878 to 90 and -85.05112878 to -90
+ */
+ public static final double LATITUDE_MASK = 85.0511287798066;
+
+ /**
+ * Since shapes are encoded, their boundaries are to be compared to against the encoded/decoded values of LATITUDE_MASK
+ */
+ static final double NORMALIZED_LATITUDE_MASK = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(LATITUDE_MASK));
+ static final double NORMALIZED_NEGATIVE_LATITUDE_MASK =
+ GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(-LATITUDE_MASK));
+
/**
* Bit position of the zoom value within hash - zoom is stored in the most significant 6 bits of a long number.
*/
@@ -81,6 +80,7 @@ private GeoTileUtils() {}
*/
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.
*
@@ -246,6 +246,11 @@ static GeoPoint keyToGeoPoint(String hashAsString) {
return zxyToGeoPoint(hashAsInts[0], hashAsInts[1], hashAsInts[2]);
}
+ static Rectangle toBoundingBox(long hash) {
+ int[] hashAsInts = parseHash(hash);
+ return toBoundingBox(hashAsInts[1], hashAsInts[2], hashAsInts[0]);
+ }
+
static Rectangle toBoundingBox(int xTile, int yTile, int precision) {
final double tiles = validateZXY(precision, xTile, yTile);
final double minN = Math.PI - (2.0 * Math.PI * (yTile + 1)) / tiles;
diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java
index 15585c2f4a3a8..22249d8aa6a94 100644
--- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java
+++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java
@@ -175,7 +175,8 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th
return builder;
}
- private GeoBoundingBox resolveGeoBoundingBox() {
+ // used for testing
+ GeoBoundingBox resolveGeoBoundingBox() {
if (Double.isInfinite(top)) {
return null;
} else if (Double.isInfinite(posLeft)) {
diff --git a/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java b/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java
index 3de9d70d459ab..f7561508f831d 100644
--- a/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java
+++ b/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java
@@ -257,9 +257,7 @@ public void testPacManPoints() throws Exception {
assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(xMin, yMin, xMax, yMax));
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/37206")
public void testRandomMultiLineIntersections() throws IOException {
- double extentSize = randomDoubleBetween(0.01, 10, true);
GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
MultiLine geometry = randomMultiLine(false);
geometry = (MultiLine) indexer.prepareForIndexing(geometry);
@@ -267,14 +265,7 @@ public void testRandomMultiLineIntersections() throws IOException {
Extent readerExtent = reader.getExtent();
for (Line line : geometry) {
- // extent that intersects edges
- assertRelation(GeoRelation.QUERY_CROSSES, reader, bufferedExtentFromGeoPoint(line.getX(0), line.getY(0), extentSize));
-
- // TODO(talevy): resolve definition. when line is on a specific edge it is not considered crossing due to latest changes
- // extent that fully encloses a line in the MultiLine
Extent lineExtent = triangleTreeReader(line, GeoShapeCoordinateEncoder.INSTANCE).getExtent();
- assertRelation(GeoRelation.QUERY_CROSSES, reader, lineExtent);
-
if (lineExtent.minX() != Integer.MIN_VALUE && lineExtent.maxX() != Integer.MAX_VALUE
&& lineExtent.minY() != Integer.MIN_VALUE && lineExtent.maxY() != Integer.MAX_VALUE) {
assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(lineExtent.minX() - 1, lineExtent.minY() - 1,
@@ -292,9 +283,8 @@ public void testRandomMultiLineIntersections() throws IOException {
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/37206")
- public void testRandomGeometryIntersection() throws IOException {
- int testPointCount = randomIntBetween(100, 200);
+ public void testRandomPolygonIntersection() throws IOException {
+ int testPointCount = randomIntBetween(50, 100);
Point[] testPoints = new Point[testPointCount];
double extentSize = randomDoubleBetween(1, 10, true);
boolean[] intersects = new boolean[testPointCount];
@@ -302,7 +292,7 @@ public void testRandomGeometryIntersection() throws IOException {
testPoints[i] = randomPoint(false);
}
- Geometry geometry = randomGeometryTreeGeometry();
+ Geometry geometry = randomMultiPolygon(false);
GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
Geometry preparedGeometry = indexer.prepareForIndexing(geometry);
diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java
index 3840b3423a94e..42417db2484db 100644
--- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java
+++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java
@@ -30,14 +30,17 @@
import org.apache.lucene.store.Directory;
import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.geo.CentroidCalculator;
+import org.elasticsearch.common.geo.GeoRelation;
+import org.elasticsearch.common.geo.GeoShapeCoordinateEncoder;
import org.elasticsearch.common.geo.GeoTestUtils;
+import org.elasticsearch.common.geo.TriangleTreeReader;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.MultiPoint;
import org.elasticsearch.geometry.Point;
-import org.elasticsearch.index.mapper.BinaryGeoShapeDocValuesField;
import org.elasticsearch.common.geo.GeoBoundingBox;
import org.elasticsearch.common.geo.GeoBoundingBoxTests;
-import org.elasticsearch.common.geo.GeoUtils;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.index.mapper.BinaryGeoShapeDocValuesField;
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
@@ -54,14 +57,13 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Function;
+import static org.elasticsearch.common.geo.GeoTestUtils.triangleTreeReader;
import static org.hamcrest.Matchers.equalTo;
public abstract class GeoGridAggregatorTestCase extends AggregatorTestCase {
private static final String FIELD_NAME = "location";
- protected static final double GEOHASH_TOLERANCE = 1E-5D;
/**
* Generate a random precision according to the rules of the given aggregation.
@@ -73,6 +75,16 @@ public abstract class GeoGridAggregatorTestCase
*/
protected abstract String hashAsString(double lng, double lat, int precision);
+ /**
+ * Return a point within the bounds of the tile grid
+ */
+ protected abstract Point randomPoint();
+
+ /**
+ * Return the bounding tile as a {@link Rectangle} for a given point
+ */
+ protected abstract Rectangle getTile(double lng, double lat, int precision);
+
/**
* Create a new named {@link GeoGridAggregationBuilder}-derived builder
*/
@@ -175,57 +187,55 @@ public void testGeoPointWithSeveralDocs() throws IOException {
}, new GeoPointFieldMapper.GeoPointFieldType());
}
- public void testBounds() throws IOException {
- final int numDocs = randomIntBetween(64, 256);
+ public void testGeoPointBounds() throws IOException {
+ final int precision = randomPrecision();
+ final int numDocs = randomIntBetween(100, 200);
+ int numDocsWithin = 0;
final GeoGridAggregationBuilder builder = createBuilder("_name");
expectThrows(IllegalArgumentException.class, () -> builder.precision(-1));
expectThrows(IllegalArgumentException.class, () -> builder.precision(30));
- // only consider bounding boxes that are at least GEOHASH_TOLERANCE wide and have quantized coordinates
- GeoBoundingBox bbox = randomValueOtherThanMany(
- (b) -> Math.abs(GeoUtils.normalizeLon(b.right()) - GeoUtils.normalizeLon(b.left())) < GEOHASH_TOLERANCE,
- GeoBoundingBoxTests::randomBBox);
- Function encodeDecodeLat = (lat) -> GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat));
- Function encodeDecodeLon = (lon) -> GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon));
- bbox.topLeft().reset(encodeDecodeLat.apply(bbox.top()), encodeDecodeLon.apply(bbox.left()));
- bbox.bottomRight().reset(encodeDecodeLat.apply(bbox.bottom()), encodeDecodeLon.apply(bbox.right()));
+ GeoBoundingBox bbox = GeoBoundingBoxTests.randomBBox();
+ final double boundsTop = bbox.top();
+ final double boundsBottom = bbox.bottom();
+ final double boundsWestLeft;
+ final double boundsWestRight;
+ final double boundsEastLeft;
+ final double boundsEastRight;
+ final boolean crossesDateline;
+ if (bbox.right() < bbox.left()) {
+ boundsWestLeft = -180;
+ boundsWestRight = bbox.right();
+ boundsEastLeft = bbox.left();
+ boundsEastRight = 180;
+ crossesDateline = true;
+ } else { // only set east bounds
+ boundsEastLeft = bbox.left();
+ boundsEastRight = bbox.right();
+ boundsWestLeft = 0;
+ boundsWestRight = 0;
+ crossesDateline = false;
+ }
- int in = 0, out = 0;
List docs = new ArrayList<>();
- while (in + out < numDocs) {
- if (bbox.left() > bbox.right()) {
- if (randomBoolean()) {
- double lonWithin = randomBoolean() ?
- randomDoubleBetween(bbox.left(), 180.0, true)
- : randomDoubleBetween(-180.0, bbox.right(), true);
- double latWithin = randomDoubleBetween(bbox.bottom(), bbox.top(), true);
- in++;
- docs.add(new LatLonDocValuesField(FIELD_NAME, latWithin, lonWithin));
- } else {
- double lonOutside = randomDoubleBetween(bbox.left(), bbox.right(), true);
- double latOutside = randomDoubleBetween(bbox.top(), -90, false);
- out++;
- docs.add(new LatLonDocValuesField(FIELD_NAME, latOutside, lonOutside));
- }
- } else {
- if (randomBoolean()) {
- double lonWithin = randomDoubleBetween(bbox.left(), bbox.right(), true);
- double latWithin = randomDoubleBetween(bbox.bottom(), bbox.top(), true);
- in++;
- docs.add(new LatLonDocValuesField(FIELD_NAME, latWithin, lonWithin));
- } else {
- double lonOutside = GeoUtils.normalizeLon(randomDoubleBetween(bbox.right(), 180.001, false));
- double latOutside = GeoUtils.normalizeLat(randomDoubleBetween(bbox.top(), 90.001, false));
- out++;
- docs.add(new LatLonDocValuesField(FIELD_NAME, latOutside, lonOutside));
- }
+ for (int i = 0; i < numDocs; i++) {
+ Point p;
+ p = randomPoint();
+ double x = GeoTestUtils.encodeDecodeLon(p.getX());
+ double y = GeoTestUtils.encodeDecodeLat(p.getY());
+ Rectangle pointTile = getTile(x, y, precision);
+
+ boolean intersectsBounds = boundsTop >= pointTile.getMinY() && boundsBottom <= pointTile.getMaxY()
+ && (boundsEastLeft <= pointTile.getMaxX() && boundsEastRight >= pointTile.getMinX()
+ || (crossesDateline && boundsWestLeft <= pointTile.getMaxX() && boundsWestRight >= pointTile.getMinX()));
+ if (intersectsBounds) {
+ numDocsWithin += 1;
}
-
+ docs.add(new LatLonDocValuesField(FIELD_NAME, p.getLat(), p.getLon()));
}
- final long numDocsInBucket = in;
- final int precision = randomPrecision();
+ final long numDocsInBucket = numDocsWithin;
testCase(new MatchAllDocsQuery(), FIELD_NAME, precision, bbox, iw -> {
for (LatLonDocValuesField docField : docs) {
@@ -242,6 +252,82 @@ public void testBounds() throws IOException {
}, new GeoPointFieldMapper.GeoPointFieldType());
}
+ public void testGeoShapeBounds() throws IOException {
+ final int precision = randomPrecision();
+ final int numDocs = randomIntBetween(100, 200);
+ int numDocsWithin = 0;
+ final GeoGridAggregationBuilder builder = createBuilder("_name");
+
+ expectThrows(IllegalArgumentException.class, () -> builder.precision(-1));
+ expectThrows(IllegalArgumentException.class, () -> builder.precision(30));
+
+ GeoBoundingBox bbox = GeoBoundingBoxTests.randomBBox();
+ final double boundsTop = bbox.top();
+ final double boundsBottom = bbox.bottom();
+ final double boundsWestLeft;
+ final double boundsWestRight;
+ final double boundsEastLeft;
+ final double boundsEastRight;
+ final boolean crossesDateline;
+ if (bbox.right() < bbox.left()) {
+ boundsWestLeft = -180;
+ boundsWestRight = bbox.right();
+ boundsEastLeft = bbox.left();
+ boundsEastRight = 180;
+ crossesDateline = true;
+ } else { // only set east bounds
+ boundsEastLeft = bbox.left();
+ boundsEastRight = bbox.right();
+ boundsWestLeft = 0;
+ boundsWestRight = 0;
+ crossesDateline = false;
+ }
+
+ List docs = new ArrayList<>();
+ List points = new ArrayList<>();
+ for (int i = 0; i < numDocs; i++) {
+ Point p;
+ p = randomPoint();
+ double x = GeoTestUtils.encodeDecodeLon(p.getX());
+ double y = GeoTestUtils.encodeDecodeLat(p.getY());
+ Rectangle pointTile = getTile(x, y, precision);
+
+
+ TriangleTreeReader reader = triangleTreeReader(p, GeoShapeCoordinateEncoder.INSTANCE);
+ GeoRelation tileRelation = reader.relateTile(GeoShapeCoordinateEncoder.INSTANCE.encodeX(pointTile.getMinX()),
+ GeoShapeCoordinateEncoder.INSTANCE.encodeY(pointTile.getMinY()),
+ GeoShapeCoordinateEncoder.INSTANCE.encodeX(pointTile.getMaxX()),
+ GeoShapeCoordinateEncoder.INSTANCE.encodeY(pointTile.getMaxY()));
+ boolean intersectsBounds = boundsTop >= pointTile.getMinY() && boundsBottom <= pointTile.getMaxY()
+ && (boundsEastLeft <= pointTile.getMaxX() && boundsEastRight >= pointTile.getMinX()
+ || (crossesDateline && boundsWestLeft <= pointTile.getMaxX() && boundsWestRight >= pointTile.getMinX()));
+ if (tileRelation != GeoRelation.QUERY_DISJOINT && intersectsBounds) {
+ numDocsWithin += 1;
+ }
+
+
+ points.add(p);
+ docs.add(new BinaryGeoShapeDocValuesField(FIELD_NAME,
+ GeoTestUtils.toDecodedTriangles(p), new CentroidCalculator(p)));
+ }
+
+ final long numDocsInBucket = numDocsWithin;
+
+ testCase(new MatchAllDocsQuery(), FIELD_NAME, precision, bbox, iw -> {
+ for (BinaryGeoShapeDocValuesField docField : docs) {
+ iw.addDocument(Collections.singletonList(docField));
+ }
+ },
+ geoGrid -> {
+ assertThat(AggregationInspectionHelper.hasValue(geoGrid), equalTo(numDocsInBucket > 0));
+ long docCount = 0;
+ for (int i = 0; i < geoGrid.getBuckets().size(); i++) {
+ docCount += geoGrid.getBuckets().get(i).getDocCount();
+ }
+ assertThat(docCount, equalTo(numDocsInBucket));
+ }, new GeoShapeFieldMapper.GeoShapeFieldType());
+ }
+
public void testGeoShapeWithSeveralDocs() throws IOException {
int precision = randomIntBetween(1, 4);
int numShapes = randomIntBetween(8, 128);
diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTilerTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTilerTests.java
index 8d3f8b04aa7d3..9c64dccfafff9 100644
--- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTilerTests.java
+++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridTilerTests.java
@@ -43,13 +43,15 @@
import static org.elasticsearch.common.geo.GeoTestUtils.encodeDecodeLat;
import static org.elasticsearch.common.geo.GeoTestUtils.encodeDecodeLon;
import static org.elasticsearch.common.geo.GeoTestUtils.triangleTreeReader;
+import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.LATITUDE_MASK;
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.NORMALIZED_LATITUDE_MASK;
import static org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils.NORMALIZED_NEGATIVE_LATITUDE_MASK;
+
import static org.hamcrest.Matchers.equalTo;
public class GeoGridTilerTests extends ESTestCase {
- private static final GeoGridTiler.GeoTileGridTiler GEOTILE = GeoGridTiler.GeoTileGridTiler.INSTANCE;
- private static final GeoGridTiler.GeoHashGridTiler GEOHASH = GeoGridTiler.GeoHashGridTiler.INSTANCE;
+ private static final GeoTileGridTiler GEOTILE = new GeoTileGridTiler();
+ private static final GeoHashGridTiler GEOHASH = new GeoHashGridTiler();
public void testGeoTile() throws Exception {
double x = randomDouble();
@@ -82,6 +84,65 @@ public void testGeoTile() throws Exception {
}
}
+ public void testAdvancePointValue() {
+ for (int i = 0; i < 100; i++) {
+ int precision = randomIntBetween(1, 6);
+ int size = randomIntBetween(1, 10);
+ long[] values = new long[size];
+ int idx = randomIntBetween(0, size - 1);
+ Point point = GeometryTestUtils.randomPoint(false);
+ for (GeoGridTiler tiler : List.of(GEOTILE, GEOHASH)) {
+ int newIdx = tiler.advancePointValue(values, point.getX(), point.getY(), precision, idx);
+ assertThat(newIdx, equalTo(idx + 1));
+ assertThat(values[idx], equalTo(tiler.encode(point.getX(), point.getY(), precision)));
+ }
+ }
+ }
+
+ public void testBoundedGeotileAdvancePointValue() {
+ for (int i = 0; i < 100; i++) {
+ int precision = randomIntBetween(1, 6);
+ int size = randomIntBetween(1, 10);
+ long[] values = new long[size];
+ int idx = randomIntBetween(0, size - 1);
+ Point point = GeometryTestUtils.randomPoint(false);
+ GeoBoundingBox geoBoundingBox = GeoBoundingBoxTests.randomBBox();
+
+ BoundedGeoTileGridTiler tiler = new BoundedGeoTileGridTiler(geoBoundingBox);
+ int newIdx = tiler.advancePointValue(values, point.getX(), point.getY(), precision, idx);
+ if (newIdx == idx + 1) {
+ assertTrue(tiler.cellIntersectsGeoBoundingBox(GeoTileUtils.toBoundingBox(values[idx])));
+ assertThat(values[idx], equalTo(tiler.encode(point.getX(), point.getY(), precision)));
+ assertThat(newIdx, equalTo(idx + 1));
+ } else {
+ assertThat(newIdx, equalTo(idx));
+ assertThat(values[idx], equalTo(0L));
+ }
+ }
+ }
+
+ public void testBoundedGeohashAdvancePointValue() {
+ for (int i = 0; i < 100; i++) {
+ int precision = randomIntBetween(1, 6);
+ int size = randomIntBetween(1, 10);
+ long[] values = new long[size];
+ int idx = randomIntBetween(0, size - 1);
+ Point point = GeometryTestUtils.randomPoint(false);
+ GeoBoundingBox geoBoundingBox = GeoBoundingBoxTests.randomBBox();
+
+ BoundedGeoHashGridTiler tiler = new BoundedGeoHashGridTiler(geoBoundingBox);
+ int newIdx = tiler.advancePointValue(values, point.getX(), point.getY(), precision, idx);
+ if (newIdx == idx + 1) {
+ assertTrue(tiler.cellIntersectsGeoBoundingBox(Geohash.toBoundingBox(Geohash.stringEncode(values[idx]))));
+ assertThat(values[idx], equalTo(tiler.encode(point.getX(), point.getY(), precision)));
+ assertThat(newIdx, equalTo(idx + 1));
+ } else {
+ assertThat(newIdx, equalTo(idx));
+ assertThat(values[idx], equalTo(0L));
+ }
+ }
+ }
+
public void testGeoTileSetValuesBruteAndRecursiveMultiline() throws Exception {
MultiLine geometry = GeometryTestUtils.randomMultiLine(false);
checkGeoTileSetValuesBruteAndRecursive(geometry);
@@ -100,61 +161,30 @@ public void testGeoTileSetValuesBruteAndRecursivePoints() throws Exception {
checkGeoHashSetValuesBruteAndRecursive(geometry);
}
- private void checkGeoTileSetValuesBruteAndRecursive(Geometry geometry) throws Exception {
- int precision = randomIntBetween(1, 5);
- GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
- geometry = indexer.prepareForIndexing(geometry);
- TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
- MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
- GeoShapeCellValues recursiveValues = new GeoShapeCellValues(null, precision, GEOTILE);
- int recursiveCount;
- {
- recursiveCount = GEOTILE.setValuesByRasterization(0, 0, 0, recursiveValues, 0, precision, value);
- }
- GeoShapeCellValues bruteForceValues = new GeoShapeCellValues(null, precision, GEOTILE);
- int bruteForceCount;
- {
- final double tiles = 1 << precision;
- MultiGeoValues.BoundingBox bounds = value.boundingBox();
- int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
- int minYTile = GeoTileUtils.getYTile(bounds.maxY(), (long) tiles);
- int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
- int maxYTile = GeoTileUtils.getYTile(bounds.minY(), (long) tiles);
- bruteForceCount = GEOTILE.setValuesByBruteForceScan(bruteForceValues, value, precision, minXTile, minYTile, maxXTile, maxYTile);
- }
- assertThat(geometry.toString(), recursiveCount, equalTo(bruteForceCount));
- long[] recursive = Arrays.copyOf(recursiveValues.getValues(), recursiveCount);
- long[] bruteForce = Arrays.copyOf(bruteForceValues.getValues(), bruteForceCount);
- Arrays.sort(recursive);
- Arrays.sort(bruteForce);
- assertArrayEquals(geometry.toString(), recursive, bruteForce);
- }
+ // tests that bounding boxes of shapes crossing the dateline are correctly wrapped
+ public void testGeoTileSetValuesBoundingBoxes_BoundedGeoShapeCellValues() throws Exception {
+ for (int i = 0; i < 1; i++) {
+ int precision = randomIntBetween(0, 4);
+ GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
+ Geometry geometry = indexer.prepareForIndexing(randomValueOtherThanMany(g -> {
+ try {
+ indexer.prepareForIndexing(g);
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }, () -> boxToGeo(GeoBoundingBoxTests.randomBBox())));
- private void checkGeoHashSetValuesBruteAndRecursive(Geometry geometry) throws Exception {
- int precision = randomIntBetween(1, 3);
- GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
- geometry = indexer.prepareForIndexing(geometry);
- TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
- MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
- GeoShapeCellValues recursiveValues = new GeoShapeCellValues(null, precision, GEOHASH);
- int recursiveCount;
- {
- recursiveCount = GEOHASH.setValuesByRasterization("", recursiveValues, 0, precision, value);
- }
- GeoShapeCellValues bruteForceValues = new GeoShapeCellValues(null, precision, GEOHASH);
- int bruteForceCount;
- {
- MultiGeoValues.BoundingBox bounds = value.boundingBox();
- bruteForceCount = GEOHASH.setValuesByBruteForceScan(bruteForceValues, value, precision, bounds);
- }
+ TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
+ GeoBoundingBox geoBoundingBox = GeoBoundingBoxTests.randomBBox();
+ MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
+ GeoShapeCellValues cellValues = new GeoShapeCellValues(null, precision, GEOTILE);
- assertThat(geometry.toString(), recursiveCount, equalTo(bruteForceCount));
+ int numTiles = new BoundedGeoTileGridTiler(geoBoundingBox).setValues(cellValues, value, precision);
+ int expected = numTiles(value, precision, geoBoundingBox);
- long[] recursive = Arrays.copyOf(recursiveValues.getValues(), recursiveCount);
- long[] bruteForce = Arrays.copyOf(bruteForceValues.getValues(), bruteForceCount);
- Arrays.sort(recursive);
- Arrays.sort(bruteForce);
- assertArrayEquals(geometry.toString(), recursive, bruteForce);
+ assertThat(numTiles, equalTo(expected));
+ }
}
// test random rectangles that can cross the date-line and verify that there are an expected
@@ -174,7 +204,7 @@ public void testGeoTileSetValuesBoundingBoxes_UnboundedGeoShapeCellValues() thro
TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
- GeoShapeCellValues unboundedCellValues = new GeoShapeCellValues(null, precision, GEOTILE);
+ CellValues unboundedCellValues = new GeoShapeCellValues(null, precision, GEOTILE);
int numTiles = GEOTILE.setValues(unboundedCellValues, value, precision);
int expected = numTiles(value, precision);
assertThat(numTiles, equalTo(expected));
@@ -202,6 +232,9 @@ public void testTilerMatchPoint() throws Exception {
};
for (Point point : pointCorners) {
+ if (point.getX() == GeoUtils.MAX_LON || point.getY() == -LATITUDE_MASK) {
+ continue;
+ }
TriangleTreeReader reader = triangleTreeReader(point, GeoShapeCoordinateEncoder.INSTANCE);
MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
GeoShapeCellValues unboundedCellValues = new GeoShapeCellValues(null, precision, GEOTILE);
@@ -216,7 +249,7 @@ public void testTilerMatchPoint() throws Exception {
public void testGeoHash() throws Exception {
double x = randomDouble();
double y = randomDouble();
- int precision = randomIntBetween(0, Geohash.PRECISION);
+ int precision = randomIntBetween(0, 6);
assertThat(GEOHASH.encode(x, y, precision), equalTo(Geohash.longEncode(x, y, precision)));
Rectangle tile = Geohash.toBoundingBox(Geohash.stringEncode(x, y, 5));
@@ -228,23 +261,175 @@ public void testGeoHash() throws Exception {
// test shape within tile bounds
{
- GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOTILE);
+ GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOHASH);
int count = GEOHASH.setValues(values, value, 5);
assertThat(count, equalTo(1));
}
{
- GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOTILE);
+ GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOHASH);
int count = GEOHASH.setValues(values, value, 6);
assertThat(count, equalTo(32));
}
{
- GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOTILE);
+ GeoShapeCellValues values = new GeoShapeCellValues(null, precision, GEOHASH);
int count = GEOHASH.setValues(values, value, 7);
assertThat(count, equalTo(1024));
}
}
- private Geometry boxToGeo(GeoBoundingBox geoBox) {
+ private boolean tileIntersectsBounds(int x, int y, int precision, GeoBoundingBox bounds) {
+ if (bounds == null) {
+ return true;
+ }
+ final double boundsWestLeft;
+ final double boundsWestRight;
+ final double boundsEastLeft;
+ final double boundsEastRight;
+ final boolean crossesDateline;
+ if (bounds.right() < bounds.left()) {
+ boundsWestLeft = -180;
+ boundsWestRight = bounds.right();
+ boundsEastLeft = bounds.left();
+ boundsEastRight = 180;
+ crossesDateline = true;
+ } else {
+ boundsEastLeft = bounds.left();
+ boundsEastRight = bounds.right();
+ boundsWestLeft = 0;
+ boundsWestRight = 0;
+ crossesDateline = false;
+ }
+
+ Rectangle tile = GeoTileUtils.toBoundingBox(x, y, precision);
+
+ return (bounds.top() >= tile.getMinY() && bounds.bottom() <= tile.getMaxY()
+ && (boundsEastLeft <= tile.getMaxX() && boundsEastRight >= tile.getMinX()
+ || (crossesDateline && boundsWestLeft <= tile.getMaxX() && boundsWestRight >= tile.getMinX())));
+ }
+
+ private int numTiles(MultiGeoValues.GeoValue geoValue, int precision, GeoBoundingBox geoBox) throws Exception {
+ MultiGeoValues.BoundingBox bounds = geoValue.boundingBox();
+ int count = 0;
+
+ if (precision == 0) {
+ return 1;
+ } else if ((bounds.top > LATITUDE_MASK && bounds.bottom > LATITUDE_MASK)
+ || (bounds.top < -LATITUDE_MASK && bounds.bottom < -LATITUDE_MASK)) {
+ return 0;
+ }
+ final double tiles = 1 << precision;
+ int minYTile = GeoTileUtils.getYTile(bounds.maxY(), (long) tiles);
+ int maxYTile = GeoTileUtils.getYTile(bounds.minY(), (long) tiles);
+ if ((bounds.posLeft >= 0 && bounds.posRight >= 0) && (bounds.negLeft < 0 && bounds.negRight < 0)) {
+ // box one
+ int minXTileNeg = GeoTileUtils.getXTile(bounds.negLeft, (long) tiles);
+ int maxXTileNeg = GeoTileUtils.getXTile(bounds.negRight, (long) tiles);
+
+ for (int x = minXTileNeg; x <= maxXTileNeg; x++) {
+ for (int y = minYTile; y <= maxYTile; y++) {
+ Rectangle r = GeoTileUtils.toBoundingBox(x, y, precision);
+ if (tileIntersectsBounds(x, y, precision, geoBox) && geoValue.relate(r) != GeoRelation.QUERY_DISJOINT) {
+ count += 1;
+ }
+ }
+ }
+
+ // box two
+ int minXTilePos = GeoTileUtils.getXTile(bounds.posLeft, (long) tiles);
+ if (minXTilePos > maxXTileNeg + 1) {
+ minXTilePos -= 1;
+ }
+
+ int maxXTilePos = GeoTileUtils.getXTile(bounds.posRight, (long) tiles);
+
+ for (int x = minXTilePos; x <= maxXTilePos; x++) {
+ for (int y = minYTile; y <= maxYTile; y++) {
+ Rectangle r = GeoTileUtils.toBoundingBox(x, y, precision);
+ if (tileIntersectsBounds(x, y, precision, geoBox) && geoValue.relate(r) != GeoRelation.QUERY_DISJOINT) {
+ count += 1;
+ }
+ }
+ }
+ return count;
+ } else {
+ int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
+ int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
+
+ if (minXTile == maxXTile && minYTile == maxYTile) {
+ return tileIntersectsBounds(minXTile, minYTile, precision, geoBox) ? 1 : 0;
+ }
+
+ for (int x = minXTile; x <= maxXTile; x++) {
+ for (int y = minYTile; y <= maxYTile; y++) {
+ Rectangle r = GeoTileUtils.toBoundingBox(x, y, precision);
+ if (tileIntersectsBounds(x, y, precision, geoBox) && geoValue.relate(r) != GeoRelation.QUERY_DISJOINT) {
+ count += 1;
+ }
+ }
+ }
+ return count;
+ }
+ }
+
+ private void checkGeoTileSetValuesBruteAndRecursive(Geometry geometry) throws Exception {
+ int precision = randomIntBetween(1, 4);
+ GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
+ geometry = indexer.prepareForIndexing(geometry);
+ TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
+ MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
+ GeoShapeCellValues recursiveValues = new GeoShapeCellValues(null, precision, GEOTILE);
+ int recursiveCount;
+ {
+ recursiveCount = GEOTILE.setValuesByRasterization(0, 0, 0, recursiveValues, 0, precision, value);
+ }
+ GeoShapeCellValues bruteForceValues = new GeoShapeCellValues(null, precision, GEOTILE);
+ int bruteForceCount;
+ {
+ final double tiles = 1 << precision;
+ MultiGeoValues.BoundingBox bounds = value.boundingBox();
+ int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
+ int minYTile = GeoTileUtils.getYTile(bounds.maxY(), (long) tiles);
+ int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
+ int maxYTile = GeoTileUtils.getYTile(bounds.minY(), (long) tiles);
+ bruteForceCount = GEOTILE.setValuesByBruteForceScan(bruteForceValues, value, precision, minXTile, minYTile, maxXTile, maxYTile);
+ }
+ assertThat(geometry.toString(), recursiveCount, equalTo(bruteForceCount));
+ long[] recursive = Arrays.copyOf(recursiveValues.getValues(), recursiveCount);
+ long[] bruteForce = Arrays.copyOf(bruteForceValues.getValues(), bruteForceCount);
+ Arrays.sort(recursive);
+ Arrays.sort(bruteForce);
+ assertArrayEquals(geometry.toString(), recursive, bruteForce);
+ }
+
+ private void checkGeoHashSetValuesBruteAndRecursive(Geometry geometry) throws Exception {
+ int precision = randomIntBetween(1, 3);
+ GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test");
+ geometry = indexer.prepareForIndexing(geometry);
+ TriangleTreeReader reader = triangleTreeReader(geometry, GeoShapeCoordinateEncoder.INSTANCE);
+ MultiGeoValues.GeoShapeValue value = new MultiGeoValues.GeoShapeValue(reader);
+ GeoShapeCellValues recursiveValues = new GeoShapeCellValues(null, precision, GEOHASH);
+ int recursiveCount;
+ {
+ recursiveCount = GEOHASH.setValuesByRasterization("", recursiveValues, 0, precision, value);
+ }
+ GeoShapeCellValues bruteForceValues = new GeoShapeCellValues(null, precision, GEOHASH);
+ int bruteForceCount;
+ {
+ MultiGeoValues.BoundingBox bounds = value.boundingBox();
+ bruteForceCount = GEOHASH.setValuesByBruteForceScan(bruteForceValues, value, precision, bounds);
+ }
+
+ assertThat(geometry.toString(), recursiveCount, equalTo(bruteForceCount));
+
+ long[] recursive = Arrays.copyOf(recursiveValues.getValues(), recursiveCount);
+ long[] bruteForce = Arrays.copyOf(bruteForceValues.getValues(), bruteForceCount);
+ Arrays.sort(recursive);
+ Arrays.sort(bruteForce);
+ assertArrayEquals(geometry.toString(), recursive, bruteForce);
+ }
+
+
+ static Geometry boxToGeo(GeoBoundingBox geoBox) {
// turn into polygon
if (geoBox.right() < geoBox.left() && geoBox.right() != -180) {
return new MultiPolygon(List.of(
@@ -313,6 +498,11 @@ private int numTiles(MultiGeoValues.GeoValue geoValue, int precision) {
} else {
int minXTile = GeoTileUtils.getXTile(bounds.minX(), (long) tiles);
int maxXTile = GeoTileUtils.getXTile(bounds.maxX(), (long) tiles);
+
+ if (minXTile == maxXTile && minYTile == maxYTile) {
+ return 1;
+ }
+
for (int x = minXTile; x <= maxXTile; x++) {
for (int y = minYTile; y <= maxYTile; y++) {
Rectangle r = GeoTileUtils.toBoundingBox(x, y, precision);
diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorTests.java
index 7fa517807f619..2aab993c1d8c0 100644
--- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorTests.java
+++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoHashGridAggregatorTests.java
@@ -19,6 +19,11 @@
package org.elasticsearch.search.aggregations.bucket.geogrid;
+import org.elasticsearch.geo.GeometryTestUtils;
+import org.elasticsearch.geometry.Point;
+import org.elasticsearch.geometry.Rectangle;
+import org.elasticsearch.geometry.utils.Geohash;
+
import static org.elasticsearch.geometry.utils.Geohash.stringEncode;
public class GeoHashGridAggregatorTests extends GeoGridAggregatorTestCase {
@@ -33,6 +38,16 @@ protected String hashAsString(double lng, double lat, int precision) {
return stringEncode(lng, lat, precision);
}
+ @Override
+ protected Point randomPoint() {
+ return GeometryTestUtils.randomPoint(false);
+ }
+
+ @Override
+ protected Rectangle getTile(double lng, double lat, int precision) {
+ return Geohash.toBoundingBox(Geohash.stringEncode(lng, lat, precision));
+ }
+
@Override
protected GeoGridAggregationBuilder createBuilder(String name) {
return new GeoHashGridAggregationBuilder(name);
diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorTests.java
index 85b2306403230..fd4c52a0f7d8d 100644
--- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorTests.java
+++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridAggregatorTests.java
@@ -19,6 +19,10 @@
package org.elasticsearch.search.aggregations.bucket.geogrid;
+import org.elasticsearch.common.geo.GeoUtils;
+import org.elasticsearch.geometry.Point;
+import org.elasticsearch.geometry.Rectangle;
+
public class GeoTileGridAggregatorTests extends GeoGridAggregatorTestCase {
@Override
@@ -31,6 +35,17 @@ protected String hashAsString(double lng, double lat, int precision) {
return GeoTileUtils.stringEncode(GeoTileUtils.longEncode(lng, lat, precision));
}
+ @Override
+ protected Point randomPoint() {
+ return new Point(randomDoubleBetween(GeoUtils.MIN_LON, GeoUtils.MAX_LON, true),
+ randomDoubleBetween(-GeoTileUtils.LATITUDE_MASK, GeoTileUtils.LATITUDE_MASK, false));
+ }
+
+ @Override
+ protected Rectangle getTile(double lng, double lat, int precision) {
+ return GeoTileUtils.toBoundingBox(GeoTileUtils.longEncode(lng, lat, precision));
+ }
+
@Override
protected GeoGridAggregationBuilder createBuilder(String name) {
return new GeoTileGridAggregationBuilder(name);