Skip to content

Commit

Permalink
added rewrite_coords accessor (#308)
Browse files Browse the repository at this point in the history
* added rewrite_coords accessor

* update tests

* update docstrings

* update docstrings

* update whats new

* update whats new
  • Loading branch information
larsbuntemeyer authored Dec 22, 2024
1 parent 196622e commit 95830ed
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 12 deletions.
88 changes: 78 additions & 10 deletions cordex/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# from .utils import _get_info, _guess_domain
from .tables import domains
from .domain import rewrite_coords

IDS = ["domain_id", "CORDEX_domain"]

Expand Down Expand Up @@ -91,12 +92,22 @@ def __init__(self, xarray_obj):

@property
def domain_id(self, guess=True):
"""Returns the domain_id.
"""
Returns the domain_id.
This property will return the ``CORDEX_domain`` or ``domain_id`` global
attribute if present. If none of those attributes are found, the
domain information will be guessed.
Parameters
----------
guess : bool, optional
If True, the domain information will be guessed if not found. Default is True.
Returns
-------
str
The domain_id.
"""
if self._domain_id is None:
self._domain_id = _get_domain_id(self._obj)
Expand All @@ -106,37 +117,44 @@ def domain_id(self, guess=True):

@property
def grid_mapping(self):
"""Returns the grid_mapping variable."""
"""
Returns the grid_mapping variable.
Returns
-------
xarray.DataArray
The grid_mapping variable from the xarray object.
"""
return self._obj.cf["grid_mapping"]

def info(self):
"""Return domain info in CORDEX format.
"""
Return domain info in CORDEX format.
The function returns a dictionary containing domain
information in the format of the CORDEX archive specifications.
Returns
-------
domain info : dict
dict
A dictionary that contains domain information.
"""
if self._info is None:
self._info = _get_info(self._obj)
return self._info

def guess(self):
"""Guess which domain this could be.
"""
Guess which domain this could be.
Compares the coordinate axis information to known the
Compares the coordinate axis information to known
coordinates of known CORDEX domains to guess the
``domain_id``.
Returns
-------
domain info : dict
A dictionary that contains domain information.
dict
A dictionary containing the guessed domain information.
"""
if self._guess is None:
self._guess = _guess_domain(self._obj)
Expand Down Expand Up @@ -209,6 +227,56 @@ def map(self, projection=None):
return ax
# ax.set_title(CORDEX_domain)

def rewrite_coords(
self,
coords="xy",
bounds=False,
domain_id=None,
mip_era="CMIP5",
method="nearest",
):
"""
Rewrite coordinates in a dataset.
This function ensures that the coordinates in a dataset are consistent and can be
compared to other datasets. It can reindex the dataset based on specified coordinates
or domain information while trying to keep the original coordinate attributes.
Parameters
----------
coords : str, optional
Specifies which coordinates to rewrite. Options are:
- "xy": Rewrite only the X and Y coordinates.
- "lonlat": Rewrite only the longitude and latitude coordinates.
- "all": Rewrite both X, Y, longitude, and latitude coordinates.
Default is "xy". If longitude and latitude coordinates are not present in the dataset, they will be added.
Rewriting longitude and latitude coordinates is only possible if the dataset contains a grid mapping variable.
bounds : bool, optional
If True, the function will also handle the bounds of the coordinates. If the dataset already has bounds,
they will be updated while preserving attributes and shape. If not, the bounds will be assigned.
domain_id : str, optional
The domain identifier used to obtain grid information. If not provided, the function will attempt
to use the domain_id attribute from the dataset.
mip_era : str, optional
The MIP era (e.g., "CMIP5", "CMIP6") used to determine coordinate attributes. Default is "CMIP5".
Only used if the dataset does not already contain coordinate attributes.
method : str, optional
The method used for reindexing the X and Y axis. Options include "nearest", "linear", etc. Default is "nearest".
Returns
-------
ds : xr.Dataset
The dataset with rewritten coordinates.
"""
return rewrite_coords(
self._obj,
coords=coords,
bounds=bounds,
domain_id=domain_id,
mip_era=mip_era,
method=method,
)


@xr.register_dataset_accessor("cx")
class CordexDatasetAccessor(CordexAccessor):
Expand Down
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,4 @@ Methods
Dataset.cx.info
Dataset.cx.guess
Dataset.cx.map
Dataset.cx.rewrite_coords
15 changes: 13 additions & 2 deletions docs/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,30 @@ What's new
v0.10.0 (Unreleased)
--------------------

New function :py:meth:`cordex.rewrite_coords` that rewrites coordinates (X and Y axes and transformed coordinates) in a dataset to correct
New function :py:meth:`rewrite_coords` that rewrites coordinates (X and Y axes and transformed coordinates) in a dataset to correct
rounding errors. This version drops python3.8 support.

New Features
~~~~~~~~~~~~

- New function :py:meth:`cordex.rewrite_coords` (:pull:`306`, :pull:`307`).
- New function :py:meth:`rewrite_coords` (:pull:`306`, :pull:`307`). The function is also available as accessor function.

Internal Changes
~~~~~~~~~~~~~~~~

- Update of API documentation in accessor (:pull:`308`).

Breaking Changes
~~~~~~~~~~~~~~~~

- Drop python3.8 support (:pull:`306`).

Deprecations
~~~~~~~~~~~~

- :py:meth:`preprocessing.replace_coords` is deprecated in favour of :py:meth:`rewrite_coords`.


v0.9.0 (18 November 2024)
-------------------------

Expand Down
23 changes: 23 additions & 0 deletions tests/test_accessor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
import xarray as xr
import numpy as np

import cordex as cx
from cordex.accessor import CordexDataArrayAccessor, CordexDatasetAccessor # noqa
Expand Down Expand Up @@ -33,3 +34,25 @@ def test_guess_info(domain_id):
def test_dataset_guess(filename):
ds = cx.tutorial.open_dataset(filename)
assert ds.cx.guess() == cx.domain_info(ds.cx.domain_id)


@pytest.mark.parametrize("domain_id", ["EUR-11", "EUR-44", "SAM-44", "AFR-22"])
def test_rewrite_coords(domain_id):
# Create a sample dataset
grid = cx.domain(domain_id)

# Create typical coordinate precision issue by adding random noise
rlon_noise = np.random.randn(*grid.rlon.shape) * np.finfo("float32").eps
rlat_noise = np.random.randn(*grid.rlat.shape) * np.finfo("float32").eps
# lon_noise = np.random.randn(*grid.lon.shape) * np.finfo("float32").eps
# lat_noise = np.random.randn(*grid.lat.shape) * np.finfo("float32").eps

# Call the rewrite_coords function for "xy" coordinates
grid_noise = grid.assign_coords(
rlon=grid.rlon + rlon_noise, rlat=grid.rlat + rlat_noise
)
rewritten_data = grid_noise.cx.rewrite_coords(coords="xy")

np.testing.assert_array_equal(rewritten_data.rlon, grid.rlon)
np.testing.assert_array_equal(rewritten_data.rlat, grid.rlat)
xr.testing.assert_identical(rewritten_data, grid)

0 comments on commit 95830ed

Please sign in to comment.