disdrodb 0.1.2__py3-none-any.whl → 0.1.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.
Files changed (123) hide show
  1. disdrodb/__init__.py +64 -34
  2. disdrodb/_config.py +5 -4
  3. disdrodb/_version.py +16 -3
  4. disdrodb/accessor/__init__.py +20 -0
  5. disdrodb/accessor/methods.py +125 -0
  6. disdrodb/api/checks.py +139 -9
  7. disdrodb/api/configs.py +4 -2
  8. disdrodb/api/info.py +10 -10
  9. disdrodb/api/io.py +237 -18
  10. disdrodb/api/path.py +81 -75
  11. disdrodb/api/search.py +6 -6
  12. disdrodb/cli/disdrodb_create_summary_station.py +91 -0
  13. disdrodb/cli/disdrodb_run_l0.py +1 -1
  14. disdrodb/cli/disdrodb_run_l0_station.py +1 -1
  15. disdrodb/cli/disdrodb_run_l0b.py +1 -1
  16. disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
  17. disdrodb/cli/disdrodb_run_l0c.py +1 -1
  18. disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
  19. disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
  20. disdrodb/configs.py +149 -4
  21. disdrodb/constants.py +61 -0
  22. disdrodb/data_transfer/download_data.py +5 -5
  23. disdrodb/etc/configs/attributes.yaml +339 -0
  24. disdrodb/etc/configs/encodings.yaml +473 -0
  25. disdrodb/etc/products/L1/global.yaml +13 -0
  26. disdrodb/etc/products/L2E/10MIN.yaml +12 -0
  27. disdrodb/etc/products/L2E/1MIN.yaml +1 -0
  28. disdrodb/etc/products/L2E/global.yaml +22 -0
  29. disdrodb/etc/products/L2M/10MIN.yaml +12 -0
  30. disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
  31. disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
  32. disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
  33. disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
  34. disdrodb/etc/products/L2M/global.yaml +26 -0
  35. disdrodb/l0/__init__.py +13 -0
  36. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
  37. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  38. disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
  39. disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
  40. disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
  41. disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
  42. disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
  43. disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
  44. disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
  45. disdrodb/l0/l0a_processing.py +30 -30
  46. disdrodb/l0/l0b_nc_processing.py +108 -2
  47. disdrodb/l0/l0b_processing.py +4 -4
  48. disdrodb/l0/l0c_processing.py +5 -13
  49. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
  50. disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
  51. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
  52. disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
  53. disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
  54. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  55. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  56. disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
  57. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
  58. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
  59. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
  60. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
  61. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
  62. disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
  63. disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
  64. disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
  65. disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
  66. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
  67. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  68. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
  69. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
  70. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  71. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
  72. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +2 -0
  73. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
  74. disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
  75. disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
  76. disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → USA/C3WE.py} +65 -85
  77. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
  78. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
  79. disdrodb/l0/routines.py +105 -14
  80. disdrodb/l1/__init__.py +5 -0
  81. disdrodb/l1/filters.py +34 -20
  82. disdrodb/l1/processing.py +45 -44
  83. disdrodb/l1/resampling.py +77 -66
  84. disdrodb/l1/routines.py +35 -43
  85. disdrodb/l1_env/routines.py +18 -3
  86. disdrodb/l2/__init__.py +7 -0
  87. disdrodb/l2/empirical_dsd.py +58 -10
  88. disdrodb/l2/event.py +27 -120
  89. disdrodb/l2/processing.py +267 -116
  90. disdrodb/l2/routines.py +618 -254
  91. disdrodb/metadata/standards.py +3 -1
  92. disdrodb/psd/fitting.py +463 -144
  93. disdrodb/psd/models.py +8 -5
  94. disdrodb/routines.py +3 -3
  95. disdrodb/scattering/__init__.py +16 -4
  96. disdrodb/scattering/axis_ratio.py +56 -36
  97. disdrodb/scattering/permittivity.py +486 -0
  98. disdrodb/scattering/routines.py +701 -159
  99. disdrodb/summary/__init__.py +17 -0
  100. disdrodb/summary/routines.py +4120 -0
  101. disdrodb/utils/attrs.py +68 -125
  102. disdrodb/utils/compression.py +30 -1
  103. disdrodb/utils/dask.py +59 -8
  104. disdrodb/utils/dataframe.py +61 -7
  105. disdrodb/utils/directories.py +35 -15
  106. disdrodb/utils/encoding.py +33 -19
  107. disdrodb/utils/logger.py +13 -6
  108. disdrodb/utils/manipulations.py +71 -0
  109. disdrodb/utils/subsetting.py +214 -0
  110. disdrodb/utils/time.py +165 -19
  111. disdrodb/utils/writer.py +20 -7
  112. disdrodb/utils/xarray.py +2 -4
  113. disdrodb/viz/__init__.py +13 -0
  114. disdrodb/viz/plots.py +327 -0
  115. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
  116. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/RECORD +121 -88
  117. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
  118. disdrodb/l1/encoding_attrs.py +0 -642
  119. disdrodb/l2/processing_options.py +0 -213
  120. /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
  121. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
  122. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
  123. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
