cloudnetpy 1.72.3__py3-none-any.whl → 1.73.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/disdrometer.py +10 -10
- cloudnetpy/categorize/itu.py +5 -5
- cloudnetpy/categorize/melting.py +1 -1
- cloudnetpy/instruments/__init__.py +1 -0
- cloudnetpy/instruments/bowtie.py +95 -0
- cloudnetpy/instruments/ceilo.py +1 -0
- cloudnetpy/instruments/ceilometer.py +3 -3
- cloudnetpy/instruments/cloudnet_instrument.py +12 -73
- cloudnetpy/instruments/hatpro.py +1 -15
- cloudnetpy/instruments/pollyxt.py +1 -0
- cloudnetpy/instruments/rpg.py +0 -8
- cloudnetpy/metadata.py +8 -0
- cloudnetpy/model_evaluation/plotting/plotting.py +1 -1
- cloudnetpy/plotting/plotting.py +1 -1
- cloudnetpy/utils.py +99 -0
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/METADATA +1 -1
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/RECORD +22 -21
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.72.3.dist-info → cloudnetpy-1.73.1.dist-info}/top_level.txt +0 -0
@@ -37,18 +37,18 @@ class Disdrometer(DataSource):
|
|
37
37
|
self.append_data(self.dataset.variables[key][:], key)
|
38
38
|
|
39
39
|
def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray:
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
fun = interp1d(
|
40
|
+
time = self.time
|
41
|
+
mask = ma.getmask(y)
|
42
|
+
if mask is not ma.nomask:
|
43
|
+
if np.all(mask):
|
44
|
+
return ma.masked_all(x_new.shape)
|
45
|
+
not_masked = ~mask
|
46
|
+
y = y[not_masked]
|
47
|
+
time = time[not_masked]
|
48
|
+
fun = interp1d(time, y, fill_value="extrapolate")
|
49
49
|
interpolated_array = ma.array(fun(x_new))
|
50
50
|
max_time = 1 / 60 # min -> fraction hour
|
51
|
-
mask_ind = get_gap_ind(
|
51
|
+
mask_ind = get_gap_ind(time, x_new, max_time)
|
52
52
|
|
53
53
|
if len(mask_ind) > 0:
|
54
54
|
msg = f"Unable to interpolate disdrometer for {len(mask_ind)} time steps"
|
cloudnetpy/categorize/itu.py
CHANGED
@@ -5,7 +5,7 @@ import cloudnetpy.constants as con
|
|
5
5
|
|
6
6
|
|
7
7
|
def calc_liquid_specific_attenuation(
|
8
|
-
temperature: npt.NDArray, frequency: float
|
8
|
+
temperature: npt.NDArray, frequency: float | np.floating
|
9
9
|
) -> npt.NDArray:
|
10
10
|
"""Calculate cloud liquid water specific attenuation coefficient for
|
11
11
|
frequency up to 200 GHz.
|
@@ -43,7 +43,7 @@ def calc_gas_specific_attenuation(
|
|
43
43
|
pressure: npt.NDArray,
|
44
44
|
vapor_pressure: npt.NDArray,
|
45
45
|
temperature: npt.NDArray,
|
46
|
-
frequency: float,
|
46
|
+
frequency: float | np.floating,
|
47
47
|
) -> npt.NDArray:
|
48
48
|
"""Calculate specific attenuation due to dry air and water vapor for
|
49
49
|
frequency up to 1000 GHz.
|
@@ -72,7 +72,7 @@ def calc_gas_specific_attenuation(
|
|
72
72
|
|
73
73
|
|
74
74
|
def _calc_line_shape(
|
75
|
-
frequency: float,
|
75
|
+
frequency: float | np.floating,
|
76
76
|
center: npt.NDArray,
|
77
77
|
width: npt.NDArray,
|
78
78
|
correction: npt.NDArray | float,
|
@@ -92,7 +92,7 @@ def _calc_line_shape(
|
|
92
92
|
def _calc_oxygen_refractivity(
|
93
93
|
dry_pressure: npt.NDArray,
|
94
94
|
vapor_pressure: npt.NDArray,
|
95
|
-
frequency: float,
|
95
|
+
frequency: float | np.floating,
|
96
96
|
theta: npt.NDArray,
|
97
97
|
) -> npt.NDArray:
|
98
98
|
f0, a1, a2, a3, a4, a5, a6 = OXYGEN_TABLE[:, :, np.newaxis, np.newaxis]
|
@@ -119,7 +119,7 @@ def _calc_oxygen_refractivity(
|
|
119
119
|
def _calc_vapor_refractivity(
|
120
120
|
dry_pressure: npt.NDArray,
|
121
121
|
vapor_pressure: npt.NDArray,
|
122
|
-
frequency: float,
|
122
|
+
frequency: float | np.floating,
|
123
123
|
theta: npt.NDArray,
|
124
124
|
) -> npt.NDArray:
|
125
125
|
f0, b1, b2, b3, b4, b5, b6 = VAPOR_TABLE[:, :, np.newaxis, np.newaxis]
|
cloudnetpy/categorize/melting.py
CHANGED
@@ -166,7 +166,7 @@ def _basetop(dprof: np.ndarray, pind: int) -> tuple[int, int]:
|
|
166
166
|
def _get_temp_indices(t_prof: np.ndarray, t_range: tuple) -> np.ndarray:
|
167
167
|
"""Finds indices of temperature profile covering the given range."""
|
168
168
|
ind = np.where((t_prof > min(t_range) + T0) & (t_prof < max(t_range) + T0))[0]
|
169
|
-
return np.array([]) if len(ind) == 0 else np.arange(min(ind), max(ind) + 1)
|
169
|
+
return np.array([]) if len(ind) == 0 else np.arange(np.min(ind), np.max(ind) + 1)
|
170
170
|
|
171
171
|
|
172
172
|
def _find_model_temperature_range(model_type: str) -> tuple[float, float]:
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from os import PathLike
|
2
|
+
|
3
|
+
from cloudnetpy import output
|
4
|
+
from cloudnetpy.constants import G_TO_KG, MM_H_TO_M_S
|
5
|
+
from cloudnetpy.exceptions import ValidTimeStampError
|
6
|
+
from cloudnetpy.instruments.instruments import FMCW94
|
7
|
+
from cloudnetpy.instruments.nc_radar import NcRadar
|
8
|
+
from cloudnetpy.metadata import MetaData
|
9
|
+
|
10
|
+
|
11
|
+
def bowtie2nc(
|
12
|
+
bowtie_file: str | PathLike,
|
13
|
+
output_file: str,
|
14
|
+
site_meta: dict,
|
15
|
+
uuid: str | None = None,
|
16
|
+
date: str | None = None,
|
17
|
+
) -> str:
|
18
|
+
"""Converts data from 'BOW-TIE' campaign cloud radar on RV-Meteor into
|
19
|
+
Cloudnet Level 1b netCDF file.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
bowtie_file: Input filename.
|
23
|
+
output_file: Output filename.
|
24
|
+
site_meta: Dictionary containing information about the site. Required key
|
25
|
+
value pair is `name`. Optional are `latitude`, `longitude`, `altitude`.
|
26
|
+
uuid: Set specific UUID for the file.
|
27
|
+
date: Expected date as YYYY-MM-DD of all profiles in the file.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
UUID of the generated file.
|
31
|
+
|
32
|
+
Raises:
|
33
|
+
ValidTimeStampError: No valid timestamps found.
|
34
|
+
|
35
|
+
"""
|
36
|
+
keymap = {
|
37
|
+
"Zh": "Zh",
|
38
|
+
"v": "v",
|
39
|
+
"width": "width",
|
40
|
+
"ldr": "ldr",
|
41
|
+
"kurt": "kurtosis",
|
42
|
+
"Skew": "skewness",
|
43
|
+
"SNR": "SNR",
|
44
|
+
"time": "time",
|
45
|
+
"range": "range",
|
46
|
+
"lwp": "lwp",
|
47
|
+
"SurfRelHum": "relative_humidity",
|
48
|
+
"rain": "rainfall_rate",
|
49
|
+
"Nyquist_velocity": "nyquist_velocity",
|
50
|
+
"range_offsets": "chirp_start_indices",
|
51
|
+
}
|
52
|
+
|
53
|
+
with Bowtie(bowtie_file, site_meta) as bowtie:
|
54
|
+
bowtie.init_data(keymap)
|
55
|
+
bowtie.add_time_and_range()
|
56
|
+
if date is not None:
|
57
|
+
bowtie.check_date(date)
|
58
|
+
bowtie.add_radar_specific_variables()
|
59
|
+
bowtie.add_site_geolocation()
|
60
|
+
bowtie.add_height()
|
61
|
+
bowtie.convert_units()
|
62
|
+
bowtie.test_if_all_masked()
|
63
|
+
attributes = output.add_time_attribute(ATTRIBUTES, bowtie.date)
|
64
|
+
output.update_attributes(bowtie.data, attributes)
|
65
|
+
return output.save_level1b(bowtie, output_file, uuid)
|
66
|
+
|
67
|
+
|
68
|
+
class Bowtie(NcRadar):
|
69
|
+
def __init__(self, full_path: str | PathLike, site_meta: dict):
|
70
|
+
super().__init__(full_path, site_meta)
|
71
|
+
self.instrument = FMCW94
|
72
|
+
self.date = self.get_date()
|
73
|
+
|
74
|
+
def convert_units(self):
|
75
|
+
self.data["lwp"].data *= G_TO_KG
|
76
|
+
self.data["rainfall_rate"].data *= MM_H_TO_M_S
|
77
|
+
self.data["relative_humidity"].data /= 100
|
78
|
+
|
79
|
+
def check_date(self, date: str):
|
80
|
+
if "-".join(self.date) != date:
|
81
|
+
raise ValidTimeStampError
|
82
|
+
|
83
|
+
|
84
|
+
ATTRIBUTES: dict = {
|
85
|
+
"v": MetaData(
|
86
|
+
long_name="Doppler velocity",
|
87
|
+
units="m s-1",
|
88
|
+
comment=(
|
89
|
+
"This parameter is the radial component of the velocity, with positive\n"
|
90
|
+
"velocities are away from the radar. It was corrected for the heave\n"
|
91
|
+
"motion of the ship. A rolling average over 3 time steps has been\n"
|
92
|
+
"applied to it."
|
93
|
+
),
|
94
|
+
),
|
95
|
+
}
|
cloudnetpy/instruments/ceilo.py
CHANGED
@@ -105,6 +105,7 @@ def ceilo2nc(
|
|
105
105
|
ceilo_obj.screen_invalid_values()
|
106
106
|
ceilo_obj.prepare_data()
|
107
107
|
ceilo_obj.data_to_cloudnet_arrays()
|
108
|
+
ceilo_obj.add_site_geolocation()
|
108
109
|
attributes = output.add_time_attribute(ATTRIBUTES, ceilo_obj.date)
|
109
110
|
output.update_attributes(ceilo_obj.data, attributes)
|
110
111
|
for key in ("beta", "beta_smooth"):
|
@@ -93,9 +93,6 @@ class Ceilometer:
|
|
93
93
|
msg = "Instrument wavelength not defined"
|
94
94
|
raise RuntimeError(msg)
|
95
95
|
self.data["wavelength"] = float(self.instrument.wavelength)
|
96
|
-
for key in ("latitude", "longitude", "altitude"):
|
97
|
-
if key in self.site_meta:
|
98
|
-
self.data[key] = float(self.site_meta[key])
|
99
96
|
|
100
97
|
def get_date_and_time(self, epoch: Epoch) -> None:
|
101
98
|
if "time" not in self.data:
|
@@ -113,6 +110,9 @@ class Ceilometer:
|
|
113
110
|
else:
|
114
111
|
self.data[key] = CloudnetArray(array, key)
|
115
112
|
|
113
|
+
def add_site_geolocation(self):
|
114
|
+
utils.add_site_geolocation(self.data, gps=False, site_meta=self.site_meta)
|
115
|
+
|
116
116
|
def screen_depol(self) -> None:
|
117
117
|
key = "depolarisation"
|
118
118
|
if key in self.data:
|
@@ -1,13 +1,16 @@
|
|
1
1
|
import logging
|
2
|
+
from typing import TYPE_CHECKING
|
2
3
|
|
3
|
-
import netCDF4
|
4
4
|
import numpy as np
|
5
5
|
from numpy import ma
|
6
6
|
|
7
7
|
from cloudnetpy import utils
|
8
8
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
9
9
|
from cloudnetpy.exceptions import ValidTimeStampError
|
10
|
-
from cloudnetpy.instruments.instruments import BASTA, FMCW35, FMCW94, Instrument
|
10
|
+
from cloudnetpy.instruments.instruments import BASTA, FMCW35, FMCW94, HATPRO, Instrument
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
import netCDF4
|
11
14
|
|
12
15
|
|
13
16
|
class CloudnetInstrument:
|
@@ -20,77 +23,13 @@ class CloudnetInstrument:
|
|
20
23
|
self.instrument: Instrument | None = None
|
21
24
|
|
22
25
|
def add_site_geolocation(self) -> None:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
value is None
|
31
|
-
and self.instrument is not None
|
32
|
-
and self.instrument in (BASTA, FMCW94, FMCW35)
|
33
|
-
):
|
34
|
-
data = None
|
35
|
-
if (
|
36
|
-
hasattr(self, "dataset")
|
37
|
-
and isinstance(self.dataset, netCDF4.Dataset)
|
38
|
-
and key in self.dataset.variables
|
39
|
-
):
|
40
|
-
data = self.dataset[key][:]
|
41
|
-
elif key in self.data:
|
42
|
-
data = self.data[key].data
|
43
|
-
if (
|
44
|
-
data is not None
|
45
|
-
and not np.all(ma.getmaskarray(data))
|
46
|
-
and np.any(data != 0)
|
47
|
-
):
|
48
|
-
value = data[data != 0]
|
49
|
-
source = "GPS"
|
50
|
-
# User-supplied site coordinate.
|
51
|
-
if value is None and key in self.site_meta:
|
52
|
-
value = self.site_meta[key]
|
53
|
-
source = "site coordinates"
|
54
|
-
# From source data (CHM15k, CL61, MRR-PRO, Copernicus, Galileo...).
|
55
|
-
# Assume value is manually set, so cannot trust it.
|
56
|
-
if (
|
57
|
-
value is None
|
58
|
-
and hasattr(self, "dataset")
|
59
|
-
and isinstance(self.dataset, netCDF4.Dataset)
|
60
|
-
and key in self.dataset.variables
|
61
|
-
and not np.all(ma.getmaskarray(self.dataset[key][:]))
|
62
|
-
):
|
63
|
-
value = self.dataset[key][:]
|
64
|
-
source = "raw file"
|
65
|
-
# From source global attributes (MIRA).
|
66
|
-
# Seems to be manually set, so cannot trust it.
|
67
|
-
if (
|
68
|
-
value is None
|
69
|
-
and hasattr(self, "dataset")
|
70
|
-
and isinstance(self.dataset, netCDF4.Dataset)
|
71
|
-
and hasattr(
|
72
|
-
self.dataset,
|
73
|
-
key.capitalize(),
|
74
|
-
)
|
75
|
-
):
|
76
|
-
value = self._parse_global_attribute_numeral(key.capitalize())
|
77
|
-
source = "raw file"
|
78
|
-
if value is not None:
|
79
|
-
value = float(ma.mean(value))
|
80
|
-
# Convert from 0...360 to -180...180
|
81
|
-
if key == "longitude" and value > 180:
|
82
|
-
value -= 360
|
83
|
-
self.data[key] = CloudnetArray(value, key, source=source)
|
84
|
-
|
85
|
-
def _parse_global_attribute_numeral(self, key: str) -> float | None:
|
86
|
-
new_str = ""
|
87
|
-
attr = getattr(self.dataset, key)
|
88
|
-
if attr == "Unknown":
|
89
|
-
return None
|
90
|
-
for char in attr:
|
91
|
-
if char.isdigit() or char == ".":
|
92
|
-
new_str += char
|
93
|
-
return float(new_str)
|
26
|
+
has_gps = self.instrument in (BASTA, FMCW94, FMCW35, HATPRO)
|
27
|
+
utils.add_site_geolocation(
|
28
|
+
self.data,
|
29
|
+
gps=has_gps,
|
30
|
+
site_meta=self.site_meta,
|
31
|
+
dataset=self.dataset if hasattr(self, "dataset") else None,
|
32
|
+
)
|
94
33
|
|
95
34
|
def add_height(self) -> None:
|
96
35
|
zenith_angle = self._get_zenith_angle()
|
cloudnetpy/instruments/hatpro.py
CHANGED
@@ -106,21 +106,7 @@ def hatpro2l1c(
|
|
106
106
|
if "irt" in hatpro.data:
|
107
107
|
hatpro.data["irt"].dimensions = ("time", "ir_channel")
|
108
108
|
|
109
|
-
|
110
|
-
lon = hatpro.data["longitude"].data
|
111
|
-
valid_latlon = (lat != 0) | (lon != 0)
|
112
|
-
if np.any(valid_latlon):
|
113
|
-
lat = float(ma.mean(lat[valid_latlon]))
|
114
|
-
lon = float(ma.mean(lon[valid_latlon]))
|
115
|
-
latlon_source = "GPS"
|
116
|
-
else:
|
117
|
-
lat = site_meta["latitude"]
|
118
|
-
lon = site_meta["longitude"]
|
119
|
-
latlon_source = "site coordinates"
|
120
|
-
alt = float(site_meta["altitude"])
|
121
|
-
hatpro.data["latitude"] = CloudnetArray(lat, "latitude", source=latlon_source)
|
122
|
-
hatpro.data["longitude"] = CloudnetArray(lon, "longitude", source=latlon_source)
|
123
|
-
hatpro.data["altitude"] = CloudnetArray(alt, "altitude", source="site coordinates")
|
109
|
+
utils.add_site_geolocation(hatpro.data, gps=True, site_meta=site_meta)
|
124
110
|
|
125
111
|
attrs_copy = ATTRIBUTES_1B01.copy()
|
126
112
|
attributes = output.add_time_attribute(attrs_copy, hatpro.date)
|
@@ -60,6 +60,7 @@ def pollyxt2nc(
|
|
60
60
|
polly.prepare_data()
|
61
61
|
polly.screen_completely_masked_profiles()
|
62
62
|
polly.data_to_cloudnet_arrays(time_dtype="f8")
|
63
|
+
polly.add_site_geolocation()
|
63
64
|
attributes = output.add_time_attribute(ATTRIBUTES, polly.date)
|
64
65
|
output.update_attributes(polly.data, attributes)
|
65
66
|
polly.add_snr_info("beta", snr_limit)
|
cloudnetpy/instruments/rpg.py
CHANGED
@@ -445,10 +445,6 @@ RPG_ATTRIBUTES = {
|
|
445
445
|
long_name="Number of spectral samples in each chirp sequence",
|
446
446
|
units="1",
|
447
447
|
),
|
448
|
-
"chirp_start_indices": MetaData(
|
449
|
-
long_name="Chirp sequences start indices",
|
450
|
-
units="1",
|
451
|
-
),
|
452
448
|
"number_of_averaged_chirps": MetaData(
|
453
449
|
long_name="Number of averaged chirps in sequence",
|
454
450
|
units="1",
|
@@ -517,10 +513,6 @@ RPG_ATTRIBUTES = {
|
|
517
513
|
long_name="PC temperature",
|
518
514
|
units="K",
|
519
515
|
),
|
520
|
-
"skewness": MetaData(
|
521
|
-
long_name="Skewness of spectra",
|
522
|
-
units="1",
|
523
|
-
),
|
524
516
|
"kurtosis": MetaData(
|
525
517
|
long_name="Kurtosis of spectra",
|
526
518
|
units="1",
|
cloudnetpy/metadata.py
CHANGED
@@ -93,6 +93,10 @@ COMMON_ATTRIBUTES = {
|
|
93
93
|
long_name="Kurtosis of spectra",
|
94
94
|
units="1",
|
95
95
|
),
|
96
|
+
"skewness": MetaData(
|
97
|
+
long_name="Skewness of spectra",
|
98
|
+
units="1",
|
99
|
+
),
|
96
100
|
"nyquist_velocity": MetaData(long_name="Nyquist velocity", units="m s-1"),
|
97
101
|
"radar_frequency": MetaData(long_name="Radar transmit frequency", units="GHz"),
|
98
102
|
"beta": MetaData(
|
@@ -190,4 +194,8 @@ COMMON_ATTRIBUTES = {
|
|
190
194
|
units="dB",
|
191
195
|
comment="SNR threshold used in data screening.",
|
192
196
|
),
|
197
|
+
"chirp_start_indices": MetaData(
|
198
|
+
long_name="Chirp sequences start indices",
|
199
|
+
units="1",
|
200
|
+
),
|
193
201
|
}
|
@@ -352,7 +352,7 @@ def plot_colormesh(ax, data: np.ndarray, axes: tuple, variable_info) -> None:
|
|
352
352
|
colorbar = init_colorbar(pl, ax)
|
353
353
|
if variable_info.plot_scale == "logarithmic":
|
354
354
|
tick_labels = get_log_cbar_tick_labels(vmin, vmax)
|
355
|
-
colorbar.set_ticks(np.arange(vmin, vmax + 1).tolist())
|
355
|
+
colorbar.set_ticks(np.arange(vmin, vmax + 1).tolist()) # type: ignore[arg-type]
|
356
356
|
colorbar.ax.set_yticklabels(tick_labels)
|
357
357
|
ax.set_facecolor("white")
|
358
358
|
colorbar.set_label(variable_info.clabel, fontsize=13)
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -589,7 +589,7 @@ class Plot2D(Plot):
|
|
589
589
|
cbar.set_label(str(self._plot_meta.clabel), fontsize=13)
|
590
590
|
|
591
591
|
if self._is_log:
|
592
|
-
cbar.set_ticks(np.arange(vmin, vmax + 1).tolist())
|
592
|
+
cbar.set_ticks(np.arange(vmin, vmax + 1).tolist()) # type: ignore[arg-type]
|
593
593
|
tick_labels = get_log_cbar_tick_labels(vmin, vmax)
|
594
594
|
cbar.ax.set_yticklabels(tick_labels)
|
595
595
|
|
cloudnetpy/utils.py
CHANGED
@@ -15,10 +15,12 @@ from typing import Literal, TypeVar
|
|
15
15
|
|
16
16
|
import netCDF4
|
17
17
|
import numpy as np
|
18
|
+
import numpy.typing as npt
|
18
19
|
from numpy import ma
|
19
20
|
from scipy import ndimage, stats
|
20
21
|
from scipy.interpolate import RectBivariateSpline, RegularGridInterpolator, griddata
|
21
22
|
|
23
|
+
from cloudnetpy.cloudnetarray import CloudnetArray
|
22
24
|
from cloudnetpy.constants import SEC_IN_DAY, SEC_IN_HOUR, SEC_IN_MINUTE
|
23
25
|
from cloudnetpy.exceptions import ValidTimeStampError
|
24
26
|
|
@@ -1050,3 +1052,100 @@ def _calc_hash_sum(filename, method, *, is_base64: bool) -> str:
|
|
1050
1052
|
if is_base64:
|
1051
1053
|
return base64.encodebytes(hash_sum.digest()).decode("utf-8").strip()
|
1052
1054
|
return hash_sum.hexdigest()
|
1055
|
+
|
1056
|
+
|
1057
|
+
def add_site_geolocation(
|
1058
|
+
data: dict,
|
1059
|
+
*,
|
1060
|
+
gps: bool,
|
1061
|
+
site_meta: dict | None = None,
|
1062
|
+
dataset: netCDF4.Dataset | None = None,
|
1063
|
+
):
|
1064
|
+
tmp_data = {}
|
1065
|
+
tmp_source = {}
|
1066
|
+
|
1067
|
+
for key in ("latitude", "longitude", "altitude"):
|
1068
|
+
value = None
|
1069
|
+
source = None
|
1070
|
+
# Prefer accurate GPS coordinates.
|
1071
|
+
if gps:
|
1072
|
+
values = None
|
1073
|
+
if isinstance(dataset, netCDF4.Dataset) and key in dataset.variables:
|
1074
|
+
values = dataset[key][:]
|
1075
|
+
elif key in data:
|
1076
|
+
values = data[key].data
|
1077
|
+
if (
|
1078
|
+
values is not None
|
1079
|
+
and not np.all(ma.getmaskarray(values))
|
1080
|
+
and np.any(values != 0)
|
1081
|
+
):
|
1082
|
+
value = ma.masked_where(values == 0, values)
|
1083
|
+
source = "GPS"
|
1084
|
+
# User-supplied site coordinate.
|
1085
|
+
if value is None and site_meta is not None and key in site_meta:
|
1086
|
+
value = float(site_meta[key])
|
1087
|
+
source = "site coordinates"
|
1088
|
+
# From source data (CHM15k, CL61, MRR-PRO, Copernicus, Galileo...).
|
1089
|
+
# Assume value is manually set, so cannot trust it.
|
1090
|
+
if (
|
1091
|
+
value is None
|
1092
|
+
and isinstance(dataset, netCDF4.Dataset)
|
1093
|
+
and key in dataset.variables
|
1094
|
+
and not np.all(ma.getmaskarray(dataset[key][:]))
|
1095
|
+
):
|
1096
|
+
value = dataset[key][:]
|
1097
|
+
source = "raw file"
|
1098
|
+
# From source global attributes (MIRA).
|
1099
|
+
# Seems to be manually set, so cannot trust it.
|
1100
|
+
if (
|
1101
|
+
value is None
|
1102
|
+
and isinstance(dataset, netCDF4.Dataset)
|
1103
|
+
and hasattr(dataset, key.capitalize())
|
1104
|
+
):
|
1105
|
+
value = _parse_global_attribute_numeral(dataset, key.capitalize())
|
1106
|
+
source = "raw file"
|
1107
|
+
if value is not None:
|
1108
|
+
tmp_data[key] = value
|
1109
|
+
tmp_source[key] = source
|
1110
|
+
|
1111
|
+
if "latitude" in tmp_data and "longitude" in tmp_data:
|
1112
|
+
lat = np.atleast_1d(tmp_data["latitude"])
|
1113
|
+
lon = np.atleast_1d(tmp_data["longitude"])
|
1114
|
+
lon[lon > 180] - 360
|
1115
|
+
if _are_stationary(lat, lon):
|
1116
|
+
tmp_data["latitude"] = float(ma.mean(lat))
|
1117
|
+
tmp_data["longitude"] = float(ma.mean(lon))
|
1118
|
+
else:
|
1119
|
+
tmp_data["latitude"] = lat
|
1120
|
+
tmp_data["longitude"] = lon
|
1121
|
+
|
1122
|
+
if "altitude" in tmp_data:
|
1123
|
+
alt = np.atleast_1d(tmp_data["altitude"])
|
1124
|
+
if ma.max(alt) - ma.min(alt) < 100:
|
1125
|
+
tmp_data["altitude"] = float(ma.mean(alt))
|
1126
|
+
|
1127
|
+
for key in ("latitude", "longitude", "altitude"):
|
1128
|
+
if key in tmp_data:
|
1129
|
+
data[key] = CloudnetArray(tmp_data[key], key, source=tmp_source[key])
|
1130
|
+
|
1131
|
+
|
1132
|
+
def _parse_global_attribute_numeral(dataset: netCDF4.Dataset, key: str) -> float | None:
|
1133
|
+
new_str = ""
|
1134
|
+
attr = getattr(dataset, key)
|
1135
|
+
if attr == "Unknown":
|
1136
|
+
return None
|
1137
|
+
for char in attr:
|
1138
|
+
if char.isdigit() or char == ".":
|
1139
|
+
new_str += char
|
1140
|
+
return float(new_str)
|
1141
|
+
|
1142
|
+
|
1143
|
+
def _are_stationary(latitude: npt.NDArray, longitude: npt.NDArray) -> bool:
|
1144
|
+
min_lat, max_lat = np.min(latitude), np.max(latitude)
|
1145
|
+
min_lon, max_lon = np.min(longitude), np.max(longitude)
|
1146
|
+
lat_threshold = 0.01 # deg, around 1 km
|
1147
|
+
avg_lat = (min_lat + max_lat) / 2
|
1148
|
+
lon_threshold = lat_threshold / np.cos(np.radians(avg_lat))
|
1149
|
+
lat_diff = max_lat - min_lat
|
1150
|
+
lon_diff = max_lon - min_lon
|
1151
|
+
return lat_diff <= lat_threshold and lon_diff <= lon_threshold
|
cloudnetpy/version.py
CHANGED
@@ -5,25 +5,25 @@ cloudnetpy/concat_lib.py,sha256=jcLppqAmVHVkykcXBcpwUr8MS_k8v2Xl2xBLmVRE_DI,1262
|
|
5
5
|
cloudnetpy/constants.py,sha256=YnoSzZm35NDooJfhlulSJBc7g0eSchT3yGytRaTaJEI,845
|
6
6
|
cloudnetpy/datasource.py,sha256=FcWS77jz56gIzwnbafDLdj-HjAyu0P_VtY7gkeVZThU,7952
|
7
7
|
cloudnetpy/exceptions.py,sha256=hYbUtBwjCIfxnPe_5mELDEw87AWITBrwuo7WYIEKmJ8,1579
|
8
|
-
cloudnetpy/metadata.py,sha256=
|
8
|
+
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
|
-
cloudnetpy/utils.py,sha256=
|
12
|
-
cloudnetpy/version.py,sha256=
|
11
|
+
cloudnetpy/utils.py,sha256=ui6sq8g1HlgfOQO644iNHsRGqpDwS3q-5uGR2y2tOAk,33325
|
12
|
+
cloudnetpy/version.py,sha256=rvARV3v_Ikvuo3dtTDySv9IDxv3ulpF2u_dwfzI6_hk,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
16
|
cloudnetpy/categorize/categorize.py,sha256=HtttmUlnLE4Cw3QLdz-DRfRpd8sVr_Tp4A62XI67x24,20739
|
17
17
|
cloudnetpy/categorize/classify.py,sha256=qovHgHsMku5kpl3cJxKteNBsG8GAkfI3Zo8QhJwZSFQ,8512
|
18
18
|
cloudnetpy/categorize/containers.py,sha256=evqVQOoUycBtaFeKpms7_NuM9z8jKWq37nLIhoDxa8k,5278
|
19
|
-
cloudnetpy/categorize/disdrometer.py,sha256=
|
19
|
+
cloudnetpy/categorize/disdrometer.py,sha256=sRSt2B932lrrkvycKoSaKEIaDVfq9Z7uU-4iHRr-fC0,1893
|
20
20
|
cloudnetpy/categorize/droplet.py,sha256=t49KEsH5ZM68JQ4NvAf9kGgQ-evic1T4de2-jgJ2f4M,8683
|
21
21
|
cloudnetpy/categorize/falling.py,sha256=lok0HMi1ewf9pS70mq62nRKL6wJzMyWbYmv1cdwrwnA,4404
|
22
22
|
cloudnetpy/categorize/freezing.py,sha256=eSFD37R7vBrg7mgfSanrwhBjnFyWNBpjw2AtvRmSh48,3753
|
23
23
|
cloudnetpy/categorize/insects.py,sha256=9J5agmktit8Or66GGNue-bThiaG9rB2SuPNZBXI7FCE,5243
|
24
|
-
cloudnetpy/categorize/itu.py,sha256=
|
24
|
+
cloudnetpy/categorize/itu.py,sha256=ffXK27guyRS4d66VWQ2h4UEGjUIhGjPKbFmj7kh698c,10304
|
25
25
|
cloudnetpy/categorize/lidar.py,sha256=YQrM_LOz8NQrrD9l9HyujV1GSGwkQ8LMqXN13bEJRW4,2605
|
26
|
-
cloudnetpy/categorize/melting.py,sha256=
|
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
29
|
cloudnetpy/categorize/radar.py,sha256=0Wg5u2aLXRVhHiFiXb4fSqY_iGgSwcCMMVshM3wBoo0,14149
|
@@ -32,25 +32,26 @@ 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=
|
35
|
+
cloudnetpy/instruments/__init__.py,sha256=sbJZBYWynZbGAWb8VMMaT5qXuCyzG1LwEdHhVxFXVMk,500
|
36
36
|
cloudnetpy/instruments/basta.py,sha256=Lb_EhQTI93S5Bd9osDbCE_tC8gZreRsHz7D2_dFOjmE,3793
|
37
|
-
cloudnetpy/instruments/
|
38
|
-
cloudnetpy/instruments/
|
37
|
+
cloudnetpy/instruments/bowtie.py,sha256=Hp4mzjGqvYw5bhgAy_LvScYrf3Xm3ULbtPjhG9GnAJ8,2977
|
38
|
+
cloudnetpy/instruments/ceilo.py,sha256=qM3AkQKHUblhRCD42HsB6lr82giBH-0g_VzoWHZDgeA,9535
|
39
|
+
cloudnetpy/instruments/ceilometer.py,sha256=ati9-fUQ54K9tvynIPB-nlBYwtvBVaQtUCjVCLNB67w,12059
|
39
40
|
cloudnetpy/instruments/cl61d.py,sha256=g6DNBFju3wYhLFl32DKmC8pUup7y-EupXoUU0fuoGGA,1990
|
40
|
-
cloudnetpy/instruments/cloudnet_instrument.py,sha256=
|
41
|
+
cloudnetpy/instruments/cloudnet_instrument.py,sha256=3qJe8STIvsU8irj79xuElFUZa0jUsSSg2lq7Ozo1om4,4401
|
41
42
|
cloudnetpy/instruments/copernicus.py,sha256=99idcn6-iKOSvSslNjwFRng3gwlTLFjKPiT1tnVytpQ,6613
|
42
43
|
cloudnetpy/instruments/galileo.py,sha256=BjWE15_S3tTCOmAM5k--oicI3wghKaO0hv9EUBxtbl8,4830
|
43
|
-
cloudnetpy/instruments/hatpro.py,sha256=
|
44
|
+
cloudnetpy/instruments/hatpro.py,sha256=D1iTR58ao6zA556LegPDhh4JHozxiup_9mUuLjR0NzE,9006
|
44
45
|
cloudnetpy/instruments/instruments.py,sha256=97hHMjp8fp2IKihr0XJYY3BrOlBArU7gYwYmt3OxqvU,4124
|
45
46
|
cloudnetpy/instruments/lufft.py,sha256=nIoEKuuFGKq2dLqkX7zW-HpAifefG472tZhKfXE1yoA,4212
|
46
47
|
cloudnetpy/instruments/mira.py,sha256=Wofp8HbiAwJce_IbOLjpEFV07H_Kh4170C9Wygiz-ew,11401
|
47
48
|
cloudnetpy/instruments/mrr.py,sha256=eeAzCp3CiHGauywjwvMUAFwZ4vBOZMcd3IlF8KsrLQo,5711
|
48
49
|
cloudnetpy/instruments/nc_lidar.py,sha256=5gQG9PApnNPrHmS9_zanl8HEYIQuGRpbnzC3wfTcOyQ,1705
|
49
50
|
cloudnetpy/instruments/nc_radar.py,sha256=HlaZeH5939R86ukF8K-P4Kfzb5-CpLB15LU2u94C5eI,7330
|
50
|
-
cloudnetpy/instruments/pollyxt.py,sha256=
|
51
|
+
cloudnetpy/instruments/pollyxt.py,sha256=U3g-ttmcs02LuLwVOydP3GjeNcmDyoYQroB-leIGdHY,10060
|
51
52
|
cloudnetpy/instruments/radiometrics.py,sha256=ySG4a042XkgjMTG8d20oAPNvFvw9bMwwiqS3zv-JF_w,11825
|
52
53
|
cloudnetpy/instruments/rain_e_h3.py,sha256=9TdpP4UzMBNIt2iE2GL6K9dFldzTHPLOrU8Q3tcosCU,5317
|
53
|
-
cloudnetpy/instruments/rpg.py,sha256=
|
54
|
+
cloudnetpy/instruments/rpg.py,sha256=gjvDomyoqSaeS3-X4EuesvWBp32BNoD-CeuvBOP19AI,17359
|
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=RKAw_fVry4YOUF0i2_-2jLIc6_H85oL8USA4ji9rh0o,4583
|
@@ -67,7 +68,7 @@ cloudnetpy/model_evaluation/utils.py,sha256=Z9VqYVdtY9yTr2JeVfBn4nccIVWCN5Fd-BCy
|
|
67
68
|
cloudnetpy/model_evaluation/plotting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
69
|
cloudnetpy/model_evaluation/plotting/plot_meta.py,sha256=K18Ugohh24uVAIxjZgJsmK80YwsMstm6B7ptVafONAw,3557
|
69
70
|
cloudnetpy/model_evaluation/plotting/plot_tools.py,sha256=gV042W_AHidwPsRe2L57xdWbt3W-utcHMt_9FmfYK3M,5033
|
70
|
-
cloudnetpy/model_evaluation/plotting/plotting.py,sha256=
|
71
|
+
cloudnetpy/model_evaluation/plotting/plotting.py,sha256=mGgSnQoRTh04v5RSJHsYPaqUEIR82eZqAuiszrh9rjY,31235
|
71
72
|
cloudnetpy/model_evaluation/products/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
73
|
cloudnetpy/model_evaluation/products/advance_methods.py,sha256=rng3ZLR1Arv1AGUzq0Ehu-65628PC5LZVKpHSUpCIW8,8526
|
73
74
|
cloudnetpy/model_evaluation/products/grid_methods.py,sha256=4no7mbKc9HlEXSNKPioqLmFZxUefuI-yqX0-Ej2jMzU,9067
|
@@ -101,7 +102,7 @@ cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V
|
|
101
102
|
cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
|
102
103
|
cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
|
103
104
|
cloudnetpy/plotting/plot_meta.py,sha256=qfyZJNis937uM-NJseer8i4FO7I_v5jhQPyFl5Uszi8,17390
|
104
|
-
cloudnetpy/plotting/plotting.py,sha256=
|
105
|
+
cloudnetpy/plotting/plotting.py,sha256=yQKidTESio0i8cQ_ep7dEunnx02ITgzExAa2yCqQj3s,38422
|
105
106
|
cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
|
106
107
|
cloudnetpy/products/classification.py,sha256=KwAiBSgFwDqhM114NIgYiUjj8HoYc7gAlc8E1QgcSig,8207
|
107
108
|
cloudnetpy/products/der.py,sha256=soypE7uSEP4uHUCCQVEhyXsKY6e9mzV9B_2S5GUizqk,12729
|
@@ -115,10 +116,10 @@ cloudnetpy/products/lwc.py,sha256=sl6Al2tuH3KkCBrPbWTmuz3jlD5UQJ4D6qBsn1tt2CQ,18
|
|
115
116
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
116
117
|
cloudnetpy/products/mwr_tools.py,sha256=rd7UC67O4fsIE5SaHVZ4qWvUJTj41ZGwgQWPwZzOM14,5377
|
117
118
|
cloudnetpy/products/product_tools.py,sha256=uu4l6reuGbPcW3TgttbaSrqIKbyYGhBVTdnC7opKvmg,11101
|
118
|
-
cloudnetpy-1.
|
119
|
+
cloudnetpy-1.73.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
119
120
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
120
|
-
cloudnetpy-1.
|
121
|
-
cloudnetpy-1.
|
122
|
-
cloudnetpy-1.
|
123
|
-
cloudnetpy-1.
|
124
|
-
cloudnetpy-1.
|
121
|
+
cloudnetpy-1.73.1.dist-info/METADATA,sha256=XUqHEX3wWJGCJubVV_lBR__4JEG_uPl3TBF1D6ZY31k,5796
|
122
|
+
cloudnetpy-1.73.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
123
|
+
cloudnetpy-1.73.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
|
124
|
+
cloudnetpy-1.73.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
125
|
+
cloudnetpy-1.73.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|