disdrodb 0.1.4__py3-none-any.whl → 0.1.5__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.
Files changed (85) hide show
  1. disdrodb/_version.py +2 -2
  2. disdrodb/api/create_directories.py +0 -2
  3. disdrodb/cli/disdrodb_create_summary.py +10 -0
  4. disdrodb/cli/disdrodb_create_summary_station.py +10 -0
  5. disdrodb/constants.py +1 -1
  6. disdrodb/etc/products/L1/global.yaml +1 -1
  7. disdrodb/etc/products/L2E/5MIN.yaml +1 -0
  8. disdrodb/etc/products/L2E/global.yaml +1 -1
  9. disdrodb/etc/products/L2M/GAMMA_GS_ND_MAE.yaml +6 -0
  10. disdrodb/etc/products/L2M/GAMMA_ML.yaml +1 -1
  11. disdrodb/etc/products/L2M/LOGNORMAL_GS_LOG_ND_MAE.yaml +6 -0
  12. disdrodb/etc/products/L2M/LOGNORMAL_GS_ND_MAE.yaml +6 -0
  13. disdrodb/etc/products/L2M/LOGNORMAL_ML.yaml +8 -0
  14. disdrodb/etc/products/L2M/global.yaml +11 -3
  15. disdrodb/l0/check_configs.py +49 -16
  16. disdrodb/l0/configs/LPM/l0a_encodings.yml +2 -2
  17. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +2 -2
  18. disdrodb/l0/configs/LPM/l0b_encodings.yml +2 -2
  19. disdrodb/l0/configs/LPM/raw_data_format.yml +2 -2
  20. disdrodb/l0/configs/PWS100/l0b_encodings.yml +1 -0
  21. disdrodb/l0/configs/SWS250/bins_diameter.yml +108 -0
  22. disdrodb/l0/configs/SWS250/bins_velocity.yml +83 -0
  23. disdrodb/l0/configs/SWS250/l0a_encodings.yml +18 -0
  24. disdrodb/l0/configs/SWS250/l0b_cf_attrs.yml +72 -0
  25. disdrodb/l0/configs/SWS250/l0b_encodings.yml +155 -0
  26. disdrodb/l0/configs/SWS250/raw_data_format.yml +148 -0
  27. disdrodb/l0/l0b_processing.py +70 -15
  28. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +1 -1
  29. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +2 -2
  30. disdrodb/l0/readers/LPM/BELGIUM/ULIEGE.py +256 -0
  31. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +2 -2
  32. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +2 -2
  33. disdrodb/l0/readers/LPM/GERMANY/DWD.py +491 -0
  34. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +2 -2
  35. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +2 -2
  36. disdrodb/l0/readers/LPM/KIT/CHWALA.py +2 -2
  37. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +107 -12
  38. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +3 -3
  39. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +2 -2
  40. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +5 -14
  41. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +5 -14
  42. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +117 -8
  43. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +10 -14
  44. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +10 -14
  45. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +8 -14
  46. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +382 -0
  47. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +4 -0
  48. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
  49. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +127 -0
  50. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +239 -0
  51. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +5 -11
  52. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +4 -17
  53. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +5 -14
  54. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +10 -13
  55. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +10 -13
  56. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PANGASA.py +232 -0
  57. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +6 -18
  58. disdrodb/l0/readers/PARSIVEL2/SPAIN/GRANADA.py +120 -0
  59. disdrodb/l0/readers/PARSIVEL2/USA/C3WE.py +7 -25
  60. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +321 -0
  61. disdrodb/l0/readers/SW250/BELGIUM/KMI.py +239 -0
  62. disdrodb/l1/beard_model.py +31 -129
  63. disdrodb/l1/fall_velocity.py +136 -83
  64. disdrodb/l1/filters.py +25 -28
  65. disdrodb/l1/processing.py +11 -13
  66. disdrodb/l1_env/routines.py +46 -17
  67. disdrodb/l2/empirical_dsd.py +6 -0
  68. disdrodb/l2/processing.py +2 -2
  69. disdrodb/metadata/geolocation.py +0 -2
  70. disdrodb/psd/fitting.py +16 -13
  71. disdrodb/routines/l2.py +35 -23
  72. disdrodb/routines/wrappers.py +5 -0
  73. disdrodb/scattering/axis_ratio.py +90 -84
  74. disdrodb/scattering/permittivity.py +6 -0
  75. disdrodb/summary/routines.py +38 -12
  76. disdrodb/utils/attrs.py +2 -0
  77. disdrodb/utils/encoding.py +5 -0
  78. disdrodb/utils/time.py +2 -2
  79. disdrodb/viz/plots.py +24 -1
  80. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/METADATA +2 -1
  81. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/RECORD +85 -65
  82. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/WHEEL +0 -0
  83. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/entry_points.txt +0 -0
  84. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/licenses/LICENSE +0 -0
  85. {disdrodb-0.1.4.dist-info → disdrodb-0.1.5.dist-info}/top_level.txt +0 -0