@@ -17,20 +17,33 @@
17
17
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  # -----------------------------------------------------------------------------.
19
19
  """DISDRODB netCDF4 encoding utilities."""
20
+ import os
21
+
20
22
  import xarray as xr
21
23
 
24
+ from disdrodb.utils.yaml import read_yaml
25
+
22
26
  EPOCH = "seconds since 1970-01-01 00:00:00"
23
27
 
24
28
 
25
- def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
29
+ def get_encodings_dict():
30
+ """Get encoding dictionary for DISDRODB product variables and coordinates."""
31
+ import disdrodb
32
+
33
+ configs_path = os.path.join(disdrodb.__root_path__, "disdrodb", "etc", "configs")
34
+ encodings_dict = read_yaml(os.path.join(configs_path, "encodings.yaml"))
35
+ return encodings_dict
36
+
37
+
38
+ def set_encodings(ds: xr.Dataset, encodings_dict: dict) -> xr.Dataset:
26
39
  """Apply the encodings to the xarray Dataset.
27
40
 
28
41
  Parameters
29
42
  ----------
30
43
  ds : xarray.Dataset
31
44
  Input xarray dataset.
32
- encoding_dict : dict
33
- Dictionary with encoding specifications.
45
+ encodings_dict : dict
46
+ Dictionary with encodings specifications.
34
47
 
35
48
  Returns
36
49
  -------
@@ -38,21 +51,22 @@ def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
38
51
  Output xarray dataset.
39
52
  """
40
53
  # Subset encoding dictionary
41
- # - Here below encoding_dict contains only keys (variables) within the dataset
42
- encoding_dict = {var: encoding_dict[var] for var in ds.data_vars if var in encoding_dict}
54
+ # - Here below encodings_dict contains only keys (variables) within the dataset
55
+ encodings_dict = {var: encodings_dict[var] for var in ds.data_vars if var in encodings_dict}
43
56
 
44
57
  # Ensure chunksize smaller than the array shape
45
- encoding_dict = sanitize_encodings_dict(encoding_dict, ds)
58
+ encodings_dict = sanitize_encodings_dict(encodings_dict, ds)
46
59
 
47
60
  # Rechunk variables for fast writing !
48
61
  # - This pop the chunksize argument from the encoding dict !
49
- ds = rechunk_dataset(ds, encoding_dict)
62
+ ds = rechunk_dataset(ds, encodings_dict)
50
63
 
51
64
  # Set time encoding
52
- ds["time"].encoding.update(get_time_encoding())
65
+ if "time" in ds:
66
+ ds["time"].encoding.update(get_time_encoding())
53
67
 
54
68
  # Set the variable encodings
55
- for var, encoding in encoding_dict.items():
69
+ for var, encoding in encodings_dict.items():
56
70
  ds[var].encoding.update(encoding)
57
71
 
58
72
  # Ensure no deprecated "missing_value" attribute
@@ -63,12 +77,12 @@ def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
63
77
  return ds
64
78
 
65
79
 
