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
@@ -0,0 +1,72 @@
1
+ precipitation_rate:
2
+ description: Rainfall rate
3
+ long_name: Precipitation rate
4
+ units: mm/h
5
+ precipitation_accumulated:
6
+ description: Accumulated rain amount over the measurement interval
7
+ long_name: Precipitation accumulated
8
+ units: mm
9
+ weather_code_synop_4680:
10
+ description: SYNOP weather code according to table 4680 of Parsivel documentation
11
+ long_name: Weather code SYNOP 4680
12
+ units: ""
13
+ weather_code_metar_4678:
14
+ description: METAR/SPECI weather code according to table 4678 of Parsivel documentation
15
+ long_name: Weather code METAR 4678
16
+ units: ""
17
+ past_weather1:
18
+ description: Past weather code 1
19
+ long_name: Past weather 1
20
+ units: ""
21
+ past_weather2:
22
+ description: Past weather code 2
23
+ long_name: Past weather 2
24
+ units: ""
25
+ mor_visibility_5min:
26
+ description: Meteorological Optical Range in precipitation (5 minute average)
27
+ long_name: MOR visibility 5 minute
28
+ units: m
29
+ mor_visibility:
30
+ description: Meteorological Optical Range in precipitation
31
+ long_name: MOR visibility
32
+ units: m
33
+ number_particles:
34
+ description: Number of particles detected and validated
35
+ long_name: Number of detected particles
36
+ units: ""
37
+ sensor_temperature:
38
+ description: Temperature in sensor housing
39
+ long_name: Temperature of the sensor
40
+ units: "degC"
41
+ obstruction_status:
42
+ description: Obstruction status of the sensor
43
+ long_name: Obstruction status
44
+ units: ""
45
+ total_extinction_coefficient:
46
+ description: Total extinction coefficient
47
+ long_name: Total extinction coefficient
48
+ units: "1/km"
49
+ transmissometer_extinction_coefficient:
50
+ description: Transmissometer extinction coefficient
51
+ long_name: Transmissometer extinction coefficient
52
+ units: "1/km"
53
+ back_scatter_extinction_coefficient:
54
+ description: Back scatter extinction coefficient
55
+ long_name: Back scatter extinction coefficient
56
+ units: "1/km"
57
+ ambient_light_sensor_signal:
58
+ description: Ambient light sensor signal
59
+ long_name: Ambient light sensor signal
60
+ units: "cd/m2"
61
+ sensor_status:
62
+ description: Sensor status
63
+ long_name: Sensor status
64
+ units: ""
65
+ ambient_light_sensor_signal_status:
66
+ description: Ambient light sensor signal status
67
+ long_name: Ambient light sensor signal status
68
+ units: ""
69
+ raw_drop_number:
70
+ description: Drop counts per diameter and velocity class
71
+ long_name: Raw drop number
72
+ units: ""
@@ -0,0 +1,155 @@
1
+ precipitation_rate:
2
+ dtype: float32
3
+ zlib: true
4
+ complevel: 3
5
+ shuffle: true
6
+ fletcher32: false
7
+ contiguous: false
8
+ chunksizes: 5000
9
+ precipitation_accumulated:
10
+ dtype: float32
11
+ zlib: true
12
+ complevel: 3
13
+ shuffle: true
14
+ fletcher32: false
15
+ contiguous: false
16
+ chunksizes: 5000
17
+ weather_code_synop_4680:
18
+ dtype: uint8
19
+ zlib: true
20
+ complevel: 3
21
+ shuffle: true
22
+ fletcher32: false
23
+ contiguous: false
24
+ chunksizes: 5000
25
+ _FillValue: 255
26
+ weather_code_metar_4678:
27
+ dtype: str
28
+ zlib: false
29
+ complevel: 3
30
+ shuffle: true
31
+ fletcher32: false
32
+ contiguous: false
33
+ chunksizes: 5000
34
+ past_weather1:
35
+ dtype: str
36
+ zlib: false
37
+ complevel: 3
38
+ shuffle: true
39
+ fletcher32: false
40
+ contiguous: false
41
+ chunksizes: 5000
42
+ past_weather2:
43
+ dtype: str
44
+ zlib: false
45
+ complevel: 3
46
+ shuffle: true
47
+ fletcher32: false
48
+ contiguous: false
49
+ chunksizes: 5000
50
+ mor_visibility_5min:
51
+ dtype: uint16
52
+ zlib: true
53
+ complevel: 3
54
+ shuffle: true
55
+ fletcher32: false
56
+ contiguous: false
57
+ chunksizes: 5000
58
+ _FillValue: 65535
59
+ mor_visibility:
60
+ dtype: uint16
61
+ zlib: true
62
+ complevel: 3
63
+ shuffle: true
64
+ fletcher32: false
65
+ contiguous: false
66
+ chunksizes: 5000
67
+ _FillValue: 65535
68
+ number_particles:
69
+ dtype: uint16
70
+ zlib: true
71
+ complevel: 3
72
+ shuffle: true
73
+ fletcher32: false
74
+ contiguous: false
75
+ chunksizes: 5000
76
+ _FillValue: 65535
77
+ sensor_temperature:
78
+ dtype: uint16
79
+ scale_factor: 0.1
80
+ add_offset: -60.0
81
+ zlib: true
82
+ complevel: 3
83
+ shuffle: true
84
+ fletcher32: false
85
+ contiguous: false
86
+ chunksizes: 5000
87
+ _FillValue: 65535
88
+ obstruction_status:
89
+ dtype: str
90
+ zlib: false
91
+ complevel: 3
92
+ shuffle: true
93
+ fletcher32: false
94
+ contiguous: false
95
+ chunksizes: 5000
96
+ sensor_status:
97
+ dtype: str
98
+ zlib: false
99
+ complevel: 3
100
+ shuffle: true
101
+ fletcher32: false
102
+ contiguous: false
103
+ chunksizes: 5000
104
+ ambient_light_sensor_signal_status:
105
+ dtype: str
106
+ zlib: false
107
+ complevel: 3
108
+ shuffle: true
109
+ fletcher32: false
110
+ contiguous: false
111
+ chunksizes: 5000
112
+ total_extinction_coefficient:
113
+ dtype: float32
114
+ zlib: true
115
+ complevel: 3
116
+ shuffle: true
117
+ fletcher32: false
118
+ contiguous: false
119
+ chunksizes: 5000
120
+ transmissometer_extinction_coefficient:
121
+ dtype: float32
122
+ zlib: true
123
+ complevel: 3
124
+ shuffle: true
125
+ fletcher32: false
126
+ contiguous: false
127
+ chunksizes: 5000
128
+ back_scatter_extinction_coefficient:
129
+ dtype: float32
130
+ zlib: true
131
+ complevel: 3
132
+ shuffle: true
133
+ fletcher32: false
134
+ contiguous: false
135
+ chunksizes: 5000
136
+ ambient_light_sensor_signal:
137
+ dtype: float32
138
+ zlib: true
139
+ complevel: 3
140
+ shuffle: true
141
+ fletcher32: false
142
+ contiguous: false
143
+ chunksizes: 5000
144
+ raw_drop_number:
145
+ dtype: uint16
146
+ zlib: true
147
+ complevel: 3
148
+ shuffle: true
149
+ fletcher32: false
150
+ contiguous: false
151
+ _FillValue: 65535
152
+ chunksizes:
153
+ - 5000
154
+ - 16
155
+ - 21
@@ -0,0 +1,148 @@
1
+ precipitation_rate:
2
+ n_digits: 6
3
+ n_characters: 7
4
+ n_decimals: 3
5
+ n_naturals: 3
6
+ data_range:
7
+ - 0
8
+ - 9999.999
9
+ nan_flags: null
10
+ precipitation_accumulated:
11
+ n_digits: 6
12
+ n_characters: 7
13
+ n_decimals: 2
14
+ n_naturals: 4
15
+ data_range:
16
+ - 0
17
+ - 9999.0
18
+ nan_flags: null
19
+ weather_code_synop_4680:
20
+ n_digits: 2
21
+ n_characters: 2
22
+ n_decimals: 0
23
+ n_naturals: 2
24
+ data_range:
25
+ - 0
26
+ - 89
27
+ nan_flags: null
28
+ weather_code_metar_4678:
29
+ n_digits: null
30
+ n_characters: null
31
+ n_decimals: null
32
+ n_naturals: null
33
+ data_range: null
34
+ nan_flags: null
35
+ past_weather1:
36
+ n_digits: null
37
+ n_characters: null
38
+ n_decimals: null
39
+ n_naturals: null
40
+ data_range: null
41
+ nan_flags: null
42
+ past_weather2:
43
+ n_digits: null
44
+ n_characters: null
45
+ n_decimals: null
46
+ n_naturals: null
47
+ data_range: null
48
+ nan_flags: null
49
+ mor_visibility_5min:
50
+ n_digits: null
51
+ n_characters: null
52
+ n_decimals: null
53
+ n_naturals: null
54
+ data_range: null
55
+ nan_flags: null
56
+ mor_visibility:
57
+ n_digits: null
58
+ n_characters: null
59
+ n_decimals: null
60
+ n_naturals: null
61
+ data_range: null
62
+ nan_flags: null
63
+ number_particles:
64
+ n_digits: 4
65
+ n_characters: 4
66
+ n_decimals: 0
67
+ n_naturals: 4
68
+ data_range:
69
+ - 0
70
+ - 9999
71
+ nan_flags: null
72
+ sensor_temperature:
73
+ n_digits: 4
74
+ n_characters: 6
75
+ n_decimals: 1
76
+ n_naturals: 3
77
+ data_range:
78
+ - -99
79
+ - 100
80
+ nan_flags: null
81
+ obstruction_status:
82
+ n_digits: null
83
+ n_characters: null
84
+ n_decimals: null
85
+ n_naturals: null
86
+ data_range: null
87
+ nan_flags: null
88
+ total_extinction_coefficient:
89
+ n_digits: 5
90
+ n_characters: 6
91
+ n_decimals: 2
92
+ n_naturals: 3
93
+ data_range:
94
+ - 0
95
+ - 999.99
96
+ nan_flags: null
97
+ transmissometer_extinction_coefficient:
98
+ n_digits: 5
99
+ n_characters: 6
100
+ n_decimals: 2
101
+ n_naturals: 3
102
+ data_range:
103
+ - 0
104
+ - 999.99
105
+ nan_flags: null
106
+ back_scatter_extinction_coefficient:
107
+ n_digits: 5
108
+ n_characters: 7
109
+ n_decimals: 2
110
+ n_naturals: 3
111
+ data_range:
112
+ - -999.99
113
+ - 999.99
114
+ nan_flags: null
115
+ ambient_light_sensor_signal:
116
+ n_digits: 5
117
+ n_characters: 5
118
+ n_decimals: 0
119
+ n_naturals: 5
120
+ data_range:
121
+ - 0
122
+ - 99998
123
+ nan_flags: 99999
124
+ sensor_status:
125
+ n_digits: null
126
+ n_characters: null
127
+ n_decimals: null
128
+ n_naturals: null
129
+ data_range: null
130
+ nan_flags: null
131
+ ambient_light_sensor_signal_status:
132
+ n_digits: null
133
+ n_characters: null
134
+ n_decimals: null
135
+ n_naturals: null
136
+ data_range: null
137
+ nan_flags: null
138
+ raw_drop_number:
139
+ n_digits: 0
140
+ n_characters: 4096
141
+ n_decimals: 0
142
+ n_naturals: 0
143
+ data_range: null
144
+ nan_flags: null
145
+ dimension_order:
146
+ - velocity_bin_center
147
+ - diameter_bin_center
148
+ n_values: 336
@@ -80,15 +80,16 @@ def infer_split_str(string: str) -> str:
80
80
  return split_str