@@ -14,10 +14,14 @@
14
14
  # You should have received a copy of the GNU General Public License
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  # -----------------------------------------------------------------------------.
17
- """Theoretical models to estimate the drop fall velocity."""
18
-
19
-
17
+ """Theoretical models to estimate the raindrop fall velocity based on drop diameter in mm."""
20
18
  import numpy as np
19
+ import xarray as xr
20
+
21
+ from disdrodb.constants import DIAMETER_DIMENSION
22
+ from disdrodb.l0.l0b_processing import ensure_valid_geolocation
23
+ from disdrodb.l1_env.routines import load_env_dataset
24
+ from disdrodb.utils.warnings import suppress_warnings
21
25
 
22
26
 
23
27
  def get_fall_velocity_atlas_1973(diameter):
@@ -53,7 +57,7 @@ def get_fall_velocity_atlas_1973(diameter):
53
57
 
54
58
  """
55
59
  fall_velocity = 9.65 - 10.3 * np.exp(-0.6 * diameter) # clip to 0 !
56
- fall_velocity = np.clip(fall_velocity, 0, None)
60
+ fall_velocity = fall_velocity.clip(min=0, max=None)
57
61
  return fall_velocity
58
62
 
59
63
 
@@ -80,6 +84,7 @@ def get_fall_velocity_brandes_2002(diameter):
80
84
 
81
85
  """
82
86
  fall_velocity = -0.1021 + 4.932 * diameter - 0.9551 * diameter**2 + 0.07934 * diameter**3 - 0.002362 * diameter**4
87
+ fall_velocity = fall_velocity.clip(min=0, max=None)
83
88
  return fall_velocity
84
89
 
85
90
 
@@ -107,6 +112,7 @@ def get_fall_velocity_uplinger_1981(diameter):
107
112
  """
108
113
  # Valid between 0.1 and 7 mm
109
114
  fall_velocity = 4.874 * diameter * np.exp(-0.195 * diameter)
115
+ fall_velocity = fall_velocity.clip(min=0, max=None)
110
116
  return fall_velocity
111
117
 
112
118
 
@@ -133,6 +139,7 @@ def get_fall_velocity_van_dijk_2002(diameter):
133
139
 
134
140
  """
135
141
  fall_velocity = -0.254 + 5.03 * diameter - 0.912 * diameter**2 + 0.0561 * diameter**3
142
+ fall_velocity = fall_velocity.clip(min=0, max=None)
136
143
  return fall_velocity
137
144
 
138
145
 
@@ -147,9 +154,10 @@ def get_fall_velocity_beard_1976(diameter, ds_env):
147
154
  A dataset containing the following environmental variables:
148
155
  - 'altitude' : Altitude in meters (m).
149
156
  - 'latitude' : Latitude in degrees.
150
- - 'temperature' : Temperature in degrees Celsius (°C).
157
+ - 'temperature' : Temperature in degrees Kelvin (K).
151
158
  - 'relative_humidity' : Relative humidity in percentage (%).
152
159
  - 'sea_level_air_pressure' : Sea level air pressure in Pascals (Pa).
160
+ - 'air_pressure': Air pressure in Pascals (Pa).
153
161
  - 'lapse_rate' : Lapse rate in degrees Celsius per meter (°C/m).
154
162
 
155
163
  Returns
@@ -166,121 +174,171 @@ def get_fall_velocity_beard_1976(diameter, ds_env):
166
174
  latitude=ds_env["latitude"],
167
175
  temperature=ds_env["temperature"],
168
176
  relative_humidity=ds_env["relative_humidity"],
169
- # TODO: add air_pressure # TODO
177
+ air_pressure=ds_env.get("air_pressure", None),
170
178
  sea_level_air_pressure=ds_env["sea_level_air_pressure"],
171
179
  lapse_rate=ds_env["lapse_rate"],
172
180
  )
181
+ fall_velocity = fall_velocity.clip(min=0, max=None)
173
182
  return fall_velocity
174
183
 
175
184
 
176
- def ensure_valid_coordinates(ds, default_altitude=0, default_latitude=0, default_longitude=0):
177
- """Ensure dataset valid coordinates for altitude, latitude, and longitude.
185
+ RAINDROP_FALL_VELOCITY_MODELS = {
186
+ "Atlas1973": get_fall_velocity_atlas_1973,
187
+ "Beard1976": get_fall_velocity_beard_1976,
188
+ "Brandes2002": get_fall_velocity_brandes_2002,
189
+ "Uplinger1981": get_fall_velocity_uplinger_1981,
190
+ "VanDijk2002": get_fall_velocity_van_dijk_2002,
191
+ }
192
+
193
+
194
+ def available_raindrop_fall_velocity_models():
195
+ """Return a list of the available raindrop fall velocity models."""
196
+ return list(RAINDROP_FALL_VELOCITY_MODELS)
178
197
 
179
- Invalid values are np.nan and -9999.
198
+
199
+ def check_raindrop_fall_velocity_model(model):
200
+ """Check validity of the specified raindrop fall velocity model."""
201
+ available_models = available_raindrop_fall_velocity_models()
202
+ if model not in available_models:
203
+ raise ValueError(f"{model} is an invalid raindrop fall velocity model. Valid models: {available_models}.")
204
+ return model
205
+
206
+
207
+ def get_raindrop_fall_velocity_model(model):
208
+ """Return the specified raindrop fall velocity model.
180
209
 