66
- def sanitize_encodings_dict(encoding_dict: dict, ds: xr.Dataset) -> dict:
80
+ def sanitize_encodings_dict(encodings_dict: dict, ds: xr.Dataset) -> dict:
67
81
  """Ensure chunk size to be smaller than the array shape.
68
82
 
69
83
  Parameters
70
84
  ----------
71
- encoding_dict : dict
85
+ encodings_dict : dict
72
86
  Dictionary containing the variable encodings.
73
87
  ds : xarray.Dataset
74
88
  Input dataset.
@@ -79,23 +93,23 @@ def sanitize_encodings_dict(encoding_dict: dict, ds: xr.Dataset) -> dict:
79
93
  Encoding dictionary.
80
94
  """
81
95
  for var in ds.data_vars:
82
- if var in encoding_dict:
96
+ if var in encodings_dict:
83
97
  shape = ds[var].shape
84
- chunks = encoding_dict[var].get("chunksizes", None)
98
+ chunks = encodings_dict[var].get("chunksizes", None)
85
99
  if chunks is not None:
86
100
  chunks = [shape[i] if chunks[i] > shape[i] else chunks[i] for i in range(len(chunks))]
87
- encoding_dict[var]["chunksizes"] = chunks
88
- return encoding_dict
101
+ encodings_dict[var]["chunksizes"] = chunks
102
+ return encodings_dict
89
103
 
90
104
 