81
81
 
82
82
 
83
- def _replace_empty_strings_with_zeros(values):
83
+ def replace_empty_strings_with_zeros(values):
84
+ """Replace empty comma separated strings with '0'."""
84
85
  values[np.char.str_len(values) == 0] = "0"
85
86
  return values
86
87
 
87
88
 
88
- def _format_string_array(string: str, n_values: int) -> np.array:
89
+ def format_string_array(string: str, n_values: int) -> np.array:
89
90
  """Split a string with multiple numbers separated by a delimiter into an 1D array.
90
91
 
91
- e.g. : _format_string_array("2,44,22,33", 4) will return [ 2. 44. 22. 33.]
92
+ e.g. : format_string_array("2,44,22,33", 4) will return [ 2. 44. 22. 33.]
92
93
 
93
94
  If empty string ("") --> Return an arrays of zeros
94
95
  If the list length is not n_values -> Return an arrays of np.nan
@@ -126,7 +127,7 @@ def _format_string_array(string: str, n_values: int) -> np.array:
126
127
  # Ensure string type
127
128
  values = values.astype("str")
128
129
  # Replace '' with 0
129
- values = _replace_empty_strings_with_zeros(values)
130
+ values = replace_empty_strings_with_zeros(values)
130
131
  # Replace "-9.999" with 0
131
132
  values = np.char.replace(values, "-9.999", "0")
132
133
  # Cast values to float type
@@ -135,7 +136,7 @@ def _format_string_array(string: str, n_values: int) -> np.array:
135
136
  return values
136
137
 
137
138
 
138
- def _reshape_raw_spectrum(
139
+ def reshape_raw_spectrum(
139
140
  arr: np.array,
140
141
  dims_order: list,
141
142
  dims_size_dict: dict,
@@ -243,17 +244,17 @@ def retrieve_l0b_arrays(
243
244
  # Ensure is a string, get a numpy array for each row and then stack
244
245
  # - Option 1: Clear but lot of copies
245
246
  # df_series = df[key].astype(str)
246
- # list_arr = df_series.apply(_format_string_array, n_values=n_values)
247
+ # list_arr = df_series.apply(format_string_array, n_values=n_values)
247
248
  # arr = np.stack(list_arr, axis=0)
248
249
 
249
250
  # - Option 2: still copies
250
- # arr = np.vstack(_format_string_array(s, n_values=n_values) for s in df_series.astype(str))
251
+ # arr = np.vstack(format_string_array(s, n_values=n_values) for s in df_series.astype(str))
251
252
 
252
253
  # - Option 3: more memory efficient
253
254
  n_timesteps = len(df[key])
254
255
  arr = np.empty((n_timesteps, n_values), dtype=float) # preallocates
255
256
  for i, s in enumerate(df[key].astype(str)):
256
- arr[i, :] = _format_string_array(s, n_values=n_values)
257
+ arr[i, :] = format_string_array(s, n_values=n_values)
257
258
 
258
259
  # Retrieve dimensions
259
260
  dims_order = dims_order_dict[key]
@@ -263,7 +264,7 @@ def retrieve_l0b_arrays(
263
264
  # - This applies i.e for PARSIVEL*, LPM, PWS100
264
265
  # - This does not apply to RD80
265
266
  if key == "raw_drop_number" and len(dims_order) == 2:
266
- arr, dims = _reshape_raw_spectrum(
267
+ arr, dims = reshape_raw_spectrum(
267
268
  arr=arr,
268
269
  dims_order=dims_order,
269
270
  dims_size_dict=dims_size_dict,
@@ -288,7 +289,57 @@ def retrieve_l0b_arrays(
288
289
  #### L0B Coords and attributes
289
290
 
290
291
 
291
- def _convert_object_variables_to_string(ds: xr.Dataset) -> xr.Dataset:
292
+ def ensure_valid_geolocation(ds: xr.Dataset, coord: str, errors: str = "ignore") -> xr.Dataset:
293
+ """Ensure valid geolocation coordinates.
294
+
295
+ 'altitude' must be >= 0, 'latitude' must be within [-90, 90] and
296
+ 'longitude' within [-180, 180].
297
+
298
+ It can deal with coordinates varying with time.
299
+
300
+ Parameters
301
+ ----------
302
+ ds : xarray.Dataset
303
+ Dataset containing the coordinate.
304
+ coord : str
305
+ Name of the coordinate variable to validate.
306
+ errors : {"ignore", "raise", "coerce"}, default "ignore"
307
+ - "ignore": nothing is done.
308
+ - "raise" : raise ValueError if invalid values are found.
309
+ - "coerce": out-of-range values are replaced with NaN.
310
+
311
+ Returns
312
+ -------
313
+ xr.Dataset
314
+ Dataset with validated coordinate values.
315
+ """
316
+ # Define coordinates ranges
317
+ ranges = {
318
+ "altitude": (0, np.inf),
319
+ "latitude": (-90, 90),
320
+ "longitude": (-180, 180), # used only for "raise"/"coerce"
321
+ }
322
+
323
+ # Check coordinate is available and correctly defined.
324
+ if coord not in ds:
325
+ raise ValueError(f"Coordinate '{coord}' not found in dataset.")
326
+ if coord not in list(ranges):
327
+ raise ValueError(f"Valid geolocation coordinates are: {list(ranges)}.")
328
+
329
+ # Validate coordinate
330
+ vmin, vmax = ranges[coord]
331
+ invalid = (ds[coord] < vmin) | (ds[coord] > vmax)
332
+ invalid = invalid.compute()
333
+
334
+ # Deal within invalid errors
335
+ if errors == "raise" and invalid.any():
336
+ raise ValueError(f"{coord} out of range {vmin}-{vmax}.")
337
+ if errors == "coerce":
338
+ ds[coord] = ds[coord].where(~invalid)
339
+ return ds
340
+
341
+
342
+ def convert_object_variables_to_string(ds: xr.Dataset) -> xr.Dataset:
292
343
  """Convert variables with ``object`` dtype to ``string``.