181
210
  Parameters
182
211
  ----------
183
- ds : xarray.Dataset
184
- The dataset for which to ensure valid geolocation coordinates.
185
- default_altitude : float, optional
186
- The default value to use for invalid altitude values. Defaults to 0.
187
- default_latitude : float, optional
188
- The default value to use for invalid latitude values. Defaults to 0.
189
- default_longitude : float, optional
190
- The default value to use for invalid longitude values. Defaults to 0.
212
+ model : str
213
+ The model to use for calculating the rain drop fall velocity. Available models are:
214
+ 'Atlas1973', 'Beard1976', 'Brandes2002', 'Uplinger1981', 'VanDijk2002'.
191
215
 
192
216
  Returns
193
217
  -------
194
- xarray.Dataset
195
- The dataset with invalid coordinates replaced by default values.
218
+ callable
219
+ A function which compute the raindrop fall velocity model
220
+ given the rain drop diameter in mm.
196
221
 
222
+ Notes
223
+ -----
224
+ This function serves as a wrapper to various raindrop fall velocity models.
225
+ It returns the appropriate model based on the `model` parameter.
197
226
  """
198
- # TODO raise error if not present
199
- invalid_altitude = np.logical_or(np.isnan(ds["altitude"]), ds["altitude"] == -9999)
200
- ds["altitude"] = ds["altitude"].where(~invalid_altitude, default_altitude)
201
-
202
- invalid_lat = np.logical_or(np.isnan(ds["latitude"]), ds["latitude"] == -9999)
203
- ds["latitude"] = ds["latitude"].where(~invalid_lat, default_latitude)
227
+ model = check_raindrop_fall_velocity_model(model)
228
+ return RAINDROP_FALL_VELOCITY_MODELS[model]
204
229
 
205
- invalid_lon = np.logical_or(np.isnan(ds["longitude"]), ds["longitude"] == -9999)
206
- ds["longitude"] = ds["longitude"].where(~invalid_lon, default_longitude)
207
- return ds
208
230
 
209
-
210
- def get_raindrop_fall_velocity(diameter, method, ds_env=None):
231
+ def get_raindrop_fall_velocity(diameter, model, ds_env=None):
211
232
  """Calculate the fall velocity of raindrops based on their diameter.
212
233
 
213
234
  Parameters
214
235
  ----------
215
236
  diameter : array-like
216
237
  The diameter of the raindrops in millimeters.
217
- method : str
218
- The method to use for calculating the fall velocity. Must be one of the following:
238
+ model : str
239
+ The model to use for calculating the raindrop fall velocity. Must be one of the following:
219
240
  'Atlas1973', 'Beard1976', 'Brandes2002', 'Uplinger1981', 'VanDijk2002'.
220
241
  ds_env : xr.Dataset, optional
242
+ Only required if model is 'Beard1976'.
221
243
  A dataset containing the following environmental variables:
222
- - 'altitude' : Altitude in meters (m).
223
- - 'latitude' : Latitude in degrees.
224
- - 'temperature' : Temperature in degrees Celsius (°C).
244
+ - 'altitude' (m)
245
+ - 'latitude' (°)
246
+ - 'temperature' : Temperature in degrees Kelvin (K).
225
247
  - 'relative_humidity' : Relative humidity. A value between 0 and 1.
226
248
  - 'sea_level_air_pressure' : Sea level air pressure in Pascals (Pa).
227
249
  - 'lapse_rate' : Lapse rate in degrees Celsius per meter (°C/m).
228
- It is required for for the 'Beard1976' method.
250
+ If not specified, sensible default values are used.
229
251
 
230
252
  Returns
231
253
  -------
232
- fall_velocity : array-like
233
- The calculated fall velocities of the raindrops.
254
+ fall_velocity : xr.DataArray
255
+ The calculated raindrop fall velocities per diameter.
234
256
 
235
257
  Notes
236
258
  -----
