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
|
@@ -7,19 +7,41 @@
|
|
|
7
7
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
8
|
# nor does it submit to any jurisdiction.
|
|
9
9
|
|
|
10
|
+
import datetime
|
|
10
11
|
import logging
|
|
11
|
-
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import Dict
|
|
14
|
+
from typing import List
|
|
15
|
+
from typing import Optional
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
import earthkit.data as ekd
|
|
19
|
+
import xarray as xr
|
|
12
20
|
from earthkit.data.core.fieldlist import MultiFieldList
|
|
13
21
|
|
|
22
|
+
from anemoi.datasets.create.sources.patterns import iterate_patterns
|
|
14
23
|
from anemoi.datasets.data.stores import name_to_zarr_store
|
|
15
24
|
|
|
16
|
-
from .. import
|
|
25
|
+
from ..legacy import legacy_source
|
|
17
26
|
from .fieldlist import XarrayFieldList
|
|
18
27
|
|
|
19
28
|
LOG = logging.getLogger(__name__)
|
|
20
29
|
|
|
21
30
|
|
|
22
|
-
def check(what, ds, paths, **kwargs):
|
|
31
|
+
def check(what: str, ds: xr.Dataset, paths: List[str], **kwargs: Any) -> None:
|
|
32
|
+
"""Checks if the dataset has the expected number of fields.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
what : str
|
|
37
|
+
Description of what is being checked.
|
|
38
|
+
ds : xr.Dataset
|
|
39
|
+
The dataset to check.
|
|
40
|
+
paths : List[str]
|
|
41
|
+
List of paths.
|
|
42
|
+
**kwargs : Any
|
|
43
|
+
Additional keyword arguments.
|
|
44
|
+
"""
|
|
23
45
|
count = 1
|
|
24
46
|
for k, v in kwargs.items():
|
|
25
47
|
if isinstance(v, (tuple, list)):
|
|
@@ -29,8 +51,43 @@ def check(what, ds, paths, **kwargs):
|
|
|
29
51
|
raise ValueError(f"Expected {count} fields, got {len(ds)} (kwargs={kwargs}, {what}s={paths})")
|
|
30
52
|
|
|
31
53
|
|
|
32
|
-
def load_one(
|
|
33
|
-
|
|
54
|
+
def load_one(
|
|
55
|
+
emoji: str,
|
|
56
|
+
context: Any,
|
|
57
|
+
dates: List[str],
|
|
58
|
+
dataset: Union[str, xr.Dataset],
|
|
59
|
+
*,
|
|
60
|
+
options: Optional[Dict[str, Any]] = None,
|
|
61
|
+
flavour: Optional[str] = None,
|
|
62
|
+
patch: Optional[Any] = None,
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> ekd.FieldList:
|
|
65
|
+
"""Loads a single dataset.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
emoji : str
|
|
70
|
+
Emoji for tracing.
|
|
71
|
+
context : Any
|
|
72
|
+
Context object.
|
|
73
|
+
dates : List[str]
|
|
74
|
+
List of dates.
|
|
75
|
+
dataset : Union[str, xr.Dataset]
|
|
76
|
+
The dataset to load.
|
|
77
|
+
options : Dict[str, Any], optional
|
|
78
|
+
Additional options for loading the dataset.
|
|
79
|
+
flavour : Optional[str], optional
|
|
80
|
+
Flavour of the dataset.
|
|
81
|
+
patch : Optional[Any], optional
|
|
82
|
+
Patch for the dataset.
|
|
83
|
+
**kwargs : Any
|
|
84
|
+
Additional keyword arguments.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
MultiFieldList
|
|
89
|
+
The loaded dataset.
|
|
90
|
+
"""
|
|
34
91
|
|
|
35
92
|
"""
|
|
36
93
|
We manage the S3 client ourselve, bypassing fsspec and s3fs layers, because sometimes something on the stack
|
|
@@ -41,6 +98,9 @@ def load_one(emoji, context, dates, dataset, *, options={}, flavour=None, patch=
|
|
|
41
98
|
We have seen this bug triggered when we run many clients in parallel, for example, when we create a new dataset using `xarray-zarr`.
|
|
42
99
|
"""
|
|
43
100
|
|
|
101
|
+
if options is None:
|
|
102
|
+
options = {}
|
|
103
|
+
|
|
44
104
|
context.trace(emoji, dataset, options, kwargs)
|
|
45
105
|
|
|
46
106
|
if isinstance(dataset, str) and ".zarr" in dataset:
|
|
@@ -59,6 +119,7 @@ def load_one(emoji, context, dates, dataset, *, options={}, flavour=None, patch=
|
|
|
59
119
|
if len(dates) == 0:
|
|
60
120
|
result = fs.sel(**kwargs)
|
|
61
121
|
else:
|
|
122
|
+
print("dates", dates, kwargs)
|
|
62
123
|
result = MultiFieldList([fs.sel(valid_datetime=date, **kwargs) for date in dates])
|
|
63
124
|
|
|
64
125
|
if len(result) == 0:
|
|
@@ -79,8 +140,27 @@ def load_one(emoji, context, dates, dataset, *, options={}, flavour=None, patch=
|
|
|
79
140
|
return result
|
|
80
141
|
|
|
81
142
|
|
|
82
|
-
def load_many(emoji, context, dates, pattern, **kwargs):
|
|
83
|
-
|
|
143
|
+
def load_many(emoji: str, context: Any, dates: List[datetime.datetime], pattern: str, **kwargs: Any) -> ekd.FieldList:
|
|
144
|
+
"""Loads multiple datasets.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
emoji : str
|
|
149
|
+
Emoji for tracing.
|
|
150
|
+
context : Any
|
|
151
|
+
Context object.
|
|
152
|
+
dates : List[str]
|
|
153
|
+
List of dates.
|
|
154
|
+
pattern : str
|
|
155
|
+
Pattern for loading datasets.
|
|
156
|
+
**kwargs : Any
|
|
157
|
+
Additional keyword arguments.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
MultiFieldList
|
|
162
|
+
The loaded datasets.
|
|
163
|
+
"""
|
|
84
164
|
result = []
|
|
85
165
|
|
|
86
166
|
for path, dates in iterate_patterns(pattern, dates, **kwargs):
|
|
@@ -89,5 +169,26 @@ def load_many(emoji, context, dates, pattern, **kwargs):
|
|
|
89
169
|
return MultiFieldList(result)
|
|
90
170
|
|
|
91
171
|
|
|
92
|
-
|
|
172
|
+
@legacy_source("xarray")
|
|
173
|
+
def execute(context: Any, dates: List[str], url: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
|
|
174
|
+
"""Executes the loading of datasets.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
context : Any
|
|
179
|
+
Context object.
|
|
180
|
+
dates : List[str]
|
|
181
|
+
List of dates.
|
|
182
|
+
url : str
|
|
183
|
+
URL pattern for loading datasets.
|
|
184
|
+
*args : Any
|
|
185
|
+
Additional arguments.
|
|
186
|
+
**kwargs : Any
|
|
187
|
+
Additional keyword arguments.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
ekd.FieldList
|
|
192
|
+
The loaded datasets.
|
|
193
|
+
"""
|
|
93
194
|
return load_many("🌐", context, dates, url, *args, **kwargs)
|
|
@@ -0,0 +1,442 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import datetime
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any
|
|
16
|
+
from typing import Dict
|
|
17
|
+
from typing import Optional
|
|
18
|
+
from typing import Tuple
|
|
19
|
+
from typing import Union
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import xarray as xr
|
|
23
|
+
from earthkit.data.utils.dates import to_datetime
|
|
24
|
+
|
|
25
|
+
LOG = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_scalar(variable: Any) -> bool:
|
|
29
|
+
"""Check if the variable is scalar.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
variable : Any
|
|
34
|
+
The variable to check.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
bool
|
|
39
|
+
True if the variable is scalar, False otherwise.
|
|
40
|
+
"""
|
|
41
|
+
shape = variable.shape
|
|
42
|
+
if shape == (1,):
|
|
43
|
+
return True
|
|
44
|
+
if len(shape) == 0:
|
|
45
|
+
return True
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_single_value(variable: Any) -> Any:
|
|
50
|
+
"""Extract a single value from the variable.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
variable : Any
|
|
55
|
+
The variable to extract the value from.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
Any
|
|
60
|
+
The extracted single value.
|
|
61
|
+
"""
|
|
62
|
+
shape = variable.shape
|
|
63
|
+
if np.issubdtype(variable.values.dtype, np.datetime64):
|
|
64
|
+
if len(shape) == 0:
|
|
65
|
+
return to_datetime(variable.values) # Convert to python datetime
|
|
66
|
+
if shape == (1,):
|
|
67
|
+
return to_datetime(variable.values[0])
|
|
68
|
+
assert False, (shape, variable.values[:2])
|
|
69
|
+
|
|
70
|
+
if np.issubdtype(variable.values.dtype, np.timedelta64):
|
|
71
|
+
if len(shape) == 0:
|
|
72
|
+
# Convert to python timedelta64
|
|
73
|
+
return datetime.timedelta(seconds=variable.values.astype("timedelta64[s]").astype(int).item())
|
|
74
|
+
assert False, (shape, variable.values)
|
|
75
|
+
|
|
76
|
+
if shape == (1,):
|
|
77
|
+
return variable.values[0]
|
|
78
|
+
|
|
79
|
+
if len(shape) == 0:
|
|
80
|
+
return variable.values.item()
|
|
81
|
+
|
|
82
|
+
assert False, (shape, variable.values)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Coordinate:
|
|
86
|
+
"""Base class for coordinates."""
|
|
87
|
+
|
|
88
|
+
is_grid = False
|
|
89
|
+
is_dim = True
|
|
90
|
+
is_lat = False
|
|
91
|
+
is_lon = False
|
|
92
|
+
is_time = False
|
|
93
|
+
is_step = False
|
|
94
|
+
is_date = False
|
|
95
|
+
is_member = False
|
|
96
|
+
is_x = False
|
|
97
|
+
is_y = False
|
|
98
|
+
|
|
99
|
+
def __init__(self, variable: xr.DataArray) -> None:
|
|
100
|
+
"""Initialize the coordinate.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
variable : Any
|
|
105
|
+
The variable representing the coordinate.
|
|
106
|
+
"""
|
|
107
|
+
self.variable = variable
|
|
108
|
+
self.scalar = is_scalar(variable)
|
|
109
|
+
self.kwargs: Dict[str, Any] = {} # Used when creating a new coordinate (reduced method)
|
|
110
|
+
|
|
111
|
+
def __len__(self) -> int:
|
|
112
|
+
"""Get the length of the coordinate.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
int
|
|
117
|
+
The length of the coordinate.
|
|
118
|
+
"""
|
|
119
|
+
return 1 if self.scalar else len(self.variable)
|
|
120
|
+
|
|
121
|
+
def __repr__(self) -> str:
|
|
122
|
+
"""Get the string representation of the coordinate.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
str
|
|
127
|
+
The string representation of the coordinate.
|
|
128
|
+
"""
|
|
129
|
+
return "%s[name=%s,values=%s,shape=%s]" % (
|
|
130
|
+
self.__class__.__name__,
|
|
131
|
+
self.variable.name,
|
|
132
|
+
self.variable.values if self.scalar else len(self),
|
|
133
|
+
self.variable.shape,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def reduced(self, i: int) -> Coordinate:
|
|
137
|
+
"""Create a new coordinate with a single value.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
i : int
|
|
142
|
+
The index of the value to select.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
Coordinate
|
|
147
|
+
A new coordinate with the selected value.
|
|
148
|
+
"""
|
|
149
|
+
return self.__class__(
|
|
150
|
+
self.variable.isel({self.variable.dims[0]: i}),
|
|
151
|
+
**self.kwargs,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def index(self, value: Union[Any, list, tuple]) -> Optional[Union[int, list]]:
|
|
155
|
+
"""Return the index of the value in the coordinate.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
value : Union[Any, list, tuple]
|
|
160
|
+
The value to search for.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
Optional[Union[int, list]]
|
|
165
|
+
The index or indices of the value in the coordinate, or None if not found.
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(value, (list, tuple)):
|
|
168
|
+
if len(value) == 1:
|
|
169
|
+
return self._index_single(value)
|
|
170
|
+
else:
|
|
171
|
+
return self._index_multiple(value)
|
|
172
|
+
return self._index_single(value)
|
|
173
|
+
|
|
174
|
+
def _index_single(self, value: Any) -> Optional[int]:
|
|
175
|
+
"""Return the index of a single value in the coordinate.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
value : Any
|
|
180
|
+
The value to search for.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
Optional[int]
|
|
185
|
+
The index of the value in the coordinate, or None if not found.
|
|
186
|
+
"""
|
|
187
|
+
values = self.variable.values
|
|
188
|
+
|
|
189
|
+
# Check if dimension is 0D
|
|
190
|
+
if not isinstance(values, (list, np.ndarray)):
|
|
191
|
+
values = [values]
|
|
192
|
+
|
|
193
|
+
# Assume the array is sorted
|
|
194
|
+
index = np.searchsorted(values, value)
|
|
195
|
+
|
|
196
|
+
if index < len(values) and values[index] == value:
|
|
197
|
+
return index
|
|
198
|
+
|
|
199
|
+
# If not found, we need to check if the value is in the array
|
|
200
|
+
|
|
201
|
+
index = np.where(values == value)[0]
|
|
202
|
+
if len(index) > 0:
|
|
203
|
+
return index[0]
|
|
204
|
+
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def _index_multiple(self, value: list) -> Optional[list]:
|
|
208
|
+
"""Return the indices of multiple values in the coordinate.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
value : list
|
|
213
|
+
The values to search for.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
Optional[list]
|
|
218
|
+
The indices of the values in the coordinate, or None if not found.
|
|
219
|
+
"""
|
|
220
|
+
values = self.variable.values
|
|
221
|
+
|
|
222
|
+
# Check if dimension is 0D
|
|
223
|
+
if not isinstance(values, (list, np.ndarray)):
|
|
224
|
+
values = [values]
|
|
225
|
+
|
|
226
|
+
# Assume the array is sorted
|
|
227
|
+
|
|
228
|
+
index = np.searchsorted(values, value)
|
|
229
|
+
index = index[index < len(values)]
|
|
230
|
+
|
|
231
|
+
if np.all(values[index] == value):
|
|
232
|
+
return index
|
|
233
|
+
|
|
234
|
+
# If not found, we need to check if the value is in the array
|
|
235
|
+
|
|
236
|
+
index = np.where(np.isin(values, value))[0]
|
|
237
|
+
|
|
238
|
+
# We could also return incomplete matches
|
|
239
|
+
if len(index) == len(value):
|
|
240
|
+
return index
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def name(self) -> str:
|
|
246
|
+
"""Get the name of the coordinate."""
|
|
247
|
+
return self.variable.name
|
|
248
|
+
|
|
249
|
+
def normalise(self, value: Any) -> Any:
|
|
250
|
+
"""Normalize the value for the coordinate.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
value : Any
|
|
255
|
+
The value to normalize.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
Any
|
|
260
|
+
The normalized value.
|
|
261
|
+
"""
|
|
262
|
+
# Subclasses to format values that will be added to the field metadata
|
|
263
|
+
return value
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def single_value(self) -> Any:
|
|
267
|
+
"""Get the single value of the coordinate."""
|
|
268
|
+
return extract_single_value(self.variable)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class TimeCoordinate(Coordinate):
|
|
272
|
+
"""Coordinate class for time."""
|
|
273
|
+
|
|
274
|
+
is_time = True
|
|
275
|
+
mars_names = ("valid_datetime",)
|
|
276
|
+
|
|
277
|
+
def index(self, time: datetime.datetime) -> Optional[int]:
|
|
278
|
+
"""Return the index of the time in the coordinate.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
time : datetime.datetime
|
|
283
|
+
The time to search for.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
Optional[int]
|
|
288
|
+
The index of the time in the coordinate, or None if not found.
|
|
289
|
+
"""
|
|
290
|
+
return super().index(np.datetime64(time))
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class DateCoordinate(Coordinate):
|
|
294
|
+
"""Coordinate class for date."""
|
|
295
|
+
|
|
296
|
+
is_date = True
|
|
297
|
+
mars_names = ("date",)
|
|
298
|
+
|
|
299
|
+
def index(self, date: datetime.datetime) -> Optional[int]:
|
|
300
|
+
"""Return the index of the date in the coordinate.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
date : datetime.datetime
|
|
305
|
+
The date to search for.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
Optional[int]
|
|
310
|
+
The index of the date in the coordinate, or None if not found.
|
|
311
|
+
"""
|
|
312
|
+
return super().index(np.datetime64(date))
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class StepCoordinate(Coordinate):
|
|
316
|
+
"""Coordinate class for step."""
|
|
317
|
+
|
|
318
|
+
is_step = True
|
|
319
|
+
mars_names = ("step",)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class LevelCoordinate(Coordinate):
|
|
323
|
+
"""Coordinate class for level.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
variable : Any
|
|
328
|
+
The variable representing the coordinate.
|
|
329
|
+
levtype : str
|
|
330
|
+
The type of level.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
mars_names = ("level", "levelist")
|
|
334
|
+
|
|
335
|
+
def __init__(self, variable: Any, levtype: str) -> None:
|
|
336
|
+
"""Initialize the level coordinate.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
variable : Any
|
|
341
|
+
The variable representing the coordinate.
|
|
342
|
+
levtype : str
|
|
343
|
+
The type of level.
|
|
344
|
+
"""
|
|
345
|
+
super().__init__(variable)
|
|
346
|
+
self.levtype = levtype
|
|
347
|
+
# kwargs is used when creating a new coordinate (reduced method)
|
|
348
|
+
self.kwargs = {"levtype": levtype}
|
|
349
|
+
|
|
350
|
+
def normalise(self, value: Any) -> Any:
|
|
351
|
+
"""Normalize the value for the level coordinate.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
value : Any
|
|
356
|
+
The value to normalize.
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
Any
|
|
361
|
+
The normalized value.
|
|
362
|
+
"""
|
|
363
|
+
# Some netcdf have pressue levels in float
|
|
364
|
+
if int(value) == value:
|
|
365
|
+
return int(value)
|
|
366
|
+
return value
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class EnsembleCoordinate(Coordinate):
|
|
370
|
+
"""Coordinate class for ensemble."""
|
|
371
|
+
|
|
372
|
+
is_member = True
|
|
373
|
+
mars_names = ("number",)
|
|
374
|
+
|
|
375
|
+
def normalise(self, value: Any) -> Any:
|
|
376
|
+
"""Normalize the value for the ensemble coordinate.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
value : Any
|
|
381
|
+
The value to normalize.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
Any
|
|
386
|
+
The normalized value.
|
|
387
|
+
"""
|
|
388
|
+
if int(value) == value:
|
|
389
|
+
return int(value)
|
|
390
|
+
return value
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class LongitudeCoordinate(Coordinate):
|
|
394
|
+
"""Coordinate class for longitude."""
|
|
395
|
+
|
|
396
|
+
is_grid = True
|
|
397
|
+
is_lon = True
|
|
398
|
+
mars_names = ("longitude",)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class LatitudeCoordinate(Coordinate):
|
|
402
|
+
"""Coordinate class for latitude."""
|
|
403
|
+
|
|
404
|
+
is_grid = True
|
|
405
|
+
is_lat = True
|
|
406
|
+
mars_names = ("latitude",)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class XCoordinate(Coordinate):
|
|
410
|
+
"""Coordinate class for X."""
|
|
411
|
+
|
|
412
|
+
is_grid = True
|
|
413
|
+
is_x = True
|
|
414
|
+
mars_names = ("x",)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class YCoordinate(Coordinate):
|
|
418
|
+
"""Coordinate class for Y."""
|
|
419
|
+
|
|
420
|
+
is_grid = True
|
|
421
|
+
is_y = True
|
|
422
|
+
mars_names = ("y",)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class ScalarCoordinate(Coordinate):
|
|
426
|
+
"""Coordinate class for scalar."""
|
|
427
|
+
|
|
428
|
+
is_grid = False
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def mars_names(self) -> Tuple[str, ...]:
|
|
432
|
+
"""Get the MARS names for the coordinate."""
|
|
433
|
+
return (self.variable.name,)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class UnsupportedCoordinate(Coordinate):
|
|
437
|
+
"""Coordinate class for unsupported coordinates."""
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def mars_names(self) -> tuple:
|
|
441
|
+
"""Get the MARS names for the coordinate."""
|
|
442
|
+
return (self.variable.name,)
|