anemoi-datasets 0.3.10__py3-none-any.whl → 0.4.2__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/_version.py +2 -2
- anemoi/datasets/commands/compare.py +59 -0
- anemoi/datasets/commands/create.py +84 -3
- anemoi/datasets/commands/inspect.py +9 -9
- anemoi/datasets/commands/scan.py +4 -4
- anemoi/datasets/compute/recentre.py +14 -9
- anemoi/datasets/create/__init__.py +44 -17
- anemoi/datasets/create/check.py +6 -5
- anemoi/datasets/create/chunks.py +1 -1
- anemoi/datasets/create/config.py +6 -27
- anemoi/datasets/create/functions/__init__.py +3 -3
- anemoi/datasets/create/functions/filters/empty.py +4 -4
- anemoi/datasets/create/functions/filters/rename.py +14 -6
- anemoi/datasets/create/functions/filters/rotate_winds.py +16 -60
- anemoi/datasets/create/functions/filters/unrotate_winds.py +14 -64
- anemoi/datasets/create/functions/sources/__init__.py +39 -0
- anemoi/datasets/create/functions/sources/accumulations.py +38 -56
- anemoi/datasets/create/functions/sources/constants.py +11 -4
- anemoi/datasets/create/functions/sources/empty.py +2 -2
- anemoi/datasets/create/functions/sources/forcings.py +3 -3
- anemoi/datasets/create/functions/sources/grib.py +8 -4
- anemoi/datasets/create/functions/sources/hindcasts.py +32 -364
- anemoi/datasets/create/functions/sources/mars.py +57 -26
- anemoi/datasets/create/functions/sources/netcdf.py +2 -60
- anemoi/datasets/create/functions/sources/opendap.py +3 -2
- anemoi/datasets/create/functions/sources/source.py +3 -3
- anemoi/datasets/create/functions/sources/tendencies.py +7 -7
- anemoi/datasets/create/functions/sources/xarray/__init__.py +73 -0
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +234 -0
- anemoi/datasets/create/functions/sources/xarray/field.py +109 -0
- anemoi/datasets/create/functions/sources/xarray/fieldlist.py +171 -0
- anemoi/datasets/create/functions/sources/xarray/flavour.py +330 -0
- anemoi/datasets/create/functions/sources/xarray/grid.py +46 -0
- anemoi/datasets/create/functions/sources/xarray/metadata.py +161 -0
- anemoi/datasets/create/functions/sources/xarray/time.py +98 -0
- anemoi/datasets/create/functions/sources/xarray/variable.py +198 -0
- anemoi/datasets/create/functions/sources/xarray_kerchunk.py +42 -0
- anemoi/datasets/create/functions/sources/xarray_zarr.py +15 -0
- anemoi/datasets/create/functions/sources/zenodo.py +40 -0
- anemoi/datasets/create/input.py +309 -191
- anemoi/datasets/create/loaders.py +155 -77
- anemoi/datasets/create/patch.py +17 -14
- anemoi/datasets/create/persistent.py +1 -1
- anemoi/datasets/create/size.py +4 -5
- anemoi/datasets/create/statistics/__init__.py +51 -17
- anemoi/datasets/create/template.py +11 -61
- anemoi/datasets/create/trace.py +91 -0
- anemoi/datasets/create/utils.py +5 -52
- anemoi/datasets/create/zarr.py +24 -10
- anemoi/datasets/data/dataset.py +4 -4
- anemoi/datasets/data/misc.py +9 -37
- anemoi/datasets/data/stores.py +37 -14
- anemoi/datasets/dates/__init__.py +7 -1
- anemoi/datasets/dates/groups.py +3 -0
- {anemoi_datasets-0.3.10.dist-info → anemoi_datasets-0.4.2.dist-info}/METADATA +24 -8
- anemoi_datasets-0.4.2.dist-info/RECORD +86 -0
- {anemoi_datasets-0.3.10.dist-info → anemoi_datasets-0.4.2.dist-info}/WHEEL +1 -1
- anemoi_datasets-0.3.10.dist-info/RECORD +0 -73
- {anemoi_datasets-0.3.10.dist-info → anemoi_datasets-0.4.2.dist-info}/LICENSE +0 -0
- {anemoi_datasets-0.3.10.dist-info → anemoi_datasets-0.4.2.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.3.10.dist-info → anemoi_datasets-0.4.2.dist-info}/top_level.txt +0 -0
|
@@ -9,61 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"""Code provided by MetNO"""
|
|
17
|
-
import numpy as np
|
|
18
|
-
import pyproj
|
|
19
|
-
|
|
20
|
-
if source_projection == target_projection:
|
|
21
|
-
return x_wind, x_wind
|
|
22
|
-
|
|
23
|
-
source_projection = pyproj.Proj(source_projection)
|
|
24
|
-
target_projection = pyproj.Proj(target_projection)
|
|
25
|
-
|
|
26
|
-
transformer = pyproj.transformer.Transformer.from_proj(source_projection, target_projection)
|
|
27
|
-
|
|
28
|
-
# To compute the new vector components:
|
|
29
|
-
# 1) perturb each position in the direction of the winds
|
|
30
|
-
# 2) convert the perturbed positions into the new coordinate system
|
|
31
|
-
# 3) measure the new x/y components.
|
|
32
|
-
#
|
|
33
|
-
# A complication occurs when using the longlat "projections", since this is not a cartesian grid
|
|
34
|
-
# (i.e. distances in each direction is not consistent), we need to deal with the fact that the
|
|
35
|
-
# width of a longitude varies with latitude
|
|
36
|
-
orig_speed = np.sqrt(x_wind**2 + y_wind**2)
|
|
37
|
-
|
|
38
|
-
x0, y0 = source_projection(lons, lats)
|
|
39
|
-
|
|
40
|
-
if source_projection.name != "longlat":
|
|
41
|
-
x1 = x0 + x_wind
|
|
42
|
-
y1 = y0 + y_wind
|
|
43
|
-
else:
|
|
44
|
-
# Reduce the perturbation, since x_wind and y_wind are in meters, which would create
|
|
45
|
-
# large perturbations in lat, lon. Also, deal with the fact that the width of longitude
|
|
46
|
-
# varies with latitude.
|
|
47
|
-
factor = 3600000.0
|
|
48
|
-
x1 = x0 + x_wind / factor / np.cos(np.deg2rad(lats))
|
|
49
|
-
y1 = y0 + y_wind / factor
|
|
50
|
-
|
|
51
|
-
X0, Y0 = transformer.transform(x0, y0)
|
|
52
|
-
X1, Y1 = transformer.transform(x1, y1)
|
|
53
|
-
|
|
54
|
-
new_x_wind = X1 - X0
|
|
55
|
-
new_y_wind = Y1 - Y0
|
|
56
|
-
if target_projection.name == "longlat":
|
|
57
|
-
new_x_wind *= np.cos(np.deg2rad(lats))
|
|
58
|
-
|
|
59
|
-
if target_projection.name == "longlat" or source_projection.name == "longlat":
|
|
60
|
-
# Ensure the wind speed is not changed (which might not the case since the units in longlat
|
|
61
|
-
# is degrees, not meters)
|
|
62
|
-
curr_speed = np.sqrt(new_x_wind**2 + new_y_wind**2)
|
|
63
|
-
new_x_wind *= orig_speed / curr_speed
|
|
64
|
-
new_y_wind *= orig_speed / curr_speed
|
|
65
|
-
|
|
66
|
-
return new_x_wind, new_y_wind
|
|
12
|
+
import tqdm
|
|
13
|
+
from anemoi.utils.humanize import plural
|
|
14
|
+
from earthkit.data.indexing.fieldlist import FieldArray
|
|
15
|
+
from earthkit.geo.rotate import rotate_vector
|
|
67
16
|
|
|
68
17
|
|
|
69
18
|
class NewDataField:
|
|
@@ -77,6 +26,9 @@ class NewDataField:
|
|
|
77
26
|
def __getattr__(self, name):
|
|
78
27
|
return getattr(self.field, name)
|
|
79
28
|
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return repr(self.field)
|
|
31
|
+
|
|
80
32
|
|
|
81
33
|
def execute(
|
|
82
34
|
context,
|
|
@@ -88,13 +40,15 @@ def execute(
|
|
|
88
40
|
):
|
|
89
41
|
from pyproj import CRS
|
|
90
42
|
|
|
43
|
+
context.trace("🔄", "Rotating winds (extracting winds from ", plural(len(input), "field"))
|
|
44
|
+
|
|
91
45
|
result = FieldArray()
|
|
92
46
|
|
|
93
47
|
wind_params = (x_wind, y_wind)
|
|
94
48
|
wind_pairs = defaultdict(dict)
|
|
95
49
|
|
|
96
50
|
for f in input:
|
|
97
|
-
key = f.
|
|
51
|
+
key = f.metadata(namespace="mars")
|
|
98
52
|
param = key.pop("param")
|
|
99
53
|
|
|
100
54
|
if param not in wind_params:
|
|
@@ -108,7 +62,9 @@ def execute(
|
|
|
108
62
|
|
|
109
63
|
wind_pairs[key][param] = f
|
|
110
64
|
|
|
111
|
-
|
|
65
|
+
context.trace("🔄", "Rotating", plural(len(wind_pairs), "wind"), "(speed will likely include data download)")
|
|
66
|
+
|
|
67
|
+
for _, pairs in tqdm.tqdm(list(wind_pairs.items())):
|
|
112
68
|
if len(pairs) != 2:
|
|
113
69
|
raise ValueError("Missing wind component")
|
|
114
70
|
|
|
@@ -118,11 +74,11 @@ def execute(
|
|
|
118
74
|
assert x.grid_mapping == y.grid_mapping
|
|
119
75
|
|
|
120
76
|
lats, lons = x.grid_points()
|
|
121
|
-
x_new, y_new =
|
|
77
|
+
x_new, y_new = rotate_vector(
|
|
122
78
|
lats,
|
|
123
79
|
lons,
|
|
124
|
-
x.to_numpy(
|
|
125
|
-
y.to_numpy(
|
|
80
|
+
x.to_numpy(flatten=True),
|
|
81
|
+
y.to_numpy(flatten=True),
|
|
126
82
|
(source_projection if source_projection is not None else CRS.from_cf(x.grid_mapping)),
|
|
127
83
|
target_projection,
|
|
128
84
|
)
|
|
@@ -9,60 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
from collections import defaultdict
|
|
11
11
|
|
|
12
|
-
import numpy as np
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def normalise(x):
|
|
17
|
-
return max(min(x, 1.0), -1.0)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def normalise_longitude(lon, minimum):
|
|
21
|
-
while lon < minimum:
|
|
22
|
-
lon += 360
|
|
23
|
-
|
|
24
|
-
while lon >= minimum + 360:
|
|
25
|
-
lon -= 360
|
|
26
|
-
|
|
27
|
-
return lon
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def rotate_winds(
|
|
31
|
-
lats,
|
|
32
|
-
lons,
|
|
33
|
-
raw_lats,
|
|
34
|
-
raw_lons,
|
|
35
|
-
x_wind,
|
|
36
|
-
y_wind,
|
|
37
|
-
south_pole_latitude,
|
|
38
|
-
south_pole_longitude,
|
|
39
|
-
south_pole_rotation_angle=0,
|
|
40
|
-
):
|
|
41
|
-
# Code from MIR
|
|
42
|
-
assert south_pole_rotation_angle == 0
|
|
43
|
-
C = np.deg2rad(90 - south_pole_latitude)
|
|
44
|
-
cos_C = np.cos(C)
|
|
45
|
-
sin_C = np.sin(C)
|
|
46
|
-
|
|
47
|
-
new_x = np.zeros_like(x_wind)
|
|
48
|
-
new_y = np.zeros_like(y_wind)
|
|
49
|
-
|
|
50
|
-
for i, (vx, vy, lat, lon, raw_lat, raw_lon) in enumerate(zip(x_wind, y_wind, lats, lons, raw_lats, raw_lons)):
|
|
51
|
-
lonRotated = south_pole_longitude - lon
|
|
52
|
-
lon_rotated = normalise_longitude(lonRotated, -180)
|
|
53
|
-
lon_unrotated = raw_lon
|
|
54
|
-
|
|
55
|
-
a = np.deg2rad(lon_rotated)
|
|
56
|
-
b = np.deg2rad(lon_unrotated)
|
|
57
|
-
q = 1 if (sin_C * lon_rotated < 0.0) else -1.0 # correct quadrant
|
|
58
|
-
|
|
59
|
-
cos_c = normalise(np.cos(a) * np.cos(b) + np.sin(a) * np.sin(b) * cos_C)
|
|
60
|
-
sin_c = q * np.sqrt(1.0 - cos_c * cos_c)
|
|
61
|
-
|
|
62
|
-
new_x[i] = cos_c * vx + sin_c * vy
|
|
63
|
-
new_y[i] = -sin_c * vx + cos_c * vy
|
|
64
|
-
|
|
65
|
-
return new_x, new_y
|
|
12
|
+
# import numpy as np
|
|
13
|
+
from earthkit.data.indexing.fieldlist import FieldArray
|
|
14
|
+
from earthkit.geo.rotate import unrotate_vector
|
|
66
15
|
|
|
67
16
|
|
|
68
17
|
class NewDataField:
|
|
@@ -85,7 +34,7 @@ def execute(context, input, u, v):
|
|
|
85
34
|
wind_pairs = defaultdict(dict)
|
|
86
35
|
|
|
87
36
|
for f in input:
|
|
88
|
-
key = f.
|
|
37
|
+
key = f.metadata(namespace="mars")
|
|
89
38
|
param = key.pop("param")
|
|
90
39
|
|
|
91
40
|
if param not in wind_params:
|
|
@@ -107,18 +56,19 @@ def execute(context, input, u, v):
|
|
|
107
56
|
y = pairs[v]
|
|
108
57
|
|
|
109
58
|
lats, lons = x.grid_points()
|
|
110
|
-
raw_lats, raw_longs = x.
|
|
59
|
+
raw_lats, raw_longs = x.grid_points_unrotated()
|
|
111
60
|
|
|
112
61
|
assert x.rotation == y.rotation
|
|
113
62
|
|
|
114
|
-
u_new, v_new =
|
|
63
|
+
u_new, v_new = unrotate_vector(
|
|
115
64
|
lats,
|
|
116
65
|
lons,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
x.
|
|
120
|
-
|
|
121
|
-
|
|
66
|
+
x.to_numpy(flatten=True),
|
|
67
|
+
y.to_numpy(flatten=True),
|
|
68
|
+
*x.rotation[:2],
|
|
69
|
+
south_pole_rotation_angle=x.rotation[2],
|
|
70
|
+
lat_unrotated=raw_lats,
|
|
71
|
+
lon_unrotated=raw_longs,
|
|
122
72
|
)
|
|
123
73
|
|
|
124
74
|
result.append(NewDataField(x, u_new))
|
|
@@ -128,9 +78,9 @@ def execute(context, input, u, v):
|
|
|
128
78
|
|
|
129
79
|
|
|
130
80
|
if __name__ == "__main__":
|
|
131
|
-
from
|
|
81
|
+
from earthkit.data import from_source
|
|
132
82
|
|
|
133
|
-
source =
|
|
83
|
+
source = from_source(
|
|
134
84
|
"mars",
|
|
135
85
|
date=-1,
|
|
136
86
|
param="10u/10v",
|
|
@@ -6,3 +6,42 @@
|
|
|
6
6
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
|
+
|
|
10
|
+
import glob
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from earthkit.data.utils.patterns import Pattern
|
|
14
|
+
|
|
15
|
+
LOG = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _expand(paths):
|
|
19
|
+
for path in paths:
|
|
20
|
+
if path.startswith("file://"):
|
|
21
|
+
path = path[7:]
|
|
22
|
+
|
|
23
|
+
if path.startswith("http://"):
|
|
24
|
+
yield path
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
if path.startswith("https://"):
|
|
28
|
+
yield path
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
cnt = 0
|
|
32
|
+
for p in glob.glob(path):
|
|
33
|
+
yield p
|
|
34
|
+
cnt += 1
|
|
35
|
+
if cnt == 0:
|
|
36
|
+
yield path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def iterate_patterns(path, dates, **kwargs):
|
|
40
|
+
given_paths = path if isinstance(path, list) else [path]
|
|
41
|
+
|
|
42
|
+
dates = [d.isoformat() for d in dates]
|
|
43
|
+
|
|
44
|
+
for path in given_paths:
|
|
45
|
+
paths = Pattern(path, ignore_missing_keys=True).substitute(date=dates, **kwargs)
|
|
46
|
+
for path in _expand(paths):
|
|
47
|
+
yield path, dates
|
|
@@ -11,11 +11,11 @@ import logging
|
|
|
11
11
|
import warnings
|
|
12
12
|
from copy import deepcopy
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import earthkit.data as ekd
|
|
15
15
|
import numpy as np
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
16
|
+
from earthkit.data.core.temporary import temp_file
|
|
17
|
+
from earthkit.data.readers.grib.output import new_grib_output
|
|
18
|
+
from earthkit.data.utils.availability import Availability
|
|
19
19
|
|
|
20
20
|
from anemoi.datasets.create.utils import to_datetime_list
|
|
21
21
|
|
|
@@ -24,9 +24,9 @@ from .mars import use_grib_paramid
|
|
|
24
24
|
LOG = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def _member(field):
|
|
28
28
|
# Bug in eccodes has number=0 randomly
|
|
29
|
-
number = field.metadata("number")
|
|
29
|
+
number = field.metadata("number", default=0)
|
|
30
30
|
if number is None:
|
|
31
31
|
number = 0
|
|
32
32
|
return number
|
|
@@ -54,16 +54,25 @@ class Accumulation:
|
|
|
54
54
|
|
|
55
55
|
def check(self, field):
|
|
56
56
|
if self._check is None:
|
|
57
|
-
self._check = field.
|
|
57
|
+
self._check = field.metadata(namespace="mars")
|
|
58
58
|
|
|
59
|
-
assert self.param == field.metadata("param"), (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
assert self.param == field.metadata("param"), (
|
|
60
|
+
self.param,
|
|
61
|
+
field.metadata("param"),
|
|
62
|
+
)
|
|
63
|
+
assert self.date == field.metadata("date"), (
|
|
64
|
+
self.date,
|
|
65
|
+
field.metadata("date"),
|
|
66
|
+
)
|
|
67
|
+
assert self.time == field.metadata("time"), (
|
|
68
|
+
self.time,
|
|
69
|
+
field.metadata("time"),
|
|
70
|
+
)
|
|
71
|
+
assert self.number == _member(field), (self.number, _member(field))
|
|
63
72
|
|
|
64
73
|
return
|
|
65
74
|
|
|
66
|
-
mars = field.
|
|
75
|
+
mars = field.metadata(namespace="mars")
|
|
67
76
|
keys1 = sorted(self._check.keys())
|
|
68
77
|
keys2 = sorted(mars.keys())
|
|
69
78
|
|
|
@@ -196,7 +205,11 @@ class AccumulationFromLastStep(Accumulation):
|
|
|
196
205
|
|
|
197
206
|
def compute(self, values, startStep, endStep):
|
|
198
207
|
|
|
199
|
-
assert endStep - startStep == self.frequency, (
|
|
208
|
+
assert endStep - startStep == self.frequency, (
|
|
209
|
+
startStep,
|
|
210
|
+
endStep,
|
|
211
|
+
self.frequency,
|
|
212
|
+
)
|
|
200
213
|
|
|
201
214
|
if self.startStep is None:
|
|
202
215
|
self.startStep = startStep
|
|
@@ -228,17 +241,17 @@ class AccumulationFromLastStep(Accumulation):
|
|
|
228
241
|
)
|
|
229
242
|
|
|
230
243
|
|
|
231
|
-
def
|
|
244
|
+
def _identity(x):
|
|
232
245
|
return x
|
|
233
246
|
|
|
234
247
|
|
|
235
|
-
def
|
|
248
|
+
def _compute_accumulations(
|
|
236
249
|
context,
|
|
237
250
|
dates,
|
|
238
251
|
request,
|
|
239
252
|
user_accumulation_period=6,
|
|
240
253
|
data_accumulation_period=None,
|
|
241
|
-
patch=
|
|
254
|
+
patch=_identity,
|
|
242
255
|
base_times=None,
|
|
243
256
|
):
|
|
244
257
|
adjust_step = isinstance(user_accumulation_period, int)
|
|
@@ -307,14 +320,13 @@ def compute_accumulations(
|
|
|
307
320
|
)
|
|
308
321
|
|
|
309
322
|
compressed = Availability(requests)
|
|
310
|
-
ds =
|
|
323
|
+
ds = ekd.from_source("empty")
|
|
311
324
|
for r in compressed.iterate():
|
|
312
325
|
request.update(r)
|
|
313
326
|
if context.use_grib_paramid and "param" in request:
|
|
314
327
|
request = use_grib_paramid(request)
|
|
315
328
|
print("🌧️", request)
|
|
316
|
-
|
|
317
|
-
ds = ds + cml.load_source("mars", **request)
|
|
329
|
+
ds = ds + ekd.from_source("mars", **request)
|
|
318
330
|
|
|
319
331
|
accumulations = {}
|
|
320
332
|
for a in [AccumulationClass(out, frequency=frequency, **r) for r in requests]:
|
|
@@ -328,7 +340,7 @@ def compute_accumulations(
|
|
|
328
340
|
field.metadata("date"),
|
|
329
341
|
field.metadata("time"),
|
|
330
342
|
field.metadata("step"),
|
|
331
|
-
|
|
343
|
+
_member(field),
|
|
332
344
|
)
|
|
333
345
|
values = field.values # optimisation
|
|
334
346
|
assert accumulations[key], key
|
|
@@ -341,7 +353,7 @@ def compute_accumulations(
|
|
|
341
353
|
|
|
342
354
|
out.close()
|
|
343
355
|
|
|
344
|
-
ds =
|
|
356
|
+
ds = ekd.from_source("file", path)
|
|
345
357
|
|
|
346
358
|
assert len(ds) / len(param) / len(number) == len(dates), (
|
|
347
359
|
len(ds),
|
|
@@ -353,43 +365,13 @@ def compute_accumulations(
|
|
|
353
365
|
return ds
|
|
354
366
|
|
|
355
367
|
|
|
356
|
-
def
|
|
368
|
+
def _to_list(x):
|
|
357
369
|
if isinstance(x, (list, tuple)):
|
|
358
370
|
return x
|
|
359
371
|
return [x]
|
|
360
372
|
|
|
361
373
|
|
|
362
|
-
def
|
|
363
|
-
r = deepcopy(r)
|
|
364
|
-
if "time" not in r:
|
|
365
|
-
return r
|
|
366
|
-
|
|
367
|
-
times = []
|
|
368
|
-
for t in to_list(r["time"]):
|
|
369
|
-
assert len(t) == 4, r
|
|
370
|
-
assert t.endswith("00"), r
|
|
371
|
-
times.append(int(t) // 100)
|
|
372
|
-
r["time"] = tuple(times)
|
|
373
|
-
return r
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def normalise_number(r):
|
|
377
|
-
if "number" not in r:
|
|
378
|
-
return r
|
|
379
|
-
number = r["number"]
|
|
380
|
-
number = to_list(number)
|
|
381
|
-
|
|
382
|
-
if len(number) > 4 and (number[1] == "to" and number[3] == "by"):
|
|
383
|
-
return list(range(int(number[0]), int(number[2]) + 1, int(number[4])))
|
|
384
|
-
|
|
385
|
-
if len(number) > 2 and number[1] == "to":
|
|
386
|
-
return list(range(int(number[0]), int(number[2]) + 1))
|
|
387
|
-
|
|
388
|
-
r["number"] = number
|
|
389
|
-
return r
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def scda(request):
|
|
374
|
+
def _scda(request):
|
|
393
375
|
if request["time"] in (6, 18, 600, 1800):
|
|
394
376
|
request["stream"] = "scda"
|
|
395
377
|
else:
|
|
@@ -398,14 +380,14 @@ def scda(request):
|
|
|
398
380
|
|
|
399
381
|
|
|
400
382
|
def accumulations(context, dates, **request):
|
|
401
|
-
|
|
383
|
+
_to_list(request["param"])
|
|
402
384
|
class_ = request.get("class", "od")
|
|
403
385
|
stream = request.get("stream", "oper")
|
|
404
386
|
|
|
405
387
|
user_accumulation_period = request.pop("accumulation_period", 6)
|
|
406
388
|
|
|
407
389
|
KWARGS = {
|
|
408
|
-
("od", "oper"): dict(patch=
|
|
390
|
+
("od", "oper"): dict(patch=_scda),
|
|
409
391
|
("od", "elda"): dict(base_times=(6, 18)),
|
|
410
392
|
("ea", "oper"): dict(data_accumulation_period=1, base_times=(6, 18)),
|
|
411
393
|
("ea", "enda"): dict(data_accumulation_period=3, base_times=(6, 18)),
|
|
@@ -415,7 +397,7 @@ def accumulations(context, dates, **request):
|
|
|
415
397
|
|
|
416
398
|
context.trace("🌧️", f"accumulations {request} {user_accumulation_period} {kwargs}")
|
|
417
399
|
|
|
418
|
-
return
|
|
400
|
+
return _compute_accumulations(
|
|
419
401
|
context,
|
|
420
402
|
dates,
|
|
421
403
|
request,
|
|
@@ -6,15 +6,22 @@
|
|
|
6
6
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
|
-
from
|
|
9
|
+
from earthkit.data import from_source
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def constants(context, dates, template, param):
|
|
13
13
|
from warnings import warn
|
|
14
14
|
|
|
15
|
-
warn(
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
warn(
|
|
16
|
+
"The source `constants` is deprecated, use `forcings` instead.",
|
|
17
|
+
DeprecationWarning,
|
|
18
|
+
stacklevel=2,
|
|
19
|
+
)
|
|
20
|
+
context.trace("✅", f"from_source(constants, {template}, {param}")
|
|
21
|
+
if len(template) == 0:
|
|
22
|
+
raise ValueError("Forcings template is empty.")
|
|
23
|
+
|
|
24
|
+
return from_source("forcings", source_or_dataset=template, date=dates, param=param)
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
execute = constants
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
|
-
from
|
|
9
|
+
from earthkit.data import from_source
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def forcings(context, dates, template, param):
|
|
13
|
-
context.trace("✅", f"
|
|
14
|
-
return
|
|
13
|
+
context.trace("✅", f"from_source(forcings, {template}, {param}")
|
|
14
|
+
return from_source("forcings", source_or_dataset=template, date=dates, param=param)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
execute = forcings
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import glob
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
13
|
+
from earthkit.data import from_source
|
|
14
|
+
from earthkit.data.utils.patterns import Pattern
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def check(ds, paths, **kwargs):
|
|
@@ -26,14 +26,18 @@ def check(ds, paths, **kwargs):
|
|
|
26
26
|
|
|
27
27
|
def _expand(paths):
|
|
28
28
|
for path in paths:
|
|
29
|
+
cnt = 0
|
|
29
30
|
for p in glob.glob(path):
|
|
30
31
|
yield p
|
|
32
|
+
cnt += 1
|
|
33
|
+
if cnt == 0:
|
|
34
|
+
yield path
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def execute(context, dates, path, *args, **kwargs):
|
|
34
38
|
given_paths = path if isinstance(path, list) else [path]
|
|
35
39
|
|
|
36
|
-
ds =
|
|
40
|
+
ds = from_source("empty")
|
|
37
41
|
dates = [d.isoformat() for d in dates]
|
|
38
42
|
|
|
39
43
|
for path in given_paths:
|
|
@@ -45,7 +49,7 @@ def execute(context, dates, path, *args, **kwargs):
|
|
|
45
49
|
|
|
46
50
|
for path in _expand(paths):
|
|
47
51
|
context.trace("📁", "PATH", path)
|
|
48
|
-
s =
|
|
52
|
+
s = from_source("file", path)
|
|
49
53
|
s = s.sel(valid_datetime=dates, **kwargs)
|
|
50
54
|
ds = ds + s
|
|
51
55
|
|