rasterize: drop stale transform/res from like.attrs when grid is overridden#2253
Conversation
…ridden Closes #2251 When rasterize(geoms, like=template, ...) is called with an explicit bounds/width/height/resolution that reshapes the output relative to the template, the inherited attrs['res'] and attrs['transform'] from ``like`` describe the template's grid rather than the actual output. ``get_dataarray_resolution`` prefers attrs['res'] over computing from coords, so a stale res silently poisoned downstream slope/aspect/ proximity callers with the template's cellsize. Same class as the sky_view_factor cellsize bug. The fix strips res/transform from the propagated like.attrs when the output grid no longer matches the template (the existing reuse_like_coords flag already captures exactly this condition). Crs, spatial_ref, and other non-grid-shape attrs still propagate. When the output grid matches the template (no override), res and transform stay so chained pipelines see the unchanged spatial metadata. 9 new tests in TestLikeStaleGridAttrs2251 cover bounds / width-height / resolution overrides, matching-size keeps the attrs, get_dataarray_ resolution consistency end-to-end, and parity across numpy / cupy / dask+numpy / dask+cupy backends. State CSV updated to record the re-audit on 2026-05-21.
brendancol
left a comment
There was a problem hiding this comment.
PR Review: rasterize -- drop stale transform/res from like.attrs when grid is overridden
Summary
I read through the fix in xrspatial/rasterize.py lines 3216-3227. The guard if like_attrs is not None and not reuse_like_coords does the right thing: it drops res and transform exactly when the output grid was reshaped relative to like. Reusing the existing reuse_like_coords flag is the right call -- there's no need to invent a second predicate for the same condition.
I also checked one thing that worried me: out_attrs = like_attrs followed by pop could mutate the caller's template attrs if like_attrs were a live reference. It isn't. _extract_grid_from_like returns attrs=dict(like.attrs) at line 2846, so it's already a copy.
get_dataarray_resolution in xrspatial/utils.py:483 does prefer attrs.get('res') over calc_res, so the symptom the PR describes is real and not theoretical.
Blockers
None.
Suggestions
None.
Nits
xrspatial/rasterize.py:3225: one slightly over-eager edge case. If a caller passesbounds=that exactly matches the template's bounds (with matchingwidth/height),bounds_explicit=Truesoreuse_like_coordsisFalse, andres/transformget stripped even though the grid is effectively identical. That said, the rebuilt linspace coords may drift by a ulp or two, so the strip is still arguably correct. Not worth changing; maybe worth a sentence in the comment if someone touches this block again.
What looks good
- 9 tests, and they hit the three override paths plus the matching-size case plus the
get_dataarray_resolutionend-to-end symptom plus dask/cupy/dask+cupy parity. - The strip-case tests assert
crsstill propagates, which keeps anyone from later "fixing" this by stripping everything. - All four backends funnel through the same return statement, so one 2-line patch covers all of them.
- The inline comment at lines 3216-3224 explains the why, not the what. Future-me will appreciate that.
…asterize-2026-05-21
Closes #2251
Summary
rasterize(geoms, like=template, ...)is called with an explicitbounds,width/height, orresolutionthat reshapes the outputrelative to the template, the inherited
attrs['res']andattrs['transform']describe the template's grid, not the actualoutput.
get_dataarray_resolutionprefersattrs['res']overcomputing from coords, so downstream slope/aspect/proximity callers
silently used the wrong cellsize.
res/transformfrom the propagatedlike.attrswhenthe output grid no longer matches the template (the existing
reuse_like_coordsflag already captures exactly this condition).crs,spatial_ref, and the nodata triplet still propagate.TestLikeStaleGridAttrs2251cover bounds /width-height / resolution overrides, matching-size keeps the attrs,
get_dataarray_resolutionconsistency, and parity across numpy /cupy / dask+numpy / dask+cupy backends.
Test plan
TestLikeStaleGridAttrs2251pass (9/9, all 4 backends)TestMetadataPropagationtests still pass (13/13)test_rasterize.pysuite passes (224 passed, 2 skipped)test_rasterize_accuracy,test_rasterize_all_touched_supercover_2169,test_rasterize_coverage_2026_05_17,test_rasterize_gpu_race_2167,test_rasterize_tile_props_slice_2020: 153/153)test_zonal,test_polygon_clip,test_accessor:173/173)
Found by
/deep-sweepmetadata propagation audit (Cat 1, HIGH).