Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
5b0af86
added grid_mapping attribute
larsbuntemeyer Jan 22, 2023
015f19e
added get_grid_mapping_name function
larsbuntemeyer Jan 22, 2023
9192470
updated rotds with grid_mapping
larsbuntemeyer Jan 23, 2023
3f72bdc
renamed dummy variable
larsbuntemeyer Jan 23, 2023
9ae838a
updated grid mapping scalar
larsbuntemeyer Jan 23, 2023
252f8bf
update repr tests and rotds dims
larsbuntemeyer Jan 23, 2023
19224af
api docs update
larsbuntemeyer Jan 23, 2023
400e3dd
updated examples in grid_mappings docstring
larsbuntemeyer Jan 23, 2023
ab08961
added references
larsbuntemeyer Jan 23, 2023
824b929
remove zero dim from rotds
larsbuntemeyer Jan 23, 2023
f165325
format fix
larsbuntemeyer Jan 23, 2023
ea53b0b
added basic grid mappings section in docs
larsbuntemeyer Jan 23, 2023
b9cd3fe
update section
larsbuntemeyer Jan 23, 2023
2f09483
update docs section
larsbuntemeyer Jan 23, 2023
82d7b7c
added grid_mappgin to associated variables
larsbuntemeyer Jan 24, 2023
b0f796f
added grid_mapping accessor tests
larsbuntemeyer Jan 24, 2023
88d8c95
updated docs section
larsbuntemeyer Jan 24, 2023
6f03e4b
syntax fix
larsbuntemeyer Jan 24, 2023
37f27b8
eval-rst
larsbuntemeyer Jan 24, 2023
2615635
typo
larsbuntemeyer Jan 24, 2023
e2b18a6
fix eval-rst
larsbuntemeyer Jan 24, 2023
576f31c
polish
larsbuntemeyer Jan 24, 2023
a960700
Update cf_xarray/accessor.py
larsbuntemeyer Jan 24, 2023
8df4f0d
Update cf_xarray/accessor.py
larsbuntemeyer Jan 24, 2023
e789cce
Update cf_xarray/accessor.py
larsbuntemeyer Jan 24, 2023
e4b60bd
Update cf_xarray/accessor.py
larsbuntemeyer Jan 24, 2023
1f5fda7
Update doc/grid_mappings.md
larsbuntemeyer Jan 25, 2023
c13e7ba
updated see also sections
larsbuntemeyer Jan 25, 2023
0811b29
added warning tests
larsbuntemeyer Jan 25, 2023
dccb7f4
extract warning tests
larsbuntemeyer Jan 25, 2023
182a54c
remove skip_grid_mappings
larsbuntemeyer Jan 27, 2023
6d982ce
added dataarray grid_mapping accessor
larsbuntemeyer Jan 27, 2023
903bb9e
added dataarray grid_mapping_name
larsbuntemeyer Feb 3, 2023
aeb20be
docstrings
larsbuntemeyer Feb 3, 2023
d17d7f7
docs updates
larsbuntemeyer Feb 3, 2023
4df3c04
merge upstream/main
larsbuntemeyer Feb 3, 2023
2d0d353
fix
larsbuntemeyer Feb 3, 2023
ea45eda
skip grid_mapping coords repr
larsbuntemeyer Feb 3, 2023
38e61c7
cf.grid_mappings switchted logic
larsbuntemeyer Feb 3, 2023
0cddee4
update grid_mapping accessor logic
larsbuntemeyer Feb 3, 2023
79ebe4f
test updates
larsbuntemeyer Feb 3, 2023
bacd42a
Merge remote-tracking branch 'upstream/main' into grid_mapping
larsbuntemeyer Feb 6, 2023
bf532e5
revert changes
larsbuntemeyer Feb 6, 2023
423b9f6
fixed grid_mapping key critera
larsbuntemeyer Feb 6, 2023
49ea7b2
adapted rotds dim names
larsbuntemeyer Feb 6, 2023
ead0928
small cleanup
dcherian Feb 6, 2023
daed53e
Add "grid_mapping" as special name
dcherian Feb 6, 2023
17e0724
Swtich to .cf.grid_mapping_names
dcherian Feb 6, 2023
9f61859
Avoid DataArray.cf.grid_mapping
dcherian Feb 6, 2023
8dc1293
Avoid misleading n/a grid mapping in DataArray repr
dcherian Feb 6, 2023
4fdb192
Fix repr
dcherian Feb 6, 2023
147544b
Merge branch 'main' into grid_mapping
dcherian Feb 6, 2023
d487330
Fix typing
dcherian Feb 6, 2023
301809f
Update docs.
dcherian Feb 6, 2023
fcddfac
fix cartopy cell
dcherian Feb 6, 2023
19945f5
fix typing
dcherian Feb 6, 2023
1807e51
Add to quickstart too
dcherian Feb 6, 2023
06f6692
docs udpates
larsbuntemeyer Feb 8, 2023
998e895
Merge branch 'main' into grid_mapping
dcherian Feb 8, 2023
21e9188
Add whats-new note.
dcherian Feb 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 121 additions & 2 deletions cf_xarray/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,32 @@ def _get_bounds(obj: DataArray | Dataset, key: str) -> list[str]:
return list(results)


