Summary
A DataArray that is georeferenced but degenerate along one spatial axis (1 row or 1 column, e.g. a single scanline or single profile) silently loses its transform when written with to_geotiff. coords_to_transform returns None whenever either coord array has length < 2, and the writer then falls through and produces a non-georeferenced TIFF. On read, the file comes back with integer pixel coords and no transform attribute, so the user has no signal that the spatial metadata was dropped.
Reproduction
import numpy as np, xarray as xr
from xrspatial.geotiff import to_geotiff, open_geotiff
da = xr.DataArray(
np.arange(8, dtype='float32').reshape(1, 8),
dims=('y', 'x'),
coords={'x': np.linspace(-120.0, -113.0, 8), 'y': np.array([45.0])},
attrs={'crs': 4326},
)
to_geotiff(da, '/tmp/strip.tif')
r = open_geotiff('/tmp/strip.tif')
print(r.coords['x'].values[:3], r.coords['x'].dtype) # [0 1 2] int64
print(r.attrs.get('transform')) # None
The same thing happens for Nx1.
Affected code
xrspatial/geotiff/_coords.py:277:
if len(x) < 2 or len(y) < 2:
return None
xrspatial/geotiff/_writers/eager.py:516-519 calls _transform_from_attr first, then falls back to _coords_to_transform. If both return None, the writer proceeds without raising and produces a non-georeferenced TIFF. The GPU writer at gpu.py:348-350 has the same fall-through.
Fix direction
Two parts.
-
In coords_to_transform, don't bail when only one axis has length 1. The non-degenerate axis still pins a pixel size, and the file's row/column count fixes the other extent; the writer only needs an origin and a pixel size per axis. For the 1x1 case there is no way to recover pixel size from coords alone, so prefer attrs['transform'] when the caller supplied one and otherwise raise a clear ValueError.
-
In the eager, dask, and GPU writer paths, when coord arrays are present but no transform can be derived and no attrs['transform'] was supplied, raise rather than silently writing a non-georeferenced file. A degenerate-shape array with real spatial coords expresses intent to georeference, and the writer should fail closed instead of dropping metadata on the floor.
Round-trip invariant tests covering 1xN, Nx1, and 1x1 writes across numpy, dask+numpy, dask+cupy, and cupy backends.
Summary
A DataArray that is georeferenced but degenerate along one spatial axis (1 row or 1 column, e.g. a single scanline or single profile) silently loses its transform when written with
to_geotiff.coords_to_transformreturnsNonewhenever either coord array has length < 2, and the writer then falls through and produces a non-georeferenced TIFF. On read, the file comes back with integer pixel coords and notransformattribute, so the user has no signal that the spatial metadata was dropped.Reproduction
The same thing happens for Nx1.
Affected code
xrspatial/geotiff/_coords.py:277:xrspatial/geotiff/_writers/eager.py:516-519calls_transform_from_attrfirst, then falls back to_coords_to_transform. If both returnNone, the writer proceeds without raising and produces a non-georeferenced TIFF. The GPU writer atgpu.py:348-350has the same fall-through.Fix direction
Two parts.
In
coords_to_transform, don't bail when only one axis has length 1. The non-degenerate axis still pins a pixel size, and the file's row/column count fixes the other extent; the writer only needs an origin and a pixel size per axis. For the 1x1 case there is no way to recover pixel size from coords alone, so preferattrs['transform']when the caller supplied one and otherwise raise a clearValueError.In the eager, dask, and GPU writer paths, when coord arrays are present but no transform can be derived and no
attrs['transform']was supplied, raise rather than silently writing a non-georeferenced file. A degenerate-shape array with real spatial coords expresses intent to georeference, and the writer should fail closed instead of dropping metadata on the floor.Round-trip invariant tests covering 1xN, Nx1, and 1x1 writes across numpy, dask+numpy, dask+cupy, and cupy backends.