|
23 | 23 | import matplotlib.pyplot as plt |
24 | 24 | import numpy as np |
25 | 25 | import xarray as xr |
| 26 | +from xarray.core import nputils |
26 | 27 |
|
27 | 28 | if TYPE_CHECKING: |
28 | 29 | # Avoid circular import at runtime: |
@@ -308,6 +309,73 @@ def apply_mask( |
308 | 309 | result = self._da.where(mask, drop=drop) |
309 | 310 |
|
310 | 311 | return self._wrap(result) |
| 312 | + |
| 313 | + def clip( |
| 314 | + self, |
| 315 | + bbox: tuple[float | int, float | int, float | int, float | int], |
| 316 | + ) -> Self: |
| 317 | + """ |
| 318 | + Clip variable defined on a NEMO model grid to specified |
| 319 | + longitude and latitude range. |
| 320 | +
|
| 321 | + Parameters |
| 322 | + ---------- |
| 323 | + bbox : tuple |
| 324 | + Bounding box in the form (lon_min, lon_max, lat_min, lat_max). |
| 325 | +
|
| 326 | + Returns |
| 327 | + ------- |
| 328 | + NEMODataArray |
| 329 | + Variable defined on a NEMO model grid clipped to bounding box. |
| 330 | +
|
| 331 | + Examples |
| 332 | + -------- |
| 333 | + Clip sea surface temperature `tos_con` defined on T-points in a NEMO |
| 334 | + model parent domain in the bounding box (-40°E, 10°E, 35°N, 60°N): |
| 335 | +
|
| 336 | + >>> nemo['gridT/tos_con'].clip(bbox=(-40, 10, 35, 60)) |
| 337 | +
|
| 338 | + See Also |
| 339 | + -------- |
| 340 | + sel_like |
| 341 | + """ |
| 342 | + # -- Validate Inputs -- # |
| 343 | + if not isinstance(bbox, tuple) or len(bbox) != 4: |
| 344 | + raise ValueError( |
| 345 | + "bounding box must be a tuple (lon_min, lon_max, lat_min, lat_max)." |
| 346 | + ) |
| 347 | + |
| 348 | + # -- Clip data to bounding box & return NEMODataArray -- # |
| 349 | + # Define longitude & latitude coordinates of NEMO model grid: |
| 350 | + glam = self[f"{self._dom_prefix}glam{self._grid_suffix}"] |
| 351 | + gphi = self[f"{self._dom_prefix}gphi{self._grid_suffix}"] |
| 352 | + |
| 353 | + # Define bbox mask: |
| 354 | + mask = ( |
| 355 | + (glam >= bbox[0]) |
| 356 | + & (glam <= bbox[1]) |
| 357 | + & (gphi >= bbox[2]) |
| 358 | + & (gphi <= bbox[3]) |
| 359 | + ) |
| 360 | + |
| 361 | + # Find rows/columns containing at least one valid grid point: |
| 362 | + rows = mask.any(dim=self.i_name) |
| 363 | + cols = mask.any(dim=self.j_name) |
| 364 | + j_idx = np.where(rows.compute())[0] |
| 365 | + i_idx = np.where(cols.compute())[0] |
| 366 | + |
| 367 | + if len(j_idx) == 0 or len(i_idx) == 0: |
| 368 | + raise ValueError(f"No {self._grid[-1]}-grid points found inside specified bbox.") |
| 369 | + |
| 370 | + # Subset NEMODataArray data within bounding box: |
| 371 | + result = (self |
| 372 | + .where(mask, drop=False) |
| 373 | + .isel({self.j_name: slice(j_idx.min(), j_idx.max() + 1), |
| 374 | + self.i_name: slice(i_idx.min(), i_idx.max() + 1), |
| 375 | + }) |
| 376 | + ) |
| 377 | + |
| 378 | + return result |
311 | 379 |
|
312 | 380 | def sel_like( |
313 | 381 | self, |
@@ -1369,6 +1437,33 @@ def __truediv__(self, other: Self | xr.DataArray | int | float) -> Self: |
1369 | 1437 | def __rtruediv__(self, other: Self | xr.DataArray | int | float) -> Self: |
1370 | 1438 | return self._rbinary_op(other, operator.truediv) |
1371 | 1439 |
|
| 1440 | + def __and__(self, other: Self | xr.DataArray) -> Self: |
| 1441 | + return self._binary_op(other, operator.and_) |
| 1442 | + |
| 1443 | + def __xor__(self, other: Self | xr.DataArray) -> Self: |
| 1444 | + return self._binary_op(other, operator.xor) |
| 1445 | + |
| 1446 | + def __or__(self, other: Self | xr.DataArray) -> Self: |
| 1447 | + return self._binary_op(other, operator.or_) |
| 1448 | + |
| 1449 | + def __lt__(self, other: Self | xr.DataArray | int | float) -> Self: |
| 1450 | + return self._binary_op(other, operator.lt) |
| 1451 | + |
| 1452 | + def __le__(self, other: Self | xr.DataArray | int | float) -> Self: |
| 1453 | + return self._binary_op(other, operator.le) |
| 1454 | + |
| 1455 | + def __gt__(self, other: Self | xr.DataArray | int | float) -> Self: |
| 1456 | + return self._binary_op(other, operator.gt) |
| 1457 | + |
| 1458 | + def __ge__(self, other: Self | xr.DataArray | int | float) -> Self: |
| 1459 | + return self._binary_op(other, operator.ge) |
| 1460 | + |
| 1461 | + def __eq__(self, other: Self | xr.DataArray) -> Self: |
| 1462 | + return self._binary_op(other, nputils.array_eq) |
| 1463 | + |
| 1464 | + def __ne__(self, other: Self | xr.DataArray) -> Self: |
| 1465 | + return self._binary_op(other, nputils.array_ne) |
| 1466 | + |
1372 | 1467 | # ---------------- |
1373 | 1468 | # Utility Methods |
1374 | 1469 | # ---------------- |
|
0 commit comments