Propagate friction geo-attrs through least_cost_corridor (#3446)#3449
Merged
Conversation
The corridor is cd_a + cd_b, and each cost-distance surface carries its source raster's attrs. Source masks usually have no geo-attrs, so the xarray binary sum dropped res/crs/transform/nodatavals even when the friction surface had them. Re-emit friction's attrs and name on the output for the non-precomputed path; leave the precomputed path on its source-derived behaviour. Covers single, threshold, unreachable, and pairwise outputs across all four backends.
brendancol
commented
Jun 22, 2026
brendancol
left a comment
Contributor
Author
There was a problem hiding this comment.
PR Review: Propagate friction geo-attrs through least_cost_corridor (#3446)
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
None.
Nits (optional improvements)
- In the pairwise branch,
_apply_geo_metadatacalls.rename(geo_source.name)on each corridor, but the DataArray then goes intoxr.Dataset(corridors)keyed bycorridor_i_j, and the dict key wins as the variable name. So the rename is a no-op for the Dataset variables; only the attrs propagation does anything there. Harmless, just slightly redundant, not worth a change on its own.
What looks good
- The fix goes at the root cause: the corridor is
cd_a + cd_b, each surface carries its source raster's attrs, and the binary sum drops anything that differs between the two operands. Re-emitting friction's attrs and name is the right call, since friction is the raster that defines the grid. - The precomputed path is left alone, which is correct. There is no friction surface to draw from there, and
test_precomputed_keeps_source_attrs_not_frictionpins that behavior. _apply_geo_metadatacallsresult.copy()before touching attrs, so it does not mutate the input and keeps dask arrays lazy (checked: no compute triggered)..rename(None)yields a None name when friction is unnamed.- Test coverage is thorough: all four backends for the main and threshold paths, plus the unreachable all-NaN and pairwise paths. Full corridor suite passes (43 tests).
Checklist
- Algorithm matches reference/paper (N/A; metadata-only change, values untouched)
- All implemented backends produce consistent results (numpy/cupy/dask+numpy/dask+cupy verified live)
- NaN handling is correct (unreachable all-NaN path keeps attrs)
- Edge cases are covered by tests (unreachable, pairwise, precomputed)
- Dask chunk boundaries handled correctly (no neighborhood op; copy stays lazy)
- No premature materialization or unnecessary copies (.copy() is a shallow metadata copy)
- Benchmark exists or is not needed (not needed; no perf-sensitive change)
- README feature matrix updated (N/A; no new function or backend change)
- Docstrings present and accurate (helper docstring explains the root cause)
…orridor-2026-06-22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3446
least_cost_corridorbuilds the corridor ascd_a + cd_b, where each cost-distance surface carries the attrs of its source raster. Source rasters are usually plain marker masks with no geo-attrs, so xarray's binary sum droppedres/crs/transform/nodatavalsfrom the output even when the friction surface that defines the grid had them.Changes
_apply_geo_metadatato re-emit the friction surface's attrs and name on the corridor output..attrsand.nameare set; data, coords, dims, and dtype are untouched, and dask stays lazy.Backends
Verified live on numpy, cupy, dask+numpy, and dask+cupy (CUDA host).
Test plan
test_corridor_inherits_friction_geo_attrsacross all four backendstest_corridor_threshold_keeps_geo_attrsacross all four backendstest_corridor_unreachable_keeps_geo_attrstest_pairwise_inherits_friction_geo_attrstest_precomputed_keeps_source_attrs_not_frictionFound by the metadata propagation sweep (
/sweep-metadata).