cloudnetpy 1.49.9__py3-none-any.whl → 1.87.3__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 +1 -2
- cloudnetpy/categorize/atmos_utils.py +297 -67
- 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 +84 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +332 -156
- cloudnetpy/categorize/classify.py +127 -125
- cloudnetpy/categorize/containers.py +107 -76
- cloudnetpy/categorize/disdrometer.py +40 -0
- cloudnetpy/categorize/droplet.py +23 -21
- cloudnetpy/categorize/falling.py +53 -24
- cloudnetpy/categorize/freezing.py +25 -12
- cloudnetpy/categorize/insects.py +35 -23
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/lidar.py +36 -41
- cloudnetpy/categorize/melting.py +34 -26
- cloudnetpy/categorize/model.py +84 -37
- cloudnetpy/categorize/mwr.py +18 -14
- cloudnetpy/categorize/radar.py +215 -102
- cloudnetpy/cli.py +578 -0
- cloudnetpy/cloudnetarray.py +43 -89
- cloudnetpy/concat_lib.py +218 -78
- cloudnetpy/constants.py +28 -10
- cloudnetpy/datasource.py +61 -86
- cloudnetpy/exceptions.py +49 -20
- cloudnetpy/instruments/__init__.py +5 -0
- cloudnetpy/instruments/basta.py +29 -12
- cloudnetpy/instruments/bowtie.py +135 -0
- cloudnetpy/instruments/ceilo.py +138 -115
- cloudnetpy/instruments/ceilometer.py +164 -80
- cloudnetpy/instruments/cl61d.py +21 -5
- cloudnetpy/instruments/cloudnet_instrument.py +74 -36
- cloudnetpy/instruments/copernicus.py +108 -30
- cloudnetpy/instruments/da10.py +54 -0
- cloudnetpy/instruments/disdrometer/common.py +126 -223
- cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
- cloudnetpy/instruments/disdrometer/thies.py +254 -87
- cloudnetpy/instruments/fd12p.py +201 -0
- cloudnetpy/instruments/galileo.py +65 -23
- cloudnetpy/instruments/hatpro.py +123 -49
- cloudnetpy/instruments/instruments.py +113 -1
- cloudnetpy/instruments/lufft.py +39 -17
- cloudnetpy/instruments/mira.py +268 -61
- cloudnetpy/instruments/mrr.py +187 -0
- cloudnetpy/instruments/nc_lidar.py +19 -8
- cloudnetpy/instruments/nc_radar.py +109 -55
- cloudnetpy/instruments/pollyxt.py +135 -51
- cloudnetpy/instruments/radiometrics.py +313 -59
- cloudnetpy/instruments/rain_e_h3.py +171 -0
- cloudnetpy/instruments/rpg.py +321 -189
- cloudnetpy/instruments/rpg_reader.py +74 -40
- cloudnetpy/instruments/toa5.py +49 -0
- cloudnetpy/instruments/vaisala.py +95 -343
- cloudnetpy/instruments/weather_station.py +774 -105
- cloudnetpy/metadata.py +90 -19
- cloudnetpy/model_evaluation/file_handler.py +55 -52
- cloudnetpy/model_evaluation/metadata.py +46 -20
- cloudnetpy/model_evaluation/model_metadata.py +1 -1
- cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
- cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
- cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
- cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
- cloudnetpy/model_evaluation/products/model_products.py +43 -35
- cloudnetpy/model_evaluation/products/observation_products.py +41 -35
- cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
- cloudnetpy/model_evaluation/products/tools.py +29 -20
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
- cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
- cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
- cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
- cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
- cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
- cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
- cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
- cloudnetpy/model_evaluation/utils.py +2 -1
- cloudnetpy/output.py +170 -111
- cloudnetpy/plotting/__init__.py +2 -1
- cloudnetpy/plotting/plot_meta.py +562 -822
- cloudnetpy/plotting/plotting.py +1142 -704
- cloudnetpy/products/__init__.py +1 -0
- cloudnetpy/products/classification.py +370 -88
- cloudnetpy/products/der.py +85 -55
- cloudnetpy/products/drizzle.py +77 -34
- cloudnetpy/products/drizzle_error.py +15 -11
- cloudnetpy/products/drizzle_tools.py +79 -59
- cloudnetpy/products/epsilon.py +211 -0
- cloudnetpy/products/ier.py +27 -50
- cloudnetpy/products/iwc.py +55 -48
- cloudnetpy/products/lwc.py +96 -70
- cloudnetpy/products/mwr_tools.py +186 -0
- cloudnetpy/products/product_tools.py +170 -128
- cloudnetpy/utils.py +455 -240
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
- cloudnetpy-1.87.3.dist-info/RECORD +127 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
- cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
- docs/source/conf.py +2 -2
- cloudnetpy/categorize/atmos.py +0 -361
- cloudnetpy/products/mwr_multi.py +0 -68
- cloudnetpy/products/mwr_single.py +0 -75
- cloudnetpy-1.49.9.dist-info/RECORD +0 -112
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
cloudnetpy/datasource.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
"""Datasource module, containing the :class:`DataSource class
|
|
1
|
+
"""Datasource module, containing the :class:`DataSource` class."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
2
4
|
import logging
|
|
3
5
|
import os
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from os import PathLike
|
|
8
|
+
from types import TracebackType
|
|
6
9
|
|
|
7
10
|
import netCDF4
|
|
8
11
|
import numpy as np
|
|
12
|
+
import numpy.typing as npt
|
|
13
|
+
from typing_extensions import Self
|
|
9
14
|
|
|
10
15
|
from cloudnetpy import utils
|
|
11
16
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
|
@@ -44,19 +49,25 @@ class DataSource:
|
|
|
44
49
|
radar_frequency: float
|
|
45
50
|
data_dense: dict
|
|
46
51
|
data_sparse: dict
|
|
47
|
-
|
|
52
|
+
source_type: str
|
|
48
53
|
|
|
49
|
-
def __init__(self, full_path: str, radar: bool = False):
|
|
54
|
+
def __init__(self, full_path: PathLike | str, *, radar: bool = False) -> None:
|
|
50
55
|
self.filename = os.path.basename(full_path)
|
|
51
56
|
self.dataset = netCDF4.Dataset(full_path)
|
|
52
57
|
self.source = getattr(self.dataset, "source", "")
|
|
53
|
-
self.
|
|
58
|
+
self.instrument_pid = getattr(self.dataset, "instrument_pid", "")
|
|
59
|
+
self.time: npt.NDArray = self._init_time()
|
|
54
60
|
self.altitude = self._init_altitude()
|
|
55
61
|
self.height = self._init_height()
|
|
62
|
+
self.height_agl = (
|
|
63
|
+
self.height - self.altitude
|
|
64
|
+
if self.height is not None and self.altitude is not None
|
|
65
|
+
else None
|
|
66
|
+
)
|
|
56
67
|
self.data: dict = {}
|
|
57
68
|
self._is_radar = radar
|
|
58
69
|
|
|
59
|
-
def getvar(self, *args) ->
|
|
70
|
+
def getvar(self, *args: str) -> npt.NDArray:
|
|
60
71
|
"""Returns data array from the source file variables.
|
|
61
72
|
|
|
62
73
|
Returns just the data (and no attributes) from the original
|
|
@@ -69,21 +80,23 @@ class DataSource:
|
|
|
69
80
|
ndarray: The actual data.
|
|
70
81
|
|
|
71
82
|
Raises:
|
|
72
|
-
|
|
83
|
+
KeyError: The variable is not found.
|
|
73
84
|
|
|
74
85
|
"""
|
|
75
86
|
for arg in args:
|
|
76
87
|
if arg in self.dataset.variables:
|
|
77
88
|
return self.dataset.variables[arg][:]
|
|
78
|
-
|
|
89
|
+
msg = f"Missing variable {args[0]} in the input file."
|
|
90
|
+
raise KeyError(msg)
|
|
79
91
|
|
|
80
92
|
def append_data(
|
|
81
93
|
self,
|
|
82
|
-
variable: netCDF4.Variable |
|
|
94
|
+
variable: netCDF4.Variable | npt.NDArray | float,
|
|
83
95
|
key: str,
|
|
84
96
|
name: str | None = None,
|
|
85
97
|
units: str | None = None,
|
|
86
|
-
|
|
98
|
+
dtype: str | None = None,
|
|
99
|
+
) -> None:
|
|
87
100
|
"""Adds new CloudnetVariable or RadarVariable into `data` attribute.
|
|
88
101
|
|
|
89
102
|
Args:
|
|
@@ -92,123 +105,85 @@ class DataSource:
|
|
|
92
105
|
attribute (dictionary).
|
|
93
106
|
name: CloudnetArray.name attribute. Default value is *key*.
|
|
94
107
|
units: CloudnetArray.units attribute.
|
|
108
|
+
dtype: CloudnetArray.data_type attribute.
|
|
95
109
|
|
|
96
110
|
"""
|
|
97
|
-
self.data[key] = CloudnetArray(variable, name or key, units)
|
|
111
|
+
self.data[key] = CloudnetArray(variable, name or key, units, data_type=dtype)
|
|
98
112
|
|
|
99
|
-
def get_date(self) ->
|
|
113
|
+
def get_date(self) -> datetime.date:
|
|
100
114
|
"""Returns date components.
|
|
101
115
|
|
|
102
116
|
Returns:
|
|
103
|
-
|
|
117
|
+
date object
|
|
104
118
|
|
|
105
119
|
Raises:
|
|
106
120
|
RuntimeError: Not found or invalid date.
|
|
107
121
|
|
|
108
122
|
"""
|
|
109
123
|
try:
|
|
110
|
-
year =
|
|
111
|
-
month =
|
|
112
|
-
day =
|
|
113
|
-
datetime.
|
|
124
|
+
year = int(self.dataset.year)
|
|
125
|
+
month = int(self.dataset.month)
|
|
126
|
+
day = int(self.dataset.day)
|
|
127
|
+
return datetime.date(year, month, day)
|
|
114
128
|
except (AttributeError, ValueError) as read_error:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
) from read_error
|
|
118
|
-
return [year, month, day]
|
|
129
|
+
msg = "Missing or invalid date in global attributes."
|
|
130
|
+
raise RuntimeError(msg) from read_error
|
|
119
131
|
|
|
120
132
|
def close(self) -> None:
|
|
121
133
|
"""Closes the open file."""
|
|
122
134
|
self.dataset.close()
|
|
123
135
|
|
|
124
136
|
@staticmethod
|
|
125
|
-
def
|
|
137
|
+
def to_m(var: netCDF4.Variable) -> npt.NDArray:
|
|
126
138
|
"""Converts km to m."""
|
|
127
139
|
alt = var[:]
|
|
128
140
|
if var.units == "km":
|
|
129
141
|
alt *= 1000
|
|
142
|
+
elif var.units not in ("m", "meters"):
|
|
143
|
+
msg = f"Unexpected unit: {var.units}"
|
|
144
|
+
raise ValueError(msg)
|
|
130
145
|
return alt
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
def m2km(var: netCDF4.Variable) -> np.ndarray:
|
|
134
|
-
"""Converts m to km."""
|
|
135
|
-
alt = var[:]
|
|
136
|
-
if var.units == "m":
|
|
137
|
-
alt /= 1000
|
|
138
|
-
return alt
|
|
139
|
-
|
|
140
|
-
def _init_time(self) -> np.ndarray:
|
|
147
|
+
def _init_time(self) -> npt.NDArray:
|
|
141
148
|
time = self.getvar("time")
|
|
142
149
|
if len(time) == 0:
|
|
143
|
-
|
|
150
|
+
msg = "Empty time vector"
|
|
151
|
+
raise ValidTimeStampError(msg)
|
|
144
152
|
if max(time) > 25:
|
|
145
|
-
logging.
|
|
153
|
+
logging.debug("Assuming time as seconds, converting to fraction hour")
|
|
146
154
|
time = utils.seconds2hours(time)
|
|
147
155
|
return time
|
|
148
156
|
|
|
149
157
|
def _init_altitude(self) -> float | None:
|
|
150
158
|
"""Returns altitude of the instrument (m)."""
|
|
151
159
|
if "altitude" in self.dataset.variables:
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
var = self.dataset.variables["altitude"]
|
|
161
|
+
if utils.is_all_masked(var[:]):
|
|
162
|
+
return None
|
|
163
|
+
altitude_above_sea = self.to_m(var)
|
|
164
|
+
return float(
|
|
165
|
+
altitude_above_sea
|
|
166
|
+
if utils.isscalar(altitude_above_sea)
|
|
167
|
+
else np.mean(altitude_above_sea),
|
|
168
|
+
)
|
|
154
169
|
return None
|
|
155
170
|
|
|
156
|
-
def _init_height(self) ->
|
|
171
|
+
def _init_height(self) -> npt.NDArray | None:
|
|
157
172
|
"""Returns height array above mean sea level (m)."""
|
|
158
173
|
if "height" in self.dataset.variables:
|
|
159
|
-
return self.
|
|
174
|
+
return self.to_m(self.dataset.variables["height"])
|
|
160
175
|
if "range" in self.dataset.variables and self.altitude is not None:
|
|
161
|
-
range_instrument = self.
|
|
176
|
+
range_instrument = self.to_m(self.dataset.variables["range"])
|
|
162
177
|
return np.array(range_instrument + self.altitude)
|
|
163
178
|
return None
|
|
164
179
|
|
|
165
|
-
def
|
|
166
|
-
"""Transforms netCDF4-variables into CloudnetArrays.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
keys: netCDF4-variables to be converted. The results
|
|
170
|
-
are saved in *self.data* dictionary with *fields*
|
|
171
|
-
strings as keys.
|
|
172
|
-
|
|
173
|
-
Notes:
|
|
174
|
-
The attributes of the variables are not copied. Just the data.
|
|
175
|
-
|
|
176
|
-
"""
|
|
177
|
-
for key in keys:
|
|
178
|
-
self.append_data(self.dataset.variables[key], key)
|
|
179
|
-
|
|
180
|
-
def _unknown_variable_to_cloudnet_array(
|
|
181
|
-
self,
|
|
182
|
-
possible_names: tuple,
|
|
183
|
-
key: str,
|
|
184
|
-
units: str | None = None,
|
|
185
|
-
ignore_mask: bool = False,
|
|
186
|
-
):
|
|
187
|
-
"""Transforms single netCDF4 variable into CloudnetArray.
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
possible_names: Tuple of strings containing the possible
|
|
191
|
-
names of the variable in the input NetCDF file.
|
|
192
|
-
key: Key for self.data dictionary and name-attribute
|
|
193
|
-
for the saved CloudnetArray object.
|
|
194
|
-
units: Units attribute for the CloudnetArray object.
|
|
195
|
-
ignore_mask: If true, always writes an ordinary numpy array.
|
|
196
|
-
|
|
197
|
-
Raises:
|
|
198
|
-
RuntimeError: No variable found.
|
|
199
|
-
|
|
200
|
-
"""
|
|
201
|
-
for name in possible_names:
|
|
202
|
-
if name in self.dataset.variables:
|
|
203
|
-
array = self.dataset.variables[name]
|
|
204
|
-
if ignore_mask is True:
|
|
205
|
-
array = np.array(array)
|
|
206
|
-
self.append_data(array, key, units=units)
|
|
207
|
-
return
|
|
208
|
-
raise RuntimeError("Missing variable in the input file.")
|
|
209
|
-
|
|
210
|
-
def __enter__(self):
|
|
180
|
+
def __enter__(self) -> Self:
|
|
211
181
|
return self
|
|
212
182
|
|
|
213
|
-
def __exit__(
|
|
183
|
+
def __exit__(
|
|
184
|
+
self,
|
|
185
|
+
exc_type: type[BaseException] | None,
|
|
186
|
+
exc_val: BaseException | None,
|
|
187
|
+
exc_tb: TracebackType | None,
|
|
188
|
+
) -> None:
|
|
214
189
|
self.close()
|
cloudnetpy/exceptions.py
CHANGED
|
@@ -1,38 +1,67 @@
|
|
|
1
|
-
class
|
|
1
|
+
class CloudnetException(Exception):
|
|
2
|
+
"""Base class for exceptions in this module."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InconsistentDataError(CloudnetException):
|
|
6
|
+
"""Internal exception class."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, msg: str) -> None:
|
|
9
|
+
super().__init__(msg)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DisdrometerDataError(CloudnetException):
|
|
13
|
+
"""Internal exception class."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, msg: str) -> None:
|
|
16
|
+
super().__init__(msg)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RadarDataError(CloudnetException):
|
|
20
|
+
"""Internal exception class."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, msg: str) -> None:
|
|
23
|
+
super().__init__(msg)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LidarDataError(CloudnetException):
|
|
27
|
+
"""Internal exception class."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, msg: str) -> None:
|
|
30
|
+
super().__init__(msg)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PlottingError(CloudnetException):
|
|
2
34
|
"""Internal exception class."""
|
|
3
35
|
|
|
4
|
-
def __init__(self, msg: str
|
|
5
|
-
|
|
6
|
-
super().__init__(self.message)
|
|
36
|
+
def __init__(self, msg: str) -> None:
|
|
37
|
+
super().__init__(msg)
|
|
7
38
|
|
|
8
39
|
|
|
9
|
-
class
|
|
40
|
+
class ModelDataError(CloudnetException):
|
|
10
41
|
"""Internal exception class."""
|
|
11
42
|
|
|
12
|
-
def __init__(
|
|
13
|
-
self
|
|
14
|
-
|
|
43
|
+
def __init__(
|
|
44
|
+
self, msg: str = "Invalid model file: not enough proper profiles"
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(msg)
|
|
15
47
|
|
|
16
48
|
|
|
17
|
-
class
|
|
49
|
+
class ValidTimeStampError(CloudnetException):
|
|
18
50
|
"""Internal exception class."""
|
|
19
51
|
|
|
20
|
-
def __init__(self, msg: str = ""):
|
|
21
|
-
|
|
22
|
-
super().__init__(self.message)
|
|
52
|
+
def __init__(self, msg: str = "No valid timestamps found") -> None:
|
|
53
|
+
super().__init__(msg)
|
|
23
54
|
|
|
24
55
|
|
|
25
|
-
class
|
|
56
|
+
class HatproDataError(CloudnetException):
|
|
26
57
|
"""Internal exception class."""
|
|
27
58
|
|
|
28
|
-
def __init__(self, msg: str = "Invalid
|
|
29
|
-
|
|
30
|
-
super().__init__(self.message)
|
|
59
|
+
def __init__(self, msg: str = "Invalid HATPRO file") -> None:
|
|
60
|
+
super().__init__(msg)
|
|
31
61
|
|
|
32
62
|
|
|
33
|
-
class
|
|
63
|
+
class InvalidSourceFileError(CloudnetException):
|
|
34
64
|
"""Internal exception class."""
|
|
35
65
|
|
|
36
|
-
def __init__(self, msg: str = "
|
|
37
|
-
|
|
38
|
-
super().__init__(self.message)
|
|
66
|
+
def __init__(self, msg: str = "Invalid source file") -> None:
|
|
67
|
+
super().__init__(msg)
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
from .basta import basta2nc
|
|
2
|
+
from .bowtie import bowtie2nc
|
|
2
3
|
from .ceilo import ceilo2nc
|
|
3
4
|
from .copernicus import copernicus2nc
|
|
4
5
|
from .disdrometer import parsivel2nc, thies2nc
|
|
6
|
+
from .fd12p import fd12p2nc
|
|
5
7
|
from .galileo import galileo2nc
|
|
6
8
|
from .hatpro import hatpro2l1c, hatpro2nc
|
|
9
|
+
from .instruments import Instrument
|
|
7
10
|
from .mira import mira2nc
|
|
11
|
+
from .mrr import mrr2nc
|
|
8
12
|
from .pollyxt import pollyxt2nc
|
|
9
13
|
from .radiometrics import radiometrics2nc
|
|
14
|
+
from .rain_e_h3 import rain_e_h32nc
|
|
10
15
|
from .rpg import rpg2nc
|
|
11
16
|
from .weather_station import ws2nc
|
cloudnetpy/instruments/basta.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"""Module for reading / converting BASTA radar data."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from os import PathLike
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
2
7
|
import numpy as np
|
|
3
8
|
|
|
4
9
|
from cloudnetpy import output
|
|
@@ -6,15 +11,16 @@ from cloudnetpy.exceptions import ValidTimeStampError
|
|
|
6
11
|
from cloudnetpy.instruments import instruments
|
|
7
12
|
from cloudnetpy.instruments.nc_radar import NcRadar
|
|
8
13
|
from cloudnetpy.metadata import MetaData
|
|
14
|
+
from cloudnetpy.utils import get_uuid
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
def basta2nc(
|
|
12
|
-
basta_file: str,
|
|
13
|
-
output_file: str,
|
|
18
|
+
basta_file: str | PathLike,
|
|
19
|
+
output_file: str | PathLike,
|
|
14
20
|
site_meta: dict,
|
|
15
|
-
uuid: str | None = None,
|
|
16
|
-
date: str | None = None,
|
|
17
|
-
) ->
|
|
21
|
+
uuid: str | UUID | None = None,
|
|
22
|
+
date: str | datetime.date | None = None,
|
|
23
|
+
) -> UUID:
|
|
18
24
|
"""Converts BASTA cloud radar data into Cloudnet Level 1b netCDF file.
|
|
19
25
|
|
|
20
26
|
This function converts daily BASTA file into a much smaller file that
|
|
@@ -41,6 +47,10 @@ def basta2nc(
|
|
|
41
47
|
>>> basta2nc('basta_file.nc', 'radar.nc', site_meta)
|
|
42
48
|
|
|
43
49
|
"""
|
|
50
|
+
if isinstance(date, str):
|
|
51
|
+
date = datetime.date.fromisoformat(date)
|
|
52
|
+
uuid = get_uuid(uuid)
|
|
53
|
+
|
|
44
54
|
keymap = {
|
|
45
55
|
"reflectivity": "Zh",
|
|
46
56
|
"velocity": "v",
|
|
@@ -54,15 +64,17 @@ def basta2nc(
|
|
|
54
64
|
if date is not None:
|
|
55
65
|
basta.validate_date(date)
|
|
56
66
|
basta.screen_data(keymap)
|
|
57
|
-
basta.add_time_and_range()
|
|
67
|
+
basta.add_time_and_range(time_dtype="f8")
|
|
58
68
|
basta.add_site_geolocation()
|
|
59
69
|
basta.add_zenith_angle()
|
|
60
70
|
basta.add_radar_specific_variables()
|
|
61
71
|
basta.add_height()
|
|
62
72
|
basta.sort_timestamps()
|
|
73
|
+
basta.remove_duplicate_timestamps()
|
|
74
|
+
basta.test_if_all_masked()
|
|
63
75
|
attributes = output.add_time_attribute(ATTRIBUTES, basta.date)
|
|
64
76
|
output.update_attributes(basta.data, attributes)
|
|
65
|
-
|
|
77
|
+
output.save_level1b(basta, output_file, uuid)
|
|
66
78
|
return uuid
|
|
67
79
|
|
|
68
80
|
|
|
@@ -75,9 +87,9 @@ class Basta(NcRadar):
|
|
|
75
87
|
|
|
76
88
|
"""
|
|
77
89
|
|
|
78
|
-
def __init__(self, full_path: str, site_meta: dict):
|
|
90
|
+
def __init__(self, full_path: str | PathLike, site_meta: dict) -> None:
|
|
79
91
|
super().__init__(full_path, site_meta)
|
|
80
|
-
self.date
|
|
92
|
+
self.date = self.get_date()
|
|
81
93
|
self.instrument = instruments.BASTA
|
|
82
94
|
|
|
83
95
|
def screen_data(self, keymap: dict) -> None:
|
|
@@ -87,12 +99,14 @@ class Basta(NcRadar):
|
|
|
87
99
|
if key in self.data and self.data[key].data.ndim == mask.ndim:
|
|
88
100
|
self.data[key].mask_indices(np.where(mask != 1))
|
|
89
101
|
|
|
90
|
-
def validate_date(self, expected_date:
|
|
102
|
+
def validate_date(self, expected_date: datetime.date) -> None:
|
|
91
103
|
"""Validates expected data."""
|
|
92
104
|
date_units = self.dataset.variables["time"].units
|
|
93
|
-
|
|
105
|
+
date_str = date_units.split()[2]
|
|
106
|
+
date = datetime.date.fromisoformat(date_str)
|
|
94
107
|
if expected_date != date:
|
|
95
|
-
|
|
108
|
+
msg = "Time units doesn't match expected date"
|
|
109
|
+
raise ValidTimeStampError(msg)
|
|
96
110
|
|
|
97
111
|
def add_zenith_angle(self) -> None:
|
|
98
112
|
elevation = self.getvar("elevation")
|
|
@@ -105,15 +119,18 @@ ATTRIBUTES = {
|
|
|
105
119
|
long_name="Radar pitch angle",
|
|
106
120
|
units="degree",
|
|
107
121
|
standard_name="platform_roll",
|
|
122
|
+
dimensions=("time",),
|
|
108
123
|
),
|
|
109
124
|
"radar_yaw": MetaData(
|
|
110
125
|
long_name="Radar yaw angle",
|
|
111
126
|
units="degree",
|
|
112
127
|
standard_name="platform_yaw",
|
|
128
|
+
dimensions=("time",),
|
|
113
129
|
),
|
|
114
130
|
"radar_roll": MetaData(
|
|
115
131
|
long_name="Radar roll angle",
|
|
116
132
|
units="degree",
|
|
117
133
|
standard_name="platform_roll",
|
|
134
|
+
dimensions=("time",),
|
|
118
135
|
),
|
|
119
136
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy import ma
|
|
7
|
+
|
|
8
|
+
from cloudnetpy import output
|
|
9
|
+
from cloudnetpy.constants import G_TO_KG, MM_H_TO_M_S
|
|
10
|
+
from cloudnetpy.exceptions import ValidTimeStampError
|
|
11
|
+
from cloudnetpy.instruments.instruments import FMCW94
|
|
12
|
+
from cloudnetpy.instruments.nc_radar import NcRadar
|
|
13
|
+
from cloudnetpy.instruments.rpg import RPG_ATTRIBUTES
|
|
14
|
+
from cloudnetpy.metadata import MetaData
|
|
15
|
+
from cloudnetpy.utils import bit_field_definition, get_uuid
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def bowtie2nc(
|
|
19
|
+
bowtie_file: str | PathLike,
|
|
20
|
+
output_file: str | PathLike,
|
|
21
|
+
site_meta: dict,
|
|
22
|
+
uuid: str | UUID | None = None,
|
|
23
|
+
date: str | datetime.date | None = None,
|
|
24
|
+
) -> UUID:
|
|
25
|
+
"""Converts data from 'BOW-TIE' campaign cloud radar on RV-Meteor into
|
|
26
|
+
Cloudnet Level 1b netCDF file.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
bowtie_file: Input filename.
|
|
30
|
+
output_file: Output filename.
|
|
31
|
+
site_meta: Dictionary containing information about the site. Required key
|
|
32
|
+
value pair is `name`. Optional are `latitude`, `longitude`, `altitude`.
|
|
33
|
+
uuid: Set specific UUID for the file.
|
|
34
|
+
date: Expected date as YYYY-MM-DD of all profiles in the file.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
UUID of the generated file.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValidTimeStampError: No valid timestamps found.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
keymap = {
|
|
44
|
+
"Zh": "Zh",
|
|
45
|
+
"v": "v",
|
|
46
|
+
"width": "width",
|
|
47
|
+
"ldr": "ldr",
|
|
48
|
+
"kurt": "kurtosis",
|
|
49
|
+
"Skew": "skewness",
|
|
50
|
+
"SNR": "SNR",
|
|
51
|
+
"time": "time",
|
|
52
|
+
"range": "range",
|
|
53
|
+
"lwp": "lwp",
|
|
54
|
+
"SurfRelHum": "relative_humidity",
|
|
55
|
+
"rain": "rainfall_rate",
|
|
56
|
+
"Nyquist_velocity": "nyquist_velocity",
|
|
57
|
+
"range_offsets": "chirp_start_indices",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if isinstance(date, str):
|
|
61
|
+
date = datetime.date.fromisoformat(date)
|
|
62
|
+
uuid = get_uuid(uuid)
|
|
63
|
+
|
|
64
|
+
with Bowtie(bowtie_file, site_meta) as bowtie:
|
|
65
|
+
bowtie.init_data(keymap)
|
|
66
|
+
bowtie.add_time_and_range()
|
|
67
|
+
if date is not None:
|
|
68
|
+
bowtie.check_date(date)
|
|
69
|
+
bowtie.add_radar_specific_variables()
|
|
70
|
+
bowtie.add_site_geolocation()
|
|
71
|
+
bowtie.add_height()
|
|
72
|
+
bowtie.convert_units()
|
|
73
|
+
bowtie.fix_chirp_start_indices()
|
|
74
|
+
bowtie.test_if_all_masked()
|
|
75
|
+
bowtie.add_correction_bits()
|
|
76
|
+
attributes = output.add_time_attribute(ATTRIBUTES, bowtie.date)
|
|
77
|
+
output.update_attributes(bowtie.data, attributes)
|
|
78
|
+
output.save_level1b(bowtie, output_file, uuid)
|
|
79
|
+
return uuid
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Bowtie(NcRadar):
|
|
83
|
+
def __init__(self, full_path: str | PathLike, site_meta: dict) -> None:
|
|
84
|
+
super().__init__(full_path, site_meta)
|
|
85
|
+
self.instrument = FMCW94
|
|
86
|
+
self.date = self.get_date()
|
|
87
|
+
|
|
88
|
+
def convert_units(self) -> None:
|
|
89
|
+
self.data["lwp"].data *= G_TO_KG
|
|
90
|
+
self.data["rainfall_rate"].data *= MM_H_TO_M_S
|
|
91
|
+
self.data["relative_humidity"].data /= 100
|
|
92
|
+
|
|
93
|
+
def fix_chirp_start_indices(self) -> None:
|
|
94
|
+
array = self.data["chirp_start_indices"].data
|
|
95
|
+
self.data["chirp_start_indices"].data = np.array(array, dtype=np.int32)
|
|
96
|
+
self.data["chirp_start_indices"].data_type = "int32"
|
|
97
|
+
|
|
98
|
+
def add_correction_bits(self) -> None:
|
|
99
|
+
bits = ma.ones(self.data["v"].data.shape, dtype=np.uint32)
|
|
100
|
+
bits.mask = self.data["v"].data.mask
|
|
101
|
+
self.append_data(bits, "correction_bits")
|
|
102
|
+
|
|
103
|
+
def check_date(self, date: datetime.date) -> None:
|
|
104
|
+
if self.date != date:
|
|
105
|
+
raise ValidTimeStampError
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
ATTRIBUTES = RPG_ATTRIBUTES | {
|
|
109
|
+
"v": MetaData(
|
|
110
|
+
long_name="Doppler velocity",
|
|
111
|
+
units="m s-1",
|
|
112
|
+
comment=(
|
|
113
|
+
"This parameter is the radial component of the velocity, with positive\n"
|
|
114
|
+
"velocities are away from the radar. It was corrected for the heave\n"
|
|
115
|
+
"motion of the ship. A rolling average over 3 time steps has been\n"
|
|
116
|
+
"applied to it."
|
|
117
|
+
),
|
|
118
|
+
dimensions=("time", "range"),
|
|
119
|
+
),
|
|
120
|
+
"correction_bits": MetaData(
|
|
121
|
+
long_name="Correction bits",
|
|
122
|
+
units="1",
|
|
123
|
+
definition=bit_field_definition({0: """Doppler velocity is dealiased."""}),
|
|
124
|
+
comment=(
|
|
125
|
+
"This parameter is a bit field that indicates which corrections have\n"
|
|
126
|
+
"been applied to radar measurements."
|
|
127
|
+
),
|
|
128
|
+
dimensions=("time", "range"),
|
|
129
|
+
),
|
|
130
|
+
"nyquist_velocity": MetaData(
|
|
131
|
+
long_name="Nyquist velocity",
|
|
132
|
+
units="m s-1",
|
|
133
|
+
dimensions=("time", "chirp_sequence"),
|
|
134
|
+
),
|
|
135
|
+
}
|