293
344
 
294
345
  Parameters
@@ -307,7 +358,7 @@ def _convert_object_variables_to_string(ds: xr.Dataset) -> xr.Dataset:
307
358
  return ds
308
359
 
309
360
 
310
- def _set_variable_attributes(ds: xr.Dataset, sensor_name: str) -> xr.Dataset:
361
+ def set_variable_attributes(ds: xr.Dataset, sensor_name: str) -> xr.Dataset:
311
362
  """Set attributes to each ``xr.Dataset`` variable.
312
363
 
313
364
  Parameters
@@ -353,7 +404,7 @@ def add_dataset_crs_coords(ds):
353
404
 
354
405
 
355
406
  def _define_dataset_variables(df, sensor_name, logger=None, verbose=False):
356
- """Define DISDRODB L0B netCDF variables."""
407
+ """Define DISDRODB L0B netCDF array variables."""
357
408
  # Preprocess raw_spectrum, diameter and velocity arrays if available
358
409
  raw_fields = ["raw_drop_concentration", "raw_drop_average_velocity", "raw_drop_number"]
359
410
  if np.any(np.isin(raw_fields, df.columns)):
@@ -436,7 +487,7 @@ def set_geolocation_coordinates(ds, metadata):
436
487
  # If coordinate not present, add it from dictionary
437
488
  if coord not in ds:
438
489
  ds = ds.assign_coords({coord: metadata.pop(coord, np.nan)})
439
- # Else if set coordinates the variable in the dataset (present in the raw data)
490
+ # Else ensure coord is a dataset coordinates
440
491
  else:
441
492
  ds = ds.set_coords(coord)
442
493
  _ = metadata.pop(coord, None)
@@ -445,6 +496,10 @@ def set_geolocation_coordinates(ds, metadata):
445
496
  for coord in coords:
446
497
  ds[coord] = xr.where(ds[coord] == -9999, np.nan, ds[coord])
447
498
 
499
+ # Ensure valid geolocation coordinates
500
+ for coord in coords:
501
+ ds = ensure_valid_geolocation(ds=ds, coord=coord, errors="coerce")
502
+
448
503
  # Set attributes without geolocation coordinates
449
504
  ds.attrs = metadata
450
505
  return ds
@@ -469,11 +524,11 @@ def finalize_dataset(ds, sensor_name, metadata):
469
524
  ds = ds.transpose("time", "diameter_bin_center", ...)
470
525
 
471
526
  # Ensure variables with dtype object are converted to string
472
- ds = _convert_object_variables_to_string(ds)
527
+ ds = convert_object_variables_to_string(ds)
473
528
 
474
529
  # Add netCDF variable and coordinate attributes
475
530
  # - Add variable attributes: long_name, units, descriptions, valid_min, valid_max
476
- ds = _set_variable_attributes(ds=ds, sensor_name=sensor_name)
531
+ ds = set_variable_attributes(ds=ds, sensor_name=sensor_name)
477
532
  # - Add netCDF coordinate attributes
478
533
  ds = set_coordinate_attributes(ds=ds)
479
534
  # - Set DISDRODB global attributes
@@ -69,7 +69,7 @@ def reader(
69
69
  "quality_measurement": "quality_index",
70
70
  "max_diameter_hail": "max_hail_diameter",
71
71
  "laser_status": "laser_status",
72
- "static_signal": "static_signal",
72
+ "static_signal_status": "static_signal_status",
73
73
  "interior_temperature": "temperature_interior",
74
74
  "laser_temperature": "laser_temperature",
75
75
  "laser_temperature_analog_status": "laser_temperature_analog_status",
@@ -137,7 +137,7 @@ def reader(
137
137
  "quality_index",
138
138
  "max_hail_diameter",
139
139
  "laser_status",
140
- "static_signal",
140
+ "static_signal_status",
141
141
  "laser_temperature_analog_status",
142
142
  "laser_temperature_digital_status",
143
143
  "laser_current_analog_status",
@@ -151,7 +151,7 @@ def reader(
151
151
  "current_heating_heads_status",
152
152
  "current_heating_carriers_status",
153
153
  "control_output_laser_power_status",
154
- "reserve_status",
154
+ "reserved_status",
155
155
  "temperature_interior",
156
156
  "laser_temperature",
157
157
  "laser_current_average",