237
- The 'Beard1976' method requires additional environmental parameters such as altitude and latitude.
238
- These parameters can be provided through the `ds_env` argument. If not provided, default values will be used.
259
+ The 'Beard1976' model requires additional environmental parameters.
260
+ These parameters can be provided through the `ds_env` argument.
261
+ If not provided, default values are be used.
262
+
263
+ For D < 0.12, Atlas1973 relationship results output V = 0 m/s !
264
+ For D < 0.05, VanDijk2002 relationship results output V = 0 m/s !
265
+ For D < 0.02, Brandes relationship results output V = 0 m/s !
266
+
239
267
  """
240
- # Input diameter in mm
241
- dict_methods = {
242
- "Atlas1973": get_fall_velocity_atlas_1973,
243
- "Beard1976": get_fall_velocity_beard_1976,
244
- "Brandes2002": get_fall_velocity_brandes_2002,
245
- "Uplinger1981": get_fall_velocity_uplinger_1981,
246
- "VanDijk2002": get_fall_velocity_van_dijk_2002,
247
- }
248
268
  # Check valid method
249
- available_methods = list(dict_methods)
250
- if method not in dict_methods:
251
- raise ValueError(f"{method} is an invalid fall velocity method. Valid methods: {available_methods}.")
252
- # Copy diameter
253
- diameter = diameter.copy()
254
- # Initialize ds_env if None
255
- # if ds_env is None:
256
- # ds_env = load_env_dataset(ds_env)
269
+ model = check_raindrop_fall_velocity_model(model)
257
270
 
258
- # TODO: wrapper for DISDRODB product !
271
+ # Copy diameter
272
+ if isinstance(diameter, xr.DataArray):
273
+ diameter = diameter.copy()
274
+ else:
275
+ diameter = np.atleast_1d(diameter)
276
+ diameter = xr.DataArray(diameter, dims=DIAMETER_DIMENSION, coords={DIAMETER_DIMENSION: diameter.copy()})
277
+
278
+ # Initialize ds_env if None and method == "Beard1976"
279
+ if model == "Beard1976":
280
+ if ds_env is None:
281
+ ds_env = load_env_dataset()
282
+
283
+ # Ensure valid altitude and geolocation
284
+ # - altitude required by Beard
285
+ # - latitude required for gravity
286
+ for coord in ["altitude", "latitude"]:
287
+ ds_env = ensure_valid_geolocation(ds_env, coord=coord, errors="raise")
259
288
 
260
- # Ensure valid altitude and geolocation (if missing set defaults)
261
- # - altitude required by Beard
262
- # - latitude required for gravity
263
- ds_env = ensure_valid_coordinates(ds_env)
264
289
  # Retrieve fall velocity
265
- func = dict_methods[method]
266
- fall_velocity = func(diameter, ds_env=ds_env) if method == "Beard1976" else func(diameter)
267
- return fall_velocity
290
+ func = get_raindrop_fall_velocity_model(model)
291
+ with suppress_warnings(): # e.g. when diameter = 0 for Beard1976
292
+ fall_velocity = func(diameter, ds_env=ds_env) if model == "Beard1976" else func(diameter)
293
+
294
+ # Set to NaN for diameter outside [0, 10)
295
+ fall_velocity = fall_velocity.where(diameter < 10).where(diameter > 0)
296
+ # Ensure fall velocity is > 0 to avoid division by zero
297
+ # - Some models, at small diameter, can return negative/zero fall velocity
298
+ fall_velocity = fall_velocity.where(fall_velocity > 0)
268
299
 
300
+ # Add attributes
301
+ fall_velocity.name = "fall_velocity"
302
+ fall_velocity.attrs["units"] = "m/s"
303
+ fall_velocity.attrs["model"] = model
304
+ return fall_velocity.squeeze()
269
305
 
270
- def get_dataset_fall_velocity(ds, method="Brandes2002"):
271
- """Compute the fall velocity and add it to the dataset.
306
+
307
+ def get_raindrop_fall_velocity_from_ds(ds, ds_env=None, model="Beard1976"):
308
+ """Compute the raindrop fall velocity.
272
309
 
273
310
  Parameters
274
311
  ----------
275
312
  ds : xarray.Dataset
276
- DISDRODB L0C dataset.
277
- method : str, optional
278
- Method to compute fall velocity. The default method is ``"Brandes2002"``.
313
+ DISDRODB dataset with the ``'diameter_bin_center'`` coordinate.
314
+ The ``'altitude'`` and ``'latitude'`` coordinates are used if ``model='Beard1976'``.
315
+ model : str, optional
316
+ Model to compute rain drop fall velocity.
317
+ The default model is ``"Beard1976"``.
318
+ ds_env : xr.Dataset, optional
319
+ Only required if model is 'Beard1976'.
320
+ A dataset containing the following environmental variables:
321
+ - 'temperature' : Temperature in degrees Kelvin (K).
322
+ - 'relative_humidity' : Relative humidity. A value between 0 and 1.
323
+ - 'sea_level_air_pressure' : Sea level air pressure in Pascals (Pa).
324
+ - 'lapse_rate' : Lapse rate in degrees Celsius per meter (°C/m).
325
+ If not specified, sensible default values are used.
279
326
 
280
327
  Returns
281
328
  -------
282
- xarray.Dataset
283
- DISDRODB L0C dataset with an additional variable 'fall_velocity'.
329
+ xarray.DataArray
330
+ Rain drop fall velocity DataArray.
331
+
332
+ Notes
333
+ -----
334
+ The 'Beard1976' model requires additional environmental parameters.
335
+ These parameters can be provided through the `ds_env` argument.
336
+ If not provided, default values are be used.
337
+
338
+ For D < 0.12, Atlas1973 relationship results output V = 0 m/s
339
+ For D < 0.05, VanDijk2002 relationship results output V = 0 m/s
340
+ For D < 0.02, Brandes relationship results output V = 0 m/s
341
+
284
342
  """
