Skip to content

Commit 711e3fc

Browse files
committed
Merge branch 'main' into sgrid
* main: Add rich repr (xarray-contrib#409) add `cf.grid_mapping_names` (xarray-contrib#391) Update CF standard name table v80 (xarray-contrib#423)
2 parents f034126 + 1d8fe9d commit 711e3fc

18 files changed

+2369
-1317
lines changed

cf_xarray/accessor.py

Lines changed: 212 additions & 77 deletions
Large diffs are not rendered by default.

cf_xarray/criteria.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
Copyright (c) 2017 MetPy Developers.
55
"""
66

7+
try:
8+
import regex as re
9+
except ImportError:
10+
import re # type: ignore
711

8-
import re
9-
from typing import Mapping, MutableMapping, Tuple
12+
from typing import Any, Mapping, MutableMapping, Tuple
1013

1114
cf_role_criteria: Mapping[str, Mapping[str, str]] = {
1215
k: {"cf_role": k}
@@ -22,6 +25,11 @@
2225
)
2326
}
2427

28+
# A grid mapping varibale is anything with a grid_mapping_name attribute
29+
grid_mapping_var_criteria: Mapping[str, Mapping[str, Any]] = {
30+
"grid_mapping": {"grid_mapping_name": re.compile(".")}
31+
}
32+
2533
coordinate_criteria: MutableMapping[str, MutableMapping[str, Tuple]] = {
2634
"latitude": {
2735
"standard_name": ("latitude",),

cf_xarray/data/cf-standard-name-table.xml

Lines changed: 1503 additions & 1125 deletions
Large diffs are not rendered by default.

cf_xarray/datasets.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ def _create_mollw_ds():
228228
def _create_inexact_bounds():
229229
# Dataset that creates rotated pole curvilinear coordinates with CF bounds in
230230
# counterclockwise order that have precision issues.
231+
# dataset created using: https://gist.github.com/larsbuntemeyer/105d83c1eb39b1462150d3fabca0b66b
232+
rlon = np.array([17.935, 18.045, 18.155])
233+
rlat = np.array([21.615, 21.725, 21.835])
234+
231235
lon = np.array(
232236
[
233237
[64.21746363939087, 64.42305921561967, 64.62774455060337],
@@ -296,23 +300,70 @@ def _create_inexact_bounds():
296300

297301
rotated = xr.Dataset(
298302
coords=dict(
303+
rlon=xr.DataArray(
304+
rlon,
305+
dims="rlon",
306+
attrs={
307+
"units": "degrees",
308+
"axis": "X",
309+
"standard_name": "grid_longitude",
310+
},
311+
),
312+
rlat=xr.DataArray(
313+
rlat,
314+
dims="rlat",
315+
attrs={
316+
"units": "degrees",
317+
"axis": "Y",
318+
"standard_name": "grid_latitude",
319+
},
320+
),
299321
lon=xr.DataArray(
300322
lon,
301-
dims=("x", "y"),
302-
attrs={"units": "degrees_east", "bounds": "lon_bounds"},
323+
dims=("rlon", "rlat"),
324+
attrs={
325+
"units": "degrees_east",
326+
"bounds": "lon_bounds",
327+
"standard_name": "longitude",
328+
},
303329
),
304330
lat=xr.DataArray(
305331
lat,
306-
dims=("x", "y"),
307-
attrs={"units": "degrees_north", "bounds": "lat_bounds"},
332+
dims=("rlon", "rlat"),
333+
attrs={
334+
"units": "degrees_north",
335+
"bounds": "lat_bounds",
336+
"standard_name": "latitude",
337+
},
308338
),
309339
),
310340
data_vars=dict(
311341
lon_bounds=xr.DataArray(
312-
lon_bounds, dims=("bounds", "x", "y"), attrs={"units": "degrees_east"}
342+
lon_bounds,
343+
dims=("bounds", "rlon", "rlat"),
344+
attrs={"units": "degrees_east"},
313345
),
314346
lat_bounds=xr.DataArray(
315-
lat_bounds, dims=("bounds", "x", "y"), attrs={"units": "degrees_north"}
347+
lat_bounds,
348+
dims=("bounds", "rlon", "rlat"),
349+
attrs={"units": "degrees_north"},
350+
),
351+
rotated_pole=xr.DataArray(
352+
np.zeros((), dtype=np.int32),
353+
dims=None,
354+
attrs={
355+
"grid_mapping_name": "rotated_latitude_longitude",
356+
"grid_north_pole_latitude": 39.25,
357+
"grid_north_pole_longitude": -162.0,
358+
},
359+
),
360+
temp=xr.DataArray(
361+
np.random.rand(3, 3),
362+
dims=("rlat", "rlon"),
363+
attrs={
364+
"standard_name": "air_temperature",
365+
"grid_mapping": "rotated_pole",
366+
},
316367
),
317368
),
318369
)

cf_xarray/formatting.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import warnings
2+
from typing import Dict, Hashable, Iterable, List
3+
4+
STAR = " * "
5+
TAB = len(STAR) * " "
6+
7+
8+
def _format_missing_row(row: str, rich: bool) -> str:
9+
if rich:
10+
return f"[grey62]{row}[/grey62]"
11+
else:
12+
return row
13+
14+
15+
def _format_varname(name, rich: bool):
16+
return name
17+
18+
19+
def _format_subtitle(name: str, rich: bool) -> str:
20+
if rich:
21+
return f"[bold]{name}[/bold]"
22+
else:
23+
return name
24+
25+
26+
def _format_cf_name(name: str, rich: bool) -> str:
27+
if rich:
28+
return f"[color(33)]{name}[/color(33)]"
29+
else:
30+
return name
31+
32+
33+
def make_text_section(
34+
accessor,
35+
subtitle: str,
36+
attr: str,
37+
dims=None,
38+
valid_values=None,
39+
default_keys=None,
40+
rich: bool = False,
41+
):
42+
43+
from .accessor import sort_maybe_hashable
44+
45+
if dims is None:
46+
dims = []
47+
with warnings.catch_warnings():
48+
warnings.simplefilter("ignore")
49+
try:
50+
vardict: Dict[str, Iterable[Hashable]] = getattr(accessor, attr, {})
51+
except ValueError:
52+
vardict = {}
53+
54+
# Sort keys if there aren't extra keys,
55+
# preserve default keys order otherwise.
56+
default_keys = [] if not default_keys else list(default_keys)
57+
extra_keys = list(set(vardict) - set(default_keys))
58+
ordered_keys = sorted(vardict) if extra_keys else default_keys
59+
vardict = {key: vardict[key] for key in ordered_keys if key in vardict}
60+
61+
# Keep only valid values (e.g., coords or data_vars)
62+
if valid_values is not None:
63+
vardict = {
64+
key: set(value).intersection(valid_values)
65+
for key, value in vardict.items()
66+
if set(value).intersection(valid_values)
67+
}
68+
69+
# Star for keys with dims only, tab otherwise
70+
rows = [
71+
(
72+
f"{STAR if dims and set(value) <= set(dims) else TAB}"
73+
f"{_format_cf_name(key, rich)}: "
74+
f"{_format_varname(sort_maybe_hashable(value), rich)}"
75+
)
76+
for key, value in vardict.items()
77+
]
78+
79+
# Append missing default keys followed by n/a
80+
if default_keys:
81+
missing_keys = [key for key in default_keys if key not in vardict]
82+
if missing_keys:
83+
rows.append(
84+
_format_missing_row(TAB + ", ".join(missing_keys) + ": n/a", rich)
85+
)
86+
elif not rows:
87+
rows.append(_format_missing_row(TAB + "n/a", rich))
88+
89+
return _print_rows(subtitle, rows, rich)
90+
91+
92+
def _print_rows(subtitle: str, rows: List[str], rich: bool):
93+
subtitle = f"{subtitle.rjust(20)}:"
94+
95+
# Add subtitle to the first row, align other rows
96+
rows = [
97+
_format_subtitle(subtitle, rich=rich) + row
98+
if i == 0
99+
else len(subtitle) * " " + row
100+
for i, row in enumerate(rows)
101+
]
102+
103+
return "\n".join(rows) + "\n\n"
104+
105+
106+
def _format_conventions(string: str, rich: bool):
107+
row = _print_rows(
108+
subtitle="Conventions",
109+
rows=[_format_cf_name(TAB + string, rich=rich)],
110+
rich=rich,
111+
)
112+
if rich:
113+
row = row.rstrip()
114+
return row
115+
116+
117+
def _maybe_panel(textgen, title: str, rich: bool):
118+
text = "".join(textgen)
119+
if rich:
120+
from rich.panel import Panel
121+
122+
return Panel(
123+
f"[color(241)]{text.rstrip()}[/color(241)]",
124+
expand=True,
125+
title_align="left",
126+
title=f"[bold][color(244)]{title}[/bold][/color(244)]",
127+
highlight=True,
128+
width=100,
129+
)
130+
else:
131+
return title + ":\n" + text
132+
133+
134+
def _format_flags(accessor, rich):
135+
from .accessor import create_flag_dict
136+
137+
flag_dict = create_flag_dict(accessor._obj)
138+
rows = [
139+
f"{TAB}{_format_varname(v, rich)}: {_format_cf_name(k, rich)}"
140+
for k, v in flag_dict.items()
141+
]
142+
return _print_rows("Flag Meanings", rows, rich)
143+
144+
145+
def _format_roles(accessor, dims, rich):
146+
yield make_text_section(accessor, "CF Roles", "cf_roles", dims=dims, rich=rich)
147+
148+
149+
def _format_coordinates(accessor, dims, coords, rich):
150+
from .accessor import _AXIS_NAMES, _CELL_MEASURES, _COORD_NAMES
151+
152+
yield make_text_section(
153+
accessor, "CF Axes", "axes", dims, coords, _AXIS_NAMES, rich=rich
154+
)
155+
yield make_text_section(
156+
accessor, "CF Coordinates", "coordinates", dims, coords, _COORD_NAMES, rich=rich
157+
)
158+
yield make_text_section(
159+
accessor,
160+
"Cell Measures",
161+
"cell_measures",
162+
dims,
163+
coords,
164+
_CELL_MEASURES,
165+
rich=rich,
166+
)
167+
yield make_text_section(
168+
accessor, "Standard Names", "standard_names", dims, coords, rich=rich
169+
)
170+
yield make_text_section(accessor, "Bounds", "bounds", dims, coords, rich=rich)
171+
yield make_text_section(
172+
accessor, "Grid Mappings", "grid_mapping_names", dims, coords, rich=rich
173+
)
174+
175+
176+
def _format_data_vars(accessor, data_vars, rich):
177+
from .accessor import _CELL_MEASURES
178+
179+
yield make_text_section(
180+
accessor,
181+
"Cell Measures",
182+
"cell_measures",
183+
None,
184+
data_vars,
185+
_CELL_MEASURES,
186+
rich=rich,
187+
)
188+
yield make_text_section(
189+
accessor, "Standard Names", "standard_names", None, data_vars, rich=rich
190+
)
191+
yield make_text_section(accessor, "Bounds", "bounds", None, data_vars, rich=rich)
192+
yield make_text_section(
193+
accessor, "Grid Mappings", "grid_mapping_names", None, data_vars, rich=rich
194+
)

cf_xarray/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ def LooseVersion(vstring):
6767
has_scipy, requires_scipy = _importorskip("scipy")
6868
has_shapely, requires_shapely = _importorskip("shapely")
6969
has_pint, requires_pint = _importorskip("pint")
70+
_, requires_rich = _importorskip("rich")
7071
has_regex, requires_regex = _importorskip("regex")

0 commit comments

Comments
 (0)