/
xyz.py
55 lines (40 loc) · 1.85 KB
/
xyz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""xyz.py
Utilities to work with XYZ Mercator tiles.
"""
from typing import Sequence, Union, Mapping, Tuple, Any
import mercantile
from terracotta import exceptions
from terracotta.drivers.base import Driver
# TODO: add accurate signature if mypy ever supports conditional return types
def get_tile_data(driver: Driver,
keys: Union[Sequence[str], Mapping[str, str]],
tile_xyz: Tuple[int, int, int] = None,
*, tile_size: Tuple[int, int] = (256, 256),
preserve_values: bool = False,
asynchronous: bool = False) -> Any:
"""Retrieve raster image from driver for given XYZ tile and keys"""
if tile_xyz is None:
# read whole dataset
return driver.get_raster_tile(
keys, tile_size=tile_size, preserve_values=preserve_values,
asynchronous=asynchronous
)
# determine bounds for given tile
metadata = driver.get_metadata(keys)
wgs_bounds = metadata['bounds']
tile_x, tile_y, tile_z = tile_xyz
if not tile_exists(wgs_bounds, tile_x, tile_y, tile_z):
raise exceptions.TileOutOfBoundsError(
f'Tile {tile_z}/{tile_x}/{tile_y} is outside image bounds'
)
mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z)
target_bounds = mercantile.xy_bounds(mercator_tile)
return driver.get_raster_tile(
keys, tile_bounds=target_bounds, tile_size=tile_size,
preserve_values=preserve_values, asynchronous=asynchronous
)
def tile_exists(bounds: Sequence[float], tile_x: int, tile_y: int, tile_z: int) -> bool:
"""Check if an XYZ tile is inside the given physical bounds."""
mintile = mercantile.tile(bounds[0], bounds[3], tile_z)
maxtile = mercantile.tile(bounds[2], bounds[1], tile_z)
return mintile.x <= tile_x <= maxtile.x and mintile.y <= tile_y <= maxtile.y