cloudnetpy 1.73.2__py3-none-any.whl → 1.74.1__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/categorize.py +1 -0
- cloudnetpy/categorize/radar.py +16 -2
- cloudnetpy/instruments/instruments.py +14 -0
- cloudnetpy/instruments/weather_station.py +109 -0
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/METADATA +1 -1
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/RECORD +11 -11
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.73.2.dist-info → cloudnetpy-1.74.1.dist-info}/top_level.txt +0 -0
@@ -166,6 +166,7 @@ def generate_categorize(
|
|
166
166
|
except DisdrometerDataError as err:
|
167
167
|
logging.warning("Unable to use disdrometer: %s", err)
|
168
168
|
time, height = _define_dense_grid()
|
169
|
+
data.radar.add_location(time)
|
169
170
|
valid_ind = _interpolate_to_cloudnet_grid()
|
170
171
|
if len(valid_ind) < 2:
|
171
172
|
msg = "Less than 2 overlapping radar, lidar and model timestamps found"
|
cloudnetpy/categorize/radar.py
CHANGED
@@ -316,11 +316,25 @@ class Radar(DataSource):
|
|
316
316
|
|
317
317
|
def add_meta(self) -> None:
|
318
318
|
"""Copies misc. metadata from the input file."""
|
319
|
-
for key in ("latitude", "longitude", "altitude"):
|
320
|
-
self.append_data(np.array(self.getvar(key)), key)
|
321
319
|
for key in ("time", "height", "radar_frequency"):
|
322
320
|
self.append_data(np.array(getattr(self, key)), key)
|
323
321
|
|
322
|
+
def add_location(self, time_new: np.ndarray):
|
323
|
+
"""Add latitude, longitude and altitude from nearest timestamp."""
|
324
|
+
idx = np.searchsorted(self.time, time_new)
|
325
|
+
idx_left = np.clip(idx - 1, 0, len(self.time) - 1)
|
326
|
+
idx_right = np.clip(idx, 0, len(self.time) - 1)
|
327
|
+
diff_left = np.abs(time_new - self.time[idx_left])
|
328
|
+
diff_right = np.abs(time_new - self.time[idx_right])
|
329
|
+
idx_closest = np.where(diff_left < diff_right, idx_left, idx_right)
|
330
|
+
for key in ("latitude", "longitude", "altitude"):
|
331
|
+
data = self.getvar(key)
|
332
|
+
if not utils.isscalar(data):
|
333
|
+
data = data[idx_closest]
|
334
|
+
if not np.any(ma.getmaskarray(data)):
|
335
|
+
data = np.array(data)
|
336
|
+
self.append_data(data, key)
|
337
|
+
|
324
338
|
def _init_data(self) -> None:
|
325
339
|
self.append_data(self.getvar("Zh"), "Z", units="dBZ")
|
326
340
|
for key in ("v", "ldr", "width", "sldr", "rainfall_rate"):
|
@@ -150,6 +150,13 @@ HALO = Instrument(
|
|
150
150
|
model="StreamLine",
|
151
151
|
)
|
152
152
|
|
153
|
+
WINDCUBE_WLS100S = Instrument(
|
154
|
+
manufacturer="Vaisala",
|
155
|
+
domain="lidar",
|
156
|
+
category="Doppler lidar",
|
157
|
+
model="WindCube WLS100S",
|
158
|
+
)
|
159
|
+
|
153
160
|
WINDCUBE_WLS200S = Instrument(
|
154
161
|
manufacturer="Vaisala",
|
155
162
|
domain="lidar",
|
@@ -157,6 +164,13 @@ WINDCUBE_WLS200S = Instrument(
|
|
157
164
|
model="WindCube WLS200S",
|
158
165
|
)
|
159
166
|
|
167
|
+
WINDCUBE_WLS400S = Instrument(
|
168
|
+
manufacturer="Vaisala",
|
169
|
+
domain="lidar",
|
170
|
+
category="Doppler lidar",
|
171
|
+
model="WindCube WLS400S",
|
172
|
+
)
|
173
|
+
|
160
174
|
WINDCUBE_WLS70 = Instrument(
|
161
175
|
manufacturer="Vaisala",
|
162
176
|
domain="lidar",
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import csv
|
2
2
|
import datetime
|
3
3
|
import math
|
4
|
+
import re
|
5
|
+
from collections import defaultdict
|
4
6
|
from collections.abc import Iterable
|
7
|
+
from os import PathLike
|
5
8
|
|
6
9
|
import numpy as np
|
7
10
|
from numpy import ma
|
@@ -59,6 +62,8 @@ def ws2nc(
|
|
59
62
|
ws = JuelichWS(weather_station_file, site_meta)
|
60
63
|
elif site_meta["name"] == "Lampedusa":
|
61
64
|
ws = LampedusaWS(weather_station_file, site_meta)
|
65
|
+
elif site_meta["name"] == "Limassol":
|
66
|
+
ws = LimassolWS(weather_station_file, site_meta)
|
62
67
|
else:
|
63
68
|
msg = "Unsupported site"
|
64
69
|
raise ValueError(msg)
|
@@ -122,6 +127,8 @@ class WS(CSVFile):
|
|
122
127
|
self.data["rainfall_rate"].data = rainfall_rate / 60 / 1000 # mm/min -> m/s
|
123
128
|
|
124
129
|
def convert_pressure(self) -> None:
|
130
|
+
if "air_pressure" not in self.data:
|
131
|
+
return
|
125
132
|
self.data["air_pressure"].data = self.data["air_pressure"][:] * HPA_TO_PA
|
126
133
|
|
127
134
|
def convert_time(self) -> None:
|
@@ -539,3 +546,105 @@ class LampedusaWS(WS):
|
|
539
546
|
"rainfall_rate": raw_data["rain1m"],
|
540
547
|
}
|
541
548
|
return self.format_data(data)
|
549
|
+
|
550
|
+
|
551
|
+
class LimassolWS(WS):
|
552
|
+
def __init__(self, filenames: list[str], site_meta: dict):
|
553
|
+
super().__init__(site_meta)
|
554
|
+
self.filenames = filenames
|
555
|
+
self._data = defaultdict(list)
|
556
|
+
for filename in filenames:
|
557
|
+
for key, values in _parse_sirta(filename).items():
|
558
|
+
self._data[key].extend(values)
|
559
|
+
self._data["time"] = np.array(
|
560
|
+
self._data.pop("Date Time (yyyy-mm-ddThh:mm:ss)")
|
561
|
+
) - datetime.timedelta(hours=2)
|
562
|
+
|
563
|
+
def convert_time(self) -> None:
|
564
|
+
decimal_hours = datetime2decimal_hours(self._data["time"])
|
565
|
+
self.data["time"] = CloudnetArray(decimal_hours, "time")
|
566
|
+
|
567
|
+
def screen_timestamps(self, date: str) -> None:
|
568
|
+
dates = [str(d.date()) for d in self._data["time"]]
|
569
|
+
valid_ind = [ind for ind, d in enumerate(dates) if d == date]
|
570
|
+
if not valid_ind:
|
571
|
+
raise ValidTimeStampError
|
572
|
+
for key in self._data:
|
573
|
+
self._data[key] = [
|
574
|
+
x for ind, x in enumerate(self._data[key]) if ind in valid_ind
|
575
|
+
]
|
576
|
+
|
577
|
+
def add_data(self) -> None:
|
578
|
+
self.data["air_temperature"] = CloudnetArray(
|
579
|
+
np.array(self._data["Air temperature (°C)"]), "air_temperature"
|
580
|
+
)
|
581
|
+
self.data["relative_humidity"] = CloudnetArray(
|
582
|
+
np.array(self._data["Relative humidity (%)"]), "relative_humidity"
|
583
|
+
)
|
584
|
+
self.data["rainfall_rate"] = CloudnetArray(
|
585
|
+
np.array(self._data["Total precipitation (mm)"]), "rainfall_rate"
|
586
|
+
)
|
587
|
+
# Wind speed and direction are available since 2025-02-13:
|
588
|
+
if (
|
589
|
+
"Wind speed at 10m (m/s)" in self._data
|
590
|
+
and "Wind direction at 10m (degrees)" in self._data
|
591
|
+
):
|
592
|
+
self.data["wind_speed"] = CloudnetArray(
|
593
|
+
np.array(self._data["Wind speed at 10m (m/s)"]), "wind_speed"
|
594
|
+
)
|
595
|
+
self.data["wind_direction"] = CloudnetArray(
|
596
|
+
np.array(self._data["Wind direction at 10m (degrees)"]),
|
597
|
+
"wind_direction",
|
598
|
+
)
|
599
|
+
else:
|
600
|
+
self.data["wind_speed"] = CloudnetArray(
|
601
|
+
np.array(self._data["Wind speed (m/s)"]), "wind_speed"
|
602
|
+
)
|
603
|
+
|
604
|
+
def convert_rainfall_rate(self) -> None:
|
605
|
+
rainfall_rate = self.data["rainfall_rate"][:]
|
606
|
+
self.data["rainfall_rate"].data = (
|
607
|
+
rainfall_rate / (10 * 60) / 1000
|
608
|
+
) # mm/(10 min) -> m/s
|
609
|
+
|
610
|
+
|
611
|
+
def _parse_sirta(filename: str | PathLike):
|
612
|
+
"""Parse SIRTA-style weather station file."""
|
613
|
+
with open(filename, "rb") as f:
|
614
|
+
raw_content = f.read()
|
615
|
+
try:
|
616
|
+
content = raw_content.decode("utf-8")
|
617
|
+
except UnicodeDecodeError:
|
618
|
+
content = raw_content.decode("latin-1")
|
619
|
+
lines = [line.strip() for line in content.splitlines()]
|
620
|
+
columns: list[str] = []
|
621
|
+
output: dict = {}
|
622
|
+
for line in lines:
|
623
|
+
m = re.fullmatch(r"#\s*Col.\s*(\d+)\s*:\s*(.*)", line)
|
624
|
+
if m is None:
|
625
|
+
continue
|
626
|
+
if m[1] != str(len(columns) + 1):
|
627
|
+
msg = f"Expected column {m[1]}, found {len(columns)+1}"
|
628
|
+
raise ValueError(msg)
|
629
|
+
columns.append(m[2])
|
630
|
+
output[m[2]] = []
|
631
|
+
for line in lines:
|
632
|
+
if not line or line.startswith("#"):
|
633
|
+
continue
|
634
|
+
values = line.split()
|
635
|
+
if len(columns) != len(values):
|
636
|
+
continue
|
637
|
+
for column, value in zip(columns, values, strict=False):
|
638
|
+
parsed: float | datetime.datetime
|
639
|
+
if column == "Date Time (yyyy-mm-ddThh:mm:ss)":
|
640
|
+
parsed = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S").replace(
|
641
|
+
tzinfo=datetime.timezone.utc
|
642
|
+
)
|
643
|
+
elif column == "Date Time (yyyy-mm-ddThh:mm:ssZ)":
|
644
|
+
parsed = datetime.datetime.strptime(
|
645
|
+
value, "%Y-%m-%dT%H:%M:%SZ"
|
646
|
+
).replace(tzinfo=datetime.timezone.utc)
|
647
|
+
else:
|
648
|
+
parsed = float(value)
|
649
|
+
output[column].append(parsed)
|
650
|
+
return output
|
cloudnetpy/version.py
CHANGED
@@ -9,11 +9,11 @@ cloudnetpy/metadata.py,sha256=lO7BCbVAzFoH3Nq-VuezYX0f7MnbG1Zp11g5GSiuQwM,6189
|
|
9
9
|
cloudnetpy/output.py,sha256=l0LoOhcGCBrg2EJ4NT1xZ7-UKWdV7X7yQ0fJmhkwJVc,15829
|
10
10
|
cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
cloudnetpy/utils.py,sha256=ui6sq8g1HlgfOQO644iNHsRGqpDwS3q-5uGR2y2tOAk,33325
|
12
|
-
cloudnetpy/version.py,sha256=
|
12
|
+
cloudnetpy/version.py,sha256=0Q_I-dFOXJ0ERNPLZ0BKTY2uygAYMMjfkWYx4lCm684,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
|
16
|
-
cloudnetpy/categorize/categorize.py,sha256=
|
16
|
+
cloudnetpy/categorize/categorize.py,sha256=PzkgQ63MtEHlz_0govFkoM6F41OKgQLsxDh7GLQHyrs,20777
|
17
17
|
cloudnetpy/categorize/classify.py,sha256=qovHgHsMku5kpl3cJxKteNBsG8GAkfI3Zo8QhJwZSFQ,8512
|
18
18
|
cloudnetpy/categorize/containers.py,sha256=evqVQOoUycBtaFeKpms7_NuM9z8jKWq37nLIhoDxa8k,5278
|
19
19
|
cloudnetpy/categorize/disdrometer.py,sha256=sRSt2B932lrrkvycKoSaKEIaDVfq9Z7uU-4iHRr-fC0,1893
|
@@ -26,7 +26,7 @@ cloudnetpy/categorize/lidar.py,sha256=YQrM_LOz8NQrrD9l9HyujV1GSGwkQ8LMqXN13bEJRW
|
|
26
26
|
cloudnetpy/categorize/melting.py,sha256=ZnLeL_qWmiCdjXVOm9iBYHdo29Brqxu_DEErZPqUloQ,6217
|
27
27
|
cloudnetpy/categorize/model.py,sha256=QFRCY0TvM2fzGRyP8BNkqbvu13XcQjt7TsN5fhjI_Uc,6654
|
28
28
|
cloudnetpy/categorize/mwr.py,sha256=F7cquERWL6mBkgboqeaCIPf9gOlKI-NWUQIBdQXGT_I,1635
|
29
|
-
cloudnetpy/categorize/radar.py,sha256=
|
29
|
+
cloudnetpy/categorize/radar.py,sha256=PmriTnrHbgZrau1RTNKpPI_-h5Uu0kGIMrMOaoMuROY,14821
|
30
30
|
cloudnetpy/categorize/attenuations/__init__.py,sha256=CWFHVWeTIe2hrZtgkJaX2HGftbuffsFc39Mzv5B0Lw0,1037
|
31
31
|
cloudnetpy/categorize/attenuations/gas_attenuation.py,sha256=emr-RCxQT0i2N8k6eBNhRsmsCBPHJzQsWJfjC4fVSTo,975
|
32
32
|
cloudnetpy/categorize/attenuations/liquid_attenuation.py,sha256=0p0G79BPkw1itCXHMwbvkNHtJGBocJzow3gNHAirChI,3036
|
@@ -42,7 +42,7 @@ cloudnetpy/instruments/cloudnet_instrument.py,sha256=3qJe8STIvsU8irj79xuElFUZa0j
|
|
42
42
|
cloudnetpy/instruments/copernicus.py,sha256=99idcn6-iKOSvSslNjwFRng3gwlTLFjKPiT1tnVytpQ,6613
|
43
43
|
cloudnetpy/instruments/galileo.py,sha256=BjWE15_S3tTCOmAM5k--oicI3wghKaO0hv9EUBxtbl8,4830
|
44
44
|
cloudnetpy/instruments/hatpro.py,sha256=D1iTR58ao6zA556LegPDhh4JHozxiup_9mUuLjR0NzE,9006
|
45
|
-
cloudnetpy/instruments/instruments.py,sha256=
|
45
|
+
cloudnetpy/instruments/instruments.py,sha256=BdtUBvBUpjIyXubirHkP1Xa9mtlF8CgPEnFPokJgyrQ,4408
|
46
46
|
cloudnetpy/instruments/lufft.py,sha256=nIoEKuuFGKq2dLqkX7zW-HpAifefG472tZhKfXE1yoA,4212
|
47
47
|
cloudnetpy/instruments/mira.py,sha256=Wofp8HbiAwJce_IbOLjpEFV07H_Kh4170C9Wygiz-ew,11401
|
48
48
|
cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo,5711
|
@@ -55,7 +55,7 @@ cloudnetpy/instruments/rpg.py,sha256=gjvDomyoqSaeS3-X4EuesvWBp32BNoD-CeuvBOP19AI
|
|
55
55
|
cloudnetpy/instruments/rpg_reader.py,sha256=ThztFuVrWxhmWVAfZTfQDeUiKK1XMTbtv08IBe8GK98,11364
|
56
56
|
cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
|
57
57
|
cloudnetpy/instruments/vaisala.py,sha256=RKAw_fVry4YOUF0i2_-2jLIc6_H85oL8USA4ji9rh0o,4583
|
58
|
-
cloudnetpy/instruments/weather_station.py,sha256=
|
58
|
+
cloudnetpy/instruments/weather_station.py,sha256=joB3Z365t_viLmpg4R0e9eqfHLgAon5w6PxPTqvjv94,24592
|
59
59
|
cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
|
60
60
|
cloudnetpy/instruments/disdrometer/common.py,sha256=g52iK2aNp3Z88kovUmGVpC54NZomPa9D871gzO0AmQ4,9267
|
61
61
|
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=HJZrEysQkx9MiIVPDV25CYHpXi_SjgZlgO-otoaKK34,25640
|
@@ -116,10 +116,10 @@ cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18
|
|
116
116
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
117
117
|
cloudnetpy/products/mwr_tools.py,sha256=1SRaQRW6-1svfnUk25sMyWXSwRJ1fg7v48TsMENZQpg,5103
|
118
118
|
cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
|
119
|
-
cloudnetpy-1.
|
119
|
+
cloudnetpy-1.74.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
120
120
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
121
|
-
cloudnetpy-1.
|
122
|
-
cloudnetpy-1.
|
123
|
-
cloudnetpy-1.
|
124
|
-
cloudnetpy-1.
|
125
|
-
cloudnetpy-1.
|
121
|
+
cloudnetpy-1.74.1.dist-info/METADATA,sha256=zIZVu7NOmOHWkCgGAxsojX7Fg53Fs5KUY9qhwB5yxTo,5796
|
122
|
+
cloudnetpy-1.74.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
123
|
+
cloudnetpy-1.74.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
|
124
|
+
cloudnetpy-1.74.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
125
|
+
cloudnetpy-1.74.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|