anemoi-datasets 0.5.15__py3-none-any.whl → 0.5.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anemoi/datasets/__init__.py +4 -1
- anemoi/datasets/__main__.py +12 -2
- anemoi/datasets/_version.py +9 -4
- anemoi/datasets/commands/cleanup.py +17 -2
- anemoi/datasets/commands/compare.py +18 -2
- anemoi/datasets/commands/copy.py +196 -14
- anemoi/datasets/commands/create.py +50 -7
- anemoi/datasets/commands/finalise-additions.py +17 -2
- anemoi/datasets/commands/finalise.py +17 -2
- anemoi/datasets/commands/init-additions.py +17 -2
- anemoi/datasets/commands/init.py +16 -2
- anemoi/datasets/commands/inspect.py +283 -62
- anemoi/datasets/commands/load-additions.py +16 -2
- anemoi/datasets/commands/load.py +16 -2
- anemoi/datasets/commands/patch.py +17 -2
- anemoi/datasets/commands/publish.py +17 -2
- anemoi/datasets/commands/scan.py +31 -3
- anemoi/datasets/compute/recentre.py +47 -11
- anemoi/datasets/create/__init__.py +612 -85
- anemoi/datasets/create/check.py +142 -20
- anemoi/datasets/create/chunks.py +64 -4
- anemoi/datasets/create/config.py +185 -21
- anemoi/datasets/create/filter.py +50 -0
- anemoi/datasets/create/filters/__init__.py +33 -0
- anemoi/datasets/create/filters/empty.py +37 -0
- anemoi/datasets/create/filters/legacy.py +93 -0
- anemoi/datasets/create/filters/noop.py +37 -0
- anemoi/datasets/create/filters/orog_to_z.py +58 -0
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
- anemoi/datasets/create/filters/rename.py +205 -0
- anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
- anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
- anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
- anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
- anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
- anemoi/datasets/create/filters/transform.py +53 -0
- anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
- anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
- anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
- anemoi/datasets/create/input/__init__.py +76 -5
- anemoi/datasets/create/input/action.py +149 -13
- anemoi/datasets/create/input/concat.py +81 -10
- anemoi/datasets/create/input/context.py +39 -4
- anemoi/datasets/create/input/data_sources.py +72 -6
- anemoi/datasets/create/input/empty.py +21 -3
- anemoi/datasets/create/input/filter.py +60 -12
- anemoi/datasets/create/input/function.py +154 -37
- anemoi/datasets/create/input/join.py +86 -14
- anemoi/datasets/create/input/misc.py +67 -17
- anemoi/datasets/create/input/pipe.py +33 -6
- anemoi/datasets/create/input/repeated_dates.py +189 -41
- anemoi/datasets/create/input/result.py +202 -87
- anemoi/datasets/create/input/step.py +119 -22
- anemoi/datasets/create/input/template.py +100 -13
- anemoi/datasets/create/input/trace.py +62 -7
- anemoi/datasets/create/patch.py +52 -4
- anemoi/datasets/create/persistent.py +134 -17
- anemoi/datasets/create/size.py +15 -1
- anemoi/datasets/create/source.py +51 -0
- anemoi/datasets/create/sources/__init__.py +36 -0
- anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
- anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
- anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
- anemoi/datasets/create/sources/empty.py +37 -0
- anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
- anemoi/datasets/create/sources/grib.py +297 -0
- anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
- anemoi/datasets/create/sources/legacy.py +93 -0
- anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
- anemoi/datasets/create/sources/netcdf.py +42 -0
- anemoi/datasets/create/sources/opendap.py +43 -0
- anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
- anemoi/datasets/create/sources/recentre.py +150 -0
- anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
- anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
- anemoi/datasets/create/sources/xarray.py +92 -0
- anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
- anemoi/datasets/create/sources/xarray_support/README.md +1 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
- anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
- anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
- anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
- anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
- anemoi/datasets/create/sources/xarray_support/time.py +391 -0
- anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
- anemoi/datasets/create/sources/xarray_zarr.py +41 -0
- anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
- anemoi/datasets/create/statistics/__init__.py +233 -44
- anemoi/datasets/create/statistics/summary.py +52 -6
- anemoi/datasets/create/testing.py +76 -0
- anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
- anemoi/datasets/create/utils.py +97 -6
- anemoi/datasets/create/writer.py +26 -4
- anemoi/datasets/create/zarr.py +170 -23
- anemoi/datasets/data/__init__.py +51 -4
- anemoi/datasets/data/complement.py +191 -40
- anemoi/datasets/data/concat.py +141 -16
- anemoi/datasets/data/dataset.py +552 -61
- anemoi/datasets/data/debug.py +197 -26
- anemoi/datasets/data/ensemble.py +93 -8
- anemoi/datasets/data/fill_missing.py +165 -18
- anemoi/datasets/data/forwards.py +428 -56
- anemoi/datasets/data/grids.py +323 -97
- anemoi/datasets/data/indexing.py +112 -19
- anemoi/datasets/data/interpolate.py +92 -12
- anemoi/datasets/data/join.py +158 -19
- anemoi/datasets/data/masked.py +129 -15
- anemoi/datasets/data/merge.py +137 -23
- anemoi/datasets/data/misc.py +172 -16
- anemoi/datasets/data/missing.py +233 -29
- anemoi/datasets/data/rescale.py +111 -10
- anemoi/datasets/data/select.py +168 -26
- anemoi/datasets/data/statistics.py +67 -6
- anemoi/datasets/data/stores.py +149 -64
- anemoi/datasets/data/subset.py +159 -25
- anemoi/datasets/data/unchecked.py +168 -57
- anemoi/datasets/data/xy.py +168 -25
- anemoi/datasets/dates/__init__.py +191 -16
- anemoi/datasets/dates/groups.py +189 -47
- anemoi/datasets/grids.py +270 -31
- anemoi/datasets/testing.py +28 -1
- {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +10 -7
- anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
- {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
- {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +1 -1
- anemoi/datasets/create/functions/__init__.py +0 -66
- anemoi/datasets/create/functions/filters/__init__.py +0 -9
- anemoi/datasets/create/functions/filters/empty.py +0 -17
- anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
- anemoi/datasets/create/functions/filters/rename.py +0 -79
- anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
- anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
- anemoi/datasets/create/functions/sources/empty.py +0 -15
- anemoi/datasets/create/functions/sources/grib.py +0 -150
- anemoi/datasets/create/functions/sources/netcdf.py +0 -15
- anemoi/datasets/create/functions/sources/opendap.py +0 -15
- anemoi/datasets/create/functions/sources/recentre.py +0 -60
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
- anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
- anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
- anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
- anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
- anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
- anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
- anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
- anemoi/datasets/utils/fields.py +0 -47
- anemoi_datasets-0.5.15.dist-info/RECORD +0 -129
- {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
|
-
from .coordinates import DateCoordinate
|
|
14
|
-
from .coordinates import EnsembleCoordinate
|
|
15
|
-
from .coordinates import LatitudeCoordinate
|
|
16
|
-
from .coordinates import LevelCoordinate
|
|
17
|
-
from .coordinates import LongitudeCoordinate
|
|
18
|
-
from .coordinates import ScalarCoordinate
|
|
19
|
-
from .coordinates import StepCoordinate
|
|
20
|
-
from .coordinates import TimeCoordinate
|
|
21
|
-
from .coordinates import UnsupportedCoordinate
|
|
22
|
-
from .coordinates import XCoordinate
|
|
23
|
-
from .coordinates import YCoordinate
|
|
24
|
-
from .coordinates import is_scalar
|
|
25
|
-
from .grid import MeshedGrid
|
|
26
|
-
from .grid import MeshProjectionGrid
|
|
27
|
-
from .grid import UnstructuredGrid
|
|
28
|
-
from .grid import UnstructuredProjectionGrid
|
|
29
|
-
|
|
30
|
-
LOG = logging.getLogger(__name__)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class CoordinateGuesser:
|
|
34
|
-
|
|
35
|
-
def __init__(self, ds):
|
|
36
|
-
self.ds = ds
|
|
37
|
-
self._cache = {}
|
|
38
|
-
|
|
39
|
-
@classmethod
|
|
40
|
-
def from_flavour(cls, ds, flavour):
|
|
41
|
-
if flavour is None:
|
|
42
|
-
return DefaultCoordinateGuesser(ds)
|
|
43
|
-
else:
|
|
44
|
-
return FlavourCoordinateGuesser(ds, flavour)
|
|
45
|
-
|
|
46
|
-
def guess(self, c, coord):
|
|
47
|
-
if coord not in self._cache:
|
|
48
|
-
self._cache[coord] = self._guess(c, coord)
|
|
49
|
-
return self._cache[coord]
|
|
50
|
-
|
|
51
|
-
def _guess(self, c, coord):
|
|
52
|
-
|
|
53
|
-
name = c.name
|
|
54
|
-
standard_name = getattr(c, "standard_name", "").lower()
|
|
55
|
-
axis = getattr(c, "axis", "")
|
|
56
|
-
long_name = getattr(c, "long_name", "").lower()
|
|
57
|
-
units = getattr(c, "units", "")
|
|
58
|
-
|
|
59
|
-
d = self._is_longitude(
|
|
60
|
-
c,
|
|
61
|
-
axis=axis,
|
|
62
|
-
name=name,
|
|
63
|
-
long_name=long_name,
|
|
64
|
-
standard_name=standard_name,
|
|
65
|
-
units=units,
|
|
66
|
-
)
|
|
67
|
-
if d is not None:
|
|
68
|
-
return d
|
|
69
|
-
|
|
70
|
-
d = self._is_latitude(
|
|
71
|
-
c,
|
|
72
|
-
axis=axis,
|
|
73
|
-
name=name,
|
|
74
|
-
long_name=long_name,
|
|
75
|
-
standard_name=standard_name,
|
|
76
|
-
units=units,
|
|
77
|
-
)
|
|
78
|
-
if d is not None:
|
|
79
|
-
return d
|
|
80
|
-
|
|
81
|
-
d = self._is_x(
|
|
82
|
-
c,
|
|
83
|
-
axis=axis,
|
|
84
|
-
name=name,
|
|
85
|
-
long_name=long_name,
|
|
86
|
-
standard_name=standard_name,
|
|
87
|
-
units=units,
|
|
88
|
-
)
|
|
89
|
-
if d is not None:
|
|
90
|
-
return d
|
|
91
|
-
|
|
92
|
-
d = self._is_y(
|
|
93
|
-
c,
|
|
94
|
-
axis=axis,
|
|
95
|
-
name=name,
|
|
96
|
-
long_name=long_name,
|
|
97
|
-
standard_name=standard_name,
|
|
98
|
-
units=units,
|
|
99
|
-
)
|
|
100
|
-
if d is not None:
|
|
101
|
-
return d
|
|
102
|
-
|
|
103
|
-
d = self._is_time(
|
|
104
|
-
c,
|
|
105
|
-
axis=axis,
|
|
106
|
-
name=name,
|
|
107
|
-
long_name=long_name,
|
|
108
|
-
standard_name=standard_name,
|
|
109
|
-
units=units,
|
|
110
|
-
)
|
|
111
|
-
if d is not None:
|
|
112
|
-
return d
|
|
113
|
-
|
|
114
|
-
d = self._is_step(
|
|
115
|
-
c,
|
|
116
|
-
axis=axis,
|
|
117
|
-
name=name,
|
|
118
|
-
long_name=long_name,
|
|
119
|
-
standard_name=standard_name,
|
|
120
|
-
units=units,
|
|
121
|
-
)
|
|
122
|
-
if d is not None:
|
|
123
|
-
return d
|
|
124
|
-
|
|
125
|
-
d = self._is_date(
|
|
126
|
-
c,
|
|
127
|
-
axis=axis,
|
|
128
|
-
name=name,
|
|
129
|
-
long_name=long_name,
|
|
130
|
-
standard_name=standard_name,
|
|
131
|
-
units=units,
|
|
132
|
-
)
|
|
133
|
-
if d is not None:
|
|
134
|
-
return d
|
|
135
|
-
|
|
136
|
-
d = self._is_level(
|
|
137
|
-
c,
|
|
138
|
-
axis=axis,
|
|
139
|
-
name=name,
|
|
140
|
-
long_name=long_name,
|
|
141
|
-
standard_name=standard_name,
|
|
142
|
-
units=units,
|
|
143
|
-
)
|
|
144
|
-
if d is not None:
|
|
145
|
-
return d
|
|
146
|
-
|
|
147
|
-
d = self._is_number(
|
|
148
|
-
c,
|
|
149
|
-
axis=axis,
|
|
150
|
-
name=name,
|
|
151
|
-
long_name=long_name,
|
|
152
|
-
standard_name=standard_name,
|
|
153
|
-
units=units,
|
|
154
|
-
)
|
|
155
|
-
if d is not None:
|
|
156
|
-
return d
|
|
157
|
-
|
|
158
|
-
if c.shape in ((1,), tuple()):
|
|
159
|
-
return ScalarCoordinate(c)
|
|
160
|
-
|
|
161
|
-
LOG.warning(
|
|
162
|
-
f"Coordinate {coord} not supported\n{axis=}, {name=},"
|
|
163
|
-
f" {long_name=}, {standard_name=}, units\n\n{c}\n\n{type(c.values)} {c.shape}"
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
return UnsupportedCoordinate(c)
|
|
167
|
-
|
|
168
|
-
def grid(self, coordinates, variable):
|
|
169
|
-
lat = [c for c in coordinates if c.is_lat]
|
|
170
|
-
lon = [c for c in coordinates if c.is_lon]
|
|
171
|
-
|
|
172
|
-
if len(lat) == 1 and len(lon) == 1:
|
|
173
|
-
return self._lat_lon_provided(lat, lon, variable)
|
|
174
|
-
|
|
175
|
-
x = [c for c in coordinates if c.is_x]
|
|
176
|
-
y = [c for c in coordinates if c.is_y]
|
|
177
|
-
|
|
178
|
-
if len(x) == 1 and len(y) == 1:
|
|
179
|
-
return self._x_y_provided(x, y, variable)
|
|
180
|
-
|
|
181
|
-
raise NotImplementedError(f"Cannot establish grid {coordinates}")
|
|
182
|
-
|
|
183
|
-
def _check_dims(self, variable, x_or_lon, y_or_lat):
|
|
184
|
-
|
|
185
|
-
x_dims = set(x_or_lon.variable.dims)
|
|
186
|
-
y_dims = set(y_or_lat.variable.dims)
|
|
187
|
-
variable_dims = set(variable.dims)
|
|
188
|
-
|
|
189
|
-
if not (x_dims <= variable_dims) or not (y_dims <= variable_dims):
|
|
190
|
-
raise ValueError(
|
|
191
|
-
f"Dimensions do not match {variable.name}{variable.dims} !="
|
|
192
|
-
f" {x_or_lon.name}{x_or_lon.variable.dims} and {y_or_lat.name}{y_or_lat.variable.dims}"
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
variable_dims = tuple(v for v in variable.dims if v in (x_dims | y_dims))
|
|
196
|
-
if x_dims == y_dims:
|
|
197
|
-
# It's unstructured
|
|
198
|
-
return variable_dims, True
|
|
199
|
-
|
|
200
|
-
if len(x_dims) == 1 and len(y_dims) == 1:
|
|
201
|
-
# It's a mesh
|
|
202
|
-
return variable_dims, False
|
|
203
|
-
|
|
204
|
-
raise ValueError(
|
|
205
|
-
f"Cannot establish grid for {variable.name}{variable.dims},"
|
|
206
|
-
f" {x_or_lon.name}{x_or_lon.variable.dims},"
|
|
207
|
-
f" {y_or_lat.name}{y_or_lat.variable.dims}"
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
def _lat_lon_provided(self, lat, lon, variable):
|
|
211
|
-
lat = lat[0]
|
|
212
|
-
lon = lon[0]
|
|
213
|
-
|
|
214
|
-
dim_vars, unstructured = self._check_dims(variable, lon, lat)
|
|
215
|
-
|
|
216
|
-
if (lat.name, lon.name, dim_vars) in self._cache:
|
|
217
|
-
return self._cache[(lat.name, lon.name, dim_vars)]
|
|
218
|
-
|
|
219
|
-
if unstructured:
|
|
220
|
-
grid = UnstructuredGrid(lat, lon, dim_vars)
|
|
221
|
-
else:
|
|
222
|
-
grid = MeshedGrid(lat, lon, dim_vars)
|
|
223
|
-
|
|
224
|
-
self._cache[(lat.name, lon.name, dim_vars)] = grid
|
|
225
|
-
return grid
|
|
226
|
-
|
|
227
|
-
def _x_y_provided(self, x, y, variable):
|
|
228
|
-
x = x[0]
|
|
229
|
-
y = y[0]
|
|
230
|
-
|
|
231
|
-
dim_vars, unstructured = self._check_dims(variable, x, y)
|
|
232
|
-
|
|
233
|
-
if (x.name, y.name, dim_vars) in self._cache:
|
|
234
|
-
return self._cache[(x.name, y.name, dim_vars)]
|
|
235
|
-
|
|
236
|
-
grid_mapping = variable.attrs.get("grid_mapping", None)
|
|
237
|
-
if grid_mapping is not None:
|
|
238
|
-
print(f"grid_mapping: {grid_mapping}")
|
|
239
|
-
print(self.ds[grid_mapping])
|
|
240
|
-
|
|
241
|
-
if grid_mapping is None:
|
|
242
|
-
LOG.warning(f"No 'grid_mapping' attribute provided for '{variable.name}'")
|
|
243
|
-
LOG.warning("Trying to guess...")
|
|
244
|
-
|
|
245
|
-
PROBE = {
|
|
246
|
-
"prime_meridian_name",
|
|
247
|
-
"reference_ellipsoid_name",
|
|
248
|
-
"crs_wkt",
|
|
249
|
-
"horizontal_datum_name",
|
|
250
|
-
"semi_major_axis",
|
|
251
|
-
"spatial_ref",
|
|
252
|
-
"inverse_flattening",
|
|
253
|
-
"semi_minor_axis",
|
|
254
|
-
"geographic_crs_name",
|
|
255
|
-
"GeoTransform",
|
|
256
|
-
"grid_mapping_name",
|
|
257
|
-
"longitude_of_prime_meridian",
|
|
258
|
-
}
|
|
259
|
-
candidate = None
|
|
260
|
-
for v in self.ds.variables:
|
|
261
|
-
var = self.ds[v]
|
|
262
|
-
if not is_scalar(var):
|
|
263
|
-
continue
|
|
264
|
-
|
|
265
|
-
if PROBE.intersection(var.attrs.keys()):
|
|
266
|
-
if candidate:
|
|
267
|
-
raise ValueError(f"Multiple candidates for 'grid_mapping': {candidate} and {v}")
|
|
268
|
-
candidate = v
|
|
269
|
-
|
|
270
|
-
if candidate:
|
|
271
|
-
LOG.warning(f"Using '{candidate}' as 'grid_mapping'")
|
|
272
|
-
grid_mapping = candidate
|
|
273
|
-
else:
|
|
274
|
-
LOG.warning("Could not fine a candidate for 'grid_mapping'")
|
|
275
|
-
|
|
276
|
-
if grid_mapping is None:
|
|
277
|
-
if "crs" in self.ds[variable].attrs:
|
|
278
|
-
grid_mapping = self.ds[variable].attrs["crs"]
|
|
279
|
-
LOG.warning(f"Using CRS {grid_mapping} from variable '{variable.name}' attributes")
|
|
280
|
-
|
|
281
|
-
if grid_mapping is None:
|
|
282
|
-
if "crs" in self.ds.attrs:
|
|
283
|
-
grid_mapping = self.ds.attrs["crs"]
|
|
284
|
-
LOG.warning(f"Using CRS {grid_mapping} from global attributes")
|
|
285
|
-
|
|
286
|
-
grid = None
|
|
287
|
-
if grid_mapping is not None:
|
|
288
|
-
|
|
289
|
-
grid_mapping = dict(self.ds[grid_mapping].attrs)
|
|
290
|
-
|
|
291
|
-
if unstructured:
|
|
292
|
-
grid = UnstructuredProjectionGrid(x, y, grid_mapping)
|
|
293
|
-
else:
|
|
294
|
-
grid = MeshProjectionGrid(x, y, grid_mapping)
|
|
295
|
-
|
|
296
|
-
if grid is not None:
|
|
297
|
-
self._cache[(x.name, y.name, dim_vars)] = grid
|
|
298
|
-
return grid
|
|
299
|
-
|
|
300
|
-
LOG.error("Could not fine a candidate for 'grid_mapping'")
|
|
301
|
-
raise NotImplementedError(f"Unstructured grid {x.name} {y.name}")
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
class DefaultCoordinateGuesser(CoordinateGuesser):
|
|
305
|
-
def __init__(self, ds):
|
|
306
|
-
super().__init__(ds)
|
|
307
|
-
|
|
308
|
-
def _is_longitude(self, c, *, axis, name, long_name, standard_name, units):
|
|
309
|
-
if standard_name == "longitude":
|
|
310
|
-
return LongitudeCoordinate(c)
|
|
311
|
-
|
|
312
|
-
if long_name == "longitude" and units == "degrees_east":
|
|
313
|
-
return LongitudeCoordinate(c)
|
|
314
|
-
|
|
315
|
-
if name == "longitude": # WeatherBench
|
|
316
|
-
return LongitudeCoordinate(c)
|
|
317
|
-
|
|
318
|
-
def _is_latitude(self, c, *, axis, name, long_name, standard_name, units):
|
|
319
|
-
if standard_name == "latitude":
|
|
320
|
-
return LatitudeCoordinate(c)
|
|
321
|
-
|
|
322
|
-
if long_name == "latitude" and units == "degrees_north":
|
|
323
|
-
return LatitudeCoordinate(c)
|
|
324
|
-
|
|
325
|
-
if name == "latitude": # WeatherBench
|
|
326
|
-
return LatitudeCoordinate(c)
|
|
327
|
-
|
|
328
|
-
def _is_x(self, c, *, axis, name, long_name, standard_name, units):
|
|
329
|
-
if standard_name == "projection_x_coordinate":
|
|
330
|
-
return XCoordinate(c)
|
|
331
|
-
|
|
332
|
-
if name == "x":
|
|
333
|
-
return XCoordinate(c)
|
|
334
|
-
|
|
335
|
-
def _is_y(self, c, *, axis, name, long_name, standard_name, units):
|
|
336
|
-
if standard_name == "projection_y_coordinate":
|
|
337
|
-
return YCoordinate(c)
|
|
338
|
-
|
|
339
|
-
if name == "y":
|
|
340
|
-
return YCoordinate(c)
|
|
341
|
-
|
|
342
|
-
def _is_time(self, c, *, axis, name, long_name, standard_name, units):
|
|
343
|
-
if standard_name == "time":
|
|
344
|
-
return TimeCoordinate(c)
|
|
345
|
-
|
|
346
|
-
# That is the output of `cfgrib` for forecasts
|
|
347
|
-
if name == "time" and standard_name != "forecast_reference_time":
|
|
348
|
-
return TimeCoordinate(c)
|
|
349
|
-
|
|
350
|
-
def _is_date(self, c, *, axis, name, long_name, standard_name, units):
|
|
351
|
-
if standard_name == "forecast_reference_time":
|
|
352
|
-
return DateCoordinate(c)
|
|
353
|
-
|
|
354
|
-
if name == "forecast_reference_time":
|
|
355
|
-
return DateCoordinate(c)
|
|
356
|
-
|
|
357
|
-
def _is_step(self, c, *, axis, name, long_name, standard_name, units):
|
|
358
|
-
if standard_name == "forecast_period":
|
|
359
|
-
return StepCoordinate(c)
|
|
360
|
-
|
|
361
|
-
if long_name == "time elapsed since the start of the forecast":
|
|
362
|
-
return StepCoordinate(c)
|
|
363
|
-
|
|
364
|
-
if name == "prediction_timedelta": # WeatherBench
|
|
365
|
-
return StepCoordinate(c)
|
|
366
|
-
|
|
367
|
-
def _is_level(self, c, *, axis, name, long_name, standard_name, units):
|
|
368
|
-
if standard_name == "atmosphere_hybrid_sigma_pressure_coordinate":
|
|
369
|
-
return LevelCoordinate(c, "ml")
|
|
370
|
-
|
|
371
|
-
if long_name == "height" and units == "m":
|
|
372
|
-
return LevelCoordinate(c, "height")
|
|
373
|
-
|
|
374
|
-
if standard_name == "air_pressure" and units == "hPa":
|
|
375
|
-
return LevelCoordinate(c, "pl")
|
|
376
|
-
|
|
377
|
-
if name == "level":
|
|
378
|
-
return LevelCoordinate(c, "pl")
|
|
379
|
-
|
|
380
|
-
if name == "vertical" and units == "hPa":
|
|
381
|
-
return LevelCoordinate(c, "pl")
|
|
382
|
-
|
|
383
|
-
if standard_name == "depth":
|
|
384
|
-
return LevelCoordinate(c, "depth")
|
|
385
|
-
|
|
386
|
-
if name == "vertical" and units == "hPa":
|
|
387
|
-
return LevelCoordinate(c, "pl")
|
|
388
|
-
|
|
389
|
-
def _is_number(self, c, *, axis, name, long_name, standard_name, units):
|
|
390
|
-
if name in ("realization", "number"):
|
|
391
|
-
return EnsembleCoordinate(c)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
class FlavourCoordinateGuesser(CoordinateGuesser):
|
|
395
|
-
def __init__(self, ds, flavour):
|
|
396
|
-
super().__init__(ds)
|
|
397
|
-
self.flavour = flavour
|
|
398
|
-
|
|
399
|
-
def _match(self, c, key, values):
|
|
400
|
-
|
|
401
|
-
if key not in self.flavour["rules"]:
|
|
402
|
-
return None
|
|
403
|
-
|
|
404
|
-
rules = self.flavour["rules"][key]
|
|
405
|
-
|
|
406
|
-
if not isinstance(rules, list):
|
|
407
|
-
rules = [rules]
|
|
408
|
-
|
|
409
|
-
for rule in rules:
|
|
410
|
-
ok = True
|
|
411
|
-
for k, v in rule.items():
|
|
412
|
-
if isinstance(v, str) and values.get(k) != v:
|
|
413
|
-
ok = False
|
|
414
|
-
if ok:
|
|
415
|
-
return rule
|
|
416
|
-
|
|
417
|
-
return None
|
|
418
|
-
|
|
419
|
-
def _is_longitude(self, c, *, axis, name, long_name, standard_name, units):
|
|
420
|
-
if self._match(c, "longitude", locals()):
|
|
421
|
-
return LongitudeCoordinate(c)
|
|
422
|
-
|
|
423
|
-
def _is_latitude(self, c, *, axis, name, long_name, standard_name, units):
|
|
424
|
-
if self._match(c, "latitude", locals()):
|
|
425
|
-
return LatitudeCoordinate(c)
|
|
426
|
-
|
|
427
|
-
def _is_x(self, c, *, axis, name, long_name, standard_name, units):
|
|
428
|
-
if self._match(c, "x", locals()):
|
|
429
|
-
return XCoordinate(c)
|
|
430
|
-
|
|
431
|
-
def _is_y(self, c, *, axis, name, long_name, standard_name, units):
|
|
432
|
-
if self._match(c, "y", locals()):
|
|
433
|
-
return YCoordinate(c)
|
|
434
|
-
|
|
435
|
-
def _is_time(self, c, *, axis, name, long_name, standard_name, units):
|
|
436
|
-
if self._match(c, "time", locals()):
|
|
437
|
-
return TimeCoordinate(c)
|
|
438
|
-
|
|
439
|
-
def _is_step(self, c, *, axis, name, long_name, standard_name, units):
|
|
440
|
-
if self._match(c, "step", locals()):
|
|
441
|
-
return StepCoordinate(c)
|
|
442
|
-
|
|
443
|
-
def _is_date(self, c, *, axis, name, long_name, standard_name, units):
|
|
444
|
-
if self._match(c, "date", locals()):
|
|
445
|
-
return DateCoordinate(c)
|
|
446
|
-
|
|
447
|
-
def _is_level(self, c, *, axis, name, long_name, standard_name, units):
|
|
448
|
-
|
|
449
|
-
rule = self._match(c, "level", locals())
|
|
450
|
-
if rule:
|
|
451
|
-
# assert False, rule
|
|
452
|
-
return LevelCoordinate(
|
|
453
|
-
c,
|
|
454
|
-
self._levtype(
|
|
455
|
-
c,
|
|
456
|
-
axis=axis,
|
|
457
|
-
name=name,
|
|
458
|
-
long_name=long_name,
|
|
459
|
-
standard_name=standard_name,
|
|
460
|
-
units=units,
|
|
461
|
-
),
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
def _levtype(self, c, *, axis, name, long_name, standard_name, units):
|
|
465
|
-
if "levtype" in self.flavour:
|
|
466
|
-
return self.flavour["levtype"]
|
|
467
|
-
|
|
468
|
-
raise NotImplementedError(f"levtype for {c=}")
|
|
469
|
-
|
|
470
|
-
def _is_number(self, c, *, axis, name, long_name, standard_name, units):
|
|
471
|
-
if self._match(c, "number", locals()):
|
|
472
|
-
return DateCoordinate(c)
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
from functools import cached_property
|
|
13
|
-
|
|
14
|
-
from anemoi.utils.dates import as_datetime
|
|
15
|
-
from earthkit.data.core.geography import Geography
|
|
16
|
-
from earthkit.data.core.metadata import RawMetadata
|
|
17
|
-
from earthkit.data.utils.projections import Projection
|
|
18
|
-
|
|
19
|
-
LOG = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class _MDMapping:
|
|
23
|
-
|
|
24
|
-
def __init__(self, variable):
|
|
25
|
-
self.variable = variable
|
|
26
|
-
self.time = variable.time
|
|
27
|
-
# Aliases
|
|
28
|
-
self.mapping = dict(param="variable")
|
|
29
|
-
for c in variable.coordinates:
|
|
30
|
-
for v in c.mars_names:
|
|
31
|
-
assert v not in self.mapping, f"Duplicate key '{v}' in {c}"
|
|
32
|
-
self.mapping[v] = c.variable.name
|
|
33
|
-
|
|
34
|
-
def _from_user(self, key):
|
|
35
|
-
return self.mapping.get(key, key)
|
|
36
|
-
|
|
37
|
-
def from_user(self, kwargs):
|
|
38
|
-
return {self._from_user(k): v for k, v in kwargs.items()}
|
|
39
|
-
|
|
40
|
-
def __repr__(self):
|
|
41
|
-
return f"MDMapping({self.mapping})"
|
|
42
|
-
|
|
43
|
-
def fill_time_metadata(self, field, md):
|
|
44
|
-
valid_datetime = self.variable.time.fill_time_metadata(field._md, md)
|
|
45
|
-
if valid_datetime is not None:
|
|
46
|
-
md["valid_datetime"] = as_datetime(valid_datetime).isoformat()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class XArrayMetadata(RawMetadata):
|
|
50
|
-
LS_KEYS = ["variable", "level", "valid_datetime", "units"]
|
|
51
|
-
NAMESPACES = ["default", "mars"]
|
|
52
|
-
MARS_KEYS = ["param", "step", "levelist", "levtype", "number", "date", "time"]
|
|
53
|
-
|
|
54
|
-
def __init__(self, field):
|
|
55
|
-
self._field = field
|
|
56
|
-
md = field._md.copy()
|
|
57
|
-
self._mapping = _MDMapping(field.owner)
|
|
58
|
-
self._mapping.fill_time_metadata(field, md)
|
|
59
|
-
super().__init__(md)
|
|
60
|
-
|
|
61
|
-
@cached_property
|
|
62
|
-
def geography(self):
|
|
63
|
-
return XArrayFieldGeography(self._field, self._field.owner.grid)
|
|
64
|
-
|
|
65
|
-
def as_namespace(self, namespace=None):
|
|
66
|
-
if not isinstance(namespace, str) and namespace is not None:
|
|
67
|
-
raise TypeError("namespace must be a str or None")
|
|
68
|
-
|
|
69
|
-
if namespace == "default" or namespace == "" or namespace is None:
|
|
70
|
-
return dict(self)
|
|
71
|
-
|
|
72
|
-
elif namespace == "mars":
|
|
73
|
-
return self._as_mars()
|
|
74
|
-
|
|
75
|
-
def _as_mars(self):
|
|
76
|
-
return {}
|
|
77
|
-
|
|
78
|
-
def _base_datetime(self):
|
|
79
|
-
return self._field.forecast_reference_time
|
|
80
|
-
|
|
81
|
-
def _valid_datetime(self):
|
|
82
|
-
return self._get("valid_datetime")
|
|
83
|
-
|
|
84
|
-
def get(self, key, astype=None, **kwargs):
|
|
85
|
-
|
|
86
|
-
if key in self._d:
|
|
87
|
-
if astype is not None:
|
|
88
|
-
return astype(self._d[key])
|
|
89
|
-
return self._d[key]
|
|
90
|
-
|
|
91
|
-
key = self._mapping._from_user(key)
|
|
92
|
-
|
|
93
|
-
return super().get(key, astype=astype, **kwargs)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class XArrayFieldGeography(Geography):
|
|
97
|
-
def __init__(self, field, grid):
|
|
98
|
-
self._field = field
|
|
99
|
-
self._grid = grid
|
|
100
|
-
|
|
101
|
-
def _unique_grid_id(self):
|
|
102
|
-
raise NotImplementedError()
|
|
103
|
-
|
|
104
|
-
def bounding_box(self):
|
|
105
|
-
raise NotImplementedError()
|
|
106
|
-
# return BoundingBox(north=self.north, south=self.south, east=self.east, west=self.west)
|
|
107
|
-
|
|
108
|
-
def gridspec(self):
|
|
109
|
-
raise NotImplementedError()
|
|
110
|
-
|
|
111
|
-
def latitudes(self, dtype=None):
|
|
112
|
-
result = self._grid.latitudes
|
|
113
|
-
if dtype is not None:
|
|
114
|
-
return result.astype(dtype)
|
|
115
|
-
return result
|
|
116
|
-
|
|
117
|
-
def longitudes(self, dtype=None):
|
|
118
|
-
result = self._grid.longitudes
|
|
119
|
-
if dtype is not None:
|
|
120
|
-
return result.astype(dtype)
|
|
121
|
-
return result
|
|
122
|
-
|
|
123
|
-
def resolution(self):
|
|
124
|
-
# TODO: implement resolution
|
|
125
|
-
return None
|
|
126
|
-
|
|
127
|
-
# @property
|
|
128
|
-
def mars_grid(self):
|
|
129
|
-
# TODO: implement mars_grid
|
|
130
|
-
return None
|
|
131
|
-
|
|
132
|
-
# @property
|
|
133
|
-
def mars_area(self):
|
|
134
|
-
# TODO: code me
|
|
135
|
-
# return [self.north, self.west, self.south, self.east]
|
|
136
|
-
return None
|
|
137
|
-
|
|
138
|
-
def x(self, dtype=None):
|
|
139
|
-
raise NotImplementedError()
|
|
140
|
-
|
|
141
|
-
def y(self, dtype=None):
|
|
142
|
-
raise NotImplementedError()
|
|
143
|
-
|
|
144
|
-
def shape(self):
|
|
145
|
-
return self._field.shape
|
|
146
|
-
|
|
147
|
-
def projection(self):
|
|
148
|
-
return Projection.from_cf_grid_mapping(**self._field.grid_mapping)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
|
|
13
|
-
LOG = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def patch_attributes(ds, attributes):
|
|
17
|
-
for name, value in attributes.items():
|
|
18
|
-
variable = ds[name]
|
|
19
|
-
variable.attrs.update(value)
|
|
20
|
-
|
|
21
|
-
return ds
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def patch_coordinates(ds, coordinates):
|
|
25
|
-
for name in coordinates:
|
|
26
|
-
ds = ds.assign_coords({name: ds[name]})
|
|
27
|
-
|
|
28
|
-
return ds
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
PATCHES = {
|
|
32
|
-
"attributes": patch_attributes,
|
|
33
|
-
"coordinates": patch_coordinates,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def patch_dataset(ds, patch):
|
|
38
|
-
for what, values in patch.items():
|
|
39
|
-
if what not in PATCHES:
|
|
40
|
-
raise ValueError(f"Unknown patch type {what!r}")
|
|
41
|
-
|
|
42
|
-
ds = PATCHES[what](ds, values)
|
|
43
|
-
|
|
44
|
-
return ds
|