You are viewing the latest unreleased documentation v3.3.dev0. You may prefer a stable version.

Source code for iris.experimental.ugrid.cf

# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.

"""
Extensions to Iris' CF variable representation to represent CF UGrid variables.

Eventual destination: :mod:`iris.fileformats.cf`.

"""
import logging

from ...config import get_logger
from ...fileformats import cf
from .mesh import Connectivity

# Configure the logger.
logger = get_logger(__name__, propagate=True, handler=False)


[docs]class CFUGridConnectivityVariable(cf.CFVariable): """ A CF_UGRID connectivity variable points to an index variable identifying for every element (edge/face/volume) the indices of its corner nodes. The connectivity array will thus be a matrix of size n-elements x n-corners. For the indexing one may use either 0- or 1-based indexing; the convention used should be specified using a ``start_index`` attribute to the index variable. For face elements: the corner nodes should be specified in anticlockwise direction as viewed from above. For volume elements: use the additional attribute ``volume_shape_type`` which points to a flag variable that specifies for every volume its shape. Identified by a CF-netCDF variable attribute equal to any one of the values in :attr:`~iris.experimental.ugrid.mesh.Connectivity.UGRID_CF_ROLES`. .. seealso:: The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ """ cf_identity = NotImplemented cf_identities = Connectivity.UGRID_CF_ROLES
[docs] @classmethod def identify(cls, variables, ignore=None, target=None, warn=True): result = {} ignore, target = cls._identify_common(variables, ignore, target) # TODO: reconsider logging level when we have consistent practice. log_level = logging.WARNING if warn else logging.DEBUG # Identify all CF-UGRID connectivity variables. for nc_var_name, nc_var in target.items(): # Check for connectivity variable references, iterating through # the valid cf roles. for identity in cls.cf_identities: nc_var_att = getattr(nc_var, identity, None) if nc_var_att is not None: # UGRID only allows for one of each connectivity cf role. name = nc_var_att.strip() if name not in ignore: if name not in variables: message = ( f"Missing CF-UGRID connectivity variable " f"{name}, referenced by netCDF variable " f"{nc_var_name}" ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. if not cf._is_str_dtype(variables[name]): result[name] = CFUGridConnectivityVariable( name, variables[name] ) else: message = ( f"Ignoring variable {name}, identified " f"as a CF-UGRID connectivity - is a " f"CF-netCDF label variable." ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) return result
[docs]class CFUGridAuxiliaryCoordinateVariable(cf.CFVariable): """ A CF-UGRID auxiliary coordinate variable is a CF-netCDF auxiliary coordinate variable representing the element (node/edge/face/volume) locations (latitude, longitude or other spatial coordinates, and optional elevation or other coordinates). These auxiliary coordinate variables will have length n-elements. For elements other than nodes, these auxiliary coordinate variables may have in turn a ``bounds`` attribute that specifies the bounding coordinates of the element (thereby duplicating the data in the ``node_coordinates`` variables). Identified by the CF-netCDF variable attribute ``node_``/``edge_``/``face_``/``volume_coordinates``. .. seealso:: The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ """ cf_identity = NotImplemented cf_identities = [ "node_coordinates", "edge_coordinates", "face_coordinates", "volume_coordinates", ]
[docs] @classmethod def identify(cls, variables, ignore=None, target=None, warn=True): result = {} ignore, target = cls._identify_common(variables, ignore, target) # TODO: reconsider logging level when we have consistent practice. log_level = logging.WARNING if warn else logging.DEBUG # Identify any CF-UGRID-relevant auxiliary coordinate variables. for nc_var_name, nc_var in target.items(): # Check for UGRID auxiliary coordinate variable references. for identity in cls.cf_identities: nc_var_att = getattr(nc_var, identity, None) if nc_var_att is not None: for name in nc_var_att.split(): if name not in ignore: if name not in variables: message = ( f"Missing CF-netCDF auxiliary coordinate " f"variable {name}, referenced by netCDF " f"variable {nc_var_name}" ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. if not cf._is_str_dtype(variables[name]): result[ name ] = CFUGridAuxiliaryCoordinateVariable( name, variables[name] ) else: message = ( f"Ignoring variable {name}, " f"identified as a CF-netCDF " f"auxiliary coordinate - is a " f"CF-netCDF label variable." ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) return result
[docs]class CFUGridMeshVariable(cf.CFVariable): """ A CF-UGRID mesh variable is a dummy variable for storing topology information as attributes. The mesh variable has the ``cf_role`` 'mesh_topology'. The UGRID conventions describe define the mesh topology as the interconnection of various geometrical elements of the mesh. The pure interconnectivity is independent of georeferencing the individual geometrical elements, but for the practical applications for which the UGRID CF extension is defined, coordinate data will always be added. Identified by the CF-netCDF variable attribute 'mesh'. .. seealso:: The UGRID Conventions, https://ugrid-conventions.github.io/ugrid-conventions/ """ cf_identity = "mesh"
[docs] @classmethod def identify(cls, variables, ignore=None, target=None, warn=True): result = {} ignore, target = cls._identify_common(variables, ignore, target) # TODO: reconsider logging level when we have consistent practice. log_level = logging.WARNING if warn else logging.DEBUG # Identify all CF-UGRID mesh variables. all_vars = target == variables for nc_var_name, nc_var in target.items(): if all_vars: # SPECIAL BEHAVIOUR FOR MESH VARIABLES. # We are looking for all mesh variables. Check if THIS variable # is a mesh using its own attributes. if getattr(nc_var, "cf_role", "") == "mesh_topology": result[nc_var_name] = CFUGridMeshVariable( nc_var_name, nc_var ) # Check for mesh variable references. nc_var_att = getattr(nc_var, cls.cf_identity, None) if nc_var_att is not None: # UGRID only allows for 1 mesh per variable. name = nc_var_att.strip() if name not in ignore: if name not in variables: message = ( f"Missing CF-UGRID mesh variable {name}, " f"referenced by netCDF variable {nc_var_name}" ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. if not cf._is_str_dtype(variables[name]): result[name] = CFUGridMeshVariable( name, variables[name] ) else: message = ( f"Ignoring variable {name}, identified as a " f"CF-UGRID mesh - is a CF-netCDF label " f"variable." ) logger.log( level=log_level, msg=message, extra=dict(cls=cls.__name__), ) return result
[docs]class CFUGridGroup(cf.CFGroup): """ Represents a collection of 'NetCDF Climate and Forecast (CF) Metadata Conventions' variables and netCDF global attributes. Specialisation of :class:`~iris.fileformats.cf.CFGroup` that includes extra collections for CF-UGRID-specific variable types. """ @property def connectivities(self): """Collection of CF-UGRID connectivity variables.""" return self._cf_getter(CFUGridConnectivityVariable) @property def ugrid_coords(self): """Collection of CF-UGRID-relevant auxiliary coordinate variables.""" return self._cf_getter(CFUGridAuxiliaryCoordinateVariable) @property def meshes(self): """Collection of CF-UGRID mesh variables.""" return self._cf_getter(CFUGridMeshVariable) @property def non_data_variable_names(self): """ :class:`set` of the names of the CF-netCDF/CF-UGRID variables that are not the data pay-load. """ extra_variables = (self.connectivities, self.ugrid_coords, self.meshes) extra_result = set() for variable in extra_variables: extra_result |= set(variable) return super().non_data_variable_names | extra_result
[docs]class CFUGridReader(cf.CFReader): """ This class allows the contents of a netCDF file to be interpreted according to the 'NetCDF Climate and Forecast (CF) Metadata Conventions'. Specialisation of :class:`~iris.fileformats.cf.CFReader` that can also handle CF-UGRID-specific variable types. """ _variable_types = cf.CFReader._variable_types + ( CFUGridConnectivityVariable, CFUGridAuxiliaryCoordinateVariable, CFUGridMeshVariable, ) CFGroup = CFUGridGroup