xrspatial/geotiff/__init__.py has two writer entry points that disagree about compression='jpeg':
to_geotiff (around line 1313-1334) rejects compression='jpeg' with a ValueError. The CPU encoder writes self-contained JFIF streams per tile and skips the JPEGTables tag (347), so the resulting files are unreadable by libtiff, GDAL, and rasterio.
write_geotiff_gpu (around line 3314 onward) accepts the same value and writes the same broken format. The docstring at line 3374-3384 already calls the path "experimental and internal-reader-only until the JPEGTables fix lands".
A user who happens to land on the GPU writer (directly or via to_geotiff(gpu=True) once the auto-dispatch path is wider) gets a .tif on disk that decodes through xrspatial's own reader and fails everywhere else. There is no warning at write time. The asymmetry between the two writers in the same module is the surprising part.
Proposed fix
Reject compression='jpeg' on write_geotiff_gpu by default with the same error text to_geotiff raises. Add an opt-in allow_internal_only_jpeg: bool = False kwarg for users who want the existing internal-only path. When the flag is set, proceed with the current encode path and emit a GeoTIFFFallbackWarning so it cannot be missed.
An env-var gate was considered and rejected: it is harder to discover, harder to audit at the call site, and forces test code to mutate process state.
Existing tests to update
Tests in xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.py that call write_geotiff_gpu(..., compression='jpeg') will need to pass the new flag to keep exercising the internal path.
xrspatial/geotiff/__init__.pyhas two writer entry points that disagree aboutcompression='jpeg':to_geotiff(around line 1313-1334) rejectscompression='jpeg'with aValueError. The CPU encoder writes self-contained JFIF streams per tile and skips the JPEGTables tag (347), so the resulting files are unreadable by libtiff, GDAL, and rasterio.write_geotiff_gpu(around line 3314 onward) accepts the same value and writes the same broken format. The docstring at line 3374-3384 already calls the path "experimental and internal-reader-only until the JPEGTables fix lands".A user who happens to land on the GPU writer (directly or via
to_geotiff(gpu=True)once the auto-dispatch path is wider) gets a.tifon disk that decodes through xrspatial's own reader and fails everywhere else. There is no warning at write time. The asymmetry between the two writers in the same module is the surprising part.Proposed fix
Reject
compression='jpeg'onwrite_geotiff_gpuby default with the same error textto_geotiffraises. Add an opt-inallow_internal_only_jpeg: bool = Falsekwarg for users who want the existing internal-only path. When the flag is set, proceed with the current encode path and emit aGeoTIFFFallbackWarningso it cannot be missed.An env-var gate was considered and rejected: it is harder to discover, harder to audit at the call site, and forces test code to mutate process state.
Existing tests to update
Tests in
xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.pythat callwrite_geotiff_gpu(..., compression='jpeg')will need to pass the new flag to keep exercising the internal path.