diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java index 81666108b4824..a5b58c1c01de4 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/BoundedGeoTileGridTiler.java @@ -75,6 +75,17 @@ protected int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValu return 0; } + @Override + protected long getMaxTilesAtPrecision(int finalPrecision) { + if (crossesDateline) { + return numTilesFromPrecision(finalPrecision, boundsWestLeft, boundsWestRight, boundsBottom, boundsTop) + + numTilesFromPrecision(finalPrecision, boundsEastLeft, boundsEastRight, boundsBottom, boundsTop); + + } else { + return numTilesFromPrecision(finalPrecision, boundsEastLeft, boundsEastRight, boundsBottom, boundsTop); + } + } + @Override protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, GeoShapeCellValues values, int valuesIndex, int targetPrecision) { @@ -83,12 +94,12 @@ protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, Ge 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))) { + if (cellIntersectsGeoBoundingBox(GeoTileUtils.toBoundingBox(nextX, nextY, zTile))) { + if (zTile == targetPrecision) { values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY)); + } else { + valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision); } - } else { - valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision); } } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoTileGridTiler.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoTileGridTiler.java index f92138cf0ca31..cf62bba835a6b 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoTileGridTiler.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoTileGridTiler.java @@ -61,13 +61,14 @@ public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geo 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); + long count = (long) (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); + final long maxtiles = getMaxTilesAtPrecision(precision); + return setValuesByRasterization(0, 0, 0, values, 0, precision, geoValue, maxtiles); } } @@ -109,7 +110,7 @@ protected int setValuesByBruteForceScan(GeoShapeCellValues values, GeoShapeValue } protected int setValuesByRasterization(int xTile, int yTile, int zTile, GeoShapeCellValues values, int valuesIndex, - int targetPrecision, GeoShapeValues.GeoShapeValue geoValue) { + int targetPrecision, GeoShapeValues.GeoShapeValue geoValue, long maxtiles) { zTile++; for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { @@ -118,19 +119,20 @@ protected int setValuesByRasterization(int xTile, int yTile, int zTile, GeoShape GeoRelation relation = relateTile(geoValue, nextX, nextY, zTile); if (GeoRelation.QUERY_INSIDE == relation) { if (zTile == targetPrecision) { - values.resizeCell(valuesIndex + 1); + values.resizeCell(getNewSize(valuesIndex, 1)); values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY)); } else { - final int numTilesAtPrecision = 1 << (2 * (targetPrecision - zTile)); - values.resizeCell(valuesIndex + numTilesAtPrecision + 1); + int numTilesAtPrecision = getNumTilesAtPrecision(targetPrecision, zTile, maxtiles); + values.resizeCell(getNewSize(valuesIndex, numTilesAtPrecision + 1)); valuesIndex = setValuesForFullyContainedTile(nextX, nextY, zTile, values, valuesIndex, targetPrecision); } } else if (GeoRelation.QUERY_CROSSES == relation) { if (zTile == targetPrecision) { - values.resizeCell(valuesIndex + 1); + values.resizeCell(getNewSize(valuesIndex, 1)); values.add(valuesIndex++, GeoTileUtils.longEncodeTiles(zTile, nextX, nextY)); } else { - valuesIndex = setValuesByRasterization(nextX, nextY, zTile, values, valuesIndex, targetPrecision, geoValue); + valuesIndex = + setValuesByRasterization(nextX, nextY, zTile, values, valuesIndex, targetPrecision, geoValue, maxtiles); } } } @@ -138,6 +140,26 @@ protected int setValuesByRasterization(int xTile, int yTile, int zTile, GeoShape return valuesIndex; } + private int getNewSize(int valuesIndex, int increment) { + long newSize = (long) valuesIndex + increment; + if (newSize > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Tile aggregation array overflow"); + } + return (int) newSize; + } + + private int getNumTilesAtPrecision(int finalPrecision, int currentPrecision, long maxtiles) { + final long numTilesAtPrecision = Math.min(1L << (2 * (finalPrecision - currentPrecision)), maxtiles); + if (numTilesAtPrecision > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Tile aggregation array overflow"); + } + return (int) numTilesAtPrecision; + } + + protected long getMaxTilesAtPrecision(int finalPrecision) { + return Long.MAX_VALUE; + } + protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, GeoShapeCellValues values, int valuesIndex, int targetPrecision) { zTile++; @@ -154,4 +176,18 @@ protected int setValuesForFullyContainedTile(int xTile, int yTile, int zTile, Ge } return valuesIndex; } + + /** + * Return the number of tiles contained in the provided bounding box at the given zoom level + */ + protected static long numTilesFromPrecision(int zoom, double minX, double maxX, double minY, double maxY) { + final long tiles = 1L << zoom; + final double xDeltaPrecision = 360.0 / (10 * tiles); + final double yHalfPrecision = 180.0 / (10 * tiles); + final int minXTile = GeoTileUtils.getXTile(Math.max(-180, minX - xDeltaPrecision), tiles); + final int minYTile = GeoTileUtils.getYTile(maxY + yHalfPrecision, tiles); + final int maxXTile = GeoTileUtils.getXTile(Math.min(180, maxX + xDeltaPrecision), tiles); + final int maxYTile = GeoTileUtils.getYTile(minY - yHalfPrecision, tiles); + return (long) (maxXTile - minXTile + 1) * (maxYTile - minYTile + 1); + } } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTilerTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTilerTests.java index d2158c4f24c47..01bda388322d0 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTilerTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTilerTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.geo.GeoBoundingBox; +import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; @@ -214,6 +215,39 @@ public void testGeoHash() throws Exception { } } + public void testGeoTileShapeContainsBound() throws Exception { + Rectangle tile = GeoTileUtils.toBoundingBox(44140, 44140, 16); + Rectangle shapeRectangle = new Rectangle(tile.getMinX() - 15, tile.getMaxX() + 15, + tile.getMaxY() + 15, tile.getMinY() - 15); + GeoShapeValues.GeoShapeValue value = geoShapeValue(shapeRectangle); + + GeoBoundingBox boundingBox = new GeoBoundingBox( + new GeoPoint(tile.getMaxLat(), tile.getMinLon()), + new GeoPoint(tile.getMinLat(), tile.getMaxLon()) + ); + GeoShapeCellValues values = new GeoShapeCellValues(null, 24, GEOTILE, NOOP_BREAKER); + int numTiles = new BoundedGeoTileGridTiler(boundingBox).setValues(values, value, 24); + int expectedTiles = + (int) GeoTileGridTiler.numTilesFromPrecision(24, tile.getMinX(), tile.getMaxX(), tile.getMinY(), tile.getMaxY()); + assertThat(expectedTiles, equalTo(numTiles)); + } + + public void testGeoTileShapeContainsBoundDateLine() throws Exception { + Rectangle tile = new Rectangle(178, -178, 2, -2); + Rectangle shapeRectangle = new Rectangle(170, -170, 10, -10); + GeoShapeValues.GeoShapeValue value = geoShapeValue(shapeRectangle); + + GeoBoundingBox boundingBox = new GeoBoundingBox( + new GeoPoint(tile.getMaxLat(), tile.getMinLon()), + new GeoPoint(tile.getMinLat(), tile.getMaxLon()) + ); + GeoShapeCellValues values = new GeoShapeCellValues(null, 13, GEOTILE, NOOP_BREAKER); + int numTiles = new BoundedGeoTileGridTiler(boundingBox).setValues(values, value, 13); + int expectedTiles = (int) (GeoTileGridTiler.numTilesFromPrecision(13, 178, 180, -2, 2) + + GeoTileGridTiler.numTilesFromPrecision(13, -180, -178, -2, 2)); + assertThat(expectedTiles, equalTo(numTiles)); + } + private boolean tileIntersectsBounds(int x, int y, int precision, GeoBoundingBox bounds) { if (bounds == null) { return true; @@ -316,7 +350,7 @@ private void checkGeoTileSetValuesBruteAndRecursive(Geometry geometry) throws Ex GeoShapeCellValues recursiveValues = new GeoShapeCellValues(null, precision, GEOTILE, NOOP_BREAKER); int recursiveCount; { - recursiveCount = GEOTILE.setValuesByRasterization(0, 0, 0, recursiveValues, 0, precision, value); + recursiveCount = GEOTILE.setValuesByRasterization(0, 0, 0, recursiveValues, 0, precision, value, Long.MAX_VALUE); } GeoShapeCellValues bruteForceValues = new GeoShapeCellValues(null, precision, GEOTILE, NOOP_BREAKER); int bruteForceCount;