Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions presto-docs/src/main/sphinx/functions/geospatial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,28 @@ represent a valid tile will raise an exception.

Creates a Bing tile object from a quadkey.

.. function:: bing_tile_parent(tile) -> BingTile

Returns the parent of the Bing tile at one lower zoom level.
Throws an exception if tile is at zoom level 0.

.. function:: bing_tile_parent(tile, newZoom) -> BingTile

Returns the parent of the Bing tile at the specified lower zoom level.
Throws an exception if newZoom is less than 0, or newZoom is greater than
the tile's zoom.

.. function:: bing_tile_children(tile) -> array(BingTile)

Returns the children of the Bing tile at one higher zoom level.
Throws an exception if tile is at max zoom level.

.. function:: bing_tile_children(tile, newZoom) -> array(BingTile)

Returns the children of the Bing tile at the specified higher zoom level.
Throws an exception if newZoom is greater than the max zoom level, or
newZoom is less than the tile's zoom.

.. function:: bing_tile_at(latitude, longitude, zoom_level) -> BingTile

Returns a Bing tile at a given zoom level containing a point at a given latitude
Expand Down
5 changes: 5 additions & 0 deletions presto-geospatial/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>

<dependency>
<groupId>com.facebook.presto</groupId>
<artifactId>presto-tests</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;

import java.util.List;
import java.util.Objects;

import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
Expand Down Expand Up @@ -155,11 +157,55 @@ public String toQuadKey()
return String.valueOf(quadKey);
}

public List<BingTile> findChildren()
{
return findChildren(zoomLevel + 1);
}

public List<BingTile> findChildren(int newZoom)
{
if (newZoom == zoomLevel) {
return ImmutableList.of(this);
}

checkArgument(newZoom <= MAX_ZOOM_LEVEL, "newZoom must be less than or equal to %s: %s", MAX_ZOOM_LEVEL, newZoom);
checkArgument(newZoom >= zoomLevel, "newZoom must be greater than or equal to current zoom %s: %s", zoomLevel, newZoom);

int zoomDelta = newZoom - zoomLevel;
int xNew = x << zoomDelta;
int yNew = y << zoomDelta;
ImmutableList.Builder<BingTile> 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).
*
* <p>
* This arrangement maximizes low-bit entropy for the Java long hash function.
*/
public long encode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<BingTile> 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<BingTile> 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 + ")")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<String> toSortedQuadkeys(List<BingTile> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String> 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()
{
Expand Down