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.
Files changed (155) hide show
  1. anemoi/datasets/__init__.py +4 -1
  2. anemoi/datasets/__main__.py +12 -2
  3. anemoi/datasets/_version.py +9 -4
  4. anemoi/datasets/commands/cleanup.py +17 -2
  5. anemoi/datasets/commands/compare.py +18 -2
  6. anemoi/datasets/commands/copy.py +196 -14
  7. anemoi/datasets/commands/create.py +50 -7
  8. anemoi/datasets/commands/finalise-additions.py +17 -2
  9. anemoi/datasets/commands/finalise.py +17 -2
  10. anemoi/datasets/commands/init-additions.py +17 -2
  11. anemoi/datasets/commands/init.py +16 -2
  12. anemoi/datasets/commands/inspect.py +283 -62
  13. anemoi/datasets/commands/load-additions.py +16 -2
  14. anemoi/datasets/commands/load.py +16 -2
  15. anemoi/datasets/commands/patch.py +17 -2
  16. anemoi/datasets/commands/publish.py +17 -2
  17. anemoi/datasets/commands/scan.py +31 -3
  18. anemoi/datasets/compute/recentre.py +47 -11
  19. anemoi/datasets/create/__init__.py +612 -85
  20. anemoi/datasets/create/check.py +142 -20
  21. anemoi/datasets/create/chunks.py +64 -4
  22. anemoi/datasets/create/config.py +185 -21
  23. anemoi/datasets/create/filter.py +50 -0
  24. anemoi/datasets/create/filters/__init__.py +33 -0
  25. anemoi/datasets/create/filters/empty.py +37 -0
  26. anemoi/datasets/create/filters/legacy.py +93 -0
  27. anemoi/datasets/create/filters/noop.py +37 -0
  28. anemoi/datasets/create/filters/orog_to_z.py +58 -0
  29. anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
  30. anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
  31. anemoi/datasets/create/filters/rename.py +205 -0
  32. anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
  33. anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
  34. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
  35. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
  36. anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
  37. anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
  38. anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
  39. anemoi/datasets/create/filters/transform.py +53 -0
  40. anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
  41. anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
  42. anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
  43. anemoi/datasets/create/input/__init__.py +76 -5
  44. anemoi/datasets/create/input/action.py +149 -13
  45. anemoi/datasets/create/input/concat.py +81 -10
  46. anemoi/datasets/create/input/context.py +39 -4
  47. anemoi/datasets/create/input/data_sources.py +72 -6
  48. anemoi/datasets/create/input/empty.py +21 -3
  49. anemoi/datasets/create/input/filter.py +60 -12
  50. anemoi/datasets/create/input/function.py +154 -37
  51. anemoi/datasets/create/input/join.py +86 -14
  52. anemoi/datasets/create/input/misc.py +67 -17
  53. anemoi/datasets/create/input/pipe.py +33 -6
  54. anemoi/datasets/create/input/repeated_dates.py +189 -41
  55. anemoi/datasets/create/input/result.py +202 -87
  56. anemoi/datasets/create/input/step.py +119 -22
  57. anemoi/datasets/create/input/template.py +100 -13
  58. anemoi/datasets/create/input/trace.py +62 -7
  59. anemoi/datasets/create/patch.py +52 -4
  60. anemoi/datasets/create/persistent.py +134 -17
  61. anemoi/datasets/create/size.py +15 -1
  62. anemoi/datasets/create/source.py +51 -0
  63. anemoi/datasets/create/sources/__init__.py +36 -0
  64. anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
  65. anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
  66. anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
  67. anemoi/datasets/create/sources/empty.py +37 -0
  68. anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
  69. anemoi/datasets/create/sources/grib.py +297 -0
  70. anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
  71. anemoi/datasets/create/sources/legacy.py +93 -0
  72. anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
  73. anemoi/datasets/create/sources/netcdf.py +42 -0
  74. anemoi/datasets/create/sources/opendap.py +43 -0
  75. anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
  76. anemoi/datasets/create/sources/recentre.py +150 -0
  77. anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
  78. anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
  79. anemoi/datasets/create/sources/xarray.py +92 -0
  80. anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
  81. anemoi/datasets/create/sources/xarray_support/README.md +1 -0
  82. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
  83. anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
  84. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
  85. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
  86. anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
  87. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
  88. anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
  89. anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
  90. anemoi/datasets/create/sources/xarray_support/time.py +391 -0
  91. anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
  92. anemoi/datasets/create/sources/xarray_zarr.py +41 -0
  93. anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
  94. anemoi/datasets/create/statistics/__init__.py +233 -44
  95. anemoi/datasets/create/statistics/summary.py +52 -6
  96. anemoi/datasets/create/testing.py +76 -0
  97. anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
  98. anemoi/datasets/create/utils.py +97 -6
  99. anemoi/datasets/create/writer.py +26 -4
  100. anemoi/datasets/create/zarr.py +170 -23
  101. anemoi/datasets/data/__init__.py +51 -4
  102. anemoi/datasets/data/complement.py +191 -40
  103. anemoi/datasets/data/concat.py +141 -16
  104. anemoi/datasets/data/dataset.py +552 -61
  105. anemoi/datasets/data/debug.py +197 -26
  106. anemoi/datasets/data/ensemble.py +93 -8
  107. anemoi/datasets/data/fill_missing.py +165 -18
  108. anemoi/datasets/data/forwards.py +428 -56
  109. anemoi/datasets/data/grids.py +323 -97
  110. anemoi/datasets/data/indexing.py +112 -19
  111. anemoi/datasets/data/interpolate.py +92 -12
  112. anemoi/datasets/data/join.py +158 -19
  113. anemoi/datasets/data/masked.py +129 -15
  114. anemoi/datasets/data/merge.py +137 -23
  115. anemoi/datasets/data/misc.py +172 -16
  116. anemoi/datasets/data/missing.py +233 -29
  117. anemoi/datasets/data/rescale.py +111 -10
  118. anemoi/datasets/data/select.py +168 -26
  119. anemoi/datasets/data/statistics.py +67 -6
  120. anemoi/datasets/data/stores.py +149 -64
  121. anemoi/datasets/data/subset.py +159 -25
  122. anemoi/datasets/data/unchecked.py +168 -57
  123. anemoi/datasets/data/xy.py +168 -25
  124. anemoi/datasets/dates/__init__.py +191 -16
  125. anemoi/datasets/dates/groups.py +189 -47
  126. anemoi/datasets/grids.py +270 -31
  127. anemoi/datasets/testing.py +28 -1
  128. {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +10 -7
  129. anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
  131. {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +1 -1
  132. anemoi/datasets/create/functions/__init__.py +0 -66
  133. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  134. anemoi/datasets/create/functions/filters/empty.py +0 -17
  135. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  136. anemoi/datasets/create/functions/filters/rename.py +0 -79
  137. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  138. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  139. anemoi/datasets/create/functions/sources/empty.py +0 -15
  140. anemoi/datasets/create/functions/sources/grib.py +0 -150
  141. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  142. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  143. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  144. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  145. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  146. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  147. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  148. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  149. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  150. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  151. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  152. anemoi/datasets/utils/fields.py +0 -47
  153. anemoi_datasets-0.5.15.dist-info/RECORD +0 -129
  154. {anemoi_datasets-0.5.15.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
  155. {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