Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow float tile_buffer #405

Merged
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ with COGReader(

* compare dataset bounds and tile bounds in TMS crs in `rio_tiler.io.base.SpatialMixin.tile_exists` method to allow dataset and TMS not compatible with WGS84 crs (https://github.com/cogeotiff/rio-tiler/pull/429)
* use `httpx` package instead of requests (author @rodrigoalmeida94, https://github.com/cogeotiff/rio-tiler/pull/431)
* allow **half pixel** `tile_buffer` around the tile (e.g 0.5 -> 257x257, 1.5 -> 259x259) (author @bstadlbauer, https://github.com/cogeotiff/rio-tiler/pull/405)

**breaking changes**

Expand Down
4 changes: 4 additions & 0 deletions rio_tiler/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class TileOutsideBounds(RioTilerError):
"""Z-X-Y Tile is outside image bounds."""


class IncorrectTileBuffer(RioTilerError):
"""Tile buffer is a float but not half of an integer"""


class PointOutsideBounds(RioTilerError):
"""Point is outside image bounds."""

Expand Down
41 changes: 28 additions & 13 deletions rio_tiler/io/cogeo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import attr
import numpy
import rasterio
from morecantile import Tile, TileMatrixSet
from morecantile import BoundingBox, Tile, TileMatrixSet
from rasterio import transform
from rasterio.crs import CRS
from rasterio.enums import Resampling
Expand All @@ -17,8 +17,13 @@
from rasterio.warp import calculate_default_transform, transform_bounds

from .. import reader
from ..constants import WEB_MERCATOR_TMS, WGS84_CRS, BBox, Indexes, NoData
from ..errors import ExpressionMixingWarning, NoOverviewWarning, TileOutsideBounds
from ..constants import WEB_MERCATOR_TMS, WGS84_CRS, BBox, Indexes, NoData, NumType
from ..errors import (
ExpressionMixingWarning,
IncorrectTileBuffer,
NoOverviewWarning,
TileOutsideBounds,
)
from ..expression import apply_expression, parse_expression
from ..models import BandStatistics, ImageData, ImageStatistics, Info
from ..utils import (
Expand Down Expand Up @@ -325,7 +330,7 @@ def tile(
tilesize: int = 256,
indexes: Optional[Indexes] = None,
expression: Optional[str] = None,
tile_buffer: Optional[int] = None,
tile_buffer: Optional[NumType] = None,
**kwargs: Any,
) -> ImageData:
"""Read a Web Map tile from a COG.
Expand All @@ -337,6 +342,7 @@ def tile(
tilesize (int, optional): Output image size. Defaults to `256`.
indexes (int or sequence of int, optional): Band indexes.
expression (str, optional): rio-tiler expression (e.g. b1/b2+b3).
tile_buffer (int or float, optional): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).
kwargs (optional): Options to forward to the `COGReader.part` method.
Returns:
Expand All @@ -349,16 +355,25 @@ def tile(
)

tile_bounds = self.tms.xy_bounds(Tile(x=tile_x, y=tile_y, z=tile_z))
if tile_buffer:
x_res = (tile_bounds[2] - tile_bounds[0]) / tilesize
y_res = (tile_bounds[3] - tile_bounds[1]) / tilesize
tile_bounds = (
tile_bounds[0] - x_res * tile_buffer,
tile_bounds[1] - y_res * tile_buffer,
tile_bounds[2] + x_res * tile_buffer,
tile_bounds[3] + y_res * tile_buffer,
if tile_buffer is not None:
if tile_buffer % 0.5:
raise IncorrectTileBuffer(
"`tile_buffer` must be a multiple of `0.5` (e.g: 0.5, 1, 1.5, ...)."
)

x_res = (tile_bounds.right - tile_bounds.left) / tilesize
y_res = (tile_bounds.top - tile_bounds.bottom) / tilesize

# Buffered Tile Bounds
tile_bounds = BoundingBox(
tile_bounds.left - x_res * tile_buffer,
tile_bounds.bottom - y_res * tile_buffer,
tile_bounds.right + x_res * tile_buffer,
tile_bounds.top + y_res * tile_buffer,
)
tilesize += tile_buffer * 2

# Buffered Tile Size
tilesize += int(tile_buffer * 2)

return self.part(
tile_bounds,
Expand Down
26 changes: 26 additions & 0 deletions tests/test_io_cogeo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from rio_tiler.errors import (
AlphaBandWarning,
ExpressionMixingWarning,
IncorrectTileBuffer,
NoOverviewWarning,
TileOutsideBounds,
)
Expand Down Expand Up @@ -257,6 +258,31 @@ def test_tile_invalid_bounds():
cog.tile(38, 24, 7)


def test_tile_with_incorrect_float_buffer():
with pytest.raises(IncorrectTileBuffer):
with COGReader(COGEO) as cog:
cog.tile(43, 24, 7, tile_buffer=0.8)


def test_tile_with_int_buffer():
with COGReader(COGEO) as cog:
data, mask = cog.tile(43, 24, 7, tile_buffer=1)
assert data.shape == (1, 258, 258)
assert mask.all()

with COGReader(COGEO) as cog:
data, mask = cog.tile(43, 24, 7, tile_buffer=0)
assert data.shape == (1, 256, 256)
assert mask.all()


def test_tile_with_correct_float_buffer():
with COGReader(COGEO) as cog:
data, mask = cog.tile(43, 24, 7, tile_buffer=0.5)
assert data.shape == (1, 257, 257)
assert mask.all()


def test_point_valid():
"""Read point."""
lon = -56.624124590533825
Expand Down