builder = ImmutableList.builderWithExpectedSize(1 << (2 * zoomDelta));
+ for (int yDelta = 0; yDelta < 1 << zoomDelta; ++yDelta) {
+ for (int xDelta = 0; xDelta < 1 << zoomDelta; ++xDelta) {
+ builder.add(BingTile.fromCoordinates(xNew + xDelta, yNew + yDelta, newZoom));
+ }
+ }
+ return builder.build();
+ }
+
+ public BingTile findParent()
+ {
+ return findParent(zoomLevel - 1);
+ }
+
+ public BingTile findParent(int newZoom)
+ {
+ if (newZoom == zoomLevel) {
+ return this;
+ }
+
+ checkArgument(newZoom >= 0, "newZoom must be greater than or equal to 0: %s", newZoom);
+ checkArgument(newZoom <= zoomLevel, "newZoom must be less than or equal to current zoom %s: %s", zoomLevel, newZoom);
+
+ int zoomDelta = zoomLevel - newZoom;
+ return BingTile.fromCoordinates(x >> zoomDelta, y >> zoomDelta, newZoom);
+ }
+
/**
* Encodes Bing tile as a 64-bit long:
* Version (5 bits), 0 (4 bits), x (23 bits), Zoom (5 bits), 0 (4 bits), y (23 bits)
* (high bits left, low bits right).
- *
+ *
* This arrangement maximizes low-bit entropy for the Java long hash function.
*/
public long encode()
diff --git a/presto-geospatial/src/main/java/com/facebook/presto/plugin/geospatial/BingTileFunctions.java b/presto-geospatial/src/main/java/com/facebook/presto/plugin/geospatial/BingTileFunctions.java
index 6d1fd70b8c4fb..84c4717952beb 100644
--- a/presto-geospatial/src/main/java/com/facebook/presto/plugin/geospatial/BingTileFunctions.java
+++ b/presto-geospatial/src/main/java/com/facebook/presto/plugin/geospatial/BingTileFunctions.java
@@ -29,6 +29,8 @@
import com.google.common.collect.ImmutableList;
import io.airlift.slice.Slice;
+import java.util.List;
+
import static com.facebook.presto.common.function.OperatorType.CAST;
import static com.facebook.presto.common.type.BigintType.BIGINT;
import static com.facebook.presto.common.type.IntegerType.INTEGER;
@@ -64,6 +66,7 @@
import static com.facebook.presto.plugin.geospatial.BingTileUtils.tileXYToLatitudeLongitude;
import static com.facebook.presto.plugin.geospatial.GeometryType.GEOMETRY_TYPE_NAME;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
+import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
import static io.airlift.slice.Slices.utf8Slice;
@@ -375,6 +378,68 @@ public static Slice bingTilePolygon(@SqlType(BingTileType.NAME) long input)
return serialize(tileToEnvelope(tile));
}
+ @Description("Return the parent for a Bing tile")
+ @ScalarFunction("bing_tile_parent")
+ @SqlType(BingTileType.NAME)
+ public static long bingTileParent(@SqlType(BingTileType.NAME) long input)
+ {
+ BingTile tile = BingTile.decode(input);
+ try {
+ return tile.findParent().encode();
+ }
+ catch (IllegalArgumentException e) {
+ throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
+ }
+ }
+
+ @Description("Return the parent for the given zoom level for a Bing tile")
+ @ScalarFunction("bing_tile_parent")
+ @SqlType(BingTileType.NAME)
+ public static long bingTileParent(@SqlType(BingTileType.NAME) long input, @SqlType(StandardTypes.INTEGER) long newZoom)
+ {
+ BingTile tile = BingTile.decode(input);
+ try {
+ return tile.findParent(toIntExact(newZoom)).encode();
+ }
+ catch (IllegalArgumentException e) {
+ throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
+ }
+ }
+
+ @Description("Return the children for a Bing tile")
+ @ScalarFunction("bing_tile_children")
+ @SqlType("array(" + BingTileType.NAME + ")")
+ public static Block bingTileChildren(@SqlType(BingTileType.NAME) long input)
+ {
+ BingTile tile = BingTile.decode(input);
+ try {
+ List children = tile.findChildren();
+ BlockBuilder blockBuilder = BIGINT.createBlockBuilder(null, children.size());
+ children.stream().forEach(child -> BIGINT.writeLong(blockBuilder, child.encode()));
+ return blockBuilder.build();
+ }
+ catch (IllegalArgumentException e) {
+ throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
+ }
+ }
+
+ @Description("Return the children for the given zoom level for a Bing tile")
+ @ScalarFunction("bing_tile_children")
+ @SqlType("array(" + BingTileType.NAME + ")")
+ public static Block bingTileChildren(@SqlType(BingTileType.NAME) long input, @SqlType(StandardTypes.INTEGER) long newZoom)
+ {
+ BingTile tile = BingTile.decode(input);
+ try {
+ List children = tile.findChildren(toIntExact(newZoom));
+ BlockBuilder blockBuilder = BIGINT.createBlockBuilder(null, children.size());
+ children.stream().forEach(child -> BIGINT.writeLong(blockBuilder, child.encode()));
+ return blockBuilder.build();
+ }
+ catch (IllegalArgumentException e) {
+ throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
+ }
+ }
+
@Description("Given a geometry and a zoom level, returns the minimum set of Bing tiles that fully covers that geometry")
@ScalarFunction("geometry_to_bing_tiles")
@SqlType("array(" + BingTileType.NAME + ")")
diff --git a/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTile.java b/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTile.java
index 8ffc10b57a99e..f101a614307d4 100644
--- a/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTile.java
+++ b/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTile.java
@@ -15,8 +15,11 @@
import com.facebook.presto.operator.scalar.AbstractTestFunctions;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
import org.testng.annotations.Test;
+import java.util.List;
+
import static com.facebook.presto.plugin.geospatial.BingTile.MAX_ZOOM_LEVEL;
import static com.facebook.presto.plugin.geospatial.BingTile.fromCoordinates;
import static com.facebook.presto.plugin.geospatial.BingTileUtils.MAX_LATITUDE;
@@ -25,6 +28,9 @@
import static com.facebook.presto.plugin.geospatial.BingTileUtils.MIN_LONGITUDE;
import static com.facebook.presto.plugin.geospatial.BingTileUtils.tileXToLongitude;
import static com.facebook.presto.plugin.geospatial.BingTileUtils.tileYToLatitude;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.testng.Assert.assertEquals;
public class TestBingTile
@@ -90,4 +96,55 @@ public void testTileYToLatitude()
assertEquals(tileYToLatitude(1 << zoom, zoom), MIN_LATITUDE, delta);
}
}
+
+ @Test
+ public void testFindChildren()
+ {
+ assertEquals(
+ toSortedQuadkeys(BingTile.fromQuadKey("").findChildren()),
+ ImmutableList.of("0", "1", "2", "3"));
+
+ assertEquals(
+ toSortedQuadkeys(BingTile.fromQuadKey("0123").findChildren()),
+ ImmutableList.of("01230", "01231", "01232", "01233"));
+
+ assertEquals(
+ toSortedQuadkeys(BingTile.fromQuadKey("").findChildren(2)),
+ ImmutableList.of("00", "01", "02", "03", "10", "11", "12", "13", "20", "21", "22", "23", "30", "31", "32", "33"));
+
+ assertThatThrownBy(() -> BingTile.fromCoordinates(0, 0, MAX_ZOOM_LEVEL).findChildren())
+ .hasMessage(format("newZoom must be less than or equal to %s: %s", MAX_ZOOM_LEVEL, MAX_ZOOM_LEVEL + 1));
+
+ assertThatThrownBy(() -> BingTile.fromCoordinates(0, 0, 13).findChildren(MAX_ZOOM_LEVEL + 1))
+ .hasMessage(format("newZoom must be less than or equal to %s: %s", MAX_ZOOM_LEVEL, MAX_ZOOM_LEVEL + 1));
+
+ assertThatThrownBy(() -> BingTile.fromCoordinates(0, 0, 13).findChildren(12))
+ .hasMessage(format("newZoom must be greater than or equal to current zoom %s: %s", 13, 12));
+ }
+
+ private List toSortedQuadkeys(List tiles)
+ {
+ return tiles.stream()
+ .map(BingTile::toQuadKey)
+ .sorted()
+ .collect(toImmutableList());
+ }
+
+ @Test
+ public void testFindParent()
+ {
+ assertEquals(BingTile.fromQuadKey("0123").findParent().toQuadKey(), "012");
+ assertEquals(BingTile.fromQuadKey("1").findParent().toQuadKey(), "");
+ assertEquals(BingTile.fromQuadKey("0123").findParent(1).toQuadKey(), "0");
+ assertEquals(BingTile.fromQuadKey("0123").findParent(4).toQuadKey(), "0123");
+
+ assertThatThrownBy(() -> BingTile.fromQuadKey("0123").findParent(5))
+ .hasMessage(format("newZoom must be less than or equal to current zoom %s: %s", 4, 5));
+
+ assertThatThrownBy(() -> BingTile.fromQuadKey("").findParent())
+ .hasMessage(format("newZoom must be greater than or equal to 0: %s", -1));
+
+ assertThatThrownBy(() -> BingTile.fromQuadKey("12").findParent(-1))
+ .hasMessage(format("newZoom must be greater than or equal to 0: %s", -1));
+ }
}
diff --git a/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTileFunctions.java b/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTileFunctions.java
index 06f4dcb4a8fe5..86776e5b89b05 100644
--- a/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTileFunctions.java
+++ b/presto-geospatial/src/test/java/com/facebook/presto/plugin/geospatial/TestBingTileFunctions.java
@@ -29,6 +29,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.OptionalInt;
import static com.facebook.presto.block.BlockAssertions.createTypedLongsBlock;
import static com.facebook.presto.common.type.BigintType.BIGINT;
@@ -39,6 +40,7 @@
import static com.facebook.presto.metadata.FunctionExtractor.extractFunctions;
import static com.facebook.presto.operator.aggregation.AggregationTestUtils.assertAggregation;
import static com.facebook.presto.operator.scalar.ApplyFunction.APPLY_FUNCTION;
+import static com.facebook.presto.plugin.geospatial.BingTile.MAX_ZOOM_LEVEL;
import static com.facebook.presto.plugin.geospatial.BingTile.fromCoordinates;
import static com.facebook.presto.plugin.geospatial.BingTileType.BING_TILE;
import static com.facebook.presto.plugin.geospatial.BingTileUtils.MAX_LATITUDE;
@@ -130,6 +132,57 @@ public void testBingTile()
assertInvalidFunction("bing_tile(2, 7, 37)", "Zoom level must be <= 23");
}
+ @Test
+ public void testBingTileChildren()
+ {
+ assertBingTileChildren("0", OptionalInt.empty(), ImmutableList.of("00", "01", "02", "03"));
+ assertBingTileChildren("0", OptionalInt.of(3), ImmutableList.of(
+ "000", "001", "002", "003",
+ "010", "011", "012", "013",
+ "020", "021", "022", "023",
+ "030", "031", "032", "033"));
+ assertInvalidFunction("bing_tile_children(bing_tile('0'), 0)", "newZoom must be greater than or equal to current zoom 1: 0");
+ assertInvalidFunction(format("bing_tile_children(bing_tile('0'), %s)", MAX_ZOOM_LEVEL + 1), format("newZoom must be less than or equal to %s: %s", MAX_ZOOM_LEVEL, MAX_ZOOM_LEVEL + 1));
+ }
+
+ private void assertBingTileChildren(String quadkey, OptionalInt newZoom, List childQuadkeys)
+ {
+ String children;
+ if (newZoom.isPresent()) {
+ children = format("bing_tile_children(bing_tile('%s'), %s)", quadkey, newZoom.getAsInt());
+ }
+ else {
+ children = format("bing_tile_children(bing_tile('%s'))", quadkey);
+ }
+
+ assertFunction(
+ format("array_sort(transform(%s, x -> bing_tile_quadkey(x)))", children),
+ new ArrayType(VARCHAR),
+ ImmutableList.sortedCopyOf(childQuadkeys));
+ }
+
+ @Test
+ public void testBingTileParent()
+ {
+ assertBingTileParent("03", OptionalInt.empty(), "0");
+ assertBingTileParent("0123", OptionalInt.of(2), "01");
+ assertInvalidFunction("bing_tile_parent(bing_tile('0'), 2)", "newZoom must be less than or equal to current zoom 1: 2");
+ assertInvalidFunction(format("bing_tile_parent(bing_tile('0'), %s)", -1), "newZoom must be greater than or equal to 0: -1");
+ }
+
+ private void assertBingTileParent(String quadkey, OptionalInt newZoom, String parentQuadkey)
+ {
+ String parent;
+ if (newZoom.isPresent()) {
+ parent = format("bing_tile_parent(bing_tile('%s'), %s)", quadkey, newZoom.getAsInt());
+ }
+ else {
+ parent = format("bing_tile_parent(bing_tile('%s'))", quadkey);
+ }
+
+ assertFunction(format("bing_tile_quadkey(%s)", parent), VARCHAR, parentQuadkey);
+ }
+
@Test
public void testPointToBingTile()
{