Skip to content

to_geotiff silently drops rotated_affine instead of failing closed #2216

@brendancol

Description

@brendancol

Summary

to_geotiff silently drops rotation when called on a DataArray carrying attrs['rotated_affine'] from an allow_rotated=True read. The reader spells out the read-only contract at xrspatial/geotiff/_geotags.py:635, but the writer accepts the input without warning and writes an identity-affine file. The round-trip corrupts georeferencing with no signal to the caller.

Steps to reproduce

from xrspatial.geotiff import open_geotiff, to_geotiff

# Source file with a rotated ModelTransformationTag.
da = open_geotiff("rotated_src.tif", allow_rotated=True)
assert "rotated_affine" in da.attrs

# This succeeds today and writes a file with identity geotransform.
to_geotiff(da, "out.tif")

# Read it back -- rotation is gone, no warning was emitted.
da2 = open_geotiff("out.tif")
assert "rotated_affine" not in da2.attrs  # rotation silently lost

Expected behaviour

to_geotiff should reject inputs carrying attrs['rotated_affine'] by default and raise ValueError with a message that names the offending attr. The caller can then reproject onto an axis-aligned grid, or opt in to dropping rotation via a new drop_rotation=True keyword whose docstring spells out the silent-loss consequence.

Why this matters

Silent loss of georeferencing is exactly the kind of downstream poison this module should avoid. Code that branches on attrs['rotated_affine'] cannot tell that the round-trip lost the mapping, and consumers reading the output file have no way to recover the original transform. The allow_rotated=True contract was explicitly written as read-only at _geotags.py:635; the writer should fail closed rather than paper over the gap.

Proposed fix

  • Add drop_rotation: bool = False to to_geotiff (and write_geotiff_gpu for parity).
  • When attrs['rotated_affine'] is present and drop_rotation is False, raise ValueError naming the attr and pointing at the opt-in.
  • When drop_rotation=True, write the axis-aligned file and make sure the attr does not propagate to the output.
  • Document the silent-loss consequence in the docstring.

Scope

Writer-side only. The read-side allow_rotated=True contract stays unchanged. The ModelTransformationTag emit path that would let writes preserve rotation is tracked separately (#2115 follow-up); this issue closes the silent-loss gap until that work lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions