Releases: xarray-contrib/xarray-spatial
Releases · xarray-contrib/xarray-spatial
v0.10.9
Bug fixes and improvements
- cost_distance: route bounded large-radius dask path to iterative Dijkstra (#3345)
- cost_distance: guard the numpy dask chunk path with _check_memory (#3346)
- cost_distance: fix leaking dask task name as output .name (#3349)
- cost_distance: replace mutable default target_values=[] with a None sentinel (#3348)
- cost_distance: add tests for 1x1 raster, Inf friction, and metadata (#3347)
- cost_distance: fix flake8/isort findings (#3350)
- contour: batch dask chunk compute to bound client memory (#3334)
- contour: remove redundant np.empty_like allocation in the coordinate transform (#3354)
- contour: add test coverage for all-equal auto-levels and empty explicit levels (#3353)
- geotiff: reject a zero or non-finite ModelPixelScale on read (#3332)
- geotiff: fix isort import ordering in _writers/eager.py (#3330)
- docs: fix file paths in the deep-sweep documentation (#3336)
Thanks
Thanks to @Melissari1997 for code and test contributions to this release.
v0.10.8
Bug fixes and improvements
- geotiff: gate dict gdal_metadata behind the experimental rich-tag opt-in (#3327)
- name dask tasks for remaining xrspatial tool modules (#3326)
- geotiff: don't flip masked_nodata True on a caller dtype= cast of an unmasked buffer (#3325)
- to_geotiff: type-check compression_level (#3324)
- rasterize: burn all_touched lines with the supercover walk (#3322)
- focal: handle empty rasters consistently across backends (#3319)
- reproject: promote large-output in-memory cupy inputs to dask (#3318)
- reproject: serialize all parallel=True kernel launches behind one lock (#3317)
- docs: add an examples-and-data getting-started page and landing index (#3316)
v0.10.7
Version 0.10.7 - 2026-06-13
New features
- to_geotiff: add pack=True, the inverse of mask_and_scale (#3064) (#3065)
- open_geotiff: add coregister=True (mask_and_scale + reproject + match grid) (#3069) (#3070) (#3072)
- open_geotiff: pick reproject resampling by raster type under auto_reproject=True (#3067) (#3068)
- open_geotiff: rename mask_and_scale to unpack and add GPU / dask+GPU support (#3075)
Bug fixes and improvements
- polygonize: reject invalid return_type before running the computation (#3312)
- polygonize: document that column_name is also used for geojson output (#3309)
- kriging: keep template backend and variance name in the singular-matrix fallback (#3294)
- rasterize: drop non-finite-coordinate geometries and clamp row casts (#3310)
- merge: add GPU tests for mixed-type first/last ordering (#3296) (#3302)
- interpolate: add edge-case and parameter test coverage (#3300)
- polygonize: honor _xrspatial_no_georef in CRS detection (#3293) (#3308)
- polygonize: jit the float-chunk region ranges scan (#3303) (#3314)
- rasterize: burn each geometry once per pixel under merge='sum'/'count' (#3313)
- interpolate: cut redundant work in the backends (#3311)
- rasterize: widen the dtype annotation to the accepted forms (#3291) (#3297)
- kriging: name the singular-fallback variance '{name}_variance' (#3289)
- polygonize: cover the dask mask-rechunk path (#3299) (#3305)
- polygonize: raise on uint32 region overflow in the cupy integer backend (#3292) (#3301)
- interpolate: drop unused math import and fix isort drift (#3286) (#3287)
- docs: add a why-xarray-spatial page, retire raster_huh (#3249) (#3284)
- geotiff: fill pack=True nodata holes at the integer dtype's native width (#3277)
- ci: add a docs-build job (#3252)
- projection: read ITRF Helmert scale as ppm to match PROJ's data files (#3283)
- projection: reject non-WGS84 ellipsoids in the fast-path dispatch (#3282)
- projection: fix LAEA inverse rq normalization and authalic inverse series (#3280)
- reproject: upload shared coordinate arrays once per chunk in multi-band cupy (#3279)
- reproject: promote to the dask path when the output is large (#3278)
- geotiff: export VRTUnsupportedError, CloudSizeLimitError, PixelSafetyLimitError publicly (#3265) (#3273)
- geotiff: refuse pack=True values the packed integer dtype cannot hold (#3272)
- merge: keep the shared integer input dtype instead of promoting to float64 (#3271)
- geotiff: add gpu and dask+gpu legs to the pack=True feature tests (#3270)
- geotiff: correct the unpack docstring (masking still runs without scale/offset) (#3263) (#3269)
- geotiff: rewrap _attrs import in _writers/gpu.py to isort line_length=100 (#3259) (#3261)
- docs: replace the usage page with a runnable quickstart (#3249) (#3258)
- docs: rewrite the installation page (environments, extras, GPU and cloud notes) (#3257)
- dask: name compute tasks xrspatial. for readable task graphs (#3255)
- docs: fix stale Datashader claim and hardcoded links in getting-started (#3249) (#3251)
- geotiff: coregister=True reads COG overviews and fails fast on bad templates (#3254)
- geotiff: document coregister=True and register it as an experimental feature (#3248)
- geotiff: warn when coregister=True covers only a small fraction of the caller grid (#3247)
- proximity: replace the numpy line-sweep with an exact cKDTree query (#3245)
- projection: add dask end-to-end warning test, lock note on _crs_to_dict (#3242) (#3244)
- projection: silence the pyproj lossy-PROJ-string warning in param extractors (#3242) (#3243)
- geotiff: stream dask input through the GPU writer one tile-row band at a time (#3241)
- geotiff: fix to_geotiff(pack=True) crash on cupy and dask+cupy input (#3240)
- geotiff: keep float32 sources float32 through to_geotiff(pack=True) (#3080) (#3239)
- hotspots: raise instead of returning all zeros when the raster contains Inf (#3238)
- geotiff: defer the pack=True NaN guard into the dask graph so chunks compute once (#3237)
- geotiff: forward caller chunking to the coregister/auto_reproject output (#3236)
- visibility: add cupy backend, NaN, and Fresnel-blocked coverage (#3202)
- visibility: fix the cupy backend crash in cumulative_viewshed/visibility_frequency (#3205)
- zonal: add GPU backend coverage for regions and crosstab (#3203)
- focal: make the kernel memory guard dtype-aware (#3223) (#3232)
- focal: resolve apply() default func per backend (#3215) (#3224)
- focal: add edge-case and parameter tests from the test-coverage sweep (#3230)
- hotspots: fix the docstring backend list and kernel weight semantics (#3216) (#3227)
- clip_polygon: align dask+cupy mask chunks before map_blocks (#3186) (#3204)
- focal: make output dtype consistent across backends (#3226)
- clip_polygon: use the largest chunk for the rasterize mask on dask (#3191) (#3201)
- visibility: compute the dask source once in cumulative_viewshed (#3185) (#3199)
- polygon_clip: remove unused imports and fix import order (#3198)
- visibility: align output name and frequency_mhz hint with viewshed (#3183) (#3195)
- geotiff: fix to_geotiff dask+cupy crash in the CPU streaming writer (gpu=False) (#3171)
- clip_polygon: make nodata dtype consistent across backends (#3208)
- mcda: fix the owa() dask path and batch wpm() validation into one scheduler pass (#3158)
- hypsometric_integral: validate raster inputs (#3211)
- regions: vectorize the relabel loop (#3207) (#3212)
- mcda: fix cupy and dask backend failures in owa, standardize, and sensitivity (#3146) (#3157)
- mcda: fix API consistency (owa weights param, boolean_overlay Dataset input, ahp_weights docs, ConsistencyResult export) (#3148) (#3155)
- mcda: add cupy, dask, metadata, and edge-case test coverage (#3149) (#3156)
- mcda: guard monte_carlo dask materialization against OOM (#3145) (#3153)
- proximity: add coverage tests (int rasters, bounded GPU metrics, empty input) (#3139) (#3140)
- geotiff: add reader.unpack / writer.pack to SUPPORTED_FEATURES at experimental tier (#3172)
- focal: preserve input float dtype in mean() across all backends (#3214) (#3221)
- proximity: fix stale proximity/allocation/direction docstrings (#3091) (#3138)
- visibility: fix isort import-ordering (#3182) (#3189)
- geotiff: thread the nodata kwarg into _pack's NaN fill so pack=True holes match GDAL_NODATA (#3168) (#3174)
- mcda: remove unused warnings import, fix isort drift (#3143) (#3144)
- geotiff: rewrite stale per-band SCALE/OFFSET in to_geotiff(pack=True) after band-subset reads (#3175)
- geotiff: validate compression_level before GPU dispatch and pass it to fallback codecs (#3167) (#3176)
- vrt: place non-georeferenced tiles by explicit pixel offsets (#3116) (#3135)
- projection: document actual return types of vertical datum functions (#3097) (#3134)
- mcda: keep standardize piecewise/categorical on the device for cupy inputs (#3159)
- geotiff: fix the compression corpus fixture path so oracle tests actually run (#3164) (#3169)
- geotiff: warn when the GPU writer materializes dask input; scope streaming docs to the CPU path (#3173)
- focal: hoist the input cast out of the cupy focal_stats loop (#3231) (#3233)
- focal: make the memory guard backend-aware for dask input (#3218) (#3228)
- clip_polygon: add backend/edge-case/metadata test coverage (#3197) (#3206)
- docs: fix the README to_geotiff example that used the gated JPEG codec (#3162) (#3170)
- zonal: tighten crosstab zone_ids/cat_ids type hints and fix stale nodata docstring (#3196)
- mcda: preserve input attrs in constrain() (#3147) (#3154)
- reproject: keep integer dtype for empty chunks (#3096) (#3133)
- mcda: make monte_carlo sensitivity chunk-bounded on dask and drop constrain() deep copy (#3160)
- rasterize: drop two avoidable full-raster allocations in the backends (#3107) (#3131)
- proximity: fix bounded dask GREAT_CIRCLE missing antimeridian targets (#3108) (#3130)
- geotiff: mask 64-bit integer sentinels at native width in eager reads (#3098) (#3128)
- reproject: accept cupy and dask+cupy inputs in merge() (#3095) (#3125)
- geotiff: add xfail'd GPU and dask+GPU pack=True round-trip tests (#3114) (#3127)
- proximity: sort dask KDTree targets row-major so the tie-break matches numpy (#3090) (#3124)
- proximity: hoist the line-sweep kernel to module level to stop per-call recompiles (#3103) (#3126)
- rasterize: add all_touched-line and non-iterable-input coverage tests (#3120)
- rasterize: rename use_cuda to gpu with a deprecation shim (#3089) (#3122)
- geotiff: reject zero and non-finite SCALE/OFFSET under unpack=True; guard pack=True against divide-by-zero (#3104) (#3129)
- reproject: gate the CUDA fast path on WGS84-compatible datums (#3094) (#3119)
- geotiff: group streaming-write rows into bands so source chunks compute once (#3117) (#3136)
- reproject: skip the duplicate try_numba_transform probe in the numpy chunk worker (#3123)
- reproject: remove the obsolete strict xfail on the streaming 3-D test (#3100) (#3181)
- reproject: add tests for the streaming fallback path (#3101) (#3118)
- reproject: match streaming output dtype to the other backends (#3093) (#3111)
- geotiff: name unpack instead of the deprecated mask_and_scale alias in docs and errors (#3115)
- geotiff: keep regression tests and sweep state after #3109 landed the guard (#3088) (#3110)
- proximity: build dask coordinate grids with chunk-aligned broadcast (#3132) (#3137)
- rasterize: propagate GeoDataFrame CRS to output when like has none (#3087) (#3113)
- rasterize: reject non-finite burn values against integer and bool output dtypes (#3085) (#3109)
- reproject: remove unused import and dead local, fix import order in init.py (#3083) (#3092)
- geotiff: fix isort import-ordering drift (#3082) (#3084)
- merge: make the output-size guard backend-aware (#3048) (#3081)
- geotiff: restore the nodata sentinel for float rasters in to_geotiff(pack=True) (#3078) (#3079)
- projection: silence the pyproj PROJ-string warning in _get_datum_params (#3076) (#3077)
- resample: add ...
v0.10.6
New features
- emerging_hotspots: compute the Getis-Ord Gi* statistic (#3040)
- re-export open_geotiff and to_geotiff from top-level xrspatial (#3005) (#3006)
- add Cursor IDE support (.cursorrules and .cursor/rules/) (#3019)
Bug fixes and improvements
- reproject: fix flake8/isort style issues (#3052)
- reproject: parametrize dask+cupy parity over resampling modes (#3050) (#3051)
- reproject: make the output-size guard backend-aware (#3046) (#3047)
- contours: add a cross-backend NaN-input parity test (#3045)
- sweep: drop merge=union for state CSVs (#2754) (#3043)
- flow_path_dinf: fix a hang on cyclic D-inf flow directions (#2796) (#3042)
- viewshed: fix the broken bounds-check guard in _calculate_event_row_col() (#2793) (#3035)
- viewshed: validate observer_elev and target_elev before dispatch (#2794) (#3037)
- warnings: name the calling function in the unit-mismatch warning (#2782) (#3036)
- stream_link_mfd / stream_order_mfd: keep the dask assembly lazy (#2885) (#3039)
- remove dead datum-shift machinery (#2659) (#3038)
- polygonize: fix dask 8-connectivity float divergence at large rtol (#2677) (#3041)
- viewshed: validate regular grid spacing (#2789) (#3034)
- geotiff: reject to_geotiff(compression=None) with a clear TypeError (#2978) (#3029)
- geotiff: fix UnboundLocalError on restore_sentinel in plain-ndarray VRT tiled write (#2969) (#3028)
- contours: add tests for the all-NaN auto-levels RuntimeWarning regression (#2795) (#3009)
- geotiff: thread sidecar_origin through the HTTP/fsspec sidecar georef path (#3027)
- geotiff: remove stale external-overview skips on dask/fsspec/GPU golden-corpus backends (#3026)
- geotiff: route remote sidecar parse failures through the shared policy (#3022) (#3025)
- geotiff: size the streaming write budget from source chunks, not output tiles (#3008)
- contours: fix the border collar on integer dask input (#3020) (#3021)
- contours: fix return_type validation to fail fast on invalid values (#3010)
Contributors
- Thanks to @brendancol and @Melissari1997 for their contributions to this release.
v0.10.5
New features
- add Kilo CLI command definitions (#3003)
Bug fixes and improvements
- contours: add geometry deduplication (#2790) (#3001)
- geotiff: cover compress()/decompress() dispatcher error branches (#2995) (#2996)
- geotiff: fail closed on malformed GDAL_METADATA XML under mask_and_scale (#2999)
- geotiff: enforce tile_size positivity in array-level writers (#2997) (#3000)
- geotiff: reject mixed per-band SCALE/OFFSET under mask_and_scale (#2988) (#2993)
- geotiff: promote int to float on masked read regardless of sentinel hit (#2990) (#2994)
- geotiff: reject malformed SCALE/OFFSET under mask_and_scale (#2992)
- vrt: surface chunked decode-time hole gap under missing_sources='warn' (#2989) (#2991)
- geotiff: cover GPU writer degenerate shapes (1x1/1xN/Nx1) (#2986)
- geotiff: accumulate GPU overview mean in float64 for CPU byte parity (#2983) (#2985)
- accessor: surface standalone docstrings on .xrs methods; fix broken hydro delegations (#2982)
- geotiff: align direct backend mask_nodata default with open_geotiff (#2976) (#2980)
- vrt: privatize build_vrt; route to_geotiff's VRT path through it (#2979)
- geotiff: reject non-string compression at the to_geotiff boundary (#2975) (#2977)
- geotiff: cover degenerate-shape reads on dask and GPU backends (#2973)
- geotiff: fix flake8/isort style drift in tests and two source imports (#2970) (#2972)
- vrt: stop build_vrt fabricating a GeoTransform for non-georeferenced sources (#2966) (#2971)
- geotiff: clean up VRT staging dir on early validation failures (#2964) (#2968)
- geotiff: write VRT index atomically via temp-then-rename (#2965) (#2967)
- geotiff: align open_geotiff parameters with rioxarray's open_rasterio (#2961) (#2963)
- geotiff: consolidate public API on open_geotiff / to_geotiff (#2962)
Contributors
Thanks to @brendancol and @Melissari1997 for their contributions to this release.
v0.10.4
Bug fixes and improvements
- kriging: guard single-point input in the variogram (#2924)
- kriging: zero the matrix diagonal so the nugget is not placed on it (#2922)
- polygonize: cast the float mask to bool before combining it with nan_mask (#2623) (#2913)
- spline: upload invariant point/weight data to the GPU once on the dask+cupy path (#2929)
- kriging: skip the full-grid k0 memory term for dask templates (#2923) (#2926)
- kriging: fix flake8 E128 and isort drift in interpolate/_kriging.py (#2916) (#2918)
- geotiff: extract a shared GeoTIFF fixture builder (#2912)
- Add GPU and edge-case test coverage for spline() (#2927)
- Add kriging test coverage for dask+numpy variance, nlags, and edge cases (#2921) (#2928)
- Add GPU backend test coverage for idw() (#2925)
- Remove a flaky wall-clock visvalingam-whyatt regression test (#2914)
- docs: add a 1.0.0 stability policy and LTS commitment page (#2958)
New contributors
Full changelog: v0.10.3...v0.10.4
v0.10.3
New features
- focal: implement the Getis-Ord Gi* statistic in hotspots() (#2803)
Bug fixes and improvements
- slope: validate planar cell size (#2897) (#2901)
- contours: validate return_type before doing data work (#2891) (#2898)
- viewshed: build the GPU rtx mesh with real x/y resolution (#2861) (#2887)
- hydro: validate MFD fraction values in public consumers (#2873) (#2883)
- viewshed: warn and document dask Tier C divergence from the exact sweep (#2880)
- viewshed: validate max_distance, rejecting negative and non-finite values (#2874)
- contours: validate the n_levels contract (#2895) (#2899)
- proximity: fix bounded dask dropping in-range targets on irregular coords (#2908) (#2909)
- contours: drop zero-length geometry at exact-level corners (#2902)
- viewshed: stop mutating the caller's input raster (#2852) (#2871)
- proximity: fix bounded-dask crash on skinny rasters (#2877)
- contours: resolve output CRS the same way as polygonize (#2893) (#2900)
- geotiff: gate remote sources in read_geotiff_gpu under stable_only (#2867) (#2878)
- proximity: reject non-finite target_values (#2868)
- focal: reject non-binary kernels in apply() and focal_stats() (#2862)
- terrain: resolve geodesic coords from real lat/lon over numeric y/x (#2903)
- slope: set output units to degrees instead of leaking input units (#2904)
- proximity: reject non-monotonic 1D coords in proximity/allocation/direction (#2875)
- focal: fix hotspots() silent all-zeros on degenerate dask inputs (#2860)
- focal: raise a clear error for non-2D custom kernels (#2842) (#2853)
- aspect: warn on degree/meter unit mismatch in the planar path (#2839) (#2844)
- contours: fix docstring overstating the Dask peak-memory guarantee (#2906)
- viewshed: fix crash on NaN cells, marking them INVISIBLE (#2857) (#2876)
- proximity: stop the dask fallback mutating the caller's input chunks (#2847) (#2864)
- terrain: list accepted unit names in the invalid z_unit error (#2870)
- hydro: reject cyclic MFD fraction grids instead of returning wrong results or hanging (#2884)
- polygonize: replace the flaky visvalingam timing test with deterministic checks (#2889)
- proximity: make allocation/direction tie-breaking deterministic across backends (#2881)
- hydro: validate the MFD companion raster shape against the grid (#2863) (#2879)
- geotiff: gate stable_only before the bbox metadata read on remote/VRT sources (#2882)
- hydro: keep MFD dask output assembly lazy (#2865) (#2886)
- viewshed: call has_cuda_and_cupy() in has_rtx() so GPU dispatch gates correctly (#2849) (#2859)
- aspect: guard dask geodesic against single-chunk OOM (#2840) (#2846)
- aspect: fix .name leak on dask backends when name=None (#2845)
- slope: cover planar boundary modes on cupy and dask+cupy backends (#2832)
- slope: fix .name leak on dask backends when name=None (#2838)
- proximity: pin the dim-order error path and all-NaN raster across backends (#2836)
- aspect: cover the northness/eastness method='geodesic' path (#2829) (#2835)
- focal: fix GPU std/var precision on large-offset rasters (#2834)
- aspect: make the planar cupy backend match numpy in [0, 360) (#2827) (#2833)
- geotiff: enforce stable_only for HTTP/fsspec sources on non-VRT read paths (#2826)
- geotiff: synthesize x/y coords for no-georef VRT reads (#2824)
- geotiff: validate the gpu dispatch flag as bool and fail closed (#2819) (#2823)
- geotiff: correct the stable_only docstring to state HTTP/fsspec rejection (#2822)
- polygonize: optimize visvalingam_whyatt with a min-heap (#2817)
- aspect: fix the compass-direction table in the docstring (#2825) (#2828)
- proximity: raise on invalid distance_metric (#2814)
- focal: make hotspots() lazy for Dask input (#2802)
- aspect: apply cell-size correction to the planar path (#2760) (#2780)
- slope: warn on degree coords with meter elevation in the planar path (#2764) (#2777)
- focal: fix GPU variety undercount for kernels larger than 5x5 (#2800)
- aspect: add an independent rectangular-cell oracle for the planar path (#2781)
- proximity: validate negative max_distance (#2813)
- focal: preserve input float dtype in apply() and focal_stats() (#2769) (#2805)
- focal: validate the hotspots() kernel like apply() and focal_stats() (#2771) (#2799)
- terrain: warn when planar resolution averages irregular coordinates (#2766) (#2784)
- slope: propagate the NaN center cell in the planar path (#2761) (#2773)
- proximity: fix GREAT_CIRCLE on the CPU line-sweep backend (#2816)
- proximity: fix dask halo depth on irregular coords and 1xN/Nx1 rasters (#2815)
- contours: fix auto-levels silently dropping all contours with +/-inf (#2801)
- proximity: fix Great Circle docstrings to say meters, not kilometers (#2811)
- focal: validate focal_stats() stats_funcs before dispatch (#2798)
- slope: keep dask+cupy geodesic lat/lon lazy to avoid GPU OOM (#2776)
- aspect: skip the full-raster geodesic memory guard for dask (#2774)
- repo: add Codex command mirrors (#2806)
- terrain: make the geodesic memory guard backend-aware for dask (#2765) (#2779)
v0.10.2
New contributors
- @Melissari1997 made their first contribution in #2749
Bug fixes and improvements
- contours: handle infinite NaN coords (#2749)
- contours: validate input raster, reject complex dtype (#2711)
- contours: keep CRS on empty geopandas result (#2708)
- focal: pin result .name across backends in focal_stats and hotspots (#2735)
- focal: honour boundary on the cupy backend (#2736)
- focal: keep hotspots dask+cupy classification on the GPU (#2739)
- focal: align signatures across mean/apply/focal_stats/hotspots (#2699)
- aspect: keep lat/lon chunked in dask+cupy geodesic path (#2707)
- aspect: planar dask backends report float32 dtype matching computed data (#2741)
- slope: planar dask backends declare float32 meta dtype (#2694)
- slope: widen name type hint to Optional[str] for terrain-family parity (#2687)
- viewshed: consistent output name and dtype across backends (#2747)
- viewshed: guard degenerate-axis resolution in the CPU sweep (#2745)
- viewshed: size max_distance window per axis on anisotropic rasters (#2702)
- proximity: consistent output dtype and .name across allocation and direction backends (#2728)
- proximity: fix bounded GREAT_CIRCLE crash on dask backends (#2722)
- proximity: batch per-chunk-row compute in KDTree count pass (#2726)
- hydro: cap flow_path downstream walk to break cycles (#2718)
- hydro: stream_order_d8 accepts method alias; basins_d8 emits DeprecationWarning (#2716)
- resample: reject out-of-range integer nodata sentinels (#2671)
- resample: reject empty rasters with a clear ValueError (#2665)
- resample: reject irregular/non-monotonic spatial coordinates (#2667)
- resample: refresh nodata/nodatavals on identity fast path (#2670)
- resample: fix dask nearest/bilinear chunk-seam values on downsampling (#2627)
- reproject: make transform_precision=0 force exact pyproj transforms (#2654)
- reproject: account for datum shifts in output bounds estimation (#2655)
- reproject: disable numba fast path for non-WGS84 datums (#2657)
- reproject: filter footprint coordinate pairs by joint finite mask (#2652)
- reproject: fix per-band nodata masking for multi-band inputs (#2658)
- reproject: fix cupy backend divergence from numpy on pyproj-fallback CRS pairs (#2629)
- reproject: rename vertical CRS kwargs to source_/target_vertical_crs (#2626)
- zonal: raise clear ValueError for empty Dataset in stats() (#2642)
- zonal: count returns 0 for empty zones, other stats stay NaN (#2656)
- zonal: validate backend compatibility on the crosstab 3D path (#2653)
- zonal: validate backend compatibility for 3D apply() inputs (#2648)
- zonal: make apply() output .name deterministic across backends (#2622)
- zonal: fix int32 overflow in _strides() that corrupts stats/crosstab on huge rasters (#2617)
- crop: validate zones/values shapes match instead of returning nonsense (#2645)
- geotiff: forward allow_rotated/allow_invalid_nodata to VRT per-source reads (#2676)
- geotiff: make open_geotiff bbox= work for .vrt sources (#2674)
- geotiff: make VRT tiled writes atomic to avoid poisoned partial output (#2678)
- polygonize: batch dask per-chunk compute instead of serial loop (#2673)
- polygonize: batch dask compute per chunk row to recover parallelism (#2632)
- polygonize: make dask float output chunk-invariant for rtol (#2675)
- polygonize: keep diagonal-notch hole in dask 8-conn merge (#2633)
- polygonize: derive transform from x/y coords so geopandas CRS matches geometry (#2630)
v0.10.1
New features
- accessor: backend-aware .xrs.open_geotiff on DataArray and Dataset (#2598)
- geotiff: add bbox= to open_geotiff for geographic-space windowed reads (#2556)
Bug fixes and improvements
- rasterize: honor resolution= exactly when bounds don't divide evenly (#2597)
- reproject: fix vertical-datum shift crash on integer DEMs (#2596)
- resample: clamp dask interp overlap depth per axis (#2547) (#2599)
- zonal: fix trim() with NaN sentinel on numpy and cupy backends (#2559) (#2594)
- reproject: reject explicit out-of-range nodata for integer dtypes (#2591)
- zonal: fix cupy backend dropping all-NaN zones and desyncing zone_ids (#2562) (#2589)
- resample: refresh transform to match actual coord orientation (#2571) (#2587)
- rasterize: validate resolution= shape and element type (#2576) (#2586)
- resample: preserve integer precision in nodata mask comparison (#2570) (#2592)
- zonal: keep hypsometric_integral dask path fully lazy (#2563) (#2593)
- geotiff: accept projected GeoTIFFs that carry a base geographic CRS (#2603)
- polygonize: reject non-finite simplify_tolerance (#2575) (#2584)
- reproject: unit-aware bounds_policy="auto" blow-up detection (#2600)
- zonal: validate return_type at entry to stats() (#2558) (#2578)
- resample: validate target_resolution and scale_factor up front (#2574) (#2590)
- rasterize: support descending-x in like= templates (#2568) (#2595)
- rasterize: reject zig-zag duplicate-coord patterns in _check_uniform_axis (#2585)
- polygonize: fix dask chunk-dependent merging and DN corruption (#2601)
- zonal: make crop() return (0, 0) on all backends when no zone matches (#2580)
- rasterize: reject partial width/height overrides instead of silent ignore (#2579)
- zonal: fix crosstab cat_ids overcount when earlier categories are filtered out (#2560) (#2577)
- resample: preserve scalar non-dim coords and refresh stale nodata attr (#2542) (#2549)
- zonal: fix hypsometric_integral dask+cupy backend (#2525) (#2529)
- geotiff: include GB allocation estimate in max_pixels error message (#2554)
- zonal: raise on 'majority' in zonal_stats dask path instead of silent drop (#2528) (#2531)
- reproject: fix half-pixel offset in geoid and datum-shift grid lookups (#2508) (#2516)
- rasterize: reject NaN fill against integer dtype (#2504) (#2512)
- geotiff: reject distinct per-band nodatavals on write (#2514) (#2519)
- zonal: guard unbounded allocation in stats(return_type='xarray.DataArray') (#2523) (#2533)
- zonal: rechunk apply 3D dask output along stacked axis (#2526) (#2532)
- rasterize: hoist poly_props/poly_global cupy.asarray above all_touched (#2506) (#2510)
- polygonize: auto-detect attrs['transform'] for output georeferencing (#2536) (#2541)
- geotiff: reconcile COG write stability across registry, contract, and docstring (#2520)
- zonal: rename crop(zones_ids=) to crop(zone_ids=) with deprecation shim (#2521) (#2527)
- reproject: preserve integer dtype in dask+cupy fast path (#2505) (#2509)
- geotiff: scope max_pixels to the chunk when chunks= is supplied (#2501) (#2502)
v0.10.0
GeoTIFF release contract
Tiers what the public GeoTIFF and COG surface promises for this release.
xrspatial.geotiff.SUPPORTED_FEATURES is the source of truth; the bullets
below mirror it. See the reference page at docs/source/reference/geotiff.rst
for the per-tier semantics and the audit trail.
Stable
- Local-file GeoTIFF read (
open_geotiff) on the eager numpy backend, including windowed reads viawindow=. Covered by the window-read suite inxrspatial/geotiff/tests/. (epic #2340) - Dask reads (
read_geotiff_dask). Cross-backend parity against the eager numpy reader is gated bytest_backend_parity_matrix.pyandtest_backend_full_parity_2211.py. (epic #2341) - Local-file GeoTIFF write (
to_geotiff) on the CPU writer. (epic #2340) - Local COG read and write (
reader.local_cog,writer.cog) for axis-aligned 2D / 3D rasters with the lossless codecsnone,deflate,lzw,zstd,packbits, internal overviews only, and normal CRS / transform / dtype / nodata / band / pixel-is-area / pixel-is-point round-trip. Backed by the writer compliance suite (#2292), the cross-backend parity gate (#2293), and the per-tile byte-budget contract (#2294 / #2298). (epic #2286) - Lossless stable codecs:
none,deflate,lzw,zstd,packbits. Integer and float byte-for-byte round-trip. (epic #2340)
Advanced
- fsspec-routed reads (
reader.fsspec) and HTTPS reads (reader.http). The transport layer works but the redirect / retry / cache surface is not contracted at the stable bar. (epic #2344) - HTTP COG reads (
reader.http_cog). Range fetching, range coalescing, the SSRF / private-host filter, and the per-tile byte cap are pinned by the byte-budget contract (#2294 / #2298), but the broader transport surface is tracked separately for stable promotion. (epic #2344) - VRT reads (
reader.vrt). Limited to simple GDAL VRT mosaics over GeoTIFF sources that agree on CRS, transform orientation, pixel size, dtype, and band count. See the VRT support matrix indocs/source/reference/geotiff.rst. (epic #2342, PR #2321) - External
.tif.ovrsidecar reads (reader.sidecar_ovr). (epic #2340) - Writer overviews and BigTIFF (
writer.overviews,writer.bigtiff). (epic #2340) - BigTIFF COG writes (
writer.bigtiff_cog). The external-interop gate lives intest_bigtiff_cog_compliance_2286.py; promotion to stable follows the same release-cycle soak rule as the rest of the COG surface. (epic #2286)
Experimental
- GPU reads and writes (
reader.gpu,writer.gpu). Cross-backend numerical parity is not claimed at this tier. (epic #2341) - Experimental codecs:
lerc,jpeg2000,j2k,lz4. Requireallow_experimental_codecs=Trueon both read and write paths. Reader support across GDAL versions is uneven. (epic #2340) - Permissive read escape hatches:
allow_rotated=True(reader.allow_rotated) andallow_unparseable_crs=True(reader.allow_unparseable_crs). Both bypass a read-side check that the writer normally rejects. (epic #2340) - Rich-tag writes:
writer.gdal_metadata_xmlandwriter.extra_tags. Free-form payloads are written verbatim; interop with rasterio, libtiff, and GDAL depends on the payload. Gated byallow_experimental_codecs=Trueon writes from a fresh DataArray; round-tripped attrs from a previous read are exempt. (epic #2340)
Internal-only
codec.jpeg(JPEG-in-TIFF). The encoder writes self-contained JFIF tiles without the TIFF JPEGTables tag (347), so the on-disk output is not interoperable with libtiff, GDAL, or rasterio. Reads and writes require the dedicatedallow_internal_only_jpeg=Trueopt-in;allow_experimental_codecs=Truedoes not unlock this path. (epic #2340)
Unsupported for this release (these combinations fail closed: the writer or reader raises rather than silently producing a wrong result)
- Warped VRTs (
<VRTDataset subClass="VRTWarpedDataset">). (PR #2321) - Nested VRTs (a
<SourceFilename>pointing at another.vrt). (PR #2321) - VRT mosaics whose sources disagree on CRS, pixel size, dtype, or band count without an explicit opt-in. The default raises
MixedBandMetadataError. (PR #2321) to_geotiff(..., cog=True, tiled=False). COG output requires the tiled layout. (epic #2286)- Rotated transforms on the write path. The reader has an
allow_rotated=Trueescape hatch in the experimental tier; the writer has no equivalent. (epic #2340) - 1xN / Nx1 writes without an explicit
attrs['transform']. The writer used to borrow the non-degenerate axis's spacing and silently invent the wrong pixel size; the writer now raises. (epic #2340)
Bug fixes and improvements
- Move shapely from a required dependency to an optional
vectorextra. shapely (and GEOS) used to load on everyimport xrspatialbecauserasterize.pyimported it at module top level. It is now imported lazily, so a plainpip install xarray-spatialneither installs nor loads shapely. The only paths that need it are the vector-to-raster functionsrasterizeandpolygonize; installpip install xarray-spatial[vector]to use them. Callingrasterizewithout shapely raises a clear ImportError pointing at the extra. Follows the same pattern as the matplotlib change (#2494). (#2496) - Move matplotlib from a required dependency to an optional
plotextra. A plainpip install xarray-spatialno longer pulls in matplotlib (and its pillow / fonttools / kiwisolver / contourpy / cycler / pyparsing chain), since no compute path imports it -- every matplotlib use is a lazy import inside the.xrs.plotaccessor helpers. Installpip install xarray-spatial[plot]to use plotting; calling.xrs.plot()without it now raises a clear ImportError pointing at the extra instead of a bareModuleNotFoundError. pandas stays required because xarray depends on it and several core modules import it directly. (#2494) - Reconcile
xrspatial.geotiff.SUPPORTED_FEATURESwith the GeoTIFF release-contract tiering proposed in epic #2340. Addsreader.windowedatstable(covered by the existing window-read suite) andreader.daskatstable(covered by the cross-backend parity matrix intest_backend_parity_matrix.pyandtest_backend_full_parity_2211.py). Demotesreader.allow_rotatedandreader.allow_unparseable_crsfromadvancedtoexperimentalto match the epic's placement of permissive read-side escape hatches in the Experimental tier. A new shape test (test_supported_features_shape_2348.py) pins the structural invariants of the mapping (every entry carries a tier label; the tier set is closed at{stable, advanced, experimental, internal_only}; the dict literal contains no duplicate keys) so future drift fails CI. Runtime behaviour of every read and write path is unchanged; this is metadata-only. Callers gating on the exact string value of these two demoted entries will need to update their checks. (#2348) - Promote the local COG read and write paths to the
stabletier inxrspatial.geotiff.SUPPORTED_FEATURES.SUPPORTED_FEATURES['writer.cog']andSUPPORTED_FEATURES['reader.local_cog']now reportstable;reader.http_cogstaysadvancedwhile the HTTP transport surface is contracted separately. The stable COG contract covers axis-aligned 2D / 3D rasters, the CPU writer and CPU reader, the lossless codecs (none,deflate,lzw,zstd,packbits), internal overviews, and normal CRS / transform / dtype / nodata / band / pixel-is-area / pixel-is-point round-trip. GPU COG paths, experimental codecs, rotated transforms, external.tif.ovrsidecars, file-like destinations withcog=True, BigTIFF COG, and HTTP COG remain outside the contract. Backed by the writer compliance suite (#2292), the cross-backend parity gate (#2293), and the per-tile byte-budget contract (#2294 / #2298). The reference docs (docs/source/reference/geotiff.rst) and the COG overview notebook spell out the full contract. (#2300) - Resolve the GeoTIFF writer's
GeographicTypeGeoKey/ProjectedCSTypeGeoKeydecision via pyproj instead of an EPSG number range. The legacy heuristic (4326 + 4000-4999 -> geographic, else projected) silently mis-tagged geographic CRSes registered outside the 4000-4999 block (NAD83(2011) = 6318, GDA2020 = 7844, WGS 84 (G2139) = 9057, etc.) as projected and projected codes inside the block (4087 / 4088 / 4499) as geographic, corrupting the CRS at write time. The writer now callspyproj.CRS.from_epsg(...).is_geographic. When pyproj can't classify a code (uninstalled, or installed but the local PROJ database lacks the entry), the writer raises the newUnknownCRSModelTypeErrorrather than guessing -- a small vetted allowlist (4326, 4269, 4267, 4258, 4283, 4322, 4230, 4019, 4047) is still honoured for the pyproj-missing case.pyprojis now listed under thegeotiffextra. (#2277) - Shut down the per-tile compression
ThreadPoolExecutoron every exit path of the streaming tiled-write code into_geotiff. The old code only calledshutdown(wait=True)after the tile-row loop completed, so any mid-stream raise (compression failure, dask compute failure, file write failure) bypassed shutdown and leaked worker threads. The loop now runs insidetry/finallyand the finally callsshutdown(wait=True, cancel_futures=True)so queued tiles get dropped on the error path instead of blocking the unwind. The pool's workers carry anxrspatial-geotiff-tile-compressthread_name_prefixso leak-detection tests can tell them apart from dask's own offload/scheduler pools. (#2276) - Remove read-side emission of the 13 deprecated GeoTIFF attrs (
crs_name,geog_citation,datum_code,angular_units,semi_major_axis,inv_flattening,linear_units,projection_code,vertical_crs,vertical_citation,vertical_units,colormap_rgba,cmap) and bumpattrs['_xrspatial_geotiff_contract']from 1 to 2. Downstream code that read these viaattrs[key]now seesKeyError; migrate toattrs.get(key)or derive the value fromattrs['crs']/attrs['crs_wkt']...