-
Notifications
You must be signed in to change notification settings - Fork 280
/
cf.py
297 lines (237 loc) · 11.9 KB
/
cf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# Copyright Iris contributors
#
# This file is part of Iris and is released under the BSD license.
# See LICENSE 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`.
"""
from iris.exceptions import warn_once_at_level
from ...exceptions import IrisCfLabelVarWarning, IrisCfMissingVarWarning
from ...fileformats import cf
from .mesh import Connectivity
class CFUGridConnectivityVariable(cf.CFVariable):
"""A CF_UGRID connectivity variable.
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
@classmethod
def identify(cls, variables, ignore=None, target=None, warn=True):
result = {}
ignore, target = cls._identify_common(variables, ignore, target)
# 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}"
)
if warn:
warn_once_at_level(
message, category=IrisCfMissingVarWarning
)
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."
)
if warn:
warn_once_at_level(
message, category=IrisCfLabelVarWarning
)
return result
class CFUGridAuxiliaryCoordinateVariable(cf.CFVariable):
"""A CF-UGRID auxiliary coordinate variable.
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",
]
@classmethod
def identify(cls, variables, ignore=None, target=None, warn=True):
result = {}
ignore, target = cls._identify_common(variables, ignore, target)
# 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}"
)
if warn:
warn_once_at_level(
message,
category=IrisCfMissingVarWarning,
)
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."
)
if warn:
warn_once_at_level(
message,
category=IrisCfLabelVarWarning,
)
return result
class CFUGridMeshVariable(cf.CFVariable):
"""A CF-UGRID mesh variable is a dummy variable for storing topology information as attributes.
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"
@classmethod
def identify(cls, variables, ignore=None, target=None, warn=True):
result = {}
ignore, target = cls._identify_common(variables, ignore, target)
# 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}"
)
if warn:
warn_once_at_level(
message, category=IrisCfMissingVarWarning
)
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."
)
if warn:
warn_once_at_level(
message, category=IrisCfLabelVarWarning
)
return result
class CFUGridGroup(cf.CFGroup):
"""Represents a collection of CF Metadata Conventions variables and netCDF global attributes.
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 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
class CFUGridReader(cf.CFReader):
"""Allows the contents of a netCDF file to be.
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