def _get_grid_mapping(obj: DataArray | Dataset, key: str) -> list[str]:
"""
Translate from key (either CF key or variable name) to its grid mapping variable name.
This function interprets the ``grid_mapping`` attribute on DataArrays.

Parameters
----------
obj : DataArray, Dataset
DataArray belonging to the coordinate to be checked
key : str
key to check for.

Returns
-------
List[str], Variable name(s) in parent xarray object that is grid mapping of `key`
"""

results = set()
for var in apply_mapper(_get_all, obj, key, error=False, default=[key]):
attrs_or_encoding = ChainMap(obj[var].attrs, obj[var].encoding)
if "grid_mapping" in attrs_or_encoding:
results |= {attrs_or_encoding["grid_mapping"]}

return list(results)


def _get_with_standard_name(
obj: DataArray | Dataset, name: str | list[str]
) -> list[str]:
Expand Down Expand Up @@ -1371,6 +1397,7 @@ def make_text_section(subtitle, attr, valid_values=None, default_keys=None):
)
text += make_text_section("Standard Names", "standard_names", coords)
text += make_text_section("Bounds", "bounds", coords)
text += make_text_section("Grid Mappings", "grid_mappings", coords)
if isinstance(self._obj, Dataset):
data_vars = self._obj.data_vars
text += "\nData Variables:"
Expand All @@ -1379,6 +1406,7 @@ def make_text_section(subtitle, attr, valid_values=None, default_keys=None):
)
text += make_text_section("Standard Names", "standard_names", data_vars)
text += make_text_section("Bounds", "bounds", data_vars)
text += make_text_section("Grid Mappings", "grid_mappings", data_vars)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make sense for Data Variables too?


return text