285
343
  from disdrodb.constants import DIAMETER_DIMENSION
286
344
  from disdrodb.l1_env.routines import load_env_dataset
@@ -289,18 +347,13 @@ def get_dataset_fall_velocity(ds, method="Brandes2002"):
289
347
  if DIAMETER_DIMENSION not in ds.dims:
290
348
  raise ValueError(f"Diameter dimension '{DIAMETER_DIMENSION}' not found in dataset dimensions.")
291
349
 
292
- # Retrieve diameter values (in mm)
293
- diameter_bin_center = ds["diameter_bin_center"]
294
-
295
- # Ensure valid altitude and geolocation (if missing set defaults)
296
- # TODO: MOBILE CASE !
297
- default_geolocation = {"altitude": 0, "latitude": 0, "longitude": 0}
298
- dataset_coords = {key: ds[key] for key in default_geolocation if key in ds}
299
- default_geolocation.update(dataset_coords)
300
- ds = ds.assign_coords(default_geolocation)
350
+ # Retrieve ENV dataset
351
+ # - It checks and includes default geolocation if missing
352
+ # - For mobile disdrometer, infill missing geolocation with backward and forward filling
353
+ if ds_env is None:
354
+ ds_env = load_env_dataset(ds)
301
355
 
302
- # TODO: deal with ENV dataset
303
- ds_env = load_env_dataset(ds)
356
+ # Compute raindrop fall velocity
357
+ fall_velocity = get_raindrop_fall_velocity(diameter=ds["diameter_bin_center"], model=model, ds_env=ds_env) # mn
304
358
 
305
- fall_velocity = get_raindrop_fall_velocity(diameter_bin_center, method=method, ds_env=ds_env)
306
359
  return fall_velocity
disdrodb/l1/filters.py CHANGED
@@ -57,17 +57,15 @@ def filter_diameter_bins(ds, minimum_diameter=None, maximum_diameter=None):
57
57
  ds["diameter_bin_upper"] > minimum_diameter,
58
58
  ds["diameter_bin_lower"] < maximum_diameter,
59
59
  )
60
-
61
- # Select bins with diameter values entirely inside the specified min/max values
62
- # valid_indices = np.logical_and(
63
- # ds["diameter_bin_lower"] >= minimum_diameter,
64
- # ds["diameter_bin_upper"] <= maximum_diameter,
65
- # )
66
60
  ds = ds.isel({DIAMETER_DIMENSION: valid_indices})
61
+
62
+ if ds.sizes[DIAMETER_DIMENSION] == 0:
63
+ msg = f"Filtering using {minimum_diameter=} removes all diameter bins."
64
+ raise ValueError(msg)
67
65
  return ds
68
66
 
69
67
 
