Skip to content

Commit f5fbad5

Browse files
authored
Pin actionable failure modes for unsupported COG writer inputs (#2301) (#2307)
1 parent 5e8bf9a commit f5fbad5

3 files changed

Lines changed: 425 additions & 3 deletions

File tree

xrspatial/geotiff/_coords.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
from ._errors import NonUniformCoordsError
3434
from ._geotags import _NO_GEOREF_KEY, RASTER_PIXEL_IS_POINT, GeoTransform
3535

36+
# Tolerance for rotation/shear detection in affine transforms. ``b`` and
37+
# ``d`` terms below this magnitude are treated as float noise and pass
38+
# through as axis-aligned. Shared between ``transform_from_attr`` (6-tuple
39+
# attrs) and ``to_geotiff`` (rasterio ``Affine`` attrs) so the two gates
40+
# stay in lockstep on future tolerance tweaks (#2301).
41+
ROTATION_SHEAR_TOL = 1e-12
42+
3643
# Canonical georef_status string values (mirrored in ``_attrs.py`` as
3744
# ``GEOREF_STATUS_*`` constants). Issue #2225 centralises the
3845
# transform/georef contract here; ``_attrs.py`` aliases these names so
@@ -330,8 +337,7 @@ def transform_from_attr(attr_val) -> 'GeoTransform | None':
330337
a, b, c, d, e, f = (float(x) for x in seq)
331338
except (TypeError, ValueError):
332339
return None
333-
_ROT_TOL = 1e-12
334-
if abs(b) > _ROT_TOL or abs(d) > _ROT_TOL:
340+
if abs(b) > ROTATION_SHEAR_TOL or abs(d) > ROTATION_SHEAR_TOL:
335341
raise ValueError(
336342
f"attrs['transform'] has non-zero rotation/shear "
337343
f"(b={b!r}, d={d!r}); rotated or skewed affines are not "

xrspatial/geotiff/_writers/eager.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from .._attrs import (_EXPERIMENTAL_CODECS, _LEVEL_RANGES, _VALID_COMPRESSIONS, _extract_rich_tags,
2525
_resolve_nodata_attr, _should_restore_nan_sentinel)
2626
from .._backends._gpu_helpers import _is_gpu_data
27-
from .._coords import _BAND_DIM_NAMES, _has_no_georef_marker
27+
from .._coords import ROTATION_SHEAR_TOL, _BAND_DIM_NAMES, _has_no_georef_marker
2828
from .._coords import require_transform_for_georeferenced as _require_transform_for_georeferenced
2929
from .._coords import resolve_georef as _resolve_georef
3030
from .._crs import _validate_crs_arg, _validate_crs_fallback, _wkt_to_epsg
@@ -357,6 +357,47 @@ def to_geotiff(data: xr.DataArray | np.ndarray,
357357
entry_point="to_geotiff",
358358
)
359359

360+
# Issue #2301: ``attrs['transform']`` carrying a rasterio
361+
# ``Affine`` with non-zero rotation/shear (``b != 0`` or ``d != 0``)
362+
# silently slipped past ``transform_from_attr`` because ``Affine``
363+
# iterates as a 9-element augmented matrix and the 6-tuple gate
364+
# there ran ``len(seq) != 6 -> return None``. Without the
365+
# rejection the writer falls back to coord-derived or no-georef
366+
# output and the rotation is lost on disk. Detect the Affine shape
367+
# by duck-typing the (b, d) attrs and surface the same diagnostic
368+
# the 6-tuple branch raises (#1987 PR 3 wording, kept verbatim so
369+
# the existing match patterns still hit).
370+
_attr_transform = _drop_rotation_attrs.get('transform')
371+
if (_attr_transform is not None
372+
and hasattr(_attr_transform, 'b')
373+
and hasattr(_attr_transform, 'd')):
374+
try:
375+
_b = float(_attr_transform.b)
376+
_d = float(_attr_transform.d)
377+
except (TypeError, ValueError) as _exc:
378+
# Fail-closed on a malformed ``.b`` / ``.d`` rather than
379+
# zero-defaulting: an unconvertable value inside an attr
380+
# claiming to be an affine transform is itself a writer
381+
# input contract violation. Without the explicit raise the
382+
# branch would bypass every downstream georef gate that
383+
# would otherwise catch the bad value.
384+
raise ValueError(
385+
f"attrs['transform'] has unconvertable rotation/shear "
386+
f"terms (b={_attr_transform.b!r}, "
387+
f"d={_attr_transform.d!r}); expected numeric values on "
388+
f"a rasterio Affine-like object."
389+
) from _exc
390+
if abs(_b) > ROTATION_SHEAR_TOL or abs(_d) > ROTATION_SHEAR_TOL:
391+
raise ValueError(
392+
f"attrs['transform'] has non-zero rotation/shear "
393+
f"(b={_b!r}, d={_d!r}); rotated or skewed affines are "
394+
f"not supported by the GeoTIFF writers in this module "
395+
f"because the on-disk GeoTIFF representation is "
396+
f"axis-aligned and would be written at the wrong "
397+
f"location. Reproject the raster to an axis-aligned "
398+
f"grid before writing."
399+
)
400+
360401
# Issue #2075: reject zero-height / zero-width inputs before any
361402
# dispatch decision. Clip / window pipelines naturally produce empty
362403
# rasters and the writers used to accept them, produce a TIFF whose

0 commit comments

Comments
 (0)