cloudnetpy 1.61.11__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.
@@ -8,7 +8,7 @@ from numpy import ma
8
8
  from cloudnetpy import output
9
9
  from cloudnetpy.categorize import atmos_utils
10
10
  from cloudnetpy.cloudnetarray import CloudnetArray
11
- from cloudnetpy.constants import SEC_IN_HOUR
11
+ from cloudnetpy.constants import MM_H_TO_M_S, SEC_IN_HOUR
12
12
  from cloudnetpy.exceptions import ValidTimeStampError, WeatherStationDataError
13
13
  from cloudnetpy.instruments import instruments
14
14
  from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
@@ -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.convert_units()
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
- date: list[str]
74
-
75
- def convert_time(self) -> None:
76
- pass
78
+ def __init__(self):
79
+ super().__init__()
80
+ self._data: dict
77
81
 
78
- def screen_timestamps(self, date: str) -> None:
79
- pass
82
+ date: list[str]
80
83
 
81
84
  def add_date(self) -> None:
82
- pass
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
- pass
86
-
87
- def convert_units(self) -> None:
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 {"timestamps": timestamps, "values": values}
179
+ return {"time": timestamps, "values": values}
145
180
 
146
181
  def convert_time(self) -> None:
147
- decimal_hours = datetime2decimal_hours(self._data["timestamps"])
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["timestamps"]]
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 convert_units(self) -> None:
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,41 +309,89 @@ class KenttarovaWS(WS):
322
309
  merged[key] = new_value
323
310
  return merged
324
311
 
325
- def convert_time(self) -> None:
326
- pass
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 screen_timestamps(self, date: str) -> None:
329
- dates = [str(d.date()) for d in self._data["time"]]
330
- valid_ind = [ind for ind, d in enumerate(dates) if d == date]
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
- def add_data(self) -> None:
347
- for key, value in self._data.items():
348
- parsed = datetime2decimal_hours(value) if key == "time" else ma.array(value)
349
- self.data[key] = CloudnetArray(parsed, key)
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 convert_units(self) -> None:
352
- temperature_kelvins = atmos_utils.c2k(self.data["air_temperature"][:])
353
- self.data["air_temperature"].data = temperature_kelvins
354
- self.data["relative_humidity"].data = self.data["relative_humidity"][:] / 100
355
- self.data["air_pressure"].data = self.data["air_pressure"][:] * 100 # hPa -> Pa
356
- rainfall_rate = self.data["rainfall_rate"][:]
357
- self.data["rainfall_rate"].data = (
358
- rainfall_rate / 3600 / 10 / 1000
359
- ) # not sure about units
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
360
395
 
361
396
 
362
397
  ATTRIBUTES = {
@@ -4,6 +4,7 @@ import re
4
4
  import textwrap
5
5
  from dataclasses import dataclass
6
6
  from datetime import date
7
+ from typing import Any
7
8
 
8
9
  import matplotlib.pyplot as plt
9
10
  import netCDF4
@@ -356,7 +357,7 @@ class Plot:
356
357
  raise ValueError(msg)
357
358
  max_gap_fraction_hour = _get_max_gap_in_minutes(figure_data) / 60
358
359
 
359
- if figure_data.file.cloudnet_file_type == "model":
360
+ if getattr(figure_data.file, "cloudnet_file_type", "") == "model":
360
361
  time, data = screen_completely_masked_profiles(time, data)
361
362
 
362
363
  gap_indices = np.where(np.diff(time) > max_gap_fraction_hour)[0]
@@ -491,15 +492,28 @@ class Plot2D(Plot):
491
492
  smoothed_data = uniform_filter(self._data[valid_time_ind, :], sigma_units)
492
493
  self._data[valid_time_ind, :] = smoothed_data
493
494
 
494
- image = self._ax.pcolorfast(
495
- figure_data.time_including_gaps,
496
- alt,
497
- self._data.T[:-1, :-1],
498
- cmap=plt.get_cmap(str(self._plot_meta.cmap)),
499
- vmin=vmin,
500
- vmax=vmax,
501
- zorder=_get_zorder("data"),
502
- )
495
+ pcolor_kwargs = {
496
+ "cmap": plt.get_cmap(str(self._plot_meta.cmap)),
497
+ "vmin": vmin,
498
+ "vmax": vmax,
499
+ "zorder": _get_zorder("data"),
500
+ }
501
+ image: Any
502
+ if getattr(figure_data.file, "cloudnet_file_type", "") == "model":
503
+ image = self._ax.pcolor(
504
+ figure_data.time_including_gaps,
505
+ alt,
506
+ self._data.T,
507
+ **pcolor_kwargs,
508
+ shading="nearest",
509
+ )
510
+ else:
511
+ image = self._ax.pcolorfast(
512
+ figure_data.time_including_gaps,
513
+ alt,
514
+ self._data.T[:-1, :-1],
515
+ **pcolor_kwargs,
516
+ )
503
517
  cbar = self._init_colorbar(image)
504
518
  cbar.set_label(str(self._plot_meta.clabel), fontsize=13)
505
519
 
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 61
3
- PATCH = 11
3
+ PATCH = 13
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.61.11
3
+ Version: 1.61.13
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -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=B3laILu3O0Nl0QccRB9qv-lhpfU5LAfqtgMUzrcUIHg,73
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=R2b-VfCRkVluEbGd9NGuzf46k18X1BfZwJHhwWn_sVM,13801
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=bve91iM9RcWmKaZOFWxVh2y3DPmupI1944MMYDdv17I,32459
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.11.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
112
- cloudnetpy-1.61.11.dist-info/METADATA,sha256=MjuSUe-bVQa8CzRqFWeJBhqcbkcv5OkXSkM0srtkDpo,5785
113
- cloudnetpy-1.61.11.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
114
- cloudnetpy-1.61.11.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
115
- cloudnetpy-1.61.11.dist-info/RECORD,,
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,,