70
- def filter_velocity_bins(ds, minimum_velocity=0, maximum_velocity=12):
68
+ def filter_velocity_bins(ds, minimum_velocity=None, maximum_velocity=None):
71
69
  """
72
70
  Filter the dataset to include only velocity bins within specified bounds.
73
71
 
@@ -77,10 +75,10 @@ def filter_velocity_bins(ds, minimum_velocity=0, maximum_velocity=12):
77
75
  The dataset containing velocity bin data.
78
76
  minimum_velocity : float, optional
79
77
  The minimum velocity to include in the filter, in meters per second.
80
- Defaults to 0 m/s.
78
+ Defaults to the minimum value in `ds["velocity_bin_lower"]`.
81
79
  maximum_velocity : float, optional
82
80
  The maximum velocity to include in the filter, in meters per second.
83
- Defaults to 12 m/s.
81
+ Defaults to the maximum value in `ds["velocity_bin_upper"]`.
84
82
 
85
83
  Returns
86
84
  -------
@@ -103,16 +101,14 @@ def filter_velocity_bins(ds, minimum_velocity=0, maximum_velocity=12):
103
101
  ds["velocity_bin_lower"] < maximum_velocity,
104
102
  )
105
103
 
106
- # Select bins with velocity values entirely inside the specified min/max values
107
- # valid_indices = np.logical_and(
108
- # ds["velocity_bin_lower"] >= minimum_velocity,
109
- # ds["velocity_bin_upper"] <= maximum_velocity,
110
- # )
111
104
  ds = ds.isel({VELOCITY_DIMENSION: valid_indices})
105
+ if ds.sizes[VELOCITY_DIMENSION] == 0:
106
+ msg = f"Filtering using {minimum_velocity=} removes all velocity bins."
107
+ raise ValueError(msg)
112
108
  return ds
113
109
 
114
110
 
115
- def define_spectrum_mask(
111
+ def define_raindrop_spectrum_mask(
116
112
  drop_number,
117
113
  fall_velocity,
118
114
  above_velocity_fraction=None,
@@ -130,29 +126,29 @@ def define_spectrum_mask(
130
126
  drop_number : xarray.DataArray
131
127
  Array of drop counts per diameter and velocity bins.
132
128
  fall_velocity : array-like
133
- The expected terminal fall velocities for drops of given sizes.
129
+ The expected terminal fall velocities for rain drops of given sizes.
134
130
  above_velocity_fraction : float, optional
135
- Fraction of terminal fall velocity above which drops are considered too fast.
131
+ Fraction of terminal fall velocity above which rain drops are considered too fast.
136
132
  Either specify ``above_velocity_fraction`` or ``above_velocity_tolerance``.
137
133
  above_velocity_tolerance : float, optional
138
- Absolute tolerance above which drops terminal fall velocities are considered too fast.
134
+ Absolute tolerance above which rain drops terminal fall velocities are considered too fast.
139
135
  Either specify ``above_velocity_fraction`` or ``above_velocity_tolerance``.
140
136
  below_velocity_fraction : float, optional
141
- Fraction of terminal fall velocity below which drops are considered too slow.
137
+ Fraction of terminal fall velocity below which rain drops are considered too slow.
142
138
  Either specify ``below_velocity_fraction`` or ``below_velocity_tolerance``.
143
139
  below_velocity_tolerance : float, optional
144
- Absolute tolerance below which drops terminal fall velocities are considered too slow.
140
+ Absolute tolerance below which rain drops terminal fall velocities are considered too slow.
145
141
  Either specify ``below_velocity_fraction`` or ``below_velocity_tolerance``.
146
142
  maintain_smallest : bool, optional
147
- If True, ensures that the small drops in the spectrum are retained in the mask.
148
- The smallest drops are characterized by ``small_diameter_threshold``
143
+ If True, ensures that the small rain drops in the spectrum are retained in the mask.
144
+ The smallest rain drops are characterized by ``small_diameter_threshold``
149
145
  and ``small_velocity_threshold`` arguments.
150
146
  Defaults to False.
151
147
  small_diameter_threshold : float, optional
152
- The diameter threshold to use for keeping the smallest drop.
148
+ The diameter threshold to use for keeping the smallest rain drop.
153
149
  Defaults to 1 mm.
154
150
  small_velocity_threshold : float, optional
155
- The fall velocity threshold to use for keeping the smallest drops.
151
+ The fall velocity threshold to use for keeping the smallest rain drops.
156
152
  Defaults to 2.5 m/s.
157
153
 
158
154
  Returns
@@ -178,6 +174,7 @@ def define_spectrum_mask(
178
174
  above_fall_velocity = fall_velocity + above_velocity_tolerance
179
175
  else:
180
176
  above_fall_velocity = np.inf
177
+
181
178
  if below_velocity_fraction is not None:
182
179
  below_fall_velocity = fall_velocity * (1 - below_velocity_fraction)
183
180
  elif below_velocity_tolerance is not None:
@@ -191,15 +188,15 @@ def define_spectrum_mask(
191
188
 
192
189
  # Define mask
193
190
  mask = np.logical_and(
194
- np.logical_or(velocity_lower >= below_fall_velocity, velocity_upper >= below_fall_velocity),
195
- np.logical_or(velocity_lower <= above_fall_velocity, velocity_upper <= above_fall_velocity),
191
+ velocity_upper > below_fall_velocity,
192
+ velocity_lower < above_fall_velocity,
196
193
  )
197
194
 
198
195
  # Maintant smallest drops
199
196
  if maintain_smallest_drops:
200
197
  mask_smallest = np.logical_and(
201
- drop_number["diameter_bin_upper"] < small_diameter_threshold,
202
- drop_number["velocity_bin_upper"] < small_velocity_threshold,
198
+ drop_number["diameter_bin_upper"] <= small_diameter_threshold,
199
+ drop_number["velocity_bin_upper"] <= small_velocity_threshold,
203
200
  )
204
201
  mask = np.logical_or(mask, mask_smallest)
205
202
 
disdrodb/l1/processing.py CHANGED
@@ -19,8 +19,8 @@
19
19
  import xarray as xr
20
20
 
21
21
  from disdrodb.constants import DIAMETER_DIMENSION, VELOCITY_DIMENSION
22
- from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity
23
- from disdrodb.l1.filters import define_spectrum_mask, filter_diameter_bins, filter_velocity_bins
22
+ from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity_from_ds
23
+ from disdrodb.l1.filters import define_raindrop_spectrum_mask, filter_diameter_bins, filter_velocity_bins
24
24
  from disdrodb.l1.resampling import add_sample_interval
25
25
  from disdrodb.l1_env.routines import load_env_dataset
26
26
  from disdrodb.l2.empirical_dsd import ( # TODO: maybe move out of L2
@@ -34,7 +34,7 @@ from disdrodb.utils.writer import finalize_product
34
34
  def generate_l1(
35
35
  ds,
36
36
  # Fall velocity option
37
- fall_velocity_method="Beard1976",
37
+ fall_velocity_model="Beard1976",
38
38
  # Diameter-Velocity Filtering Options
39
39
  minimum_diameter=0,
40
40
  maximum_diameter=10,
@@ -54,7 +54,7 @@ def generate_l1(
54
54
  ----------
55
55
  ds : xarray.Dataset
56
56
  DISDRODB L0C dataset.
57
- fall_velocity_method : str, optional
57
+ fall_velocity_model : str, optional
58
58
  Method to compute fall velocity.
59
59
  The default method is ``"Beard1976"``.
60
60
  minimum_diameter : float, optional
@@ -106,7 +106,9 @@ def generate_l1(
106
106
 
107
107
  # ---------------------------------------------------------------------------
108
108
  # Retrieve ENV dataset or take defaults
109
- # --> Used only for Beard fall velocity currently !
109
+ # - Used only for Beard fall velocity currently !
110
+ # - It checks and includes default geolocation if missing
111
+ # - For mobile disdrometer, infill missing geolocation with backward and forward filling
110
112
  ds_env = load_env_dataset(ds)
111
113
 
112
114
  # ---------------------------------------------------------------------------
@@ -120,7 +122,7 @@ def generate_l1(
120
122
  ds_l1 = add_sample_interval(ds_l1, sample_interval=sample_interval)
121
123
 
122
124
  # Add L0C coordinates that might got lost
123
- if "time_qc" in ds_l1:
125
+ if "time_qc" in ds:
124
126
  ds_l1 = ds_l1.assign_coords({"time_qc": ds["time_qc"]})
125
127
 
126
128
  # -------------------------------------------------------------------------------------------
@@ -128,7 +130,7 @@ def generate_l1(
128
130
  if sensor_name in ["PARSIVEL", "PARSIVEL2"]:
129
131
  # - Remove first two bins because never reports data !
130
132
  # - If not removed, can alter e.g. L2M model fitting
131
- ds_l1 = filter_diameter_bins(ds=ds_l1, minimum_diameter=0.312) # it includes the 0.2495-0.3745 bin
133
+ ds_l1 = filter_diameter_bins(ds=ds_l1, minimum_diameter=0.2495) # it includes the 0.2495-0.3745 bin
132
134
 
133
135
  # - Filter diameter bins
134
136
  ds_l1 = filter_diameter_bins(ds=ds_l1, minimum_diameter=minimum_diameter, maximum_diameter=maximum_diameter)
@@ -138,16 +140,12 @@ def generate_l1(
138
140
 
139
141
  # -------------------------------------------------------------------------------------------
140
142
  # Compute fall velocity
141
- ds_l1["fall_velocity"] = get_raindrop_fall_velocity(
142
- diameter=ds_l1["diameter_bin_center"],
143
- method=fall_velocity_method,
144
- ds_env=ds_env, # mm
145
- )
143
+ ds_l1["fall_velocity"] = get_raindrop_fall_velocity_from_ds(ds=ds_l1, ds_env=ds_env, model=fall_velocity_model)
146
144
 
147
145
  # -------------------------------------------------------------------------------------------
148
146
  # Define filtering mask according to fall velocity
149
147
  if has_velocity_dimension:
150
- mask = define_spectrum_mask(
148
+ mask = define_raindrop_spectrum_mask(
151
149
  drop_number=ds_l1["raw_drop_number"],
152
150
  fall_velocity=ds_l1["fall_velocity"],
153
151
  above_velocity_fraction=above_velocity_fraction,
@@ -15,39 +15,68 @@
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  # -----------------------------------------------------------------------------.
17
17
  """Core functions for DISDRODB ENV production."""
18
+ import numpy as np
18
19
  import xarray as xr
19
20
 
20
21
  from disdrodb.constants import GEOLOCATION_COORDS
22
+ from disdrodb.l0.l0b_processing import ensure_valid_geolocation
23
+ from disdrodb.utils.logger import log_warning
24
+
25
+ DEFAULT_GEOLOCATION = {
26
+ "latitude": 46.159346,
27
+ "longitude": 8.774586,
28
+ "altitude": 0,
29
+ }
21
30
 
22
31
 
23
32
  def get_default_environment_dataset():
24
33
  """Define defaults values for the ENV dataset."""
25
34
  ds_env = xr.Dataset()
26
- ds_env["sea_level_air_pressure"] = 101_325
27
- ds_env["gas_constant_dry_air"] = 287.04
28
- ds_env["lapse_rate"] = 0.0065
29
- ds_env["relative_humidity"] = 0.95 # Value between 0 and 1 !
30
- ds_env["temperature"] = 20 + 273.15
35
+ ds_env["sea_level_air_pressure"] = 101_325 # Pa
36
+ ds_env["gas_constant_dry_air"] = 287.04 # J kg⁻¹ K⁻¹
37
+ ds_env["lapse_rate"] = 0.0065 # K m⁻¹
38
+ ds_env["relative_humidity"] = 0.95 # 0-1 !
39
+ ds_env["temperature"] = 20 + 273.15 # K
40
+ ds_env["water_density"] = 1000 # kg m⁻³ (T == 10 --> 999.7, T == 20 --> 998.2)
41
+ # get_water_density(temperature=temperature, air_pressure=air_pressure
31
42
  return ds_env
32
43
 
33
44
 
34
- def _assign_geolocation(ds_src, dst_dst):
45
+ def _assign_geolocation(ds_src, dst_dst, logger=None):
46
+ dict_coords = {}
47
+ for coord in GEOLOCATION_COORDS:
48
+ if coord in ds_src:
49
+ # Check geolocation validity
50
+ ds_src = ensure_valid_geolocation(ds_src, coord=coord, errors="coerce")
51
+ # Assign valid geolocation (or default one if invalid)
52
+ if "time" not in ds_src[coord].dims:
53
+ dict_coords[coord] = ds_src[coord] if not np.isnan(ds_src[coord]) else DEFAULT_GEOLOCATION[coord]
54
+ else: # If coordinates varies over time, infill NaN over time with forward and backward filling
55
+ dict_coords[coord] = ds_src[coord].ffill(dim="time").bfill(dim="time")
56
+ else:
57
+ dict_coords[coord] = DEFAULT_GEOLOCATION[coord]
58
+ log_warning(
59
+ logger=logger,
60
+ msg=f"{coord} not available. Setting {coord}={DEFAULT_GEOLOCATION[coord]}",
61
+ verbose=False,
62
+ )
35
63
 
36
- dict_coords = {coord: ds_src[coord] for coord in GEOLOCATION_COORDS if coord in ds_src}
64
+ # Assign geolocation
37
65
  dst_dst = dst_dst.assign_coords(dict_coords)
38
66
  return dst_dst
39
67
 
40
68
 
41
- def load_env_dataset(ds):
69
+ def load_env_dataset(ds=None, logger=None):
42
70
  """Load the ENV dataset."""
43
- # TODO: Retrieve relative_humidity and temperature from L1-ENV
71
+ # TODO: Retrieve relative_humidity, lapse_rate and temperature from DISDRODB-ENV product
72
+
73
+ # Load default environment dataset
44
74
  ds_env = get_default_environment_dataset()
45
- # Compute water density
46
- # get_water_density(
47
- # temperature=temperature,
48
- # air_pressure=air_pressure,
49
- # )
50
- # --> (T == 10 --> 999.7, T == 20 --> 998.2
51
- ds_env["water_density"] = 1000 # kg / m3 # TODO as function of ENV (temperature, ...) ?
52
- ds_env = _assign_geolocation(ds_src=ds, dst_dst=ds_env)
75
+
76
+ # Assign geolocation if input dataset provided
77
+ if ds is not None:
78
+ ds_env = _assign_geolocation(ds_src=ds, dst_dst=ds_env, logger=logger)
79
+ # Otherwise add default geolocation
80
+ else:
81
+ ds_env = ds_env.assign_coords(DEFAULT_GEOLOCATION)
53
82
  return ds_env
@@ -236,6 +236,12 @@ def get_effective_sampling_area(sensor_name, diameter):
236
236
  if sensor_name == "RD80":
237
237
  sampling_area = 0.005 # m2
238
238
  return sampling_area
239
+ if sensor_name == "SWS250": # TODO: L * (B - diameter / 2) ?
240
+ # Table 29 of the manual that the sample volume is 400cm3, path length?
241
+ # Distance between the end of the hood heaters is 291 mm.
242
+ # Adding a factor of 1.5 for better representation of the Tx-Rx distance: L= 436 mm.
243
+ sampling_area = 0.0091 # m2
244
+ return sampling_area
239
245
  raise NotImplementedError(f"Effective sampling area for {sensor_name} must yet to be specified in the software.")
240
246
 
241
247
 
disdrodb/l2/processing.py CHANGED
@@ -441,7 +441,7 @@ def generate_l2m(
441
441
  diameter_spacing=0.05,
442
442
  # Processing options
443
443
  ds_env=None,
444
- fall_velocity_method="Beard1976",
444
+ fall_velocity_model="Beard1976",
445
445
  # Filtering options
446
446
  minimum_ndrops=1,
447
447
  minimum_nbins=3,
@@ -548,7 +548,7 @@ def generate_l2m(
548
548
  drop_number_concentration = psd(diameter)
549
549
 
550
550
  # Retrieve fall velocity for each new diameter bin
551
- velocity = get_raindrop_fall_velocity(diameter=diameter, method=fall_velocity_method, ds_env=ds_env) # mm
551
+ velocity = get_raindrop_fall_velocity(diameter=diameter, model=fall_velocity_model, ds_env=ds_env) # mm
552
552
 
553
553
  # Compute integral parameters
554
554
  ds_params = compute_integral_parameters(
@@ -60,8 +60,6 @@ def infer_altitude(latitude, longitude, dem="aster30m"):
60
60
  ----------
61
61
  https://www.opentopodata.org/api/
62
62
  """
63
- import requests
64
-
65
63
  url = f"https://api.opentopodata.org/v1/{dem}?locations={latitude},{longitude}"
66
64
  r = requests.get(url)
67
65