Skip to content

Commit 52105da

Browse files
authored
Add .xrs.validate() to check a raster against the xarray-spatial contract (#3486)
* Add .xrs.validate() contract compliance check (#3485) * Document validate in utilities reference and README matrix (#3485) * Address review: guard geographic check on bare dims, align even-spacing step (#3485)
1 parent 33f6232 commit 52105da

7 files changed

Lines changed: 926 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ Built-in Numba JIT and CUDA projection kernels bypass pyproj for per-pixel coord
266266
| [rechunk_no_shuffle](xrspatial/utils.py) | Rechunk dask arrays using whole-chunk multiples (no shuffle) | Custom | 🔼 | 🔼 | 🔼 | 🔼 |
267267
| [fused_overlap](xrspatial/utils.py) | Fuse sequential map_overlap calls into a single pass | Custom | 🔼 | 🔼 | 🔼 | 🔼 |
268268
| [multi_overlap](xrspatial/utils.py) | Run multi-output kernel in a single overlap pass | Custom | 🔼 | 🔼 | 🔼 | 🔼 |
269+
| [validate](xrspatial/validate.py) | Check a raster against the xarray-spatial input contract (`.xrs.validate()`) | Custom || 🔼 | 🔼 | 🔼 |
269270

270271
-----------
271272

docs/source/reference/utilities.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,13 @@ Diagnostics
7575
:toctree: _autosummary
7676

7777
xrspatial.diagnostics.diagnose
78+
79+
Validation
80+
==========
81+
Check a raster against the xarray-spatial input contract. Also available
82+
on the accessor as ``da.xrs.validate()`` and ``ds.xrs.validate()``.
83+
84+
.. autosummary::
85+
:toctree: _autosummary
86+
87+
xrspatial.validate.validate

xrspatial/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
from xrspatial.terrain_metrics import roughness # noqa
120120
from xrspatial.terrain_metrics import tpi # noqa
121121
from xrspatial.terrain_metrics import tri # noqa
122+
from xrspatial.validate import validate # noqa
122123
from xrspatial.hydro import twi # noqa: unified wrapper
123124
from xrspatial.hydro import twi_d8 # noqa
124125
from xrspatial.polygon_clip import clip_polygon # noqa

xrspatial/accessor.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,30 @@ def multi_overlap(self, func, n_outputs, **kwargs):
15051505
from .utils import multi_overlap
15061506
return multi_overlap(self._obj, func, n_outputs, **kwargs)
15071507

1508+
# ---- Diagnostics ----
1509+
1510+
def validate(self, *, raise_on_error=False):
1511+
"""Check this DataArray against the xarray-spatial input contract.
1512+
1513+
Returns a :class:`~xrspatial.validate.ValidationReport` listing
1514+
every contract violation as an error (a spatial op will fail) or
1515+
a warning (behavior degrades), each with a suggested fix. The
1516+
report is truthy when there are no error-level issues, so
1517+
``if not da.xrs.validate(): ...`` reads naturally.
1518+
1519+
Parameters
1520+
----------
1521+
raise_on_error : bool, default False
1522+
If True, raise :class:`~xrspatial.validate.XrsContractError`
1523+
when any error-level issue is found instead of only
1524+
recording it in the report.
1525+
"""
1526+
from .validate import validate
1527+
report = validate(self._obj)
1528+
if raise_on_error:
1529+
report.raise_if_errors()
1530+
return report
1531+
15081532

15091533
@xr.register_dataset_accessor("xrs")
15101534
class XrsSpatialDatasetAccessor:
@@ -2116,6 +2140,28 @@ def rechunk_no_shuffle(self, **kwargs):
21162140
from .utils import rechunk_no_shuffle
21172141
return rechunk_no_shuffle(self._obj, **kwargs)
21182142

2143+
# ---- Diagnostics ----
2144+
2145+
def validate(self, *, raise_on_error=False):
2146+
"""Check each data variable against the xarray-spatial contract.
2147+
2148+
Returns a
2149+
:class:`~xrspatial.validate.DatasetValidationReport` holding a
2150+
per-variable :class:`~xrspatial.validate.ValidationReport`. The
2151+
report is truthy when every variable is compliant.
2152+
2153+
Parameters
2154+
----------
2155+
raise_on_error : bool, default False
2156+
If True, raise :class:`~xrspatial.validate.XrsContractError`
2157+
when any variable has an error-level issue.
2158+
"""
2159+
from .validate import validate_dataset
2160+
report = validate_dataset(self._obj)
2161+
if raise_on_error:
2162+
report.raise_if_errors()
2163+
return report
2164+
21192165

21202166
# ---------------------------------------------------------------------------
21212167
# Surface standalone-function docstrings on accessor methods so that, e.g.,

xrspatial/tests/test_accessor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def test_dataarray_accessor_has_expected_methods(elevation):
100100
'rechunk_no_shuffle',
101101
'fused_overlap',
102102
'multi_overlap',
103+
'validate',
103104
]
104105
for name in expected:
105106
assert name in names, f"Missing method: {name}"
@@ -118,6 +119,7 @@ def test_dataset_accessor_has_expected_methods():
118119
'proximity', 'allocation', 'direction', 'cost_distance',
119120
'ndvi', 'evi', 'arvi', 'savi', 'nbr', 'sipi',
120121
'rasterize',
122+
'validate',
121123
]
122124
for name in expected:
123125
assert name in names, f"Missing method: {name}"

0 commit comments

Comments
 (0)