anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.18__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 +558 -62
  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.16.dist-info → anemoi_datasets-0.5.18.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.18.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.dist-info}/WHEEL +1 -1
  131. anemoi/datasets/create/functions/__init__.py +0 -66
  132. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  133. anemoi/datasets/create/functions/filters/empty.py +0 -17
  134. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  135. anemoi/datasets/create/functions/filters/rename.py +0 -79
  136. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  137. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  138. anemoi/datasets/create/functions/sources/empty.py +0 -15
  139. anemoi/datasets/create/functions/sources/grib.py +0 -150
  140. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  141. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  142. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  143. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  144. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  145. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  146. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  147. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  148. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  149. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  150. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  151. anemoi/datasets/utils/fields.py +0 -47
  152. anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
  153. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.dist-info/licenses}/LICENSE +0 -0
  155. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.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
- def __init__(self, lat, lon, variable_dims):
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
- def __init__(self, x, y):
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
- def __init__(self, lat, lon, variable_dims):
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
- def __init__(self, x, y, projection):
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
- @cached_property
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
- # lat = lat[::len(lat)//100]
132
- # lon = lon[::len(lon)//100]
133
-
134
- # print(len(lat), len(lon))
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