cloudnetpy 1.80.7__py3-none-any.whl → 1.81.0__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.
- cloudnetpy/categorize/__init__.py +1 -1
- cloudnetpy/categorize/atmos_utils.py +31 -27
- cloudnetpy/categorize/attenuations/__init__.py +4 -4
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +7 -5
- cloudnetpy/categorize/attenuations/melting_attenuation.py +3 -3
- cloudnetpy/categorize/attenuations/rain_attenuation.py +4 -4
- cloudnetpy/categorize/categorize.py +25 -11
- cloudnetpy/categorize/classify.py +9 -8
- cloudnetpy/categorize/containers.py +13 -10
- cloudnetpy/categorize/disdrometer.py +5 -3
- cloudnetpy/categorize/droplet.py +12 -9
- cloudnetpy/categorize/falling.py +9 -8
- cloudnetpy/categorize/freezing.py +10 -7
- cloudnetpy/categorize/insects.py +18 -17
- cloudnetpy/categorize/lidar.py +7 -3
- cloudnetpy/categorize/melting.py +16 -15
- cloudnetpy/categorize/model.py +17 -10
- cloudnetpy/categorize/mwr.py +5 -3
- cloudnetpy/categorize/radar.py +15 -13
- cloudnetpy/cli.py +10 -8
- cloudnetpy/cloudnetarray.py +8 -7
- cloudnetpy/concat_lib.py +29 -20
- cloudnetpy/datasource.py +26 -21
- cloudnetpy/exceptions.py +12 -10
- cloudnetpy/instruments/basta.py +19 -9
- cloudnetpy/instruments/bowtie.py +18 -11
- cloudnetpy/instruments/ceilo.py +22 -10
- cloudnetpy/instruments/ceilometer.py +33 -34
- cloudnetpy/instruments/cl61d.py +5 -3
- cloudnetpy/instruments/cloudnet_instrument.py +7 -7
- cloudnetpy/instruments/copernicus.py +16 -7
- cloudnetpy/instruments/disdrometer/common.py +5 -4
- cloudnetpy/instruments/disdrometer/parsivel.py +14 -9
- cloudnetpy/instruments/disdrometer/thies.py +11 -7
- cloudnetpy/instruments/fd12p.py +7 -6
- cloudnetpy/instruments/galileo.py +16 -7
- cloudnetpy/instruments/hatpro.py +33 -24
- cloudnetpy/instruments/lufft.py +6 -4
- cloudnetpy/instruments/mira.py +33 -19
- cloudnetpy/instruments/mrr.py +12 -12
- cloudnetpy/instruments/nc_lidar.py +1 -1
- cloudnetpy/instruments/nc_radar.py +8 -8
- cloudnetpy/instruments/pollyxt.py +19 -12
- cloudnetpy/instruments/radiometrics.py +17 -10
- cloudnetpy/instruments/rain_e_h3.py +9 -5
- cloudnetpy/instruments/rpg.py +32 -21
- cloudnetpy/instruments/rpg_reader.py +15 -12
- cloudnetpy/instruments/vaisala.py +32 -24
- cloudnetpy/instruments/weather_station.py +22 -19
- cloudnetpy/model_evaluation/file_handler.py +27 -29
- cloudnetpy/model_evaluation/plotting/plot_tools.py +7 -5
- cloudnetpy/model_evaluation/plotting/plotting.py +41 -32
- cloudnetpy/model_evaluation/products/advance_methods.py +38 -34
- cloudnetpy/model_evaluation/products/grid_methods.py +10 -9
- cloudnetpy/model_evaluation/products/model_products.py +15 -9
- cloudnetpy/model_evaluation/products/observation_products.py +12 -10
- cloudnetpy/model_evaluation/products/product_resampling.py +11 -7
- cloudnetpy/model_evaluation/products/tools.py +18 -14
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +6 -5
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +18 -25
- cloudnetpy/model_evaluation/utils.py +3 -3
- cloudnetpy/output.py +15 -32
- cloudnetpy/plotting/plotting.py +23 -13
- cloudnetpy/products/classification.py +15 -9
- cloudnetpy/products/der.py +24 -19
- cloudnetpy/products/drizzle.py +21 -13
- cloudnetpy/products/drizzle_error.py +8 -7
- cloudnetpy/products/drizzle_tools.py +27 -23
- cloudnetpy/products/epsilon.py +6 -5
- cloudnetpy/products/ier.py +11 -5
- cloudnetpy/products/iwc.py +18 -9
- cloudnetpy/products/lwc.py +41 -31
- cloudnetpy/products/mwr_tools.py +30 -19
- cloudnetpy/products/product_tools.py +23 -19
- cloudnetpy/utils.py +84 -98
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/METADATA +2 -1
- cloudnetpy-1.81.0.dist-info/RECORD +126 -0
- cloudnetpy-1.80.7.dist-info/RECORD +0 -126
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
import csv
|
2
2
|
import datetime
|
3
3
|
from os import PathLike
|
4
|
+
from uuid import UUID
|
4
5
|
|
5
6
|
import numpy as np
|
6
7
|
|
@@ -8,15 +9,16 @@ from cloudnetpy import output
|
|
8
9
|
from cloudnetpy.exceptions import ValidTimeStampError
|
9
10
|
from cloudnetpy.instruments import instruments
|
10
11
|
from cloudnetpy.instruments.cloudnet_instrument import CSVFile
|
12
|
+
from cloudnetpy.utils import get_uuid
|
11
13
|
|
12
14
|
|
13
15
|
def rain_e_h32nc(
|
14
16
|
input_file: str | PathLike,
|
15
|
-
output_file: str,
|
17
|
+
output_file: str | PathLike,
|
16
18
|
site_meta: dict,
|
17
|
-
uuid: str | None = None,
|
19
|
+
uuid: str | UUID | None = None,
|
18
20
|
date: str | datetime.date | None = None,
|
19
|
-
):
|
21
|
+
) -> UUID:
|
20
22
|
"""Converts rain_e_h3 rain-gauge into Cloudnet Level 1b netCDF file.
|
21
23
|
|
22
24
|
Args:
|
@@ -36,6 +38,7 @@ def rain_e_h32nc(
|
|
36
38
|
rain = RainEH3(site_meta)
|
37
39
|
if isinstance(date, str):
|
38
40
|
date = datetime.date.fromisoformat(date)
|
41
|
+
uuid = get_uuid(uuid)
|
39
42
|
rain.parse_input_file(input_file, date)
|
40
43
|
rain.add_data()
|
41
44
|
rain.add_date()
|
@@ -46,11 +49,12 @@ def rain_e_h32nc(
|
|
46
49
|
rain.remove_duplicate_timestamps()
|
47
50
|
attributes = output.add_time_attribute({}, rain.date)
|
48
51
|
output.update_attributes(rain.data, attributes)
|
49
|
-
|
52
|
+
output.save_level1b(rain, output_file, uuid)
|
53
|
+
return uuid
|
50
54
|
|
51
55
|
|
52
56
|
class RainEH3(CSVFile):
|
53
|
-
def __init__(self, site_meta: dict):
|
57
|
+
def __init__(self, site_meta: dict) -> None:
|
54
58
|
super().__init__(site_meta)
|
55
59
|
self.instrument = instruments.RAIN_E_H3
|
56
60
|
self._data = {
|
cloudnetpy/instruments/rpg.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
"""This module contains RPG Cloud Radar related functions."""
|
2
2
|
|
3
|
+
import datetime
|
3
4
|
import logging
|
4
5
|
import math
|
5
|
-
from collections.abc import Sequence
|
6
|
+
from collections.abc import Callable, Sequence
|
7
|
+
from os import PathLike
|
8
|
+
from uuid import UUID
|
6
9
|
|
7
10
|
import numpy as np
|
11
|
+
import numpy.typing as npt
|
8
12
|
from numpy import ma
|
9
13
|
from rpgpy import RPGFileError
|
10
14
|
|
@@ -20,12 +24,12 @@ from cloudnetpy.metadata import MetaData
|
|
20
24
|
|
21
25
|
|
22
26
|
def rpg2nc(
|
23
|
-
path_to_l1_files: str,
|
24
|
-
output_file: str,
|
27
|
+
path_to_l1_files: str | PathLike,
|
28
|
+
output_file: str | PathLike,
|
25
29
|
site_meta: dict,
|
26
|
-
uuid: str | None = None,
|
27
|
-
date: str | None = None,
|
28
|
-
) -> tuple[
|
30
|
+
uuid: str | UUID | None = None,
|
31
|
+
date: str | datetime.date | None = None,
|
32
|
+
) -> tuple[UUID, list[str]]:
|
29
33
|
"""Converts RPG-FMCW-94 cloud radar data into Cloudnet Level 1b netCDF file.
|
30
34
|
|
31
35
|
This function reads one day of RPG Level 1 cloud radar binary files,
|
@@ -58,11 +62,14 @@ def rpg2nc(
|
|
58
62
|
>>> rpg2nc('/path/to/files/', 'test.nc', site_meta)
|
59
63
|
|
60
64
|
"""
|
65
|
+
if isinstance(date, str):
|
66
|
+
date = datetime.date.fromisoformat(date)
|
67
|
+
uuid = utils.get_uuid(uuid)
|
61
68
|
l1_files = utils.get_sorted_filenames(path_to_l1_files, ".LV1")
|
62
69
|
fmcw94_objects, valid_files = _get_fmcw94_objects(l1_files, date)
|
63
70
|
one_day_of_data = create_one_day_data_record(fmcw94_objects)
|
64
71
|
if not valid_files:
|
65
|
-
return
|
72
|
+
return uuid, []
|
66
73
|
print_info(one_day_of_data)
|
67
74
|
fmcw = Fmcw(one_day_of_data, site_meta)
|
68
75
|
fmcw.convert_time_to_fraction_hour()
|
@@ -78,7 +85,7 @@ def rpg2nc(
|
|
78
85
|
fmcw.add_height()
|
79
86
|
attributes = output.add_time_attribute(RPG_ATTRIBUTES, fmcw.date)
|
80
87
|
output.update_attributes(fmcw.data, attributes)
|
81
|
-
|
88
|
+
output.save_level1b(fmcw, output_file, uuid)
|
82
89
|
return uuid, valid_files
|
83
90
|
|
84
91
|
|
@@ -113,7 +120,7 @@ def _stack_rpg_data(rpg_objects: RpgObjects) -> tuple[dict, dict]:
|
|
113
120
|
|
114
121
|
"""
|
115
122
|
|
116
|
-
def _stack(source, target, fun) -> None:
|
123
|
+
def _stack(source: dict, target: dict, fun: Callable) -> None:
|
117
124
|
for name, value in source.items():
|
118
125
|
if not name.startswith("_"):
|
119
126
|
target[name] = fun((target[name], value)) if name in target else value
|
@@ -168,7 +175,9 @@ def _mask_invalid_data(data_in: dict) -> dict:
|
|
168
175
|
return data
|
169
176
|
|
170
177
|
|
171
|
-
def _get_fmcw94_objects(
|
178
|
+
def _get_fmcw94_objects(
|
179
|
+
files: list[str], expected_date: datetime.date | None
|
180
|
+
) -> tuple[list[Fmcw94Bin], list[str]]:
|
172
181
|
"""Creates a list of Rpg() objects from the file names."""
|
173
182
|
objects = []
|
174
183
|
valid_files = []
|
@@ -211,10 +220,10 @@ def _remove_files_with_bad_height(objects: list, files: list) -> tuple[list, lis
|
|
211
220
|
return objects, files
|
212
221
|
|
213
222
|
|
214
|
-
def _validate_date(obj, expected_date:
|
223
|
+
def _validate_date(obj: Fmcw94Bin, expected_date: datetime.date) -> None:
|
215
224
|
for t in obj.data["time"][:]:
|
216
|
-
|
217
|
-
if
|
225
|
+
date = utils.seconds2date(t).date()
|
226
|
+
if date != expected_date:
|
218
227
|
msg = "Ignoring a file (time stamps not what expected)"
|
219
228
|
raise ValueError(msg)
|
220
229
|
|
@@ -222,7 +231,7 @@ def _validate_date(obj, expected_date: str) -> None:
|
|
222
231
|
class Rpg(CloudnetInstrument):
|
223
232
|
"""Base class for RPG FMCW-94 cloud radar and HATPRO mwr."""
|
224
233
|
|
225
|
-
def __init__(self, raw_data: dict, site_meta: dict):
|
234
|
+
def __init__(self, raw_data: dict, site_meta: dict) -> None:
|
226
235
|
super().__init__()
|
227
236
|
self.raw_data = raw_data
|
228
237
|
self.site_meta = site_meta
|
@@ -242,11 +251,11 @@ class Rpg(CloudnetInstrument):
|
|
242
251
|
data_type=data_type,
|
243
252
|
)
|
244
253
|
|
245
|
-
def _get_date(self) ->
|
254
|
+
def _get_date(self) -> datetime.date:
|
246
255
|
time_first = self.raw_data["time"][0]
|
247
256
|
time_last = self.raw_data["time"][-1]
|
248
|
-
date_first = utils.seconds2date(time_first)
|
249
|
-
date_last = utils.seconds2date(time_last)
|
257
|
+
date_first = utils.seconds2date(time_first).date()
|
258
|
+
date_last = utils.seconds2date(time_last).date()
|
250
259
|
if date_first != date_last:
|
251
260
|
logging.warning("Measurements from different days")
|
252
261
|
return date_first
|
@@ -261,7 +270,7 @@ class Rpg(CloudnetInstrument):
|
|
261
270
|
class Fmcw(Rpg):
|
262
271
|
"""Class for RPG cloud radars."""
|
263
272
|
|
264
|
-
def __init__(self, raw_data: dict, site_properties: dict):
|
273
|
+
def __init__(self, raw_data: dict, site_properties: dict) -> None:
|
265
274
|
super().__init__(raw_data, site_properties)
|
266
275
|
self.instrument = self._get_instrument(raw_data)
|
267
276
|
|
@@ -313,7 +322,7 @@ class Fmcw(Rpg):
|
|
313
322
|
self.data["wind_speed"].data *= KM_H_TO_M_S
|
314
323
|
|
315
324
|
@staticmethod
|
316
|
-
def _get_instrument(data: dict):
|
325
|
+
def _get_instrument(data: dict) -> Instrument:
|
317
326
|
frequency = data["radar_frequency"]
|
318
327
|
if math.isclose(frequency, 35, abs_tol=0.1):
|
319
328
|
return instruments.FMCW35
|
@@ -326,12 +335,14 @@ class Fmcw(Rpg):
|
|
326
335
|
class Hatpro(Rpg):
|
327
336
|
"""Class for RPG HATPRO mwr."""
|
328
337
|
|
329
|
-
def __init__(
|
338
|
+
def __init__(
|
339
|
+
self, raw_data: dict, site_properties: dict, instrument: Instrument
|
340
|
+
) -> None:
|
330
341
|
super().__init__(raw_data, site_properties)
|
331
342
|
self.instrument = instrument
|
332
343
|
|
333
344
|
|
334
|
-
def _filter_zenith_angle(zenith: ma.MaskedArray) ->
|
345
|
+
def _filter_zenith_angle(zenith: ma.MaskedArray) -> npt.NDArray:
|
335
346
|
"""Returns indices of profiles with stable zenith angle close to 0 deg."""
|
336
347
|
zenith = ma.array(zenith)
|
337
348
|
if zenith.mask.all():
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import logging
|
2
|
+
from os import PathLike
|
3
|
+
from pathlib import Path
|
2
4
|
from typing import BinaryIO, Literal
|
3
5
|
|
4
6
|
import numpy as np
|
7
|
+
import numpy.typing as npt
|
5
8
|
from numpy import ma
|
6
9
|
from numpy.lib import recfunctions as rfn
|
7
10
|
from rpgpy import read_rpg
|
@@ -13,7 +16,7 @@ from cloudnetpy.exceptions import ValidTimeStampError
|
|
13
16
|
class Fmcw94Bin:
|
14
17
|
"""RPG Cloud Radar Level 1 data reader."""
|
15
18
|
|
16
|
-
def __init__(self, filename):
|
19
|
+
def __init__(self, filename: str | PathLike) -> None:
|
17
20
|
self.filename = filename
|
18
21
|
self.header, self.data = read_rpg(filename)
|
19
22
|
|
@@ -101,7 +104,7 @@ class Fmcw94Bin:
|
|
101
104
|
self.replace_keys(self.data, data_keymap)
|
102
105
|
|
103
106
|
@staticmethod
|
104
|
-
def replace_keys(d: dict, keymap: dict):
|
107
|
+
def replace_keys(d: dict, keymap: dict) -> None:
|
105
108
|
for key in d.copy():
|
106
109
|
if key in keymap:
|
107
110
|
new_key = keymap[key]
|
@@ -121,9 +124,9 @@ def _read_from_file(
|
|
121
124
|
|
122
125
|
|
123
126
|
def _decode_angles(
|
124
|
-
x:
|
127
|
+
x: npt.NDArray,
|
125
128
|
version: Literal[1, 2],
|
126
|
-
) -> tuple[
|
129
|
+
) -> tuple[npt.NDArray, npt.NDArray]:
|
127
130
|
"""Decode elevation and azimuth angles.
|
128
131
|
|
129
132
|
>>> _decode_angles(np.array([1267438.5]), version=1)
|
@@ -181,7 +184,7 @@ class HatproBin:
|
|
181
184
|
QUALITY_MEDIUM = 2
|
182
185
|
QUALITY_LOW = 3
|
183
186
|
|
184
|
-
def __init__(self, filename):
|
187
|
+
def __init__(self, filename: Path) -> None:
|
185
188
|
self.filename = filename
|
186
189
|
with open(self.filename, "rb") as file:
|
187
190
|
self._read_header(file)
|
@@ -225,7 +228,7 @@ class HatproBinLwp(HatproBin):
|
|
225
228
|
|
226
229
|
variable = "lwp"
|
227
230
|
|
228
|
-
def _read_header(self, file) -> None:
|
231
|
+
def _read_header(self, file: BinaryIO) -> None:
|
229
232
|
self.header = _read_from_file(
|
230
233
|
file,
|
231
234
|
[
|
@@ -245,7 +248,7 @@ class HatproBinLwp(HatproBin):
|
|
245
248
|
msg = f"Unknown HATPRO version. {self.header['file_code']}"
|
246
249
|
raise ValueError(msg)
|
247
250
|
|
248
|
-
def _read_data(self, file) -> None:
|
251
|
+
def _read_data(self, file: BinaryIO) -> None:
|
249
252
|
self.data = _read_from_file(
|
250
253
|
file,
|
251
254
|
[
|
@@ -264,7 +267,7 @@ class HatproBinIwv(HatproBin):
|
|
264
267
|
|
265
268
|
variable = "iwv"
|
266
269
|
|
267
|
-
def _read_header(self, file) -> None:
|
270
|
+
def _read_header(self, file: BinaryIO) -> None:
|
268
271
|
self.header = _read_from_file(
|
269
272
|
file,
|
270
273
|
[
|
@@ -284,7 +287,7 @@ class HatproBinIwv(HatproBin):
|
|
284
287
|
msg = f"Unknown HATPRO version. {self.header['file_code']}"
|
285
288
|
raise ValueError(msg)
|
286
289
|
|
287
|
-
def _read_data(self, file) -> None:
|
290
|
+
def _read_data(self, file: BinaryIO) -> None:
|
288
291
|
self.data = _read_from_file(
|
289
292
|
file,
|
290
293
|
[
|
@@ -300,10 +303,10 @@ class HatproBinIwv(HatproBin):
|
|
300
303
|
class HatproBinCombined:
|
301
304
|
"""Combine HATPRO objects that share values of the given dimensions."""
|
302
305
|
|
303
|
-
header: dict[str,
|
304
|
-
data: dict[str,
|
306
|
+
header: dict[str, npt.NDArray]
|
307
|
+
data: dict[str, npt.NDArray]
|
305
308
|
|
306
|
-
def __init__(self, files: list[HatproBin]):
|
309
|
+
def __init__(self, files: list[HatproBin]) -> None:
|
307
310
|
self.header = {}
|
308
311
|
if len(files) == 1:
|
309
312
|
arr = files[0].data
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
import datetime
|
4
4
|
from collections.abc import Callable
|
5
|
+
from os import PathLike
|
5
6
|
|
6
7
|
import ceilopyter.version
|
7
8
|
import numpy as np
|
@@ -19,20 +20,15 @@ class VaisalaCeilo(Ceilometer):
|
|
19
20
|
def __init__(
|
20
21
|
self,
|
21
22
|
reader: Callable,
|
22
|
-
full_path: str,
|
23
|
+
full_path: str | PathLike,
|
23
24
|
site_meta: dict,
|
24
|
-
expected_date:
|
25
|
-
):
|
25
|
+
expected_date: datetime.date | None = None,
|
26
|
+
) -> None:
|
26
27
|
super().__init__(self.noise_param)
|
27
28
|
self.reader = reader
|
28
29
|
self.full_path = full_path
|
29
30
|
self.site_meta = site_meta
|
30
31
|
self.expected_date = expected_date
|
31
|
-
self.sane_date = (
|
32
|
-
datetime.date.fromisoformat(self.expected_date)
|
33
|
-
if self.expected_date
|
34
|
-
else None
|
35
|
-
)
|
36
32
|
self.software = {"ceilopyter": ceilopyter.version.__version__}
|
37
33
|
|
38
34
|
def read_ceilometer_file(self, calibration_factor: float | None = None) -> None:
|
@@ -51,23 +47,21 @@ class VaisalaCeilo(Ceilometer):
|
|
51
47
|
self.convert_to_fraction_hour()
|
52
48
|
self._store_ceilometer_info()
|
53
49
|
|
54
|
-
def sort_time(self):
|
50
|
+
def sort_time(self) -> None:
|
55
51
|
"""Sorts timestamps and removes duplicates."""
|
56
52
|
time = self.data["time"]
|
57
53
|
_time, ind = np.unique(time, return_index=True)
|
58
54
|
self._screen_time_indices(ind)
|
59
55
|
|
60
|
-
def screen_date(self):
|
56
|
+
def screen_date(self) -> None:
|
61
57
|
time = self.data["time"]
|
62
|
-
if self.
|
63
|
-
|
64
|
-
self.expected_date = self.sane_date.isoformat()
|
65
|
-
is_valid = np.array([t.date() == self.sane_date for t in time])
|
58
|
+
self.date = time[0].date() if self.expected_date is None else self.expected_date
|
59
|
+
is_valid = np.array([t.date() == self.date for t in time])
|
66
60
|
self._screen_time_indices(is_valid)
|
67
61
|
|
68
62
|
def _screen_time_indices(
|
69
63
|
self, valid_indices: npt.NDArray[np.intp] | npt.NDArray[np.bool]
|
70
|
-
):
|
64
|
+
) -> None:
|
71
65
|
time = self.data["time"]
|
72
66
|
n_time = len(time)
|
73
67
|
if len(valid_indices) == 0 or (
|
@@ -79,14 +73,13 @@ class VaisalaCeilo(Ceilometer):
|
|
79
73
|
if hasattr(array, "shape") and array.shape[:1] == (n_time,):
|
80
74
|
self.data[key] = self.data[key][valid_indices]
|
81
75
|
|
82
|
-
def convert_to_fraction_hour(self):
|
76
|
+
def convert_to_fraction_hour(self) -> None:
|
83
77
|
time = self.data["time"]
|
84
78
|
midnight = time[0].replace(hour=0, minute=0, second=0, microsecond=0)
|
85
79
|
hour = datetime.timedelta(hours=1)
|
86
80
|
self.data["time"] = (time - midnight) / hour
|
87
|
-
self.date = self.expected_date.split("-") # type: ignore[union-attr]
|
88
81
|
|
89
|
-
def _store_ceilometer_info(self):
|
82
|
+
def _store_ceilometer_info(self) -> None:
|
90
83
|
raise NotImplementedError
|
91
84
|
|
92
85
|
|
@@ -95,10 +88,15 @@ class ClCeilo(VaisalaCeilo):
|
|
95
88
|
|
96
89
|
noise_param = NoiseParam(noise_min=3.1e-8, noise_smooth_min=1.1e-8)
|
97
90
|
|
98
|
-
def __init__(
|
91
|
+
def __init__(
|
92
|
+
self,
|
93
|
+
full_path: str | PathLike,
|
94
|
+
site_meta: dict,
|
95
|
+
expected_date: datetime.date | None = None,
|
96
|
+
) -> None:
|
99
97
|
super().__init__(read_cl_file, full_path, site_meta, expected_date)
|
100
98
|
|
101
|
-
def _store_ceilometer_info(self):
|
99
|
+
def _store_ceilometer_info(self) -> None:
|
102
100
|
n_gates = self.data["beta_raw"].shape[1]
|
103
101
|
if n_gates < 1540:
|
104
102
|
self.instrument = instruments.CL31
|
@@ -111,11 +109,16 @@ class Ct25k(VaisalaCeilo):
|
|
111
109
|
|
112
110
|
noise_param = NoiseParam(noise_min=0.7e-7, noise_smooth_min=1.2e-8)
|
113
111
|
|
114
|
-
def __init__(
|
112
|
+
def __init__(
|
113
|
+
self,
|
114
|
+
full_path: str | PathLike,
|
115
|
+
site_meta: dict,
|
116
|
+
expected_date: datetime.date | None = None,
|
117
|
+
) -> None:
|
115
118
|
super().__init__(read_ct_file, full_path, site_meta, expected_date)
|
116
119
|
self._store_ceilometer_info()
|
117
120
|
|
118
|
-
def _store_ceilometer_info(self):
|
121
|
+
def _store_ceilometer_info(self) -> None:
|
119
122
|
self.instrument = instruments.CT25K
|
120
123
|
|
121
124
|
|
@@ -124,8 +127,13 @@ class Cs135(VaisalaCeilo):
|
|
124
127
|
|
125
128
|
noise_param = NoiseParam()
|
126
129
|
|
127
|
-
def __init__(
|
130
|
+
def __init__(
|
131
|
+
self,
|
132
|
+
full_path: str | PathLike,
|
133
|
+
site_meta: dict,
|
134
|
+
expected_date: datetime.date | None = None,
|
135
|
+
) -> None:
|
128
136
|
super().__init__(read_cs_file, full_path, site_meta, expected_date)
|
129
137
|
|
130
|
-
def _store_ceilometer_info(self):
|
138
|
+
def _store_ceilometer_info(self) -> None:
|
131
139
|
self.instrument = instruments.CS135
|
@@ -5,6 +5,7 @@ import re
|
|
5
5
|
from collections import defaultdict
|
6
6
|
from collections.abc import Iterable, Sequence
|
7
7
|
from os import PathLike
|
8
|
+
from uuid import UUID
|
8
9
|
|
9
10
|
import numpy as np
|
10
11
|
from numpy import ma
|
@@ -17,16 +18,16 @@ from cloudnetpy.exceptions import ValidTimeStampError
|
|
17
18
|
from cloudnetpy.instruments import instruments
|
18
19
|
from cloudnetpy.instruments.cloudnet_instrument import CSVFile
|
19
20
|
from cloudnetpy.instruments.toa5 import read_toa5
|
20
|
-
from cloudnetpy.utils import datetime2decimal_hours
|
21
|
+
from cloudnetpy.utils import datetime2decimal_hours, get_uuid
|
21
22
|
|
22
23
|
|
23
24
|
def ws2nc(
|
24
25
|
weather_station_file: str | PathLike | Sequence[str | PathLike],
|
25
|
-
output_file: str,
|
26
|
+
output_file: str | PathLike,
|
26
27
|
site_meta: dict,
|
27
|
-
uuid: str | None = None,
|
28
|
+
uuid: str | UUID | None = None,
|
28
29
|
date: str | datetime.date | None = None,
|
29
|
-
) ->
|
30
|
+
) -> UUID:
|
30
31
|
"""Converts weather station data into Cloudnet Level 1b netCDF file.
|
31
32
|
|
32
33
|
Args:
|
@@ -47,6 +48,7 @@ def ws2nc(
|
|
47
48
|
weather_station_file = [weather_station_file]
|
48
49
|
if isinstance(date, str):
|
49
50
|
date = datetime.date.fromisoformat(date)
|
51
|
+
uuid = get_uuid(uuid)
|
50
52
|
ws: WS
|
51
53
|
if site_meta["name"] == "Palaiseau":
|
52
54
|
ws = PalaiseauWS(weather_station_file, site_meta)
|
@@ -85,15 +87,16 @@ def ws2nc(
|
|
85
87
|
ws.calculate_rainfall_amount()
|
86
88
|
attributes = output.add_time_attribute({}, ws.date)
|
87
89
|
output.update_attributes(ws.data, attributes)
|
88
|
-
|
90
|
+
output.save_level1b(ws, output_file, uuid)
|
91
|
+
return uuid
|
89
92
|
|
90
93
|
|
91
94
|
class WS(CSVFile):
|
92
|
-
def __init__(self, site_meta: dict):
|
95
|
+
def __init__(self, site_meta: dict) -> None:
|
93
96
|
super().__init__(site_meta)
|
94
97
|
self.instrument = instruments.GENERIC_WEATHER_STATION
|
95
98
|
|
96
|
-
date:
|
99
|
+
date: datetime.date
|
97
100
|
|
98
101
|
def calculate_rainfall_amount(self) -> None:
|
99
102
|
if "rainfall_amount" in self.data or "rainfall_rate" not in self.data:
|
@@ -143,7 +146,7 @@ class WS(CSVFile):
|
|
143
146
|
|
144
147
|
|
145
148
|
class PalaiseauWS(WS):
|
146
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
149
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
147
150
|
super().__init__(site_meta)
|
148
151
|
self.filenames = filenames
|
149
152
|
self._data = self._read_data()
|
@@ -232,7 +235,7 @@ class BucharestWS(PalaiseauWS):
|
|
232
235
|
|
233
236
|
|
234
237
|
class GranadaWS(WS):
|
235
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
238
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
236
239
|
if len(filenames) != 1:
|
237
240
|
raise ValueError
|
238
241
|
super().__init__(site_meta)
|
@@ -282,7 +285,7 @@ class GranadaWS(WS):
|
|
282
285
|
|
283
286
|
|
284
287
|
class KenttarovaWS(WS):
|
285
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
288
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
286
289
|
super().__init__(site_meta)
|
287
290
|
self.filenames = filenames
|
288
291
|
self._data = self._read_data()
|
@@ -338,7 +341,7 @@ class HyytialaWS(WS):
|
|
338
341
|
- BbRT/mm = Bucket content in real-time (Pluvio200) [mm].
|
339
342
|
"""
|
340
343
|
|
341
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
344
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
342
345
|
super().__init__(site_meta)
|
343
346
|
self.filename = filenames[0]
|
344
347
|
self._data = self._read_data()
|
@@ -399,7 +402,7 @@ class HyytialaWS(WS):
|
|
399
402
|
|
400
403
|
|
401
404
|
class GalatiWS(WS):
|
402
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
405
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
403
406
|
super().__init__(site_meta)
|
404
407
|
self.filename = filenames[0]
|
405
408
|
self._data = self._read_data()
|
@@ -422,7 +425,7 @@ class GalatiWS(WS):
|
|
422
425
|
parsed_value = math.nan
|
423
426
|
raw_data[key].append(parsed_value)
|
424
427
|
|
425
|
-
def read_value(keys: Iterable[str]):
|
428
|
+
def read_value(keys: Iterable[str]) -> list:
|
426
429
|
for key in keys:
|
427
430
|
if key in raw_data:
|
428
431
|
return raw_data[key]
|
@@ -443,7 +446,7 @@ class GalatiWS(WS):
|
|
443
446
|
|
444
447
|
def add_data(self) -> None:
|
445
448
|
# Skip wind measurements where range was limited to 0-180 degrees
|
446
|
-
if
|
449
|
+
if self.date < datetime.date(2024, 10, 29):
|
447
450
|
del self._data["wind_speed"]
|
448
451
|
del self._data["wind_direction"]
|
449
452
|
return super().add_data()
|
@@ -454,7 +457,7 @@ class GalatiWS(WS):
|
|
454
457
|
|
455
458
|
|
456
459
|
class JuelichWS(WS):
|
457
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
460
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
458
461
|
super().__init__(site_meta)
|
459
462
|
self.filename = filenames[0]
|
460
463
|
self._data = self._read_data()
|
@@ -500,7 +503,7 @@ class JuelichWS(WS):
|
|
500
503
|
class LampedusaWS(WS):
|
501
504
|
"""Read Lampedusa weather station data in ICOS format."""
|
502
505
|
|
503
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
506
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
504
507
|
super().__init__(site_meta)
|
505
508
|
self.filename = filenames[0]
|
506
509
|
self._data = self._read_data()
|
@@ -553,7 +556,7 @@ class LampedusaWS(WS):
|
|
553
556
|
|
554
557
|
|
555
558
|
class LimassolWS(WS):
|
556
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
559
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
557
560
|
super().__init__(site_meta)
|
558
561
|
self.filenames = filenames
|
559
562
|
self._data = defaultdict(list)
|
@@ -610,7 +613,7 @@ class LimassolWS(WS):
|
|
610
613
|
) # mm/(10 min) -> m/s
|
611
614
|
|
612
615
|
|
613
|
-
def _parse_sirta(filename: str | PathLike):
|
616
|
+
def _parse_sirta(filename: str | PathLike) -> dict:
|
614
617
|
"""Parse SIRTA-style weather station file."""
|
615
618
|
with open(filename, "rb") as f:
|
616
619
|
raw_content = f.read()
|
@@ -653,7 +656,7 @@ def _parse_sirta(filename: str | PathLike):
|
|
653
656
|
|
654
657
|
|
655
658
|
class LAquilaWS(WS):
|
656
|
-
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict):
|
659
|
+
def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
|
657
660
|
super().__init__(site_meta)
|
658
661
|
self.filenames = filenames
|
659
662
|
self._data = self._read_data()
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
from datetime import datetime
|
3
|
+
from os import PathLike
|
4
|
+
from uuid import UUID
|
3
5
|
|
4
6
|
import netCDF4
|
7
|
+
import numpy.typing as npt
|
5
8
|
|
6
9
|
from cloudnetpy import output, utils
|
7
10
|
|
@@ -62,11 +65,11 @@ def update_attributes(model_downsample_variables: dict, attributes: dict) -> Non
|
|
62
65
|
|
63
66
|
def save_downsampled_file(
|
64
67
|
id_mark: str,
|
65
|
-
file_name: str,
|
68
|
+
file_name: str | PathLike,
|
66
69
|
objects: tuple,
|
67
|
-
files: tuple,
|
68
|
-
uuid:
|
69
|
-
) ->
|
70
|
+
files: tuple[list[str | PathLike], str | PathLike],
|
71
|
+
uuid: UUID,
|
72
|
+
) -> None:
|
70
73
|
"""Saves a standard downsampled day product file.
|
71
74
|
|
72
75
|
Args:
|
@@ -82,36 +85,31 @@ def save_downsampled_file(
|
|
82
85
|
"""
|
83
86
|
obj = objects[0]
|
84
87
|
dimensions = {"time": len(obj.time), "level": len(obj.data["level"][:])}
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
def add_var2ncfile(obj: ModelManager, file_name: str) -> None:
|
106
|
-
nc_file = netCDF4.Dataset(file_name, "r+", format="NETCDF4_CLASSIC")
|
107
|
-
_write_vars2nc(nc_file, obj.data)
|
108
|
-
nc_file.close()
|
88
|
+
with output.init_file(file_name, dimensions, obj.data, uuid) as root_group:
|
89
|
+
_augment_global_attributes(root_group)
|
90
|
+
root_group.cloudnet_file_type = "l3-" + id_mark.split("_")[0]
|
91
|
+
root_group.title = (
|
92
|
+
f"Downsampled {id_mark.capitalize().replace('_', ' of ')} "
|
93
|
+
f"from {obj.dataset.location}"
|
94
|
+
)
|
95
|
+
_add_source(root_group, objects, files)
|
96
|
+
output.copy_global(
|
97
|
+
obj.dataset, root_group, ("location", "day", "month", "year")
|
98
|
+
)
|
99
|
+
if not hasattr(obj.dataset, "day"):
|
100
|
+
root_group.year, root_group.month, root_group.day = obj.date
|
101
|
+
output.merge_history(root_group, id_mark, obj)
|
102
|
+
|
103
|
+
|
104
|
+
def add_var2ncfile(obj: ModelManager, file_name: str | PathLike) -> None:
|
105
|
+
with netCDF4.Dataset(file_name, "r+", format="NETCDF4_CLASSIC") as nc_file:
|
106
|
+
_write_vars2nc(nc_file, obj.data)
|
109
107
|
|
110
108
|
|
111
109
|
def _write_vars2nc(rootgrp: netCDF4.Dataset, cloudnet_variables: dict) -> None:
|
112
110
|
"""Iterates over Cloudnet-ME instances and write to given rootgrp."""
|
113
111
|
|
114
|
-
def _get_dimensions(array) -> tuple:
|
112
|
+
def _get_dimensions(array: npt.NDArray) -> tuple:
|
115
113
|
"""Finds correct dimensions for a variable."""
|
116
114
|
if utils.isscalar(array):
|
117
115
|
return ()
|