91
- def rechunk_dataset(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
105
+ def rechunk_dataset(ds: xr.Dataset, encodings_dict: dict) -> xr.Dataset:
92
106
  """Coerce the dataset arrays to have the chunk size specified in the encoding dictionary.
93
107
 
94
108
  Parameters
95
109
  ----------
96
110
  ds : xarray.Dataset
97
111
  Input xarray dataset
98
- encoding_dict : dict
112
+ encodings_dict : dict
99
113
  Dictionary containing the encoding to write the xarray dataset as a netCDF.
100
114
 
101
115
  Returns
@@ -104,8 +118,8 @@ def rechunk_dataset(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
104
118
  Output xarray dataset
105
119
  """
106
120
  for var in ds.data_vars:
107
- if var in encoding_dict:
108
- chunks = encoding_dict[var].pop("chunksizes", None)
121
+ if var in encodings_dict:
122
+ chunks = encodings_dict[var].pop("chunksizes", None)
109
123
  if chunks is not None:
110
124
  dims = list(ds[var].dims)
111
125
  chunks_dict = dict(zip(dims, chunks))
disdrodb/utils/logger.py CHANGED
@@ -164,9 +164,16 @@ def _define_station_summary_log_file(list_logs, summary_filepath):
164
164
 
165
165
 
166
166
  def _define_station_problem_log_file(list_logs, problem_filepath):
167
- # - Copy the log of files with warnings and error
168
- list_keywords = ["ERROR"] # "WARNING"
169
- list_patterns = ["ValueError: Less than 5 timesteps available for day"]
167
+ # Copy the log of files with errors
168
+ list_keywords = ["ERROR"]
169
+ # Exclude lines with the following patterns
170
+ list_patterns = [
171
+ # Caused by no data with L2E and L2M filtering
172
+ "No timesteps with rain rate",
173
+ "No timesteps with N",
174
+ "No timesteps with Nbins",
175
+ ]
176
+ # Compile patterns to search, escaping any special regex characters
170
177
  re_keyword = re.compile("|".join(list_keywords))
171
178
  # Compile patterns to ignore, escaping any special regex characters
172
179
  re_patterns = re.compile("|".join(map(re.escape, list_patterns))) if list_patterns else None
@@ -221,7 +228,7 @@ def create_product_logs(
221
228
 
222
229
  The logs directory structure is the follow:
223
230
  /logs
224
- - /files/<product_acronym>/<station> (same structure as data ... a log for each processed file)
231
+ - /files/<product_name>/<station> (same structure as data ... a log for each processed file)
225
232
  - /summary
226
233
  --> SUMMARY.<PRODUCT_ACRONYM>.<CAMPAIGN_NAME>.<STATION_NAME>.log
227
234
  - /problems
@@ -269,7 +276,7 @@ def create_product_logs(
269
276
  # Product options
270
277
  **product_kwargs,
271
278
  )
272
- list_logs = list_files(logs_dir, glob_pattern="*", recursive=True)
279
+ list_logs = list_files(logs_dir, recursive=True)
273
280
 
274
281
  # --------------------------------------------------------.
275
282
  # LogCaptureHandler of pytest does not have baseFilename attribute, so it returns None
@@ -332,5 +339,5 @@ def create_product_logs(
332
339
 
333
340
  # --------------------------------------------------------.
334
341
  # Remove /problem directory if empty !
335
- if len(os.listdir(logs_problem_dir)) == 0:
342
+ if len(list_files(logs_problem_dir, glob_pattern="*.log")) == 0:
336
343
  os.rmdir(logs_problem_dir)
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # -----------------------------------------------------------------------------.
4
+ # Copyright (c) 2021-2023 DISDRODB developers
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ # -----------------------------------------------------------------------------.
19
+ """Include functions helping for DISDRODB product manipulations."""
20
+
21
+ import numpy as np
22
+
23
+ from disdrodb.utils.xarray import unstack_datarray_dimension
24
+
25
+
26
+ def get_diameter_bin_edges(ds):
27
+ """Retrieve diameter bin edges."""
28
+ bin_edges = np.append(ds["diameter_bin_lower"].compute().data, ds["diameter_bin_upper"].compute().data[-1])
29
+ return bin_edges
30
+
31
+
32
+ def convert_from_decibel(x):
33
+ """Convert dB to unit."""
34
+ return np.power(10.0, 0.1 * x) # x/10
35
+
36
+
37
+ def convert_to_decibel(x):
38
+ """Convert unit to dB."""
39
+ return 10 * np.log10(x)
40
+
41
+
42
+ def unstack_radar_variables(ds):
43
+ """Unstack radar variables."""
44
+ from disdrodb.scattering import RADAR_VARIABLES
45
+
46
+ for var in RADAR_VARIABLES:
47
+ if var in ds:
48
+ ds_unstack = unstack_datarray_dimension(ds[var], dim="frequency", prefix="", suffix="_")
49
+ ds.update(ds_unstack)
50
+ ds = ds.drop_vars(var)
51
+ if "frequency" in ds.dims:
52
+ ds = ds.drop_dims("frequency")
53
+ return ds
54
+
55
+
56
+ def resample_drop_number_concentration(da, diameter_bin_edges, method="linear"):
57
+ """Resample drop number concentration N(D) DataArray to high resolution diameter bins."""
58
+ diameters_bin_center = diameter_bin_edges[:-1] + np.diff(diameter_bin_edges) / 2
59
+
60
+ da = da.interp(coords={"diameter_bin_center": diameters_bin_center}, method=method)
61
+ diameter_bin_width = np.diff(diameter_bin_edges)
62
+ diameter_bin_lower = diameter_bin_edges[:-1]
63
+ diameter_bin_upper = diameter_bin_edges[1:]
64
+ da = da.assign_coords(
65
+ {
66
+ "diameter_bin_width": ("diameter_bin_center", diameter_bin_width),
67
+ "diameter_bin_lower": ("diameter_bin_center", diameter_bin_lower),
68
+ "diameter_bin_upper": ("diameter_bin_center", diameter_bin_upper),
69
+ },
70
+ )
71
+ return da
@@ -0,0 +1,214 @@
1
+ # -----------------------------------------------------------------------------.
2
+ # Copyright (c) 2021-2023 DISDRODB developers
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ # -----------------------------------------------------------------------------.
18
+ """This module contains functions for subsetting and aligning DISDRODB products."""
19
+
20
+ import numpy as np
21
+ from xarray.core.utils import either_dict_or_kwargs
22
+
23
+ from disdrodb.constants import DIAMETER_DIMENSION, VELOCITY_DIMENSION
24
+
25
+
26
+ def is_1d_non_dimensional_coord(xr_obj, coord):
27
+ """Checks if a coordinate is a 1d, non-dimensional coordinate."""
28
+ if coord not in xr_obj.coords:
29
+ return False
30
+ if xr_obj[coord].ndim != 1:
31
+ return False
32
+ is_1d_dim_coord = xr_obj[coord].dims[0] == coord
33
+ return not is_1d_dim_coord
34
+
35
+
36
+ def _get_dim_of_1d_non_dimensional_coord(xr_obj, coord):
37
+ """Get the dimension of a 1D non-dimension coordinate."""
38
+ if not is_1d_non_dimensional_coord(xr_obj, coord):
39
+ raise ValueError(f"'{coord}' is not a dimension or a 1D non-dimensional coordinate.")
40
+ dim = xr_obj[coord].dims[0]
41
+ return dim
42
+
43
+
44
+ def _get_dim_isel_on_non_dim_coord_from_isel(xr_obj, coord, isel_indices):
45
+ """Get dimension and isel_indices related to a 1D non-dimension coordinate.
46
+
47
+ Parameters
48
+ ----------
49
+ xr_obj : (xr.Dataset, xr.DataArray)
50
+ A xarray object.
51
+ coord : str
52
+ Name of the coordinate wishing to subset with .sel
53
+ isel_indices : (str, int, float, list, np.array)
54
+ Coordinate indices wishing to be selected.
55
+
56
+ Returns
57
+ -------
58
+ dim : str
59
+ Dimension related to the 1D non-dimension coordinate.
60
+ isel_indices : (int, list, slice)
61
+ Indices for index-based selection.
62
+ """
63
+ dim = _get_dim_of_1d_non_dimensional_coord(xr_obj, coord)
64
+ return dim, isel_indices
65
+
66
+
67
+ def _get_dim_isel_indices_from_isel_indices(xr_obj, key, indices, method="dummy"): # noqa
68
+ """Return the dimension and isel_indices related to the dimension position indices of a coordinate."""
69
+ # Non-dimensional coordinate case
70
+ if key not in xr_obj.dims:
71
+ key, indices = _get_dim_isel_on_non_dim_coord_from_isel(xr_obj, coord=key, isel_indices=indices)
72
+ return key, indices
73
+
74
+
75
+ def _get_isel_indices_from_sel_indices(xr_obj, coord, sel_indices, method):
76
+ """Get isel_indices corresponding to sel_indices."""
77
+ da_coord = xr_obj[coord]
78
+ dim = da_coord.dims[0]
79
+ da_coord = da_coord.assign_coords({"isel_indices": (dim, np.arange(0, da_coord.size))})
80
+ da_subset = da_coord.swap_dims({dim: coord}).sel({coord: sel_indices}, method=method)
81
+ isel_indices = da_subset["isel_indices"].data
82
+ return isel_indices
83
+
84
+
85
+ def _get_dim_isel_on_non_dim_coord_from_sel(xr_obj, coord, sel_indices, method):
86
+ """
87
+ Return the dimension and isel_indices related to a 1D non-dimension coordinate.
88
+
89
+ Parameters
90
+ ----------
91
+ xr_obj : (xr.Dataset, xr.DataArray)
92
+ A xarray object.
93
+ coord : str
94
+ Name of the coordinate wishing to subset with .sel
95
+ sel_indices : (str, int, float, list, np.array)
96
+ Coordinate values wishing to be selected.
97
+
98
+ Returns
99
+ -------
100
+ dim : str
101
+ Dimension related to the 1D non-dimension coordinate.
102
+ isel_indices : np.ndarray
103
+ Indices for index-based selection.
104
+ """
105
+ dim = _get_dim_of_1d_non_dimensional_coord(xr_obj, coord)
106
+ isel_indices = _get_isel_indices_from_sel_indices(xr_obj, coord=coord, sel_indices=sel_indices, method=method)
107
+ return dim, isel_indices
108
+
109
+
110
+ def _get_dim_isel_indices_from_sel_indices(xr_obj, key, indices, method):
111
+ """Return the dimension and isel_indices related to values of a coordinate."""
112
+ # Dimension case
113
+ if key in xr_obj.dims:
114
+ if key not in xr_obj.coords:
115
+ raise ValueError(f"Can not subset with disdrodb.sel the dimension '{key}' if it is not also a coordinate.")
116
+ isel_indices = _get_isel_indices_from_sel_indices(xr_obj, coord=key, sel_indices=indices, method=method)
117
+ # Non-dimensional coordinate case
118
+ else:
119
+ key, isel_indices = _get_dim_isel_on_non_dim_coord_from_sel(
120
+ xr_obj,
121
+ coord=key,
122
+ sel_indices=indices,
123
+ method=method,
124
+ )
125
+ return key, isel_indices
126
+
127
+
128
+ def _get_dim_isel_indices_function(func):
129
+ func_dict = {
130
+ "sel": _get_dim_isel_indices_from_sel_indices,
131
+ "isel": _get_dim_isel_indices_from_isel_indices,
132
+ }
133
+ return func_dict[func]
134
+
135
+
136
+ def _subset(xr_obj, indexers=None, func="isel", drop=False, method=None, **indexers_kwargs):
137
+ """Perform selection with isel or isel."""
138
+ # Retrieve indexers
139
+ indexers = either_dict_or_kwargs(indexers, indexers_kwargs, func)
140
+ # Get function returning isel_indices
141
+ get_dim_isel_indices = _get_dim_isel_indices_function(func)
142
+ # Define isel_dict
143
+ isel_dict = {}
144
+ for key, indices in indexers.items():
145
+ key, isel_indices = get_dim_isel_indices(xr_obj, key=key, indices=indices, method=method)
146
+ if key in isel_dict:
147
+ raise ValueError(f"Multiple indexers point to the '{key}' dimension.")
148
+ isel_dict[key] = isel_indices
149
+
150
+ # Subset and update area
151
+ xr_obj = xr_obj.isel(isel_dict, drop=drop)
152
+ return xr_obj
153
+
154
+
155
+ def isel(xr_obj, indexers=None, drop=False, **indexers_kwargs):
156
+ """Perform index-based dimension selection."""
157
+ return _subset(xr_obj, indexers=indexers, func="isel", drop=drop, **indexers_kwargs)
158
+
159
+
160
+ def sel(xr_obj, indexers=None, drop=False, method=None, **indexers_kwargs):
161
+ """Perform value-based coordinate selection.
162
+
163
+ Slices are treated as inclusive of both the start and stop values, unlike normal Python indexing.
164
+ The disdrodb `sel` method is empowered to:
165
+
166
+ - slice by disdrodb-id strings !
167
+ - slice by any xarray coordinate value !
168
+
169
+ You can use string shortcuts for datetime coordinates (e.g., '2000-01' to select all values in January 2000).
170
+ """
171
+ return _subset(xr_obj, indexers=indexers, func="sel", drop=drop, method=method, **indexers_kwargs)
172
+
173
+
174
+ def align(*args):
175
+ """Align DISDRODB products over time, velocity and diameter dimensions."""
176
+ list_xr_obj = args
177
+
178
+ # Check input
179
+ if len(list_xr_obj) <= 1:
180
+ raise ValueError("At least two xarray object are required for alignment.")
181
+
182
+ # Define dimensions used for alignment
183
+ dims_to_align = ["time", DIAMETER_DIMENSION, VELOCITY_DIMENSION]
184
+
185
+ # Check which dimensions and coordinates are available across all datasets
186
+ coords = [coord for coord in dims_to_align if all(coord in xr_obj.coords for xr_obj in list_xr_obj)]
187
+ if not coords:
188
+ raise ValueError("No common coordinates found among the input datasets for alignment.")
189
+
190
+ # Start with the input datasets
191
+ list_aligned = list(list_xr_obj)
192
+
193
+ # Loop over the dimensions which are available
194
+ for coord in coords:
195
+ # Retrieve list of coordinate values
196
+ list_coord_values = [xr_obj[coord].data for xr_obj in list_aligned]
197
+
198
+ # Retrieve intersection of coordinates values
199
+ # - np.atleast_1d ensure that the dimension is not dropped if only 1 value
200
+ # - np.intersect1d returns the sorted array of common unique elements
201
+ common_values = list_coord_values[0]
202
+ for coord_values in list_coord_values[1:]:
203
+ common_values = np.intersect1d(common_values, coord_values)
204
+ sel_indices = np.atleast_1d(common_values)
205
+
206
+ # Check there are common coordinate values
207
+ if len(sel_indices) == 0:
208
+ raise ValueError(f"No common {coord} values across input objects.")
209
+
210
+ # Subset dataset
211
+ new_list_aligned = [sel(xr_obj, {coord: sel_indices}) for xr_obj in list_aligned]
212
+ list_aligned = new_list_aligned
213
+
214
+ return list_aligned