1
1
"""Fix base classes for ICON on-the-fly CMORizer."""
2
+
2
3
from __future__ import annotations
3
4
4
5
import logging
27
28
class IconFix (NativeDatasetFix ):
28
29
"""Base class for all ICON fixes."""
29
30
30
- CACHE_DIR = Path .home () / ' .esmvaltool' / ' cache'
31
+ CACHE_DIR = Path .home () / " .esmvaltool" / " cache"
31
32
CACHE_VALIDITY = 7 * 24 * 60 * 60 # [s]; = 1 week
32
33
TIMEOUT = 5 * 60 # [s]; = 5 min
33
- GRID_FILE_ATTR = ' grid_file_uri'
34
+ GRID_FILE_ATTR = " grid_file_uri"
34
35
35
36
def __init__ (self , * args , ** kwargs ):
36
37
"""Initialize ICON fix."""
@@ -65,7 +66,8 @@ def _create_mesh(self, cube: Cube) -> MeshXY:
65
66
# 'vertex_of_cell'; since UGRID expects a different dimension ordering
66
67
# we transpose the cube here)
67
68
vertex_of_cell = horizontal_grid .extract_cube (
68
- NameConstraint (var_name = 'vertex_of_cell' ))
69
+ NameConstraint (var_name = "vertex_of_cell" )
70
+ )
69
71
vertex_of_cell .transpose ()
70
72
71
73
# Extract start index used to name nodes from the the horizontal grid
@@ -74,8 +76,8 @@ def _create_mesh(self, cube: Cube) -> MeshXY:
74
76
75
77
# Extract face coordinates from cube (in ICON jargon called 'cell
76
78
# latitude' and 'cell longitude')
77
- face_lat = cube .coord (' latitude' )
78
- face_lon = cube .coord (' longitude' )
79
+ face_lat = cube .coord (" latitude" )
80
+ face_lon = cube .coord (" longitude" )
79
81
80
82
# Extract node coordinates from horizontal grid
81
83
(node_lat , node_lon ) = self ._get_node_coords (horizontal_grid )
@@ -87,11 +89,11 @@ def _create_mesh(self, cube: Cube) -> MeshXY:
87
89
88
90
# Latitude: there might be slight numerical differences (-> check that
89
91
# the differences are very small before fixing it)
90
- close_kwargs = {' rtol' : 1e-3 , ' atol' : 1e-5 }
92
+ close_kwargs = {" rtol" : 1e-3 , " atol" : 1e-5 }
91
93
if not np .allclose (
92
- face_lat .bounds ,
93
- node_lat .points [conn_node_inds ],
94
- ** close_kwargs , # type: ignore
94
+ face_lat .bounds ,
95
+ node_lat .points [conn_node_inds ],
96
+ ** close_kwargs , # type: ignore
95
97
):
96
98
logger .warning (
97
99
"Latitude bounds of the face coordinate ('clat_vertices' in "
@@ -127,15 +129,15 @@ def _create_mesh(self, cube: Cube) -> MeshXY:
127
129
# Create mesh
128
130
connectivity = Connectivity (
129
131
indices = vertex_of_cell .data ,
130
- cf_role = ' face_node_connectivity' ,
132
+ cf_role = " face_node_connectivity" ,
131
133
start_index = start_index ,
132
134
location_axis = 0 ,
133
135
)
134
136
mesh = MeshXY (
135
137
topology_dimension = 2 ,
136
- node_coords_and_axes = [(node_lat , 'y' ), (node_lon , 'x' )],
138
+ node_coords_and_axes = [(node_lat , "y" ), (node_lon , "x" )],
137
139
connectivities = [connectivity ],
138
- face_coords_and_axes = [(face_lat , 'y' ), (face_lon , 'x' )],
140
+ face_coords_and_axes = [(face_lat , "y" ), (face_lon , "x" )],
139
141
)
140
142
141
143
return mesh
@@ -146,7 +148,8 @@ def _get_grid_url(self, cube):
146
148
raise ValueError (
147
149
f"Cube does not contain the attribute '{ self .GRID_FILE_ATTR } ' "
148
150
f"necessary to download the ICON horizontal grid file:\n "
149
- f"{ cube } " )
151
+ f"{ cube } "
152
+ )
150
153
grid_url = cube .attributes [self .GRID_FILE_ATTR ]
151
154
parsed_url = urlparse (grid_url )
152
155
grid_name = Path (parsed_url .path ).name
@@ -162,21 +165,22 @@ def _get_node_coords(self, horizontal_grid):
162
165
163
166
"""
164
167
dual_area_cube = horizontal_grid .extract_cube (
165
- NameConstraint (var_name = 'dual_area' ))
166
- node_lat = dual_area_cube .coord (var_name = 'vlat' )
167
- node_lon = dual_area_cube .coord (var_name = 'vlon' )
168
+ NameConstraint (var_name = "dual_area" )
169
+ )
170
+ node_lat = dual_area_cube .coord (var_name = "vlat" )
171
+ node_lon = dual_area_cube .coord (var_name = "vlon" )
168
172
169
173
# Fix metadata
170
174
node_lat .bounds = None
171
175
node_lon .bounds = None
172
- node_lat .var_name = ' nlat'
173
- node_lon .var_name = ' nlon'
174
- node_lat .standard_name = ' latitude'
175
- node_lon .standard_name = ' longitude'
176
- node_lat .long_name = ' node latitude'
177
- node_lon .long_name = ' node longitude'
178
- node_lat .convert_units (' degrees_north' )
179
- node_lon .convert_units (' degrees_east' )
176
+ node_lat .var_name = " nlat"
177
+ node_lon .var_name = " nlon"
178
+ node_lat .standard_name = " latitude"
179
+ node_lon .standard_name = " longitude"
180
+ node_lat .long_name = " node latitude"
181
+ node_lon .long_name = " node longitude"
182
+ node_lat .convert_units (" degrees_north" )
183
+ node_lon .convert_units (" degrees_east" )
180
184
181
185
# Convert longitude to [0, 360]
182
186
self ._set_range_in_0_360 (node_lon )
@@ -186,10 +190,10 @@ def _get_node_coords(self, horizontal_grid):
186
190
def _get_path_from_facet (self , facet , description = None ):
187
191
"""Try to get path from facet."""
188
192
if description is None :
189
- description = ' File'
193
+ description = " File"
190
194
path = Path (os .path .expandvars (self .extra_facets [facet ])).expanduser ()
191
195
if not path .is_file ():
192
- new_path = self .session [' auxiliary_data_dir' ] / path
196
+ new_path = self .session [" auxiliary_data_dir" ] / path
193
197
if not new_path .is_file ():
194
198
raise FileNotFoundError (
195
199
f"{ description } '{ path } ' given by facet '{ facet } ' does "
@@ -238,8 +242,8 @@ def add_additional_cubes(self, cubes):
238
242
239
243
"""
240
244
facets_to_consider = [
241
- ' zg_file' ,
242
- ' zghalf_file' ,
245
+ " zg_file" ,
246
+ " zghalf_file" ,
243
247
]
244
248
for facet in facets_to_consider :
245
249
if self .extra_facets .get (facet ) is None :
@@ -254,7 +258,7 @@ def add_additional_cubes(self, cubes):
254
258
def _get_grid_from_facet (self ):
255
259
"""Get horizontal grid from user-defined facet `horizontal_grid`."""
256
260
grid_path = self ._get_path_from_facet (
257
- ' horizontal_grid' , ' Horizontal grid file'
261
+ " horizontal_grid" , " Horizontal grid file"
258
262
)
259
263
grid_name = grid_path .name
260
264
@@ -297,7 +301,7 @@ def _get_grid_from_cube_attr(self, cube: Cube) -> Cube:
297
301
def _get_grid_from_rootpath (self , grid_name : str ) -> CubeList | None :
298
302
"""Try to get grid from the ICON rootpath."""
299
303
glob_patterns : list [Path ] = []
300
- for data_source in _get_data_sources (' ICON' ):
304
+ for data_source in _get_data_sources (" ICON" ):
301
305
glob_patterns .extend (
302
306
data_source .get_glob_patterns (** self .extra_facets )
303
307
)
@@ -334,8 +338,10 @@ def _get_downloaded_grid(self, grid_url: str, grid_name: str) -> CubeList:
334
338
logger .debug ("Using cached ICON grid file '%s'" , grid_path )
335
339
valid_cache = True
336
340
else :
337
- logger .debug ("Existing cached ICON grid file '%s' is outdated" ,
338
- grid_path )
341
+ logger .debug (
342
+ "Existing cached ICON grid file '%s' is outdated" ,
343
+ grid_path ,
344
+ )
339
345
340
346
# File is not present in cache or too old -> download it
341
347
if not valid_cache :
@@ -347,12 +353,12 @@ def _get_downloaded_grid(self, grid_url: str, grid_name: str) -> CubeList:
347
353
tmp_path ,
348
354
)
349
355
with requests .get (
350
- grid_url ,
351
- stream = True ,
352
- timeout = self .TIMEOUT ,
356
+ grid_url ,
357
+ stream = True ,
358
+ timeout = self .TIMEOUT ,
353
359
) as response :
354
360
response .raise_for_status ()
355
- with tmp_path .open ('wb' ) as file :
361
+ with tmp_path .open ("wb" ) as file :
356
362
copyfileobj (response .raw , file )
357
363
shutil .move (tmp_path , grid_path )
358
364
logger .info (
@@ -403,7 +409,7 @@ def get_horizontal_grid(self, cube):
403
409
file.
404
410
405
411
"""
406
- if self .extra_facets .get (' horizontal_grid' ) is not None :
412
+ if self .extra_facets .get (" horizontal_grid" ) is not None :
407
413
grid = self ._get_grid_from_facet ()
408
414
else :
409
415
grid = self ._get_grid_from_cube_attr (cube )
@@ -444,9 +450,9 @@ def get_mesh(self, cube):
444
450
"""
445
451
# If specified by the user, use `horizontal_grid` facet to determine
446
452
# grid name; otherwise, use the `grid_file_uri` attribute of the cube
447
- if self .extra_facets .get (' horizontal_grid' ) is not None :
453
+ if self .extra_facets .get (" horizontal_grid" ) is not None :
448
454
grid_path = self ._get_path_from_facet (
449
- ' horizontal_grid' , ' Horizontal grid file'
455
+ " horizontal_grid" , " Horizontal grid file"
450
456
)
451
457
grid_name = grid_path .name
452
458
else :
@@ -474,32 +480,33 @@ def _get_start_index(horizontal_grid):
474
480
475
481
"""
476
482
vertex_index = horizontal_grid .extract_cube (
477
- NameConstraint (var_name = 'vertex_index' ))
483
+ NameConstraint (var_name = "vertex_index" )
484
+ )
478
485
return np .int32 (np .min (vertex_index .data ))
479
486
480
487
@staticmethod
481
488
def _load_cubes (path : Path | str ) -> CubeList :
482
489
"""Load cubes and ignore certain warnings."""
483
490
with warnings .catch_warnings ():
484
491
warnings .filterwarnings (
485
- ' ignore' ,
492
+ " ignore" ,
486
493
message = "Ignoring netCDF variable .* invalid units .*" ,
487
494
category = UserWarning ,
488
- module = ' iris' ,
495
+ module = " iris" ,
489
496
) # iris < 3.8
490
497
warnings .filterwarnings (
491
- ' ignore' ,
498
+ " ignore" ,
492
499
message = "Ignoring invalid units .* on netCDF variable .*" ,
493
500
category = UserWarning ,
494
- module = ' iris' ,
501
+ module = " iris" ,
495
502
) # iris >= 3.8
496
503
warnings .filterwarnings (
497
- ' ignore' ,
504
+ " ignore" ,
498
505
message = "Failed to create 'height' dimension coordinate: The "
499
- "'height' DimCoord bounds array must be strictly "
500
- "monotonic." ,
506
+ "'height' DimCoord bounds array must be strictly "
507
+ "monotonic." ,
501
508
category = UserWarning ,
502
- module = ' iris' ,
509
+ module = " iris" ,
503
510
)
504
511
cubes = iris .load (path )
505
512
return cubes
0 commit comments