30
30
from xarray .core .rolling import Coarsen , Rolling
31
31
from xarray .core .weighted import Weighted
32
32
33
- from .criteria import cf_role_criteria , coordinate_criteria , regex
33
+ from .criteria import (
34
+ cf_role_criteria ,
35
+ coordinate_criteria ,
36
+ grid_mapping_var_criteria ,
37
+ regex ,
38
+ )
34
39
from .helpers import _guess_bounds_1d , _guess_bounds_2d , bounds_to_vertices
35
40
from .options import OPTIONS
36
41
from .utils import (
@@ -369,6 +374,41 @@ def _get_bounds(obj: DataArray | Dataset, key: Hashable) -> list[Hashable]:
369
374
return list (results )
370
375
371
376
377
+ def _get_grid_mapping_name (obj : DataArray | Dataset , key : str ) -> list [str ]:
378
+ """
379
+ Translate from grid mapping name attribute to appropriate variable name.
380
+ This function interprets the ``grid_mapping`` attribute on DataArrays.
381
+
382
+ Parameters
383
+ ----------
384
+ obj : DataArray, Dataset
385
+ DataArray belonging to the coordinate to be checked
386
+ key : str
387
+ key to check for.
388
+
389
+ Returns
390
+ -------
391
+ List[str], Variable name(s) in parent xarray object that matches grid_mapping_name `key`
392
+ """
393
+
394
+ if isinstance (obj , DataArray ):
395
+ obj = obj ._to_temp_dataset ()
396
+
397
+ results = set ()
398
+ for var in obj .variables :
399
+ da = obj [var ]
400
+ attrs_or_encoding = ChainMap (da .attrs , da .encoding )
401
+ if "grid_mapping" in attrs_or_encoding :
402
+ grid_mapping_var_name = attrs_or_encoding ["grid_mapping" ]
403
+ if grid_mapping_var_name not in obj .variables :
404
+ raise ValueError (
405
+ f"{ var } defines non-existing grid_mapping variable { grid_mapping_var_name } ."
406
+ )
407
+ if key == obj [grid_mapping_var_name ].attrs ["grid_mapping_name" ]:
408
+ results .update ([grid_mapping_var_name ])
409
+ return list (results )
410
+
411
+
372
412
def _get_with_standard_name (
373
413
obj : DataArray | Dataset , name : Hashable | Iterable [Hashable ]
374
414
) -> list [Hashable ]:
@@ -395,8 +435,10 @@ def _get_all(obj: DataArray | Dataset, key: Hashable) -> list[Hashable]:
395
435
all_mappers : tuple [Mapper ] = (
396
436
_get_custom_criteria ,
397
437
functools .partial (_get_custom_criteria , criteria = cf_role_criteria ), # type: ignore
438
+ functools .partial (_get_custom_criteria , criteria = grid_mapping_var_criteria ),
398
439
_get_axis_coord ,
399
440
_get_measure ,
441
+ _get_grid_mapping_name ,
400
442
_get_with_standard_name ,
401
443
)
402
444
results = apply_mapper (all_mappers , obj , key , error = False , default = None )
@@ -706,6 +748,15 @@ def check_results(names, key):
706
748
measures = []
707
749
warnings .warn ("Ignoring bad cell_measures attribute." , UserWarning )
708
750
751
+ if isinstance (obj , Dataset ):
752
+ grid_mapping_names = list (accessor .grid_mapping_names )
753
+ else :
754
+ try :
755
+ grid_mapping_names = [accessor .grid_mapping_name ]
756
+ except ValueError :
757
+ grid_mapping_names = []
758
+ grid_mapping_names .append ("grid_mapping" )
759
+
709
760
custom_criteria = ChainMap (* OPTIONS ["custom_criteria" ])
710
761
711
762
varnames : list [Hashable ] = []
@@ -724,6 +775,12 @@ def check_results(names, key):
724
775
successful [k ] = bool (measure )
725
776
if measure :
726
777
varnames .extend (measure )
778
+ elif "grid_mapping_names" not in skip and k in grid_mapping_names :
779
+ grid_mapping = _get_all (obj , k )
780
+ check_results (grid_mapping , k )
781
+ successful [k ] = bool (grid_mapping )
782
+ if grid_mapping :
783
+ varnames .extend (grid_mapping )
727
784
elif k in custom_criteria or k in cf_role_criteria :
728
785
names = _get_all (obj , k )
729
786
check_results (names , k )
@@ -1415,13 +1472,15 @@ def make_text_section(subtitle, attr, valid_values=None, default_keys=None):
1415
1472
text += make_text_section ("Standard Names" , "standard_names" , coords )
1416
1473
text += make_text_section ("Bounds" , "bounds" , coords )
1417
1474
if isinstance (self ._obj , Dataset ):
1475
+ text += make_text_section ("Grid Mappings" , "grid_mapping_names" , coords )
1418
1476
data_vars = self ._obj .data_vars
1419
1477
text += "\n Data Variables:"
1420
1478
text += make_text_section (
1421
1479
"Cell Measures" , "cell_measures" , data_vars , _CELL_MEASURES
1422
1480
)
1423
1481
text += make_text_section ("Standard Names" , "standard_names" , data_vars )
1424
1482
text += make_text_section ("Bounds" , "bounds" , data_vars )
1483
+ text += make_text_section ("Grid Mappings" , "grid_mapping_names" , data_vars )
1425
1484
1426
1485
return text
1427
1486
@@ -1442,6 +1501,14 @@ def keys(self) -> set[Hashable]:
1442
1501
varnames .extend (list (self .cell_measures ))
1443
1502
varnames .extend (list (self .standard_names ))
1444
1503
varnames .extend (list (self .cf_roles ))
1504
+ if isinstance (self ._obj , xr .Dataset ):
1505
+ varnames .extend (list (self .grid_mapping_names ))
1506
+ else :
1507
+ try :
1508
+ gmname = self .grid_mapping_name
1509
+ varnames .extend (list (gmname ))
1510
+ except ValueError :
1511
+ pass
1445
1512
1446
1513
return set (varnames )
1447
1514
@@ -1604,6 +1671,7 @@ def get_associated_variable_names(
1604
1671
2. "bounds"
1605
1672
3. "cell_measures"
1606
1673
4. "coordinates"
1674
+ 5. "grid_mapping"
1607
1675
to a list of variable names referred to in the appropriate attribute
1608
1676
1609
1677
Parameters
@@ -1618,7 +1686,13 @@ def get_associated_variable_names(
1618
1686
names : dict
1619
1687
Dictionary with keys "ancillary_variables", "cell_measures", "coordinates", "bounds".
1620
1688
"""
1621
- keys = ["ancillary_variables" , "cell_measures" , "coordinates" , "bounds" ]
1689
+ keys = [
1690
+ "ancillary_variables" ,
1691
+ "cell_measures" ,
1692
+ "coordinates" ,
1693
+ "bounds" ,
1694
+ "grid_mapping" ,
1695
+ ]
1622
1696
coords : dict [str , list [Hashable ]] = {k : [] for k in keys }
1623
1697
attrs_or_encoding = ChainMap (self ._obj [name ].attrs , self ._obj [name ].encoding )
1624
1698
@@ -1660,6 +1734,9 @@ def get_associated_variable_names(
1660
1734
if dbounds :
1661
1735
coords ["bounds" ].append (dbounds )
1662
1736
1737
+ if "grid_mapping" in attrs_or_encoding :
1738
+ coords ["grid_mapping" ] = [attrs_or_encoding ["grid_mapping" ]]
1739
+
1663
1740
allvars = itertools .chain (* coords .values ())
1664
1741
missing = set (allvars ) - set (self ._maybe_to_dataset ()._variables )
1665
1742
if missing :
@@ -2048,6 +2125,8 @@ def __getitem__(self, key: Hashable | Iterable[Hashable]) -> DataArray | Dataset
2048
2125
- cell measures: "area", "volume", or other names present in the \
2049
2126
``cell_measures`` attribute
2050
2127
- standard names: names present in ``standard_name`` attribute
2128
+ - cf roles: 'timeseries_id', 'profile_id', 'trajectory_id', 'mesh_topology', 'grid_topology'
2129
+ - grid mappings: 'grid_mapping' or a grid_mapping_name like 'rotated_latitude_longitude'
2051
2130
2052
2131
Returns
2053
2132
-------
@@ -2372,6 +2451,51 @@ def bounds_to_vertices(
2372
2451
)
2373
2452
return obj
2374
2453
2454
+ @property
2455
+ def grid_mapping_names (self ) -> dict [str , list [str ]]:
2456
+ """
2457
+ Property that returns a dictionary mapping the CF grid mapping name
2458
+ to the variable name containing the grid mapping attributes.
2459
+
2460
+ Returns
2461
+ -------
2462
+ dict
2463
+ Dictionary mapping the CF grid mapping name to the grid mapping variable name.
2464
+
2465
+ See Also
2466
+ --------
2467
+ DataArray.cf.grid_mapping
2468
+
2469
+ References
2470
+ ----------
2471
+ Please refer to the CF conventions document : https://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#grid-mappings-and-projections
2472
+
2473
+ 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
2474
+
2475
+ Examples
2476
+ --------
2477
+ >>> from cf_xarray.datasets import rotds
2478
+ >>> rotds.cf.grid_mapping_names
2479
+ {'rotated_latitude_longitude': ['rotated_pole']}
2480
+ """
2481
+
2482
+ obj = self ._obj
2483
+ keys = set (obj .variables )
2484
+
2485
+ vardict = {
2486
+ key : obj .variables [key ].attrs ["grid_mapping_name" ]
2487
+ for key in keys
2488
+ if "grid_mapping_name" in obj .variables [key ].attrs
2489
+ }
2490
+
2491
+ results = {}
2492
+ for k , v in vardict .items ():
2493
+ if v not in results :
2494
+ results [v ] = [k ]
2495
+ else :
2496
+ results [v ].append (k )
2497
+ return results
2498
+
2375
2499
def decode_vertical_coords (self , * , outnames = None , prefix = None ):
2376
2500
"""
2377
2501
Decode parameterized vertical coordinates in place.
@@ -2547,6 +2671,43 @@ def formula_terms(self) -> dict[str, str]:
2547
2671
terms [key ] = value
2548
2672
return terms
2549
2673
2674
+ @property
2675
+ def grid_mapping_name (self ) -> str :
2676
+ """
2677
+ Get CF grid mapping name associated with this variable.
2678
+
2679
+ Parameters
2680
+ ----------
2681
+ key : str
2682
+ Name of variable whose grid_mapping name is desired.
2683
+
2684
+ Returns
2685
+ -------
2686
+ str
2687
+ CF Name of the associated grid mapping.
2688
+
2689
+ See Also
2690
+ --------
2691
+ Dataset.cf.grid_mapping_names
2692
+
2693
+ Examples
2694
+ --------
2695
+ >>> from cf_xarray.datasets import rotds
2696
+ >>> rotds.cf["temp"].cf.grid_mapping_name
2697
+ 'rotated_latitude_longitude'
2698
+
2699
+ """
2700
+
2701
+ da = self ._obj
2702
+
2703
+ attrs_or_encoding = ChainMap (da .attrs , da .encoding )
2704
+ grid_mapping = attrs_or_encoding .get ("grid_mapping" , None )
2705
+ if not grid_mapping :
2706
+ raise ValueError ("No 'grid_mapping' attribute present." )
2707
+
2708
+ grid_mapping_var = da [grid_mapping ]
2709
+ return grid_mapping_var .attrs ["grid_mapping_name" ]
2710
+
2550
2711
def __getitem__ (self , key : Hashable | Iterable [Hashable ]) -> DataArray :
2551
2712
"""
2552
2713
Index into a DataArray making use of CF attributes.
@@ -2561,6 +2722,8 @@ def __getitem__(self, key: Hashable | Iterable[Hashable]) -> DataArray:
2561
2722
``cell_measures`` attribute
2562
2723
- standard names: names present in ``standard_name`` attribute of \
2563
2724
coordinate variables
2725
+ - cf roles: 'timeseries_id', 'profile_id', 'trajectory_id', 'mesh_topology', 'grid_topology'
2726
+ - grid mappings: 'grid_mapping' or a grid_mapping_name like 'rotated_latitude_longitude'
2564
2727
2565
2728
Returns
2566
2729
-------
0 commit comments