cloudnetpy 1.61.12__py3-none-any.whl → 1.61.13__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/instruments/weather_station.py +130 -94
- cloudnetpy/plotting/plotting.py +2 -2
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.61.12.dist-info → cloudnetpy-1.61.13.dist-info}/METADATA +1 -1
- {cloudnetpy-1.61.12.dist-info → cloudnetpy-1.61.13.dist-info}/RECORD +8 -8
- {cloudnetpy-1.61.12.dist-info → cloudnetpy-1.61.13.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.61.12.dist-info → cloudnetpy-1.61.13.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.61.12.dist-info → cloudnetpy-1.61.13.dist-info}/top_level.txt +0 -0
@@ -51,6 +51,8 @@ def ws2nc(
|
|
51
51
|
ws = GranadaWS(weather_station_file, site_meta)
|
52
52
|
elif site_meta["name"] == "Kenttärova":
|
53
53
|
ws = KenttarovaWS(weather_station_file, site_meta)
|
54
|
+
elif site_meta["name"] == "Hyytiälä":
|
55
|
+
ws = HyytialaWS(weather_station_file, site_meta)
|
54
56
|
else:
|
55
57
|
msg = "Unsupported site"
|
56
58
|
raise ValueError(msg) # noqa: TRY301
|
@@ -60,7 +62,10 @@ def ws2nc(
|
|
60
62
|
ws.add_date()
|
61
63
|
ws.add_site_geolocation()
|
62
64
|
ws.add_data()
|
63
|
-
ws.
|
65
|
+
ws.convert_temperature_and_humidity()
|
66
|
+
ws.convert_pressure()
|
67
|
+
ws.convert_rainfall_rate()
|
68
|
+
ws.convert_rainfall_amount()
|
64
69
|
ws.calculate_rainfall_amount()
|
65
70
|
attributes = output.add_time_attribute(ATTRIBUTES, ws.date)
|
66
71
|
output.update_attributes(ws.data, attributes)
|
@@ -70,22 +75,24 @@ def ws2nc(
|
|
70
75
|
|
71
76
|
|
72
77
|
class WS(CloudnetInstrument):
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
pass
|
78
|
+
def __init__(self):
|
79
|
+
super().__init__()
|
80
|
+
self._data: dict
|
77
81
|
|
78
|
-
|
79
|
-
pass
|
82
|
+
date: list[str]
|
80
83
|
|
81
84
|
def add_date(self) -> None:
|
82
|
-
|
85
|
+
first_date = self._data["time"][0].date()
|
86
|
+
self.date = [
|
87
|
+
str(first_date.year),
|
88
|
+
str(first_date.month).zfill(2),
|
89
|
+
str(first_date.day).zfill(2),
|
90
|
+
]
|
83
91
|
|
84
92
|
def add_data(self) -> None:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
pass
|
93
|
+
for key, value in self._data.items():
|
94
|
+
parsed = datetime2decimal_hours(value) if key == "time" else ma.array(value)
|
95
|
+
self.data[key] = CloudnetArray(parsed, key)
|
89
96
|
|
90
97
|
def calculate_rainfall_amount(self) -> None:
|
91
98
|
if "rainfall_amount" in self.data:
|
@@ -94,6 +101,34 @@ class WS(CloudnetInstrument):
|
|
94
101
|
rainfall_amount = ma.cumsum(self.data["rainfall_rate"].data * resolution)
|
95
102
|
self.data["rainfall_amount"] = CloudnetArray(rainfall_amount, "rainfall_amount")
|
96
103
|
|
104
|
+
def screen_timestamps(self, date: str) -> None:
|
105
|
+
dates = [str(d.date()) for d in self._data["time"]]
|
106
|
+
valid_ind = [ind for ind, d in enumerate(dates) if d == date]
|
107
|
+
if not valid_ind:
|
108
|
+
raise ValidTimeStampError
|
109
|
+
for key in self._data:
|
110
|
+
self._data[key] = [
|
111
|
+
x for ind, x in enumerate(self._data[key]) if ind in valid_ind
|
112
|
+
]
|
113
|
+
|
114
|
+
def convert_temperature_and_humidity(self) -> None:
|
115
|
+
temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
|
116
|
+
self.data["air_temperature"].data = temperature_kelvins
|
117
|
+
self.data["relative_humidity"].data = self.data["relative_humidity"][:] / 100
|
118
|
+
|
119
|
+
def convert_rainfall_rate(self) -> None:
|
120
|
+
rainfall_rate = self.data["rainfall_rate"][:]
|
121
|
+
self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
|
122
|
+
|
123
|
+
def convert_pressure(self) -> None:
|
124
|
+
self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa to Pa
|
125
|
+
|
126
|
+
def convert_time(self) -> None:
|
127
|
+
pass
|
128
|
+
|
129
|
+
def convert_rainfall_amount(self) -> None:
|
130
|
+
pass
|
131
|
+
|
97
132
|
|
98
133
|
class PalaiseauWS(WS):
|
99
134
|
def __init__(self, filenames: list[str], site_meta: dict):
|
@@ -141,14 +176,14 @@ class PalaiseauWS(WS):
|
|
141
176
|
for title, identifier in zip(column_titles, expected_identifiers, strict=True):
|
142
177
|
if identifier not in title:
|
143
178
|
raise ValueError(error_msg)
|
144
|
-
return {"
|
179
|
+
return {"time": timestamps, "values": values}
|
145
180
|
|
146
181
|
def convert_time(self) -> None:
|
147
|
-
decimal_hours = datetime2decimal_hours(self._data["
|
182
|
+
decimal_hours = datetime2decimal_hours(self._data["time"])
|
148
183
|
self.data["time"] = CloudnetArray(decimal_hours, "time")
|
149
184
|
|
150
185
|
def screen_timestamps(self, date: str) -> None:
|
151
|
-
dates = [str(d.date()) for d in self._data["
|
186
|
+
dates = [str(d.date()) for d in self._data["time"]]
|
152
187
|
valid_ind = [ind for ind, d in enumerate(dates) if d == date]
|
153
188
|
if not valid_ind:
|
154
189
|
raise ValidTimeStampError
|
@@ -157,14 +192,6 @@ class PalaiseauWS(WS):
|
|
157
192
|
x for ind, x in enumerate(self._data[key]) if ind in valid_ind
|
158
193
|
]
|
159
194
|
|
160
|
-
def add_date(self) -> None:
|
161
|
-
first_date = self._data["timestamps"][0].date()
|
162
|
-
self.date = [
|
163
|
-
str(first_date.year),
|
164
|
-
str(first_date.month).zfill(2),
|
165
|
-
str(first_date.day).zfill(2),
|
166
|
-
]
|
167
|
-
|
168
195
|
def add_data(self) -> None:
|
169
196
|
keys = (
|
170
197
|
"wind_speed",
|
@@ -180,13 +207,7 @@ class PalaiseauWS(WS):
|
|
180
207
|
array_masked = ma.masked_invalid(array)
|
181
208
|
self.data[key] = CloudnetArray(array_masked, key)
|
182
209
|
|
183
|
-
def
|
184
|
-
temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
|
185
|
-
self.data["air_temperature"].data = temperature_kelvins
|
186
|
-
self.data["relative_humidity"].data = self.data["relative_humidity"][:] / 100
|
187
|
-
self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa -> Pa
|
188
|
-
rainfall_rate = self.data["rainfall_rate"][:]
|
189
|
-
self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
|
210
|
+
def convert_rainfall_amount(self) -> None:
|
190
211
|
self.data["rainfall_amount"].data = (
|
191
212
|
self.data["rainfall_amount"][:] / 1000
|
192
213
|
) # mm -> m
|
@@ -240,40 +261,6 @@ class GranadaWS(WS):
|
|
240
261
|
data[keymap[key]].append(parsed)
|
241
262
|
return data
|
242
263
|
|
243
|
-
def convert_time(self) -> None:
|
244
|
-
pass
|
245
|
-
|
246
|
-
def screen_timestamps(self, date: str) -> None:
|
247
|
-
dates = [str(d.date()) for d in self._data["time"]]
|
248
|
-
valid_ind = [ind for ind, d in enumerate(dates) if d == date]
|
249
|
-
if not valid_ind:
|
250
|
-
raise ValidTimeStampError
|
251
|
-
for key in self._data:
|
252
|
-
self._data[key] = [
|
253
|
-
x for ind, x in enumerate(self._data[key]) if ind in valid_ind
|
254
|
-
]
|
255
|
-
|
256
|
-
def add_date(self) -> None:
|
257
|
-
first_date = self._data["time"][0].date()
|
258
|
-
self.date = [
|
259
|
-
str(first_date.year),
|
260
|
-
str(first_date.month).zfill(2),
|
261
|
-
str(first_date.day).zfill(2),
|
262
|
-
]
|
263
|
-
|
264
|
-
def add_data(self) -> None:
|
265
|
-
for key, value in self._data.items():
|
266
|
-
parsed = datetime2decimal_hours(value) if key == "time" else np.array(value)
|
267
|
-
self.data[key] = CloudnetArray(parsed, key)
|
268
|
-
|
269
|
-
def convert_units(self) -> None:
|
270
|
-
temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
|
271
|
-
self.data["air_temperature"].data = temperature_kelvins
|
272
|
-
self.data["relative_humidity"].data = self.data["relative_humidity"][:] / 100
|
273
|
-
self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa -> Pa
|
274
|
-
rainfall_rate = self.data["rainfall_rate"][:]
|
275
|
-
self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
|
276
|
-
|
277
264
|
|
278
265
|
class KenttarovaWS(WS):
|
279
266
|
def __init__(self, filenames: list[str], site_meta: dict):
|
@@ -322,40 +309,89 @@ class KenttarovaWS(WS):
|
|
322
309
|
merged[key] = new_value
|
323
310
|
return merged
|
324
311
|
|
325
|
-
def
|
326
|
-
|
312
|
+
def convert_rainfall_rate(self) -> None:
|
313
|
+
# Rainfall rate is 10-minute averaged in mm h-1
|
314
|
+
rainfall_rate = self.data["rainfall_rate"][:]
|
315
|
+
self.data["rainfall_rate"].data = rainfall_rate * MM_H_TO_M_S / 10
|
327
316
|
|
328
|
-
def
|
329
|
-
|
330
|
-
|
331
|
-
if not valid_ind:
|
332
|
-
raise ValidTimeStampError
|
333
|
-
for key in self._data:
|
334
|
-
self._data[key] = [
|
335
|
-
x for ind, x in enumerate(self._data[key]) if ind in valid_ind
|
336
|
-
]
|
317
|
+
def convert_pressure(self) -> None:
|
318
|
+
# Magic number 10 to convert to realistic Pa
|
319
|
+
self.data["air_pressure"].data = self.data["air_pressure"][:] * 10
|
337
320
|
|
338
|
-
def add_date(self) -> None:
|
339
|
-
first_date = self._data["time"][0].date()
|
340
|
-
self.date = [
|
341
|
-
str(first_date.year),
|
342
|
-
str(first_date.month).zfill(2),
|
343
|
-
str(first_date.day).zfill(2),
|
344
|
-
]
|
345
321
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
322
|
+
class HyytialaWS(WS):
|
323
|
+
"""
|
324
|
+
Hyytiälä rain-gauge variables: a = Pluvio400 and b = Pluvio200.
|
325
|
+
E.g.
|
326
|
+
- AaRNRT/mm = amount of non-real-time rain total (Pluvio400) [mm]
|
327
|
+
- BbRT/mm = Bucket content in real-time (Pluvio200) [mm]
|
328
|
+
"""
|
350
329
|
|
351
|
-
def
|
352
|
-
|
353
|
-
self.
|
354
|
-
self.
|
355
|
-
self.
|
356
|
-
|
357
|
-
|
358
|
-
|
330
|
+
def __init__(self, filenames: list[str], site_meta: dict):
|
331
|
+
super().__init__()
|
332
|
+
self.filename = filenames[0]
|
333
|
+
self.site_meta = site_meta
|
334
|
+
self.instrument = instruments.GENERIC_WEATHER_STATION
|
335
|
+
self._data = self._read_data()
|
336
|
+
|
337
|
+
def _read_data(self) -> dict:
|
338
|
+
with open(self.filename, newline="") as f:
|
339
|
+
# Skip first two lines
|
340
|
+
for _ in range(2):
|
341
|
+
next(f)
|
342
|
+
# Read header
|
343
|
+
header_line = f.readline().strip()
|
344
|
+
fields = header_line[1:].strip().split()
|
345
|
+
reader = csv.DictReader(
|
346
|
+
f, delimiter=" ", skipinitialspace=True, fieldnames=fields
|
347
|
+
)
|
348
|
+
if reader.fieldnames is None:
|
349
|
+
raise ValueError
|
350
|
+
raw_data: dict = {key: [] for key in reader.fieldnames}
|
351
|
+
raw_data["time"] = []
|
352
|
+
# Read data
|
353
|
+
for row in reader:
|
354
|
+
for key, value in row.items():
|
355
|
+
if key:
|
356
|
+
parsed_value: float | datetime.datetime
|
357
|
+
if key == "y":
|
358
|
+
current_time = datetime.datetime(
|
359
|
+
int(value),
|
360
|
+
int(row["m"]),
|
361
|
+
int(row["d"]),
|
362
|
+
int(row["minute"]) // 60,
|
363
|
+
int(row["minute"]) % 60,
|
364
|
+
)
|
365
|
+
raw_data["time"].append(current_time)
|
366
|
+
else:
|
367
|
+
try:
|
368
|
+
parsed_value = float(value)
|
369
|
+
except (TypeError, ValueError):
|
370
|
+
parsed_value = math.nan
|
371
|
+
if parsed_value == -99.99:
|
372
|
+
parsed_value = math.nan
|
373
|
+
raw_data[key].append(parsed_value)
|
374
|
+
|
375
|
+
data = {
|
376
|
+
"time": raw_data["time"],
|
377
|
+
"air_temperature": raw_data["Ta/dsC"],
|
378
|
+
"relative_humidity": raw_data["RH/pcnt"],
|
379
|
+
"air_pressure": raw_data["Pa/kPa"],
|
380
|
+
"wind_speed": raw_data["WS/(m/s)"],
|
381
|
+
"wind_direction": raw_data["WD/ds"],
|
382
|
+
"rainfall_rate": raw_data["AaNRT/mm"],
|
383
|
+
}
|
384
|
+
for key, value in data.items():
|
385
|
+
new_value = np.array(value)
|
386
|
+
if key != "time":
|
387
|
+
new_value = ma.masked_where(np.isnan(new_value), new_value)
|
388
|
+
data[key] = new_value
|
389
|
+
return data
|
390
|
+
|
391
|
+
def convert_pressure(self) -> None:
|
392
|
+
self.data["air_pressure"].data = (
|
393
|
+
self.data["air_pressure"][:] * 1000
|
394
|
+
) # kPa to Pa
|
359
395
|
|
360
396
|
|
361
397
|
ATTRIBUTES = {
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -357,7 +357,7 @@ class Plot:
|
|
357
357
|
raise ValueError(msg)
|
358
358
|
max_gap_fraction_hour = _get_max_gap_in_minutes(figure_data) / 60
|
359
359
|
|
360
|
-
if figure_data.file
|
360
|
+
if getattr(figure_data.file, "cloudnet_file_type", "") == "model":
|
361
361
|
time, data = screen_completely_masked_profiles(time, data)
|
362
362
|
|
363
363
|
gap_indices = np.where(np.diff(time) > max_gap_fraction_hour)[0]
|
@@ -499,7 +499,7 @@ class Plot2D(Plot):
|
|
499
499
|
"zorder": _get_zorder("data"),
|
500
500
|
}
|
501
501
|
image: Any
|
502
|
-
if figure_data.file
|
502
|
+
if getattr(figure_data.file, "cloudnet_file_type", "") == "model":
|
503
503
|
image = self._ax.pcolor(
|
504
504
|
figure_data.time_including_gaps,
|
505
505
|
alt,
|
cloudnetpy/version.py
CHANGED
@@ -8,7 +8,7 @@ cloudnetpy/metadata.py,sha256=v_VDo2vbdTxB0zIsfP69IcrwSKiRlLpsGdq6JPI4CoA,5306
|
|
8
8
|
cloudnetpy/output.py,sha256=UzF0w51c6-QEBj-NfCJg5zTIKVzcmq1HyQb-3_qWTgk,14767
|
9
9
|
cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
cloudnetpy/utils.py,sha256=-8x7LQ6WDHxf2lDZfhG50WYe2iSVLQObnVXZG46JzKI,28468
|
11
|
-
cloudnetpy/version.py,sha256=
|
11
|
+
cloudnetpy/version.py,sha256=s1z4RwSs7fLnDJ-S01W99lgOij26tF-b_d0xSRnP25k,73
|
12
12
|
cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
|
13
13
|
cloudnetpy/categorize/atmos.py,sha256=fWW8ye_8HZASRAiYwURFKWzcGOYIA2RKeVxCq0lVOuM,12389
|
14
14
|
cloudnetpy/categorize/atmos_utils.py,sha256=wndpwJxc2-QnNTkV8tc8I11Vs_WkNz9sVMX1fuGgUC4,3777
|
@@ -47,7 +47,7 @@ cloudnetpy/instruments/rpg.py,sha256=yQpcKcgzRvVvkl6NhKvo4PUkv9nZ69_hzzPpS2Ei-Is
|
|
47
47
|
cloudnetpy/instruments/rpg_reader.py,sha256=LAdXL3TmD5QzQbqtPOcemZji_qkXwmw6a6F8NmF6Zg8,11355
|
48
48
|
cloudnetpy/instruments/toa5.py,sha256=1JnuYViD8c_tHJZ9lf4OU44iepEkXHsXOzDfVf_b0qc,1759
|
49
49
|
cloudnetpy/instruments/vaisala.py,sha256=ektdXoID2X_V9H5Zp1fgHTUBapFMSyPVEWW_aoR6DEY,14655
|
50
|
-
cloudnetpy/instruments/weather_station.py,sha256=
|
50
|
+
cloudnetpy/instruments/weather_station.py,sha256=WnU1z7JhgsAvqzUhYg1XeUkUBgvYRnt2LUWnea8IugM,15216
|
51
51
|
cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
|
52
52
|
cloudnetpy/instruments/disdrometer/common.py,sha256=g52iK2aNp3Z88kovUmGVpC54NZomPa9D871gzO0AmQ4,9267
|
53
53
|
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=WiL-vCjw9Gmb5irvW3AXddsyprp8MGOfqcVAlfy0zpc,25521
|
@@ -94,7 +94,7 @@ cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V
|
|
94
94
|
cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
|
95
95
|
cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
|
96
96
|
cloudnetpy/plotting/plot_meta.py,sha256=cLdCZrhbP-gaobS_zjcf8d2xVALzl7zh2qpttxCHyrg,15983
|
97
|
-
cloudnetpy/plotting/plotting.py,sha256=
|
97
|
+
cloudnetpy/plotting/plotting.py,sha256=t6Bljy63HrBkBBNl6VuESV1utIS9mxrIxrNwDFNurRs,32926
|
98
98
|
cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
|
99
99
|
cloudnetpy/products/classification.py,sha256=pzFQtgOKS7g_3LqiAY84EFUUste-VES7CJNgoq2Bs34,7914
|
100
100
|
cloudnetpy/products/der.py,sha256=XZMbqDQUq0E9iBU3Axr-NfUJfRAhjsaGlyxJ4tKyGcw,12444
|
@@ -108,8 +108,8 @@ cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe5
|
|
108
108
|
cloudnetpy/products/mwr_tools.py,sha256=RuzokxxqXlTGk7XAOrif_FDPUJdf0j_wJgNq-7a_nK8,4684
|
109
109
|
cloudnetpy/products/product_tools.py,sha256=rhx_Ru9FLlQqCNM-awoiHx18-Aq1eBwL9LiUaQoJs6k,10412
|
110
110
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
111
|
-
cloudnetpy-1.61.
|
112
|
-
cloudnetpy-1.61.
|
113
|
-
cloudnetpy-1.61.
|
114
|
-
cloudnetpy-1.61.
|
115
|
-
cloudnetpy-1.61.
|
111
|
+
cloudnetpy-1.61.13.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
112
|
+
cloudnetpy-1.61.13.dist-info/METADATA,sha256=R09GyBfK_BYlnHuZyPPX8H7JXhkWNxFExGWTSP2h7D8,5785
|
113
|
+
cloudnetpy-1.61.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
114
|
+
cloudnetpy-1.61.13.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
115
|
+
cloudnetpy-1.61.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|