Expand Down Expand Up @@ -1579,6 +1607,7 @@ def get_associated_variable_names(
2. "bounds"
3. "cell_measures"
4. "coordinates"
5. "grid_mapping"
to a list of variable names referred to in the appropriate attribute

Parameters
Expand All @@ -1593,10 +1622,15 @@ def get_associated_variable_names(
names : dict
Dictionary with keys "ancillary_variables", "cell_measures", "coordinates", "bounds".
"""
keys = ["ancillary_variables", "cell_measures", "coordinates", "bounds"]
keys = [
"ancillary_variables",
"cell_measures",
"coordinates",
"bounds",
"grid_mapping",
]
coords: dict[str, list[str]] = {k: [] for k in keys}
attrs_or_encoding = ChainMap(self._obj[name].attrs, self._obj[name].encoding)

coordinates = attrs_or_encoding.get("coordinates", None)
# Handles case where the coordinates attribute is None
# This is used to tell xarray to not write a coordinates attribute
Expand Down Expand Up @@ -1635,6 +1669,9 @@ def get_associated_variable_names(
if dbounds:
coords["bounds"].append(dbounds)

if "grid_mapping" in attrs_or_encoding:
coords["grid_mapping"] = [attrs_or_encoding["grid_mapping"]]

allvars = itertools.chain(*coords.values())
missing = set(allvars) - set(self._maybe_to_dataset().variables)
if missing:
Expand Down Expand Up @@ -2335,6 +2372,88 @@ def bounds_to_vertices(
)
return obj

@property
def grid_mappings(self) -> dict[str, list[str]]:
"""
Property that returns a dictionary mapping keys
to the variable names of their grid_mapping.

Returns
-------
dict
Dictionary mapping keys to the variable names of their grid_mapping.

See Also
--------
Dataset.cf.get_grid_mapping_name

References
----------
Please refer to the CF conventions document : https://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#grid-mappings-and-projections

For a list of valid grid_mapping names, refer to: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#appendix-grid-mappings

Examples
--------
>>> from cf_xarray.datasets import rotds
>>> rotds.cf.grid_mappings
{'air_temperature': ['rotated_pole'], 'temp': ['rotated_pole']}
"""

obj = self._obj
keys = self.keys() | set(obj.variables)

vardict = {
key: self._drop_missing_variables(
apply_mapper(_get_grid_mapping, obj, key, error=False)
)
for key in keys
}

return {k: sorted(v) for k, v in vardict.items() if v}

def get_grid_mapping(self, key: str) -> DataArray | Dataset:
"""
Get grid_mapping variable corresponding to key.

Parameters
----------
key : str
Name of variable whose grid_mapping is desired

Returns
-------
DataArray
"""

results = self.grid_mappings.get(key, [])
if not results:
raise KeyError(f"No results found for {key!r}.")

return self._obj[results[0] if len(results) == 1 else results]

def get_grid_mapping_name(self, key: str) -> str:
"""
Get name of the grid_mapping for variable corresponding to key.

Parameters
----------
key : str
Name of variable whose grid_mapping name is desired.

Returns
-------
str
"""
grid_mapping = self.get_grid_mapping(key)
grid_mapping_name = grid_mapping.attrs.get("grid_mapping_name", "")
if not grid_mapping_name:
raise KeyError(
f"Missing grid_mapping_name attribute for {grid_mapping.name!r}."
)

return grid_mapping_name

def decode_vertical_coords(self, *, outnames=None, prefix=None):
"""
Decode parameterized vertical coordinates in place.
Expand Down
63 changes: 57 additions & 6 deletions cf_xarray/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ def _create_mollw_ds():
def _create_inexact_bounds():
# Dataset that creates rotated pole curvilinear coordinates with CF bounds in
# counterclockwise order that have precision issues.
# dataset created using: https://gist.github.com/larsbuntemeyer/105d83c1eb39b1462150d3fabca0b66b
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. In a future PR, it would be nice to add this as a "demo" notebook which creates a datasets, and illustrates creation of bounds/ extracting grid_mapping etc.

rlon = np.array([17.935, 18.045, 18.155])
rlat = np.array([21.615, 21.725, 21.835])

lon = np.array(
[
[64.21746363939087, 64.42305921561967, 64.62774455060337],
Expand Down Expand Up @@ -293,23 +297,70 @@ def _create_inexact_bounds():

rotated = xr.Dataset(
coords=dict(
rlon=xr.DataArray(
rlon,
dims="rlon",
attrs={
"units": "degrees",
"axis": "X",
"standard_name": "grid_longitude",
},
),
rlat=xr.DataArray(
rlat,
dims="rlat",
attrs={
"units": "degrees",
"axis": "Y",
"standard_name": "grid_latitude",
},
),
lon=xr.DataArray(
lon,
dims=("x", "y"),
attrs={"units": "degrees_east", "bounds": "lon_bounds"},
dims=("rlon", "rlat"),
attrs={
"units": "degrees_east",
"bounds": "lon_bounds",
"standard_name": "longitude",
},
),
lat=xr.DataArray(
lat,
dims=("x", "y"),
attrs={"units": "degrees_north", "bounds": "lat_bounds"},
dims=("rlon", "rlat"),
attrs={
"units": "degrees_north",
"bounds": "lat_bounds",
"standard_name": "latitude",
},
),
),
data_vars=dict(
lon_bounds=xr.DataArray(
lon_bounds, dims=("bounds", "x", "y"), attrs={"units": "degrees_east"}
lon_bounds,
dims=("bounds", "rlon", "rlat"),
attrs={"units": "degrees_east"},
),
lat_bounds=xr.DataArray(
lat_bounds, dims=("bounds", "x", "y"), attrs={"units": "degrees_north"}
lat_bounds,
dims=("bounds", "rlon", "rlat"),
attrs={"units": "degrees_north"},
),
rotated_pole=xr.DataArray(
np.zeros((), dtype=np.int32),
dims=None,
attrs={
"grid_mapping_name": "rotated_latitude_longitude",
"grid_north_pole_latitude": 39.25,
"grid_north_pole_longitude": -162.0,
},
),
temp=xr.DataArray(
np.random.rand(3, 3),
dims=("rlat", "rlon"),
attrs={
"standard_name": "air_temperature",
"grid_mapping": "rotated_pole",
},
),
),
)
Expand Down
54 changes: 53 additions & 1 deletion cf_xarray/tests/test_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@ def test_repr():

- Bounds: n/a

- Grid Mappings: n/a

Data Variables:
- Cell Measures: area, volume: n/a

- Standard Names: air_temperature: ['air']

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual == dedent(expected)

Expand All @@ -105,6 +109,8 @@ def test_repr():
* time: ['time']

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual == dedent(expected)

Expand All @@ -126,13 +132,17 @@ def test_repr():

- Bounds: n/a

- Grid Mappings: n/a

Data Variables:
- Cell Measures: area, volume: n/a

- Standard Names: sea_water_potential_temperature: ['TEMP']
sea_water_x_velocity: ['UVEL']

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual == dedent(expected)

Expand Down Expand Up @@ -162,12 +172,16 @@ def test_repr():

- Bounds: n/a

- Grid Mappings: n/a

Data Variables:
- Cell Measures: area, volume: n/a

- Standard Names: air_temperature: [<this-array>]

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual == dedent(expected)

Expand All @@ -188,12 +202,16 @@ def test_repr():

- Bounds: n/a

- Grid Mappings: n/a

Data Variables:
- Cell Measures: area, volume: n/a

- Standard Names: n/a

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual == dedent(expected)

Expand Down Expand Up @@ -259,6 +277,8 @@ def test_cell_measures():
foo_std_name: ['foo']

- Bounds: n/a

- Grid Mappings: n/a
"""
assert actual.endswith(dedent(expected))

Expand Down Expand Up @@ -822,7 +842,7 @@ def test_add_bounds_nd_variable():
# 2D rotated ds
lon_bounds = (
rotds.drop_vars(["lon_bounds"])
.assign(x=rotds["x"], y=rotds["y"])
.assign(x=rotds["rlon"], y=rotds["rlat"])
.cf.add_bounds(["lon"])
.lon_bounds
)
Expand Down Expand Up @@ -935,6 +955,38 @@ def test_get_bounds_dim_name():
assert mollwds.cf.get_bounds_dim_name("lon") == "bounds"


def test_grid_mappings():
ds = rotds # .copy(deep=False)

actual = ds.cf.grid_mappings
expected = {"air_temperature": ["rotated_pole"], "temp": ["rotated_pole"]}
assert ds.cf.grid_mappings == expected

actual = ds.cf.get_grid_mapping("temp")
expected = ds.cf["rotated_pole"]
assert_identical(actual, expected)

actual = ds.cf[["temp"]]
assert "rotated_pole" in actual.coords

# Dataset has bounds
expected = """\
- Grid Mappings: air_temperature: ['rotated_pole']
temp: ['rotated_pole']
"""
assert dedent(expected) in ds.cf.__repr__()

# DataArray
# propagation does not work yet
# actual = ds.cf["temp"].cf.__repr__()
# assert actual == expected


def test_get_grid_mapping_name():
ds = rotds
assert ds.cf.get_grid_mapping_name("temp") == "rotated_latitude_longitude"


def test_docstring():
assert "One of ('X'" in airds.cf.groupby.__doc__
assert "Time variable accessor e.g. 'T.month'" in airds.cf.groupby.__doc__
Expand Down
Loading