cloudnetpy 1.67.8__py3-none-any.whl → 1.68.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.
@@ -9,5 +9,6 @@ from .mira import mira2nc
9
9
  from .mrr import mrr2nc
10
10
  from .pollyxt import pollyxt2nc
11
11
  from .radiometrics import radiometrics2nc
12
+ from .rain_e_h3 import rain_e_h32nc
12
13
  from .rpg import rpg2nc
13
14
  from .weather_station import ws2nc
@@ -112,3 +112,34 @@ class CloudnetInstrument:
112
112
  if np.isnan(zenith_angle) or zenith_angle is ma.masked:
113
113
  return None
114
114
  return zenith_angle
115
+
116
+
117
+ class CSVFile(CloudnetInstrument):
118
+ def __init__(self, site_meta: dict):
119
+ super().__init__()
120
+ self.site_meta = site_meta
121
+ self._data: dict = {}
122
+
123
+ def add_date(self) -> None:
124
+ dt = self._data["time"][0]
125
+ self.date = dt.strftime("%Y %m %d").split()
126
+
127
+ def add_data(self) -> None:
128
+ for key, value in self._data.items():
129
+ parsed = (
130
+ utils.datetime2decimal_hours(value)
131
+ if key == "time"
132
+ else ma.array(value)
133
+ )
134
+ self.data[key] = CloudnetArray(parsed, key)
135
+
136
+ def normalize_rainfall_amount(self) -> None:
137
+ if "rainfall_amount" in self.data:
138
+ amount = self.data["rainfall_amount"][:]
139
+ offset = 0
140
+ for i in range(1, len(amount)):
141
+ if amount[i] + offset < amount[i - 1]:
142
+ offset += amount[i - 1]
143
+ amount[i] += offset
144
+ amount -= amount[0]
145
+ self.data["rainfall_amount"].data = amount
@@ -0,0 +1,166 @@
1
+ import csv
2
+ import datetime
3
+ from os import PathLike
4
+
5
+ import numpy as np
6
+
7
+ from cloudnetpy import output
8
+ from cloudnetpy.exceptions import ValidTimeStampError
9
+ from cloudnetpy.instruments import instruments
10
+ from cloudnetpy.instruments.cloudnet_instrument import CSVFile
11
+
12
+
13
+ def rain_e_h32nc(
14
+ input_file: str | PathLike,
15
+ output_file: str,
16
+ site_meta: dict,
17
+ uuid: str | None = None,
18
+ date: str | datetime.date | None = None,
19
+ ):
20
+ """Converts rain_e_h3 rain-gauge into Cloudnet Level 1b netCDF file.
21
+
22
+ Args:
23
+ input_file: Filename of rain_e_h3 CSV file.
24
+ output_file: Output filename.
25
+ site_meta: Dictionary containing information about the site. Required key
26
+ is `name`.
27
+ uuid: Set specific UUID for the file.
28
+ date: Expected date of the measurements as YYYY-MM-DD or datetime.date object.
29
+
30
+ Returns:
31
+ UUID of the generated file.
32
+
33
+ Raises:
34
+ WeatherStationDataError : Unable to read the file.
35
+ ValidTimeStampError: No valid timestamps found.
36
+ """
37
+ rain = RainEH3(site_meta)
38
+ if isinstance(date, str):
39
+ date = datetime.date.fromisoformat(date)
40
+ rain.parse_input_file(input_file, date)
41
+ rain.add_data()
42
+ rain.add_date()
43
+ rain.convert_units()
44
+ rain.normalize_rainfall_amount()
45
+ rain.add_site_geolocation()
46
+ attributes = output.add_time_attribute({}, rain.date)
47
+ output.update_attributes(rain.data, attributes)
48
+ return output.save_level1b(rain, output_file, uuid)
49
+
50
+
51
+ class RainEH3(CSVFile):
52
+ def __init__(self, site_meta: dict):
53
+ super().__init__(site_meta)
54
+ self.instrument = instruments.RAIN_E_H3
55
+ self._data = {
56
+ "time": [],
57
+ "rainfall_rate": [],
58
+ "rainfall_amount": [],
59
+ }
60
+
61
+ def parse_input_file(
62
+ self, filepath: str | PathLike, date: datetime.date | None = None
63
+ ) -> None:
64
+ with open(filepath, encoding="latin1") as f:
65
+ data = list(csv.reader(f, delimiter=";"))
66
+ n_values = np.median([len(row) for row in data]).astype(int)
67
+
68
+ if n_values == 22:
69
+ self._read_talker_protocol_22_columns(data, date)
70
+ elif n_values == 16:
71
+ self._read_talker_protocol_16_columns(data, date)
72
+ else:
73
+ msg = "Only talker protocol with 16 or 22 columns is supported."
74
+ raise NotImplementedError(msg)
75
+
76
+ def _read_talker_protocol_16_columns(
77
+ self, data: list, date: datetime.date | None = None
78
+ ) -> None:
79
+ """Old Lindenberg data format.
80
+
81
+ 0 date DD.MM.YYYY
82
+ 1 time
83
+ 2 precipitation intensity in mm/h
84
+ 3 precipitation accumulation in mm
85
+ 4 housing contact
86
+ 5 top temperature
87
+ 6 bottom temperature
88
+ 7 heater status
89
+ 8 error code
90
+ 9 system status
91
+ 10 talker interval in seconds
92
+ 11 operating hours
93
+ 12 device type
94
+ 13 user data storage 1
95
+ 14 user data storage 2
96
+ 15 user data storage 3
97
+
98
+ """
99
+ for row in data:
100
+ if len(row) != 16:
101
+ continue
102
+ try:
103
+ dt = datetime.datetime.strptime(
104
+ f"{row[0]} {row[1]}", "%d.%m.%Y %H:%M:%S"
105
+ )
106
+ except ValueError:
107
+ continue
108
+ if date and date != dt.date():
109
+ continue
110
+ self._data["time"].append(dt)
111
+ self._data["rainfall_rate"].append(float(row[2]))
112
+ self._data["rainfall_amount"].append(float(row[3]))
113
+ if not self._data["time"]:
114
+ raise ValidTimeStampError
115
+
116
+ def _read_talker_protocol_22_columns(
117
+ self, data: list, date: datetime.date | None = None
118
+ ) -> None:
119
+ """Columns according to header in Lindenberg data.
120
+
121
+ 0 datetime utc
122
+ 1 date
123
+ 2 time
124
+ 3 precipitation intensity in mm/h
125
+ 4 precipitation accumulation in mm
126
+ 5 housing contact
127
+ 6 top temperature
128
+ 7 bottom temperature
129
+ 8 heater status
130
+ 9 error code
131
+ 10 system status
132
+ 11 talker interval in seconds
133
+ 12 operating hours
134
+ 13 device type
135
+ 14 user data storage 1
136
+ 15 user data storage 2
137
+ 16 user data storage 3
138
+ 17 user data storage 4
139
+ 18 serial number
140
+ 19 hardware version
141
+ 20 firmware version
142
+ 21 external temperature * checksum
143
+
144
+ """
145
+ for row in data:
146
+ if len(row) != 22:
147
+ continue
148
+ try:
149
+ dt = datetime.datetime.strptime(f"{row[0]}", "%Y-%m-%d %H:%M:%S")
150
+ except ValueError:
151
+ continue
152
+ if date and date != dt.date():
153
+ continue
154
+ self._data["time"].append(dt)
155
+ self._data["rainfall_rate"].append(float(row[3]))
156
+ self._data["rainfall_amount"].append(float(row[4]))
157
+ self.serial_number = row[18]
158
+ if not self._data["time"]:
159
+ raise ValidTimeStampError
160
+
161
+ def convert_units(self) -> None:
162
+ rainfall_rate = self.data["rainfall_rate"][:]
163
+ self.data["rainfall_rate"].data = rainfall_rate / 3600 / 1000 # mm/h -> m/s
164
+ self.data["rainfall_amount"].data = (
165
+ self.data["rainfall_amount"][:] / 1000
166
+ ) # mm -> m
@@ -12,7 +12,7 @@ from cloudnetpy.cloudnetarray import CloudnetArray
12
12
  from cloudnetpy.constants import HPA_TO_PA, MM_H_TO_M_S, SEC_IN_HOUR
