anemoi-datasets 0.5.16__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.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
- anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +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.16.dist-info/RECORD +0 -129
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
|
@@ -9,45 +9,83 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
+
from abc import ABC
|
|
13
|
+
from abc import abstractmethod
|
|
12
14
|
from functools import cached_property
|
|
15
|
+
from typing import Any
|
|
16
|
+
from typing import Tuple
|
|
13
17
|
|
|
14
18
|
import numpy as np
|
|
15
19
|
|
|
16
20
|
LOG = logging.getLogger(__name__)
|
|
17
21
|
|
|
18
22
|
|
|
19
|
-
class Grid:
|
|
23
|
+
class Grid(ABC):
|
|
24
|
+
"""Abstract base class for grid structures."""
|
|
20
25
|
|
|
21
|
-
def __init__(self):
|
|
26
|
+
def __init__(self) -> None:
|
|
22
27
|
pass
|
|
23
28
|
|
|
24
29
|
@property
|
|
25
|
-
def latitudes(self):
|
|
30
|
+
def latitudes(self) -> Any:
|
|
31
|
+
"""Get the latitudes of the grid."""
|
|
26
32
|
return self.grid_points[0]
|
|
27
33
|
|
|
28
34
|
@property
|
|
29
|
-
def longitudes(self):
|
|
35
|
+
def longitudes(self) -> Any:
|
|
36
|
+
"""Get the longitudes of the grid."""
|
|
30
37
|
return self.grid_points[1]
|
|
31
38
|
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def grid_points(self) -> Tuple[Any, Any]:
|
|
42
|
+
"""Get the grid points."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
32
45
|
|
|
33
46
|
class LatLonGrid(Grid):
|
|
34
|
-
|
|
47
|
+
"""Grid class for latitude and longitude coordinates."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, lat: Any, lon: Any, variable_dims: Any) -> None:
|
|
50
|
+
"""Initialize the LatLonGrid class.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
lat : Any
|
|
55
|
+
The latitudes.
|
|
56
|
+
lon : Any
|
|
57
|
+
The longitudes.
|
|
58
|
+
variable_dims : Any
|
|
59
|
+
The variable dimensions.
|
|
60
|
+
"""
|
|
35
61
|
super().__init__()
|
|
36
62
|
self.lat = lat
|
|
37
63
|
self.lon = lon
|
|
38
64
|
|
|
39
65
|
|
|
40
66
|
class XYGrid(Grid):
|
|
41
|
-
|
|
67
|
+
"""Grid class for x and y coordinates."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, x: Any, y: Any) -> None:
|
|
70
|
+
"""Initialize the XYGrid class.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
x : Any
|
|
75
|
+
The x-coordinates.
|
|
76
|
+
y : Any
|
|
77
|
+
The y-coordinates.
|
|
78
|
+
"""
|
|
42
79
|
self.x = x
|
|
43
80
|
self.y = y
|
|
44
81
|
|
|
45
82
|
|
|
46
83
|
class MeshedGrid(LatLonGrid):
|
|
84
|
+
"""Grid class for meshed latitude and longitude coordinates."""
|
|
47
85
|
|
|
48
86
|
@cached_property
|
|
49
|
-
def grid_points(self):
|
|
50
|
-
|
|
87
|
+
def grid_points(self) -> Tuple[Any, Any]:
|
|
88
|
+
"""Get the grid points for the meshed grid."""
|
|
51
89
|
lat, lon = np.meshgrid(
|
|
52
90
|
self.lat.variable.values,
|
|
53
91
|
self.lon.variable.values,
|
|
@@ -57,8 +95,20 @@ class MeshedGrid(LatLonGrid):
|
|
|
57
95
|
|
|
58
96
|
|
|
59
97
|
class UnstructuredGrid(LatLonGrid):
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
"""Grid class for unstructured latitude and longitude coordinates."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, lat: Any, lon: Any, variable_dims: Any) -> None:
|
|
101
|
+
"""Initialize the UnstructuredGrid class.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
lat : Any
|
|
106
|
+
The latitudes.
|
|
107
|
+
lon : Any
|
|
108
|
+
The longitudes.
|
|
109
|
+
variable_dims : Any
|
|
110
|
+
The variable dimensions.
|
|
111
|
+
"""
|
|
62
112
|
super().__init__(lat, lon, variable_dims)
|
|
63
113
|
assert len(lat) == len(lon), (len(lat), len(lon))
|
|
64
114
|
self.variable_dims = variable_dims
|
|
@@ -67,8 +117,8 @@ class UnstructuredGrid(LatLonGrid):
|
|
|
67
117
|
assert set(self.variable_dims) == set(self.grid_dims), (self.variable_dims, self.grid_dims)
|
|
68
118
|
|
|
69
119
|
@cached_property
|
|
70
|
-
def grid_points(self):
|
|
71
|
-
|
|
120
|
+
def grid_points(self) -> Tuple[Any, Any]:
|
|
121
|
+
"""Get the grid points for the unstructured grid."""
|
|
72
122
|
assert 1 <= len(self.variable_dims) <= 2
|
|
73
123
|
|
|
74
124
|
if len(self.variable_dims) == 1:
|
|
@@ -88,11 +138,31 @@ class UnstructuredGrid(LatLonGrid):
|
|
|
88
138
|
|
|
89
139
|
|
|
90
140
|
class ProjectionGrid(XYGrid):
|
|
91
|
-
|
|
141
|
+
"""Grid class for projected x and y coordinates."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, x: Any, y: Any, projection: Any) -> None:
|
|
144
|
+
"""Initialize the ProjectionGrid class.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
x : Any
|
|
149
|
+
The x-coordinates.
|
|
150
|
+
y : Any
|
|
151
|
+
The y-coordinates.
|
|
152
|
+
projection : Any
|
|
153
|
+
The projection information.
|
|
154
|
+
"""
|
|
92
155
|
super().__init__(x, y)
|
|
93
156
|
self.projection = projection
|
|
94
157
|
|
|
95
|
-
def transformer(self):
|
|
158
|
+
def transformer(self) -> Any:
|
|
159
|
+
"""Get the transformer for the projection.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Any
|
|
164
|
+
The transformer.
|
|
165
|
+
"""
|
|
96
166
|
from pyproj import CRS
|
|
97
167
|
from pyproj import Transformer
|
|
98
168
|
|
|
@@ -107,10 +177,11 @@ class ProjectionGrid(XYGrid):
|
|
|
107
177
|
|
|
108
178
|
|
|
109
179
|
class MeshProjectionGrid(ProjectionGrid):
|
|
180
|
+
"""Grid class for meshed projected coordinates."""
|
|
110
181
|
|
|
111
182
|
@cached_property
|
|
112
|
-
def grid_points(self):
|
|
113
|
-
|
|
183
|
+
def grid_points(self) -> Tuple[Any, Any]:
|
|
184
|
+
"""Get the grid points for the mesh projection grid."""
|
|
114
185
|
transformer = self.transformer()
|
|
115
186
|
xv, yv = np.meshgrid(self.x.variable.values, self.y.variable.values) # , indexing="ij")
|
|
116
187
|
lon, lat = transformer.transform(xv, yv)
|
|
@@ -118,19 +189,9 @@ class MeshProjectionGrid(ProjectionGrid):
|
|
|
118
189
|
|
|
119
190
|
|
|
120
191
|
class UnstructuredProjectionGrid(XYGrid):
|
|
121
|
-
|
|
122
|
-
def grid_points(self):
|
|
123
|
-
assert False, "Not implemented"
|
|
124
|
-
|
|
125
|
-
# lat, lon = transformer.transform(
|
|
126
|
-
# self.y.variable.values.flatten(),
|
|
127
|
-
# self.x.variable.values.flatten(),
|
|
128
|
-
|
|
129
|
-
# )
|
|
192
|
+
"""Grid class for unstructured projected coordinates."""
|
|
130
193
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# return np.meshgrid(lat, lon)
|
|
194
|
+
@cached_property
|
|
195
|
+
def grid_points(self) -> Tuple[Any, Any]:
|
|
196
|
+
"""Get the grid points for the unstructured projection grid."""
|
|
197
|
+
raise NotImplementedError("UnstructuredProjectionGrid")
|
|
@@ -0,0 +1,395 @@
|
|
|
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 datetime
|
|
12
|
+
import logging
|
|
13
|
+
from functools import cached_property
|
|
14
|
+
from typing import Any
|
|
15
|
+
from typing import Dict
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from anemoi.utils.dates import as_datetime
|
|
19
|
+
from earthkit.data.core.geography import Geography
|
|
20
|
+
from earthkit.data.core.metadata import RawMetadata
|
|
21
|
+
from earthkit.data.utils.projections import Projection
|
|
22
|
+
|
|
23
|
+
LOG = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _MDMapping:
|
|
27
|
+
"""A class to handle metadata mapping for variables.
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
variable : Any
|
|
32
|
+
The variable to map.
|
|
33
|
+
time : Any
|
|
34
|
+
The time associated with the variable.
|
|
35
|
+
mapping : Dict[str, str]
|
|
36
|
+
A dictionary mapping keys to variable names.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, variable: Any) -> None:
|
|
40
|
+
"""Initialize the _MDMapping class.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
variable : Any
|
|
45
|
+
The variable to map.
|
|
46
|
+
"""
|
|
47
|
+
self.variable = variable
|
|
48
|
+
self.time = variable.time
|
|
49
|
+
self.mapping = dict()
|
|
50
|
+
# Aliases
|
|
51
|
+
|
|
52
|
+
def _from_user(self, key: str) -> str:
|
|
53
|
+
"""Get the internal key corresponding to a user-provided key.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
key : str
|
|
58
|
+
The user-provided key.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
str
|
|
63
|
+
The internal key corresponding to the user-provided key.
|
|
64
|
+
"""
|
|
65
|
+
return self.mapping.get(key, key)
|
|
66
|
+
|
|
67
|
+
def from_user(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
|
68
|
+
"""Convert user-provided keys to internal keys.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
kwargs : Dict[str, Any]
|
|
73
|
+
A dictionary of user-provided keys and values.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
Dict[str, Any]
|
|
78
|
+
A dictionary with internal keys and original values.
|
|
79
|
+
"""
|
|
80
|
+
return {self._from_user(k): v for k, v in kwargs.items()}
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
"""Return a string representation of the _MDMapping object.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
str
|
|
88
|
+
String representation of the _MDMapping object.
|
|
89
|
+
"""
|
|
90
|
+
return f"MDMapping({self.mapping})"
|
|
91
|
+
|
|
92
|
+
def fill_time_metadata(self, field: Any, md: Dict[str, Any]) -> None:
|
|
93
|
+
"""Fill the time metadata for a field.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
field : Any
|
|
98
|
+
The field to fill metadata for.
|
|
99
|
+
md : Dict[str, Any]
|
|
100
|
+
The metadata dictionary to update.
|
|
101
|
+
"""
|
|
102
|
+
valid_datetime = self.variable.time.fill_time_metadata(field._md, md)
|
|
103
|
+
if valid_datetime is not None:
|
|
104
|
+
md["valid_datetime"] = as_datetime(valid_datetime).isoformat()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class XArrayMetadata(RawMetadata):
|
|
108
|
+
"""A class to handle metadata for XArray fields.
|
|
109
|
+
|
|
110
|
+
Attributes
|
|
111
|
+
----------
|
|
112
|
+
LS_KEYS : List[str]
|
|
113
|
+
List of keys for the metadata.
|
|
114
|
+
NAMESPACES : List[str]
|
|
115
|
+
List of namespaces for the metadata.
|
|
116
|
+
MARS_KEYS : List[str]
|
|
117
|
+
List of MARS keys for the metadata.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
LS_KEYS = ["variable", "level", "valid_datetime", "units"]
|
|
121
|
+
NAMESPACES = ["default", "mars"]
|
|
122
|
+
MARS_KEYS = ["param", "step", "levelist", "levtype", "number", "date", "time"]
|
|
123
|
+
|
|
124
|
+
def __init__(self, field: Any) -> None:
|
|
125
|
+
"""Initialize the XArrayMetadata class.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
field : Any
|
|
130
|
+
The field to extract metadata from.
|
|
131
|
+
"""
|
|
132
|
+
self._field = field
|
|
133
|
+
md = field._md.copy()
|
|
134
|
+
self._mapping = _MDMapping(field.owner)
|
|
135
|
+
self._mapping.fill_time_metadata(field, md)
|
|
136
|
+
super().__init__(md)
|
|
137
|
+
|
|
138
|
+
@cached_property
|
|
139
|
+
def geography(self) -> "XArrayFieldGeography":
|
|
140
|
+
"""Get the geography information for the field."""
|
|
141
|
+
return XArrayFieldGeography(self._field, self._field.owner.grid)
|
|
142
|
+
|
|
143
|
+
def as_namespace(self, namespace: Optional[str] = None) -> Dict[str, Any]:
|
|
144
|
+
"""Get the metadata as a specific namespace.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
namespace : Optional[str]
|
|
149
|
+
The namespace to use.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
Dict[str, Any]
|
|
154
|
+
The metadata in the specified namespace.
|
|
155
|
+
"""
|
|
156
|
+
if not isinstance(namespace, str) and namespace is not None:
|
|
157
|
+
raise TypeError("namespace must be a str or None")
|
|
158
|
+
|
|
159
|
+
if namespace == "default" or namespace == "" or namespace is None:
|
|
160
|
+
return dict(self)
|
|
161
|
+
|
|
162
|
+
elif namespace == "mars":
|
|
163
|
+
return self._as_mars()
|
|
164
|
+
|
|
165
|
+
def _as_mars(self) -> Dict[str, Any]:
|
|
166
|
+
"""Get the metadata as MARS namespace.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
Dict[str, Any]
|
|
171
|
+
The metadata in the MARS namespace.
|
|
172
|
+
"""
|
|
173
|
+
return {}
|
|
174
|
+
|
|
175
|
+
def _base_datetime(self) -> Optional[datetime.datetime]:
|
|
176
|
+
"""Get the base datetime for the field.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
Optional[datetime.datetime]
|
|
181
|
+
The base datetime for the field.
|
|
182
|
+
"""
|
|
183
|
+
return self._field.forecast_reference_time
|
|
184
|
+
|
|
185
|
+
def _valid_datetime(self) -> Optional[datetime.datetime]:
|
|
186
|
+
"""Get the valid datetime for the field.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
Optional[datetime.datetime]
|
|
191
|
+
The valid datetime for the field.
|
|
192
|
+
"""
|
|
193
|
+
return self._get("valid_datetime")
|
|
194
|
+
|
|
195
|
+
def get(self, key: str, astype: Optional[type] = None, **kwargs: Any) -> Any:
|
|
196
|
+
"""Get a metadata value by key.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
key : str
|
|
201
|
+
The key to get the value for.
|
|
202
|
+
astype : Optional[type]
|
|
203
|
+
The type to cast the value to.
|
|
204
|
+
**kwargs : Any
|
|
205
|
+
Additional keyword arguments.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
Any
|
|
210
|
+
The value for the specified key, optionally cast to the specified type.
|
|
211
|
+
"""
|
|
212
|
+
if key in self._d:
|
|
213
|
+
if astype is not None:
|
|
214
|
+
return astype(self._d[key])
|
|
215
|
+
return self._d[key]
|
|
216
|
+
|
|
217
|
+
key = self._mapping._from_user(key)
|
|
218
|
+
|
|
219
|
+
return super().get(key, astype=astype, **kwargs)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class XArrayFieldGeography(Geography):
|
|
223
|
+
"""A class to handle geography information for XArray fields.
|
|
224
|
+
|
|
225
|
+
Attributes
|
|
226
|
+
----------
|
|
227
|
+
_field : Any
|
|
228
|
+
The field to extract geography information from.
|
|
229
|
+
_grid : Any
|
|
230
|
+
The grid associated with the field.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, field: Any, grid: Any) -> None:
|
|
234
|
+
"""Initialize the XArrayFieldGeography class.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
field : Any
|
|
239
|
+
The field to extract geography information from.
|
|
240
|
+
grid : Any
|
|
241
|
+
The grid associated with the field.
|
|
242
|
+
"""
|
|
243
|
+
self._field = field
|
|
244
|
+
self._grid = grid
|
|
245
|
+
|
|
246
|
+
def _unique_grid_id(self) -> None:
|
|
247
|
+
"""Get the unique grid ID.
|
|
248
|
+
|
|
249
|
+
Raises
|
|
250
|
+
------
|
|
251
|
+
NotImplementedError
|
|
252
|
+
This method is not implemented.
|
|
253
|
+
"""
|
|
254
|
+
raise NotImplementedError()
|
|
255
|
+
|
|
256
|
+
def bounding_box(self) -> None:
|
|
257
|
+
"""Get the bounding box for the field.
|
|
258
|
+
|
|
259
|
+
Raises
|
|
260
|
+
------
|
|
261
|
+
NotImplementedError
|
|
262
|
+
This method is not implemented.
|
|
263
|
+
"""
|
|
264
|
+
raise NotImplementedError()
|
|
265
|
+
# return BoundingBox(north=self.north, south=self.south, east=self.east, west=self.west)
|
|
266
|
+
|
|
267
|
+
def gridspec(self) -> None:
|
|
268
|
+
"""Get the grid specification for the field.
|
|
269
|
+
|
|
270
|
+
Raises
|
|
271
|
+
------
|
|
272
|
+
NotImplementedError
|
|
273
|
+
This method is not implemented.
|
|
274
|
+
"""
|
|
275
|
+
raise NotImplementedError()
|
|
276
|
+
|
|
277
|
+
def latitudes(self, dtype: Optional[type] = None) -> Any:
|
|
278
|
+
"""Get the latitudes for the field.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
dtype : Optional[type]
|
|
283
|
+
The type to cast the latitudes to.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
Any
|
|
288
|
+
The latitudes for the field.
|
|
289
|
+
"""
|
|
290
|
+
result = self._grid.latitudes
|
|
291
|
+
if dtype is not None:
|
|
292
|
+
return result.astype(dtype)
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
def longitudes(self, dtype: Optional[type] = None) -> Any:
|
|
296
|
+
"""Get the longitudes for the field.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
dtype : Optional[type]
|
|
301
|
+
The type to cast the longitudes to.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
Any
|
|
306
|
+
The longitudes for the field.
|
|
307
|
+
"""
|
|
308
|
+
result = self._grid.longitudes
|
|
309
|
+
if dtype is not None:
|
|
310
|
+
return result.astype(dtype)
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
def resolution(self) -> Optional[Any]:
|
|
314
|
+
"""Get the resolution for the field.
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
Optional[Any]
|
|
319
|
+
The resolution for the field.
|
|
320
|
+
"""
|
|
321
|
+
# TODO: implement resolution
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
def mars_grid(self) -> Optional[Any]:
|
|
325
|
+
"""Get the MARS grid for the field.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
Optional[Any]
|
|
330
|
+
The MARS grid for the field.
|
|
331
|
+
"""
|
|
332
|
+
# TODO: implement mars_grid
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
def mars_area(self) -> Optional[Any]:
|
|
336
|
+
"""Get the MARS area for the field.
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
Optional[Any]
|
|
341
|
+
The MARS area for the field.
|
|
342
|
+
"""
|
|
343
|
+
# TODO: code me
|
|
344
|
+
# return [self.north, self.west, self.south, self.east]
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def x(self, dtype: Optional[type] = None) -> None:
|
|
348
|
+
"""Get the x-coordinates for the field.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
dtype : Optional[type]
|
|
353
|
+
The type to cast the x-coordinates to.
|
|
354
|
+
|
|
355
|
+
Raises
|
|
356
|
+
------
|
|
357
|
+
NotImplementedError
|
|
358
|
+
This method is not implemented.
|
|
359
|
+
"""
|
|
360
|
+
raise NotImplementedError()
|
|
361
|
+
|
|
362
|
+
def y(self, dtype: Optional[type] = None) -> None:
|
|
363
|
+
"""Get the y-coordinates for the field.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
dtype : Optional[type]
|
|
368
|
+
The type to cast the y-coordinates to.
|
|
369
|
+
|
|
370
|
+
Raises
|
|
371
|
+
------
|
|
372
|
+
NotImplementedError
|
|
373
|
+
This method is not implemented.
|
|
374
|
+
"""
|
|
375
|
+
raise NotImplementedError()
|
|
376
|
+
|
|
377
|
+
def shape(self) -> Any:
|
|
378
|
+
"""Get the shape of the field.
|
|
379
|
+
|
|
380
|
+
Returns
|
|
381
|
+
-------
|
|
382
|
+
Any
|
|
383
|
+
The shape of the field.
|
|
384
|
+
"""
|
|
385
|
+
return self._field.shape
|
|
386
|
+
|
|
387
|
+
def projection(self) -> Projection:
|
|
388
|
+
"""Get the projection for the field.
|
|
389
|
+
|
|
390
|
+
Returns
|
|
391
|
+
-------
|
|
392
|
+
Projection
|
|
393
|
+
The projection for the field.
|
|
394
|
+
"""
|
|
395
|
+
return Projection.from_cf_grid_mapping(**self._field.grid_mapping)
|
|
@@ -0,0 +1,91 @@
|
|
|
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 typing import Any
|
|
13
|
+
from typing import Dict
|
|
14
|
+
from typing import List
|
|
15
|
+
|
|
16
|
+
import xarray as xr
|
|
17
|
+
|
|
18
|
+
LOG = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def patch_attributes(ds: xr.Dataset, attributes: Dict[str, Dict[str, Any]]) -> Any:
|
|
22
|
+
"""Patch the attributes of the dataset.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
ds : xr.Dataset
|
|
27
|
+
The dataset to patch.
|
|
28
|
+
attributes : Dict[str, Dict[str, Any]]
|
|
29
|
+
The attributes to patch.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
Any
|
|
34
|
+
The patched dataset.
|
|
35
|
+
"""
|
|
36
|
+
for name, value in attributes.items():
|
|
37
|
+
variable = ds[name]
|
|
38
|
+
variable.attrs.update(value)
|
|
39
|
+
|
|
40
|
+
return ds
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def patch_coordinates(ds: xr.Dataset, coordinates: List[str]) -> Any:
|
|
44
|
+
"""Patch the coordinates of the dataset.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
ds : xr.Dataset
|
|
49
|
+
The dataset to patch.
|
|
50
|
+
coordinates : List[str]
|
|
51
|
+
The coordinates to patch.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
Any
|
|
56
|
+
The patched dataset.
|
|
57
|
+
"""
|
|
58
|
+
for name in coordinates:
|
|
59
|
+
ds = ds.assign_coords({name: ds[name]})
|
|
60
|
+
|
|
61
|
+
return ds
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
PATCHES = {
|
|
65
|
+
"attributes": patch_attributes,
|
|
66
|
+
"coordinates": patch_coordinates,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def patch_dataset(ds: xr.Dataset, patch: Dict[str, Dict[str, Any]]) -> Any:
|
|
71
|
+
"""Patch the dataset.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
ds : xr.Dataset
|
|
76
|
+
The dataset to patch.
|
|
77
|
+
patch : Dict[str, Dict[str, Any]]
|
|
78
|
+
The patch to apply.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
Any
|
|
83
|
+
The patched dataset.
|
|
84
|
+
"""
|
|
85
|
+
for what, values in patch.items():
|
|
86
|
+
if what not in PATCHES:
|
|
87
|
+
raise ValueError(f"Unknown patch type {what!r}")
|
|
88
|
+
|
|
89
|
+
ds = PATCHES[what](ds, values)
|
|
90
|
+
|
|
91
|
+
return ds
|