diff --git a/rio_tiler/errors.py b/rio_tiler/errors.py index 44373533..355c2099 100644 --- a/rio_tiler/errors.py +++ b/rio_tiler/errors.py @@ -91,3 +91,7 @@ class MissingCRS(RioTilerError): class InvalidGeographicBounds(RioTilerError): """Invalid Geographic bounds.""" + + +class InvalidRowColOperator(RioTilerError): + """The rasterio rowcol 'op' parameter must be one of: math.floor, math.ceil, or round.""" diff --git a/rio_tiler/utils.py b/rio_tiler/utils.py index e4b2f99f..60751106 100644 --- a/rio_tiler/utils.py +++ b/rio_tiler/utils.py @@ -33,7 +33,7 @@ from rio_tiler.colormap import apply_cmap from rio_tiler.constants import WEB_MERCATOR_CRS, WGS84_CRS -from rio_tiler.errors import RioTilerError +from rio_tiler.errors import InvalidRowColOperator, RioTilerError from rio_tiler.types import BBox, ColorMapType, IntervalTuple, RIOResampling @@ -122,9 +122,12 @@ def get_array_statistics( percentiles_names = [f"percentile_{int(p)}" for p in percentiles] if coverage is not None: - assert coverage.shape == ( - data.shape[1], - data.shape[2], + assert ( + coverage.shape + == ( + data.shape[1], + data.shape[2], + ) ), f"Invalid shape ({coverage.shape}) for Coverage, expected {(data.shape[1], data.shape[2])}" else: @@ -649,7 +652,7 @@ def _calculateRatio( def _convert_to_raster_space( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], poly_coordinates: List, - op: Callable[[float], int] + op: Callable[[float], int], ) -> List[str]: polygons = [] for point in poly_coordinates: @@ -683,7 +686,9 @@ def create_cutline( # Validate that the function provided is one of the allowed methods if op not in {math.floor, math.ceil, round}: - raise RioTilerError("The 'op' parameter must be one of: math.floor, math.ceil, or round.") + raise InvalidRowColOperator( + "The rasterio rowcol 'op' parameter must be one of: math.floor, math.ceil, or round." + ) geometry = _validate_shape_input(geometry) geom_type = geometry["type"] diff --git a/tests/test_io_rasterio.py b/tests/test_io_rasterio.py index b2921393..4b8a1b1c 100644 --- a/tests/test_io_rasterio.py +++ b/tests/test_io_rasterio.py @@ -752,7 +752,9 @@ def test_equality_part_feature(): } img_feat = src.feature(feat) - cutline = create_cutline(src.dataset, feat, geometry_crs="epsg:4326", op=math.floor) + cutline = create_cutline( + src.dataset, feat, geometry_crs="epsg:4326", op=math.floor + ) bbox = featureBounds(feat) img_part = src.part(bbox, vrt_options={"cutline": cutline}) @@ -763,11 +765,14 @@ def test_equality_part_feature(): # I would assume this is due to rounding issue or reprojection of the cutline by GDAL # After some debugging locally I found out the rasterized mask is more precise # numpy.testing.assert_array_equal(img_part.mask, img_feat.mask) + # NOTE reply: can this rounding be fixed with a different operator passed to rasterio rowcol? # Re-Projection img_feat = src.feature(feat, dst_crs="epsg:3857") - cutline = create_cutline(src.dataset, feat, geometry_crs="epsg:4326", op=math.floor) + cutline = create_cutline( + src.dataset, feat, geometry_crs="epsg:4326", op=math.floor + ) bbox = featureBounds(feat) img_part = src.part(bbox, vrt_options={"cutline": cutline}, dst_crs="epsg:3857") diff --git a/tests/test_utils.py b/tests/test_utils.py index 23382fa9..84e93706 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,7 +17,7 @@ from rio_tiler import colormap, utils from rio_tiler.constants import WEB_MERCATOR_TMS, WGS84_CRS -from rio_tiler.errors import RioTilerError +from rio_tiler.errors import InvalidRowColOperator, RioTilerError from rio_tiler.expression import parse_expression from rio_tiler.io import Reader @@ -289,7 +289,9 @@ def test_cutline(): feature_bounds = featureBounds(feat) with Reader(COGEO) as src: - cutline = utils.create_cutline(src.dataset, feat, geometry_crs="epsg:4326", op=math.floor) + cutline = utils.create_cutline( + src.dataset, feat, geometry_crs="epsg:4326", op=math.floor + ) data, mask = src.part(feature_bounds, vrt_options={"cutline": cutline}) assert not mask.all() @@ -315,7 +317,9 @@ def test_cutline(): with Reader(COGEO) as src: with pytest.raises(RioTilerError): - utils.create_cutline(src.dataset, feat_line, geometry_crs="epsg:4326", op=math.floor) + utils.create_cutline( + src.dataset, feat_line, geometry_crs="epsg:4326", op=math.floor + ) feat_mp = { "type": "MultiPolygon", @@ -342,7 +346,9 @@ def test_cutline(): } with Reader(COGEO) as src: - c = utils.create_cutline(src.dataset, feat_mp, geometry_crs="epsg:4326", op=math.floor) + c = utils.create_cutline( + src.dataset, feat_mp, geometry_crs="epsg:4326", op=math.floor + ) assert "MULTIPOLYGON" in c bad_poly = { @@ -362,7 +368,9 @@ def test_cutline(): with Reader(COGEO) as src: with pytest.raises(RioTilerError): - utils.create_cutline(src.dataset, bad_poly, geometry_crs="epsg:4326", op=math.floor) + utils.create_cutline( + src.dataset, bad_poly, geometry_crs="epsg:4326", op=math.floor + ) triangle_over_image_edge = { "type": "Polygon", @@ -388,6 +396,14 @@ def test_cutline(): assert sum(mask[0, :]) == 0 # first line assert sum(mask[-1, :]) == 0 # last line + # Check create_cutline operator callable is one of rasterio rowcol accepted method + with Reader(COGEO) as src: + invalid_op = abs + with pytest.raises(InvalidRowColOperator): + utils.create_cutline( + src.dataset, bad_poly, geometry_crs="epsg:4326", op=invalid_op + ) + def test_parse_expression(): """test parsing rio-tiler expression."""