13
13
  from cloudnetpy.exceptions import ValidTimeStampError, WeatherStationDataError
14
14
  from cloudnetpy.instruments import instruments
15
- from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
15
+ from cloudnetpy.instruments.cloudnet_instrument import CSVFile
16
16
  from cloudnetpy.instruments.toa5 import read_toa5
17
17
  from cloudnetpy.utils import datetime2decimal_hours
18
18
 
@@ -79,28 +79,13 @@ def ws2nc(
79
79
  return output.save_level1b(ws, output_file, uuid)
80
80
 
81
81
 
82
- class WS(CloudnetInstrument):
82
+ class WS(CSVFile):
83
83
  def __init__(self, site_meta: dict):
84
- super().__init__()
85
- self._data: dict
86
- self.site_meta = site_meta
84
+ super().__init__(site_meta)
87
85
  self.instrument = instruments.GENERIC_WEATHER_STATION
88
86
 
89
87
  date: list[str]
90
88
 
91
- def add_date(self) -> None:
92
- first_date = self._data["time"][0].date()
93
- self.date = [
94
- str(first_date.year),
95
- str(first_date.month).zfill(2),
96
- str(first_date.day).zfill(2),
97
- ]
98
-
99
- def add_data(self) -> None:
100
- for key, value in self._data.items():
101
- parsed = datetime2decimal_hours(value) if key == "time" else ma.array(value)
102
- self.data[key] = CloudnetArray(parsed, key)
103
-
104
89
  def calculate_rainfall_amount(self) -> None:
105
90
  if "rainfall_amount" in self.data:
106
91
  return
@@ -137,17 +122,6 @@ class WS(CloudnetInstrument):
137
122
  def convert_pressure(self) -> None:
138
123
  self.data["air_pressure"].data = self.data["air_pressure"][:] * HPA_TO_PA
139
124
 
140
- def normalize_rainfall_amount(self) -> None:
141
- if "rainfall_amount" in self.data:
142
- amount = self.data["rainfall_amount"][:]
143
- offset = 0
144
- for i in range(1, len(amount)):
145
- if amount[i] + offset < amount[i - 1]:
146
- offset += amount[i - 1]
147
- amount[i] += offset
148
- amount -= amount[0]
149
- self.data["rainfall_amount"].data = amount
150
-
151
125
  def convert_time(self) -> None:
152
126
  pass
153
127
 
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 67
3
- PATCH = 8
2
+ MINOR = 68
3
+ PATCH = 0
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudnetpy
3
- Version: 1.67.8
3
+ Version: 1.68.0
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -9,7 +9,7 @@ cloudnetpy/metadata.py,sha256=gEHwqEMY9gfaqVxx2_CobLg3i5gFXAumXLwnnW4BqtU,5840
9
9
  cloudnetpy/output.py,sha256=lq4YSeMT_d-j4rlQkKm9KIZ8boupTBBBKV1eUawpmCI,15672
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  cloudnetpy/utils.py,sha256=U0iMIKPiKLrLVAfs_u9pPuoWYW1RJHcM8dbLF9a4yIA,29796
12
- cloudnetpy/version.py,sha256=s7jW9g8DRO6lagW0MNY4yj9My3nwbtL_nz4PbfmcC0E,72
12
+ cloudnetpy/version.py,sha256=pLcy0tJTzjkz7cIE9-iU77WdDdE6S-zJExkR0XtvzuQ,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=s-SJaysvVpVVo5kidiruWQO6p3gv2TXwY1wEHYO5D6I,44
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=RcmbKxm2COkE7WEya0mK3yX5rzUbrewRVh3ekm01RtM,10598
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -32,13 +32,13 @@ cloudnetpy/categorize/attenuations/gas_attenuation.py,sha256=emr-RCxQT0i2N8k6eBN
32
32
  cloudnetpy/categorize/attenuations/liquid_attenuation.py,sha256=0p0G79BPkw1itCXHMwbvkNHtJGBocJzow3gNHAirChI,3036
33
33
  cloudnetpy/categorize/attenuations/melting_attenuation.py,sha256=9c9xoZHtGUbjFYJxkVc3UUDHLDy0UbNUZ32ITtnsj5w,2333
34
34
  cloudnetpy/categorize/attenuations/rain_attenuation.py,sha256=qazJzRyXf9vbjJhh4yiFmABI4L57j5W_6YZ-6qjRiBI,2839
35
- cloudnetpy/instruments/__init__.py,sha256=0ffYMCJoqXfwYY8LUjhZowJ6X_pGyqJRXHTSy-9p2bA,434
35
+ cloudnetpy/instruments/__init__.py,sha256=2vAdceXCNxZhTujhArLf4NjYOfUdkhLpGM-NQa_LXdg,470
36
36
  cloudnetpy/instruments/basta.py,sha256=_OTnySd36ktvxk_swWBzbv_H4AVGlkF_Ce3KtPGD1rE,3758
37
37
  cloudnetpy/instruments/campbell_scientific.py,sha256=2WHfBKQjtRSl0AqvtPeX7G8Hdi3Dn0WbvoAppFOMbA8,5270
38
38
  cloudnetpy/instruments/ceilo.py,sha256=xrI7iYNftKvGZf-3C_ESUNsu-QhXV43iWkDuKp3biZU,9552
39
39
  cloudnetpy/instruments/ceilometer.py,sha256=pdmLVljsuciyKpaGxWxJ_f1IrJK-UrkBC0lSeuirLlU,12095
40
40
  cloudnetpy/instruments/cl61d.py,sha256=g6DNBFju3wYhLFl32DKmC8pUup7y-EupXoUU0fuoGGA,1990
41
- cloudnetpy/instruments/cloudnet_instrument.py,sha256=NQZ_FMXh8iyzXYSCKQSpIdp0MZFAh7WqKE8mZRmVbF4,4164
41
+ cloudnetpy/instruments/cloudnet_instrument.py,sha256=086GJ6Nfp7sK9ZK8UygJOn-aiVPreez674_gbrOZj4I,5183
42
42
  cloudnetpy/instruments/copernicus.py,sha256=he15ncH6SDCidBhr0BkOJc6Rg8cMvLWN0vGmpui6nGQ,6611
43
43
  cloudnetpy/instruments/galileo.py,sha256=qABO3IUdRpGFDgqKsN0Pl4TQ-CWHQz8s13PxnwdIUnU,4828
44
44
  cloudnetpy/instruments/hatpro.py,sha256=DzCWzTJxTc5BSOgoeyM8RjYkSXX6NDi3QXgKRp0uxlI,8759
@@ -50,11 +50,12 @@ cloudnetpy/instruments/nc_lidar.py,sha256=5gQG9PApnNPrHmS9_zanl8HEYIQuGRpbnzC3wf
50
50
  cloudnetpy/instruments/nc_radar.py,sha256=AjPn3mkq5a1mE7YzKtZnxX5suNju9NhUq-TDvs7T_uU,6911
51
51
  cloudnetpy/instruments/pollyxt.py,sha256=ra7sYQ_cmkC0T9TBYrMN6iiQEZimmWGdW9Ilv61JB-Q,10027
52
52
  cloudnetpy/instruments/radiometrics.py,sha256=ySG4a042XkgjMTG8d20oAPNvFvw9bMwwiqS3zv-JF_w,11825
53
+ cloudnetpy/instruments/rain_e_h3.py,sha256=xZWYYuSHeMPGKMcp3jG6JTLsy5VGivUiXxf1qEzzzJw,5310
53
54
  cloudnetpy/instruments/rpg.py,sha256=vfs_eGahPOxFjOIBczNywRwtdutOsJpSNeXZm99SIOo,17438
54
55
  cloudnetpy/instruments/rpg_reader.py,sha256=ThztFuVrWxhmWVAfZTfQDeUiKK1XMTbtv08IBe8GK98,11364
55
56
  cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
56
57
  cloudnetpy/instruments/vaisala.py,sha256=GGuA_v4S7kR9yApSr1-d0ETzNj4ehEZ7-pD1-AdPYRE,14662
57
- cloudnetpy/instruments/weather_station.py,sha256=bWPL5dvsxumc0-0JP81i-2c0oWy7_ds8VGOWsn8Qt3c,17982
58
+ cloudnetpy/instruments/weather_station.py,sha256=x4IwG8loGD8FgAJgLAvRQyOy05glI6bI6sz5E7GByFo,17015
58
59
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
59
60
  cloudnetpy/instruments/disdrometer/common.py,sha256=g52iK2aNp3Z88kovUmGVpC54NZomPa9D871gzO0AmQ4,9267
60
61
  cloudnetpy/instruments/disdrometer/parsivel.py,sha256=HJZrEysQkx9MiIVPDV25CYHpXi_SjgZlgO-otoaKK34,25640
@@ -115,9 +116,9 @@ cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe5
115
116
  cloudnetpy/products/mwr_tools.py,sha256=rd7UC67O4fsIE5SaHVZ4qWvUJTj41ZGwgQWPwZzOM14,5377
116
117
  cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
117
118
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
118
- cloudnetpy-1.67.8.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
119
- cloudnetpy-1.67.8.dist-info/METADATA,sha256=X2Lrmxa0YYJBgmTNueXjTBbzGKieTL9CK6r5LSNSrVA,5793
120
- cloudnetpy-1.67.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
121
- cloudnetpy-1.67.8.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
122
- cloudnetpy-1.67.8.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
123
- cloudnetpy-1.67.8.dist-info/RECORD,,
119
+ cloudnetpy-1.68.0.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
+ cloudnetpy-1.68.0.dist-info/METADATA,sha256=B6v8K31KoLzIshYnuy85fsc5CZRe-pytrv2RoFQRmPU,5793
121
+ cloudnetpy-1.68.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
122
+ cloudnetpy-1.68.0.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
123
+ cloudnetpy-1.68.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
124
+ cloudnetpy-1.68.0.dist-info/RECORD,,