Describe the bug
to_geotiff infers signed pixel sizes from coords in _coords_to_transform (xrspatial/geotiff/init.py:302-303) but the GeoTIFF writer at xrspatial/geotiff/_geotags.py:891-894 stores abs(transform.pixel_width) and abs(transform.pixel_height) in ModelPixelScaleTag. The reader at xrspatial/geotiff/_geotags.py:457-461 then reconstructs the transform as pixel_width=sx, pixel_height=-sy, hard-coding the conventional north-up / west-east orientation.
The result: any DataArray whose x decreases left-to-right or whose y increases top-to-bottom round-trips with the wrong georeferencing. The pixel data is unchanged, so the file still looks fine in a viewer, but every coord-aware operation downstream is offset.
Expected behavior
Round-tripping a DataArray through to_geotiff + open_geotiff should preserve the coordinate orientation. Concretely:
```python
import numpy as np
import xarray as xr
from xrspatial.geotiff import to_geotiff, open_geotiff
arr = xr.DataArray(
np.arange(6, dtype=np.float32).reshape(2, 3),
dims=('y', 'x'),
coords={'x': [100.0, 99.0, 98.0], 'y': [10.0, 11.0]},
)
to_geotiff(arr, 'tmp.tif')
out = open_geotiff('tmp.tif')
Today: out.coords['x'] == [101, 102, 103], out.coords['y'] == [9, 8]
Expected: same as input coords
```
Proposed fix
ModelPixelScaleTag is defined as a positive-only scale; the standard GeoTIFF way to encode non-standard orientations is ModelTransformationTag (33920), which stores the full 4x4 affine and supersedes the pixel-scale+tiepoint pair when present. So:
- In
build_geotiff_tags, emit ModelTransformationTag instead of ModelPixelScale + ModelTiepoint whenever pixel_width < 0 or pixel_height > 0.
- In the reader, read ModelTransformationTag when present and prefer it over the scale+tiepoint reconstruction. Continue to fall back to scale+tiepoint for the common north-up case.
This keeps standard-orientation files byte-identical to before and only changes output for the descending-x / ascending-y paths that are currently silently broken.
Describe the bug
to_geotiffinfers signed pixel sizes from coords in_coords_to_transform(xrspatial/geotiff/init.py:302-303) but the GeoTIFF writer at xrspatial/geotiff/_geotags.py:891-894 storesabs(transform.pixel_width)andabs(transform.pixel_height)in ModelPixelScaleTag. The reader at xrspatial/geotiff/_geotags.py:457-461 then reconstructs the transform aspixel_width=sx, pixel_height=-sy, hard-coding the conventional north-up / west-east orientation.The result: any DataArray whose x decreases left-to-right or whose y increases top-to-bottom round-trips with the wrong georeferencing. The pixel data is unchanged, so the file still looks fine in a viewer, but every coord-aware operation downstream is offset.
Expected behavior
Round-tripping a DataArray through
to_geotiff+open_geotiffshould preserve the coordinate orientation. Concretely:```python
import numpy as np
import xarray as xr
from xrspatial.geotiff import to_geotiff, open_geotiff
arr = xr.DataArray(
np.arange(6, dtype=np.float32).reshape(2, 3),
dims=('y', 'x'),
coords={'x': [100.0, 99.0, 98.0], 'y': [10.0, 11.0]},
)
to_geotiff(arr, 'tmp.tif')
out = open_geotiff('tmp.tif')
Today: out.coords['x'] == [101, 102, 103], out.coords['y'] == [9, 8]
Expected: same as input coords
```
Proposed fix
ModelPixelScaleTag is defined as a positive-only scale; the standard GeoTIFF way to encode non-standard orientations is ModelTransformationTag (33920), which stores the full 4x4 affine and supersedes the pixel-scale+tiepoint pair when present. So:
build_geotiff_tags, emit ModelTransformationTag instead of ModelPixelScale + ModelTiepoint wheneverpixel_width < 0orpixel_height > 0.This keeps standard-orientation files byte-identical to before and only changes output for the descending-x / ascending-y paths that are currently silently broken.