cloudnetpy 1.65.7__py3-none-any.whl → 1.66.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.
- cloudnetpy/categorize/__init__.py +0 -1
- cloudnetpy/categorize/atmos_utils.py +278 -59
- cloudnetpy/categorize/attenuation.py +31 -0
- cloudnetpy/categorize/attenuations/__init__.py +37 -0
- cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +80 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +75 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +140 -81
- cloudnetpy/categorize/classify.py +92 -128
- cloudnetpy/categorize/containers.py +45 -31
- cloudnetpy/categorize/droplet.py +2 -2
- cloudnetpy/categorize/falling.py +3 -3
- cloudnetpy/categorize/freezing.py +2 -2
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/melting.py +0 -3
- cloudnetpy/categorize/model.py +31 -14
- cloudnetpy/categorize/radar.py +28 -12
- cloudnetpy/constants.py +3 -6
- cloudnetpy/model_evaluation/file_handler.py +2 -2
- cloudnetpy/model_evaluation/products/observation_products.py +8 -8
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +5 -2
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +11 -11
- cloudnetpy/output.py +46 -26
- cloudnetpy/plotting/plot_meta.py +8 -2
- cloudnetpy/plotting/plotting.py +31 -8
- cloudnetpy/products/classification.py +39 -34
- cloudnetpy/products/der.py +15 -13
- cloudnetpy/products/drizzle_tools.py +22 -21
- cloudnetpy/products/ier.py +8 -45
- cloudnetpy/products/iwc.py +7 -22
- cloudnetpy/products/lwc.py +14 -15
- cloudnetpy/products/mwr_tools.py +15 -2
- cloudnetpy/products/product_tools.py +121 -119
- cloudnetpy/utils.py +4 -0
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.65.7.dist-info → cloudnetpy-1.66.0.dist-info}/METADATA +1 -1
- {cloudnetpy-1.65.7.dist-info → cloudnetpy-1.66.0.dist-info}/RECORD +41 -35
- {cloudnetpy-1.65.7.dist-info → cloudnetpy-1.66.0.dist-info}/WHEEL +1 -1
- cloudnetpy/categorize/atmos.py +0 -376
- {cloudnetpy-1.65.7.dist-info → cloudnetpy-1.66.0.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.65.7.dist-info → cloudnetpy-1.66.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from numpy import ma
|
3
|
+
from numpy.typing import NDArray
|
4
|
+
|
5
|
+
import cloudnetpy.constants as con
|
6
|
+
from cloudnetpy import utils
|
7
|
+
from cloudnetpy.categorize.attenuations import (
|
8
|
+
Attenuation,
|
9
|
+
calc_two_way_attenuation,
|
10
|
+
)
|
11
|
+
from cloudnetpy.categorize.containers import ClassificationResult, Observations
|
12
|
+
|
13
|
+
|
14
|
+
def calc_rain_attenuation(
|
15
|
+
data: Observations, classification: ClassificationResult
|
16
|
+
) -> Attenuation:
|
17
|
+
affected_region, inducing_region = _find_regions(classification)
|
18
|
+
shape = affected_region.shape
|
19
|
+
|
20
|
+
if data.disdrometer is None:
|
21
|
+
return Attenuation(
|
22
|
+
amount=ma.masked_all(shape),
|
23
|
+
error=ma.masked_all(shape),
|
24
|
+
attenuated=affected_region,
|
25
|
+
uncorrected=affected_region,
|
26
|
+
)
|
27
|
+
|
28
|
+
rainfall_rate = data.disdrometer.data["rainfall_rate"][:].copy()
|
29
|
+
rainfall_rate[classification.is_rain == 0] = ma.masked
|
30
|
+
frequency = data.radar.radar_frequency
|
31
|
+
|
32
|
+
specific_attenuation_array = _calc_rain_specific_attenuation(
|
33
|
+
rainfall_rate, frequency
|
34
|
+
)
|
35
|
+
|
36
|
+
specific_attenuation = utils.transpose(specific_attenuation_array) * ma.ones(shape)
|
37
|
+
|
38
|
+
two_way_attenuation = calc_two_way_attenuation(
|
39
|
+
data.radar.height, specific_attenuation
|
40
|
+
)
|
41
|
+
|
42
|
+
two_way_attenuation[~inducing_region] = 0
|
43
|
+
two_way_attenuation = ma.array(utils.ffill(two_way_attenuation.data))
|
44
|
+
two_way_attenuation[two_way_attenuation == 0] = ma.masked
|
45
|
+
|
46
|
+
return Attenuation(
|
47
|
+
amount=two_way_attenuation,
|
48
|
+
error=two_way_attenuation * 0.2,
|
49
|
+
attenuated=affected_region,
|
50
|
+
uncorrected=np.zeros_like(affected_region, dtype=bool),
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
def _find_regions(
|
55
|
+
classification: ClassificationResult,
|
56
|
+
) -> tuple[NDArray[np.bool_], NDArray[np.bool_]]:
|
57
|
+
"""Finds regions where rain attenuation is present and can be corrected or not."""
|
58
|
+
warm_region = ~classification.category_bits.freezing
|
59
|
+
is_rain = utils.transpose(classification.is_rain).astype(bool)
|
60
|
+
affected_region = np.ones_like(warm_region, dtype=bool) * is_rain
|
61
|
+
inducing_region = warm_region * is_rain
|
62
|
+
return affected_region, inducing_region
|
63
|
+
|
64
|
+
|
65
|
+
def _calc_rain_specific_attenuation(
|
66
|
+
rainfall_rate: np.ndarray, frequency: float
|
67
|
+
) -> np.ndarray:
|
68
|
+
"""Calculates specific attenuation due to rain (dB km-1).
|
69
|
+
|
70
|
+
References:
|
71
|
+
Crane, R. (1980). Prediction of Attenuation by Rain.
|
72
|
+
IEEE Transactions on Communications, 28(9), 1717–1733.
|
73
|
+
doi:10.1109/tcom.1980.1094844
|
74
|
+
"""
|
75
|
+
if frequency > 8 and frequency < 12:
|
76
|
+
alpha, beta = 0.0125, 1.18
|
77
|
+
if frequency > 34 and frequency < 37:
|
78
|
+
alpha, beta = 0.242, 1.04
|
79
|
+
elif frequency > 93 and frequency < 96:
|
80
|
+
alpha, beta = 0.95, 0.72
|
81
|
+
else:
|
82
|
+
msg = "Radar frequency not supported"
|
83
|
+
raise ValueError(msg)
|
84
|
+
return alpha * (rainfall_rate * con.M_S_TO_MM_H) ** beta
|
@@ -1,9 +1,15 @@
|
|
1
1
|
"""Module that generates Cloudnet categorize file."""
|
2
2
|
|
3
|
+
import dataclasses
|
3
4
|
import logging
|
5
|
+
from dataclasses import fields
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
from numpy.typing import NDArray
|
4
9
|
|
5
10
|
from cloudnetpy import output, utils
|
6
|
-
from cloudnetpy.categorize import
|
11
|
+
from cloudnetpy.categorize import attenuation, classify
|
12
|
+
from cloudnetpy.categorize.containers import Observations
|
7
13
|
from cloudnetpy.categorize.disdrometer import Disdrometer
|
8
14
|
from cloudnetpy.categorize.lidar import Lidar
|
9
15
|
from cloudnetpy.categorize.model import Model
|
@@ -12,6 +18,7 @@ from cloudnetpy.categorize.radar import Radar
|
|
12
18
|
from cloudnetpy.datasource import DataSource
|
13
19
|
from cloudnetpy.exceptions import DisdrometerDataError, ValidTimeStampError
|
14
20
|
from cloudnetpy.metadata import MetaData
|
21
|
+
from cloudnetpy.products.product_tools import CategoryBits, QualityBits
|
15
22
|
|
16
23
|
|
17
24
|
def generate_categorize(
|
@@ -66,24 +73,25 @@ def generate_categorize(
|
|
66
73
|
>>> generate_categorize(input_files, 'output.nc') # Use the Voodoo method
|
67
74
|
"""
|
68
75
|
|
69
|
-
def _interpolate_to_cloudnet_grid() -> list:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
data
|
76
|
-
model_gap_ind = data
|
77
|
-
radar_gap_ind = data
|
78
|
-
lidar_gap_ind = data
|
76
|
+
def _interpolate_to_cloudnet_grid() -> list[int]:
|
77
|
+
if data.disdrometer is not None:
|
78
|
+
data.disdrometer.interpolate_to_grid(time)
|
79
|
+
if data.mwr is not None:
|
80
|
+
data.mwr.rebin_to_grid(time)
|
81
|
+
data.model.calc_attenuations(data.radar.radar_frequency)
|
82
|
+
data.model.interpolate_to_common_height()
|
83
|
+
model_gap_ind = data.model.interpolate_to_grid(time, height)
|
84
|
+
radar_gap_ind = data.radar.rebin_to_grid(time)
|
85
|
+
lidar_gap_ind = data.lidar.interpolate_to_grid(time, height)
|
79
86
|
gap_indices = set(radar_gap_ind + lidar_gap_ind + model_gap_ind)
|
80
87
|
return [ind for ind in range(len(time)) if ind not in gap_indices]
|
81
88
|
|
82
89
|
def _screen_bad_time_indices(valid_indices: list) -> None:
|
83
90
|
n_time_full = len(time)
|
84
|
-
data
|
85
|
-
for
|
86
|
-
|
91
|
+
data.radar.time = time[valid_indices]
|
92
|
+
for field in fields(data):
|
93
|
+
obj = getattr(data, field.name)
|
94
|
+
if not hasattr(obj, "data"):
|
87
95
|
continue
|
88
96
|
for key, item in obj.data.items():
|
89
97
|
if utils.isscalar(item.data):
|
@@ -97,79 +105,92 @@ def generate_categorize(
|
|
97
105
|
else:
|
98
106
|
continue
|
99
107
|
obj.data[key].data = array
|
100
|
-
for key, item in data
|
101
|
-
data
|
108
|
+
for key, item in data.model.data_dense.items():
|
109
|
+
data.model.data_dense[key] = item[valid_indices, :]
|
102
110
|
|
103
111
|
def _prepare_output() -> dict:
|
104
|
-
data
|
105
|
-
data
|
106
|
-
|
107
|
-
|
108
|
-
data
|
109
|
-
|
110
|
-
|
112
|
+
data.radar.add_meta()
|
113
|
+
data.model.screen_sparse_fields()
|
114
|
+
|
115
|
+
if data.disdrometer is not None:
|
116
|
+
data.radar.data.pop("rainfall_rate", None)
|
117
|
+
data.disdrometer.data.pop("n_particles", None)
|
118
|
+
|
119
|
+
data.radar.append_data(attenuations.gas.amount, "radar_gas_atten")
|
120
|
+
data.radar.append_data(attenuations.liquid.amount, "radar_liquid_atten")
|
121
|
+
data.radar.append_data(attenuations.rain.amount, "radar_rain_atten")
|
122
|
+
data.radar.append_data(attenuations.melting.amount, "radar_melting_atten")
|
123
|
+
|
124
|
+
data.radar.append_data(_classes_to_bits(quality), "quality_bits")
|
125
|
+
|
126
|
+
data.radar.append_data(classification.insect_prob, "insect_prob")
|
127
|
+
data.radar.append_data(classification.is_rain, "rain_detected")
|
128
|
+
data.radar.append_data(
|
129
|
+
_classes_to_bits(classification.category_bits), "category_bits"
|
130
|
+
)
|
131
|
+
|
111
132
|
if classification.liquid_prob is not None:
|
112
|
-
data
|
113
|
-
|
114
|
-
data["radar"].append_data(attenuations[key], key)
|
115
|
-
data["radar"].append_data(quality["quality_bits"], "quality_bits")
|
116
|
-
data["radar"].append_data(classification.is_rain, "rain_detected")
|
133
|
+
data.radar.append_data(classification.liquid_prob, "liquid_prob")
|
134
|
+
|
117
135
|
return {
|
118
|
-
**data
|
119
|
-
**data
|
120
|
-
**data
|
121
|
-
**data
|
122
|
-
**(data
|
123
|
-
**(data
|
136
|
+
**data.radar.data,
|
137
|
+
**data.lidar.data,
|
138
|
+
**data.model.data,
|
139
|
+
**data.model.data_sparse,
|
140
|
+
**(data.mwr.data if data.mwr is not None else {}),
|
141
|
+
**(data.disdrometer.data if data.disdrometer is not None else {}),
|
124
142
|
}
|
125
143
|
|
126
144
|
def _define_dense_grid() -> tuple:
|
127
|
-
return utils.time_grid(), data
|
145
|
+
return utils.time_grid(), data.radar.height
|
128
146
|
|
129
147
|
def _close_all() -> None:
|
130
|
-
for
|
148
|
+
for field in fields(data):
|
149
|
+
obj = getattr(data, field.name)
|
131
150
|
if isinstance(obj, DataSource):
|
132
151
|
obj.close()
|
133
152
|
|
134
153
|
try:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
"
|
140
|
-
"
|
141
|
-
|
154
|
+
radar = Radar(input_files["radar"])
|
155
|
+
data = Observations(
|
156
|
+
radar=radar,
|
157
|
+
lidar=Lidar(input_files["lidar"]),
|
158
|
+
model=Model(input_files["model"], radar.altitude, options),
|
159
|
+
lv0_files=input_files.get("lv0_files"),
|
160
|
+
)
|
142
161
|
if "mwr" in input_files:
|
143
|
-
data
|
162
|
+
data.mwr = Mwr(input_files["mwr"])
|
144
163
|
if "disdrometer" in input_files:
|
145
164
|
try:
|
146
|
-
data
|
165
|
+
data.disdrometer = Disdrometer(input_files["disdrometer"])
|
147
166
|
except DisdrometerDataError as err:
|
148
167
|
logging.warning("Unable to use disdrometer: %s", err)
|
149
|
-
data["model"] = Model(input_files["model"], data["radar"].altitude, options)
|
150
168
|
time, height = _define_dense_grid()
|
151
169
|
valid_ind = _interpolate_to_cloudnet_grid()
|
152
170
|
if not valid_ind:
|
153
171
|
msg = "No overlapping radar and lidar timestamps found"
|
154
172
|
raise ValidTimeStampError(msg)
|
155
173
|
_screen_bad_time_indices(valid_ind)
|
156
|
-
if (
|
157
|
-
|
158
|
-
|
159
|
-
):
|
160
|
-
data["radar"].filter_speckle_noise()
|
161
|
-
data["radar"].filter_1st_gate_artifact()
|
174
|
+
if any(source in data.radar.source_type.lower() for source in ("rpg", "basta")):
|
175
|
+
data.radar.filter_speckle_noise()
|
176
|
+
data.radar.filter_1st_gate_artifact()
|
162
177
|
for variable in ("v", "v_sigma", "ldr"):
|
163
|
-
data
|
164
|
-
data
|
165
|
-
data
|
178
|
+
data.radar.filter_stripes(variable)
|
179
|
+
data.radar.remove_incomplete_pixels()
|
180
|
+
data.model.calc_wet_bulb()
|
181
|
+
|
166
182
|
classification = classify.classify_measurements(data)
|
167
|
-
|
168
|
-
|
169
|
-
|
183
|
+
|
184
|
+
attenuations = attenuation.get_attenuations(data, classification)
|
185
|
+
|
186
|
+
data.radar.correct_atten(attenuations)
|
187
|
+
data.radar.calc_errors(attenuations, classification.is_clutter)
|
188
|
+
|
170
189
|
quality = classify.fetch_quality(data, classification, attenuations)
|
190
|
+
|
171
191
|
cloudnet_arrays = _prepare_output()
|
172
|
-
|
192
|
+
|
193
|
+
date = data.radar.get_date()
|
173
194
|
attributes = output.add_time_attribute(CATEGORIZE_ATTRIBUTES, date)
|
174
195
|
attributes = output.add_time_attribute(attributes, date, "model_time")
|
175
196
|
attributes = output.add_source_attribute(attributes, data)
|
@@ -181,16 +202,16 @@ def generate_categorize(
|
|
181
202
|
|
182
203
|
def _save_cat(
|
183
204
|
full_path: str,
|
184
|
-
data_obs:
|
205
|
+
data_obs: Observations,
|
185
206
|
cloudnet_arrays: dict,
|
186
207
|
uuid: str | None,
|
187
208
|
) -> str:
|
188
209
|
"""Creates a categorize netCDF4 file and saves all data into it."""
|
189
210
|
dims = {
|
190
|
-
"time": len(data_obs
|
191
|
-
"height": len(data_obs
|
192
|
-
"model_time": len(data_obs
|
193
|
-
"model_height": len(data_obs
|
211
|
+
"time": len(data_obs.radar.time),
|
212
|
+
"height": len(data_obs.radar.height),
|
213
|
+
"model_time": len(data_obs.model.time),
|
214
|
+
"model_height": len(data_obs.model.mean_height),
|
194
215
|
}
|
195
216
|
|
196
217
|
file_type = "categorize"
|
@@ -198,12 +219,12 @@ def _save_cat(
|
|
198
219
|
uuid_out = nc.file_uuid
|
199
220
|
nc.cloudnet_file_type = file_type
|
200
221
|
output.copy_global(
|
201
|
-
data_obs
|
222
|
+
data_obs.radar.dataset,
|
202
223
|
nc,
|
203
224
|
("year", "month", "day", "location"),
|
204
225
|
)
|
205
|
-
nc.title = f"Cloud categorization products from {data_obs
|
206
|
-
nc.source_file_uuids = output.get_source_uuids(
|
226
|
+
nc.title = f"Cloud categorization products from {data_obs.radar.location}"
|
227
|
+
nc.source_file_uuids = output.get_source_uuids(data_obs)
|
207
228
|
is_voodoo = "liquid_prob" in cloudnet_arrays
|
208
229
|
extra_references = (
|
209
230
|
["https://doi.org/10.5194/amt-15-5343-2022"] if is_voodoo else None
|
@@ -221,6 +242,14 @@ def _save_cat(
|
|
221
242
|
return uuid_out
|
222
243
|
|
223
244
|
|
245
|
+
def _classes_to_bits(data: QualityBits | CategoryBits) -> NDArray[np.int_]:
|
246
|
+
shape = data.radar.shape if hasattr(data, "radar") else data.droplet.shape
|
247
|
+
quality = np.zeros(shape, dtype=np.int64)
|
248
|
+
for i, field in enumerate(dataclasses.fields(data)):
|
249
|
+
quality |= (1 << i) * getattr(data, field.name)
|
250
|
+
return quality
|
251
|
+
|
252
|
+
|
224
253
|
COMMENTS = {
|
225
254
|
"category_bits": (
|
226
255
|
"This variable contains information on the nature of the targets\n"
|
@@ -255,14 +284,17 @@ COMMENTS = {
|
|
255
284
|
"humidity, but forcing pixels containing liquid cloud to saturation with\n"
|
256
285
|
"respect to liquid water. It has been used to correct Z."
|
257
286
|
),
|
258
|
-
"
|
259
|
-
"This variable was
|
260
|
-
"humidity."
|
287
|
+
"radar_rain_atten": (
|
288
|
+
"This variable was calculated from the disdrometer rainfall rate."
|
261
289
|
),
|
290
|
+
"radar_melting_atten": (
|
291
|
+
"This variable was calculated from the disdrometer rainfall rate."
|
292
|
+
),
|
293
|
+
"Tw": "This variable was derived from model temperature, pressure and humidity.",
|
262
294
|
"Z_sensitivity": (
|
263
295
|
"This variable is an estimate of the radar sensitivity, i.e. the minimum\n"
|
264
296
|
"detectable radar reflectivity, as a function of height. It includes the\n"
|
265
|
-
"effect of ground clutter and gas attenuation but not
|
297
|
+
"effect of ground clutter and gas attenuation but not other attenuations."
|
266
298
|
),
|
267
299
|
"Z_error": (
|
268
300
|
"This variable is an estimate of the one-standard-deviation random error\n"
|
@@ -272,18 +304,19 @@ COMMENTS = {
|
|
272
304
|
" finite number of pulses\n"
|
273
305
|
"2) 10% uncertainty in gaseous attenuation correction (mainly due to error\n"
|
274
306
|
" in model humidity field)\n"
|
275
|
-
"3)
|
307
|
+
"3) 20% uncertainty in rain attenuation correction (mainly due to error\n"
|
308
|
+
" in disdrometer rainfall rate)\n"
|
309
|
+
"4) 10%-20% uncertainty in melting layer attenuation correction (mainly due\n"
|
310
|
+
" to error in disdrometer rainfall rate)\n"
|
311
|
+
"5) Error in liquid water path (given by the variable lwp_error) and its\n"
|
276
312
|
" partitioning with height)."
|
277
313
|
),
|
278
314
|
"Z": (
|
279
|
-
"This variable has been corrected for attenuation by gaseous attenuation
|
280
|
-
"
|
281
|
-
"
|
282
|
-
"
|
283
|
-
"
|
284
|
-
"Calibration convention: in the absence of attenuation, a cloud at 273 K\n"
|
285
|
-
"containing one million 100-micron droplets per cubic metre will have\n"
|
286
|
-
"a reflectivity of 0 dBZ at all frequencies."
|
315
|
+
"This variable has been corrected for attenuation by gaseous attenuation,\n"
|
316
|
+
"and possibly liquid water, rain and melting layer (see quality_bits\n"
|
317
|
+
"variable). Calibration convention: in the absence of attenuation, a cloud\n"
|
318
|
+
"at 273 K containing one million 100-micron droplets per cubic metre will\n"
|
319
|
+
"have\n a reflectivity of 0 dBZ at all frequencies."
|
287
320
|
),
|
288
321
|
"bias": (
|
289
322
|
"This variable is an estimate of the one-standard-deviation\n"
|
@@ -329,6 +362,19 @@ DEFINITIONS = {
|
|
329
362
|
liquid water path and the lidar estimation of the location of
|
330
363
|
liquid water cloud; be aware that errors in reflectivity may
|
331
364
|
result.""",
|
365
|
+
6: """Rain has caused radar attenuation; if bit 7 is set then a
|
366
|
+
correction for the radar attenuation has been performed;
|
367
|
+
otherwise do not trust the absolute values of reflectivity
|
368
|
+
factor. No correction is performed for lidar attenuation.""",
|
369
|
+
7: """Radar reflectivity has been corrected for rain attenuation
|
370
|
+
using rainfall rate from a disdrometer; be aware that errors
|
371
|
+
in reflectivity may result.""",
|
372
|
+
8: """Melting layer has caused radar attenuation; if bit 9 is set then a
|
373
|
+
correction for the radar attenuation has been performed;
|
374
|
+
otherwise do not trust the absolute values of reflectivity
|
375
|
+
factor. No correction is performed for lidar attenuation.""",
|
376
|
+
9: """Radar reflectivity has been corrected for melting layer
|
377
|
+
attenuation; be aware that errors in reflectivity may result.""",
|
332
378
|
}
|
333
379
|
),
|
334
380
|
}
|
@@ -416,12 +462,25 @@ CATEGORIZE_ATTRIBUTES = {
|
|
416
462
|
long_name="Two-way radar attenuation due to liquid water",
|
417
463
|
units="dB",
|
418
464
|
comment=COMMENTS["radar_liquid_atten"],
|
465
|
+
references="ITU-R P.840-9",
|
466
|
+
),
|
467
|
+
"radar_rain_atten": MetaData(
|
468
|
+
long_name="Two-way radar attenuation due to rain",
|
469
|
+
units="dB",
|
470
|
+
references="Crane, R. (1980)",
|
471
|
+
comment=COMMENTS["radar_rain_atten"],
|
472
|
+
),
|
473
|
+
"radar_melting_atten": MetaData(
|
474
|
+
long_name="Two-way radar attenuation due to melting ice",
|
475
|
+
units="dB",
|
476
|
+
references="Li, H., & Moisseev, D. (2019)",
|
477
|
+
comment=COMMENTS["radar_melting_atten"],
|
419
478
|
),
|
420
479
|
"radar_gas_atten": MetaData(
|
421
480
|
long_name="Two-way radar attenuation due to atmospheric gases",
|
422
481
|
units="dB",
|
423
482
|
comment=COMMENTS["radar_gas_atten"],
|
424
|
-
references="
|
483
|
+
references="ITU-R P.676-13",
|
425
484
|
),
|
426
485
|
"insect_prob": MetaData(
|
427
486
|
long_name="Insect probability",
|