From bb72994e1a653d05f239cc66cc31f86b7f52035e Mon Sep 17 00:00:00 2001 From: Bernhard Stadlbauer Date: Mon, 19 Jul 2021 11:50:58 +0200 Subject: [PATCH 1/3] Add tests of the expected behaviour --- rio_tiler/errors.py | 4 ++++ tests/test_io_cogeo.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/rio_tiler/errors.py b/rio_tiler/errors.py index 4fdd5b40..d6e3a825 100644 --- a/rio_tiler/errors.py +++ b/rio_tiler/errors.py @@ -13,6 +13,10 @@ class TileOutsideBounds(RioTilerError): """Z-X-Y Tile is outside image bounds.""" +class IncorrectTileBuffer(RioTilerError): + """Tile buffer if a float but not half of an integer""" + + class PointOutsideBounds(RioTilerError): """Point is outside image bounds.""" diff --git a/tests/test_io_cogeo.py b/tests/test_io_cogeo.py index 4cd60058..34195d77 100644 --- a/tests/test_io_cogeo.py +++ b/tests/test_io_cogeo.py @@ -14,6 +14,7 @@ from rio_tiler.errors import ( AlphaBandWarning, ExpressionMixingWarning, + IncorrectTileBuffer, NoOverviewWarning, TileOutsideBounds, ) @@ -230,6 +231,26 @@ 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() + + +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 From 8b7ea6dd31d3f042033c2be49130b8de48ceab21 Mon Sep 17 00:00:00 2001 From: Bernhard Stadlbauer Date: Mon, 19 Jul 2021 11:51:54 +0200 Subject: [PATCH 2/3] Add `tile_buffer` `float` support in `COGReader.tile()` --- rio_tiler/errors.py | 2 +- rio_tiler/io/cogeo.py | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/rio_tiler/errors.py b/rio_tiler/errors.py index d6e3a825..55126f12 100644 --- a/rio_tiler/errors.py +++ b/rio_tiler/errors.py @@ -14,7 +14,7 @@ class TileOutsideBounds(RioTilerError): class IncorrectTileBuffer(RioTilerError): - """Tile buffer if a float but not half of an integer""" + """Tile buffer is a float but not half of an integer""" class PointOutsideBounds(RioTilerError): diff --git a/rio_tiler/io/cogeo.py b/rio_tiler/io/cogeo.py index db71bb1d..f7db4261 100644 --- a/rio_tiler/io/cogeo.py +++ b/rio_tiler/io/cogeo.py @@ -18,7 +18,12 @@ from .. import reader from ..constants import WEB_MERCATOR_TMS, WGS84_CRS, BBox, Indexes, NoData -from ..errors import ExpressionMixingWarning, NoOverviewWarning, TileOutsideBounds +from ..errors import ( + ExpressionMixingWarning, + IncorrectTileBuffer, + NoOverviewWarning, + TileOutsideBounds, +) from ..expression import apply_expression, parse_expression from ..models import ImageData, ImageStatistics, Info from ..utils import create_cutline, has_alpha_band, has_mask_band @@ -258,7 +263,7 @@ def tile( tilesize: int = 256, indexes: Optional[Indexes] = None, expression: Optional[str] = None, - tile_buffer: Optional[int] = None, + tile_buffer: Optional[Union[int, float]] = None, **kwargs: Any, ) -> ImageData: """Read a Web Map tile from a COG. @@ -270,12 +275,19 @@ 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 around the given tile in pixels. Tilesize will be expanded to + `tilesize + 2 * tile_buffer`. If given as a float, must be half of an integer to have an integer `tilesize` kwargs (optional): Options to forward to the `COGReader.part` method. Returns: rio_tiler.models.ImageData: ImageData instance with data, mask and tile spatial info. """ + if isinstance(tile_buffer, float) and not (tile_buffer * 2).is_integer(): + raise IncorrectTileBuffer( + "When tile buffer is a float, it must be half of an integer" + ) + if not self.tile_exists(tile_z, tile_x, tile_y): raise TileOutsideBounds( f"Tile {tile_z}/{tile_x}/{tile_y} is outside {self.filepath} bounds" @@ -291,7 +303,9 @@ def tile( tile_bounds[2] + x_res * tile_buffer, tile_bounds[3] + y_res * tile_buffer, ) - tilesize += tile_buffer * 2 + tilesize += int( + tile_buffer * 2 + ) # We are sure this is a valid int because we checked before return self.part( tile_bounds, From 03dfa2d97d0ae483765adf6f00a4d2481f3c6537 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Mon, 18 Oct 2021 14:22:01 +0200 Subject: [PATCH 3/3] :facepalm --- CHANGES.md | 1 + rio_tiler/io/cogeo.py | 2 +- tests/test_io_cogeo.py | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2f686bf0..0b251973 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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** diff --git a/rio_tiler/io/cogeo.py b/rio_tiler/io/cogeo.py index 1d264592..c4e860a2 100644 --- a/rio_tiler/io/cogeo.py +++ b/rio_tiler/io/cogeo.py @@ -356,7 +356,7 @@ def tile( tile_bounds = self.tms.xy_bounds(Tile(x=tile_x, y=tile_y, z=tile_z)) if tile_buffer is not None: - if not tile_buffer % 0.5: + if tile_buffer % 0.5: raise IncorrectTileBuffer( "`tile_buffer` must be a multiple of `0.5` (e.g: 0.5, 1, 1.5, ...)." ) diff --git a/tests/test_io_cogeo.py b/tests/test_io_cogeo.py index 027d1231..06d392a1 100644 --- a/tests/test_io_cogeo.py +++ b/tests/test_io_cogeo.py @@ -270,6 +270,11 @@ def test_tile_with_int_buffer(): 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: