disdrodb 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. disdrodb/__init__.py +1 -5
  2. disdrodb/_version.py +2 -2
  3. disdrodb/accessor/methods.py +14 -3
  4. disdrodb/api/checks.py +10 -0
  5. disdrodb/api/create_directories.py +0 -2
  6. disdrodb/api/io.py +14 -17
  7. disdrodb/api/path.py +42 -77
  8. disdrodb/api/search.py +89 -23
  9. disdrodb/cli/disdrodb_create_summary.py +11 -1
  10. disdrodb/cli/disdrodb_create_summary_station.py +10 -0
  11. disdrodb/cli/disdrodb_run_l0.py +1 -1
  12. disdrodb/cli/disdrodb_run_l0a.py +1 -1
  13. disdrodb/cli/disdrodb_run_l0b.py +1 -1
  14. disdrodb/cli/disdrodb_run_l0c.py +1 -1
  15. disdrodb/cli/disdrodb_run_l1.py +1 -1
  16. disdrodb/cli/disdrodb_run_l2e.py +1 -1
  17. disdrodb/cli/disdrodb_run_l2m.py +1 -1
  18. disdrodb/configs.py +30 -83
  19. disdrodb/constants.py +4 -3
  20. disdrodb/data_transfer/download_data.py +4 -2
  21. disdrodb/docs.py +2 -2
  22. disdrodb/etc/products/L1/1MIN.yaml +13 -0
  23. disdrodb/etc/products/L1/LPM/1MIN.yaml +13 -0
  24. disdrodb/etc/products/L1/PARSIVEL/1MIN.yaml +13 -0
  25. disdrodb/etc/products/L1/PARSIVEL2/1MIN.yaml +13 -0
  26. disdrodb/etc/products/L1/PWS100/1MIN.yaml +13 -0
  27. disdrodb/etc/products/L1/RD80/1MIN.yaml +13 -0
  28. disdrodb/etc/products/L1/SWS250/1MIN.yaml +13 -0
  29. disdrodb/etc/products/L1/global.yaml +7 -1
  30. disdrodb/etc/products/L2E/10MIN.yaml +1 -12
  31. disdrodb/etc/products/L2E/5MIN.yaml +1 -0
  32. disdrodb/etc/products/L2E/global.yaml +1 -1
  33. disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_MAE.yaml +6 -0
  34. disdrodb/etc/products/L2M/{GAMMA_ML.yaml → MODELS/GAMMA_ML.yaml} +1 -1
  35. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_MAE.yaml +6 -0
  36. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_MAE.yaml +6 -0
  37. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_ML.yaml +8 -0
  38. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_R_MAE.yaml +6 -0
  39. disdrodb/etc/products/L2M/global.yaml +11 -3
  40. disdrodb/l0/check_configs.py +49 -16
  41. disdrodb/l0/configs/LPM/l0a_encodings.yml +2 -2
  42. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +2 -2
  43. disdrodb/l0/configs/LPM/l0b_encodings.yml +2 -2
  44. disdrodb/l0/configs/LPM/raw_data_format.yml +2 -2
  45. disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +1 -1
  46. disdrodb/l0/configs/PWS100/l0b_encodings.yml +1 -0
  47. disdrodb/l0/configs/SWS250/bins_diameter.yml +108 -0
  48. disdrodb/l0/configs/SWS250/bins_velocity.yml +83 -0
  49. disdrodb/l0/configs/SWS250/l0a_encodings.yml +18 -0
  50. disdrodb/l0/configs/SWS250/l0b_cf_attrs.yml +72 -0
  51. disdrodb/l0/configs/SWS250/l0b_encodings.yml +155 -0
  52. disdrodb/l0/configs/SWS250/raw_data_format.yml +148 -0
  53. disdrodb/l0/l0_reader.py +2 -2
  54. disdrodb/l0/l0b_processing.py +70 -15
  55. disdrodb/l0/l0c_processing.py +7 -3
  56. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +1 -1
  57. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +2 -2
  58. disdrodb/l0/readers/LPM/BELGIUM/ULIEGE.py +256 -0
  59. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +2 -2
  60. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +2 -2
  61. disdrodb/l0/readers/LPM/GERMANY/DWD.py +491 -0
  62. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +2 -2
  63. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +2 -2
  64. disdrodb/l0/readers/LPM/KIT/CHWALA.py +2 -2
  65. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +107 -12
  66. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +3 -3
  67. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +2 -2
  68. disdrodb/l0/readers/PARSIVEL/BASQUECOUNTRY/EUSKALMET_OTT.py +227 -0
  69. disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/LPVEX.py +1 -1
  70. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +5 -14
  71. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +8 -17
  72. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +117 -8
  73. disdrodb/l0/readers/PARSIVEL2/BASQUECOUNTRY/EUSKALMET_OTT2.py +232 -0
  74. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +10 -14
  75. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +10 -14
  76. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +8 -14
  77. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +382 -0
  78. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +4 -0
  79. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
  80. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +127 -0
  81. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +239 -0
  82. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +5 -11
  83. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +4 -17
  84. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +5 -14
  85. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +10 -13
  86. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +10 -13
  87. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PAGASA.py +232 -0
  88. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +6 -18
  89. disdrodb/l0/readers/PARSIVEL2/{NASA/LPVEX.py → SPAIN/GRANADA.py} +46 -35
  90. disdrodb/l0/readers/PARSIVEL2/SWEDEN/SMHI.py +189 -0
  91. disdrodb/l0/readers/PARSIVEL2/USA/{C3WE.py → CW3E.py} +10 -28
  92. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +321 -0
  93. disdrodb/l0/readers/SW250/BELGIUM/KMI.py +239 -0
  94. disdrodb/l1/beard_model.py +31 -129
  95. disdrodb/l1/fall_velocity.py +136 -83
  96. disdrodb/l1/filters.py +25 -28
  97. disdrodb/l1/processing.py +16 -17
  98. disdrodb/l1/resampling.py +101 -38
  99. disdrodb/l1_env/routines.py +46 -17
  100. disdrodb/l2/empirical_dsd.py +6 -0
  101. disdrodb/l2/processing.py +6 -5
  102. disdrodb/metadata/geolocation.py +0 -2
  103. disdrodb/metadata/search.py +3 -4
  104. disdrodb/psd/fitting.py +16 -13
  105. disdrodb/routines/l0.py +2 -2
  106. disdrodb/routines/l1.py +173 -60
  107. disdrodb/routines/l2.py +148 -284
  108. disdrodb/routines/options.py +345 -0
  109. disdrodb/routines/wrappers.py +14 -1
  110. disdrodb/scattering/axis_ratio.py +90 -84
  111. disdrodb/scattering/permittivity.py +6 -0
  112. disdrodb/summary/routines.py +735 -670
  113. disdrodb/utils/archiving.py +51 -44
  114. disdrodb/utils/attrs.py +3 -1
  115. disdrodb/utils/dask.py +4 -4
  116. disdrodb/utils/dict.py +33 -0
  117. disdrodb/utils/encoding.py +6 -1
  118. disdrodb/utils/routines.py +9 -8
  119. disdrodb/utils/time.py +11 -3
  120. disdrodb/viz/__init__.py +0 -13
  121. disdrodb/viz/plots.py +231 -1
  122. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/METADATA +2 -1
  123. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/RECORD +135 -103
  124. /disdrodb/etc/products/L2M/{NGAMMA_GS_LOG_ND_MAE.yaml → MODELS/NGAMMA_GS_LOG_ND_MAE.yaml} +0 -0
  125. /disdrodb/etc/products/L2M/{NGAMMA_GS_ND_MAE.yaml → MODELS/NGAMMA_GS_ND_MAE.yaml} +0 -0
  126. /disdrodb/etc/products/L2M/{NGAMMA_GS_Z_MAE.yaml → MODELS/NGAMMA_GS_Z_MAE.yaml} +0 -0
  127. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/IFLOODS.py +0 -0
  128. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/MC3E.py +0 -0
  129. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/PIERS.py +0 -0
  130. /disdrodb/l0/readers/PARSIVEL2/{GPM → NASA}/GCPEX.py +0 -0
  131. /disdrodb/l0/readers/PARSIVEL2/{GPM → NASA}/NSSTC.py +0 -0
  132. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/WHEEL +0 -0
  133. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/entry_points.txt +0 -0
  134. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/licenses/LICENSE +0 -0
  135. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/top_level.txt +0 -0
disdrodb/__init__.py CHANGED
@@ -39,8 +39,6 @@ from disdrodb.configs import (
39
39
  define_configs,
40
40
  get_data_archive_dir,
41
41
  get_metadata_archive_dir,
42
- get_model_options,
43
- get_product_options,
44
42
  get_scattering_table_dir,
45
43
  )
46
44
  from disdrodb.data_transfer.download_data import download_archive, download_station
@@ -145,8 +143,6 @@ __all__ = [
145
143
  "generate_l2m",
146
144
  "get_data_archive_dir",
147
145
  "get_metadata_archive_dir",
148
- "get_model_options",
149
- "get_product_options",
150
146
  "get_reader",
151
147
  "get_scattering_table_dir",
152
148
  "get_station_reader",
@@ -177,7 +173,7 @@ __all__ = [
177
173
  ]
178
174
 
179
175
 
180
- __root_path__ = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
176
+ package_dir = os.path.dirname(os.path.realpath(__file__))
181
177
 
182
178
 
183
179
  def is_pytmatrix_available():
disdrodb/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.4'
32
- __version_tuple__ = version_tuple = (0, 1, 4)
31
+ __version__ = version = '0.2.0'
32
+ __version_tuple__ = version_tuple = (0, 2, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -89,6 +89,12 @@ class DISDRODB_Base_Accessor:
89
89
 
90
90
  return align(self._obj, *args)
91
91
 
92
+ def plot_spectrum(self, **kwargs):
93
+ """Plot spectrum."""
94
+ from disdrodb.viz.plots import plot_spectrum
95
+
96
+ plot_spectrum(self._obj, **kwargs)
97
+
92
98
 
93
99
  @xr.register_dataset_accessor("disdrodb")
94
100
  class DISDRODB_Dataset_Accessor(DISDRODB_Base_Accessor):
@@ -97,7 +103,7 @@ class DISDRODB_Dataset_Accessor(DISDRODB_Base_Accessor):
97
103
  def __init__(self, xarray_obj):
98
104
  super().__init__(xarray_obj)
99
105
 
100
- def resample(self, accumulation_interval, rolling=True):
106
+ def resample(self, temporal_resolution):
101
107
  """Resample a L1 or L2 DISDRODB Product."""
102
108
  from disdrodb.l1.resampling import resample_dataset
103
109
 
@@ -105,8 +111,7 @@ class DISDRODB_Dataset_Accessor(DISDRODB_Base_Accessor):
105
111
  ds = resample_dataset(
106
112
  self._obj,
107
113
  sample_interval=sample_interval,
108
- accumulation_interval=accumulation_interval,
109
- rolling=rolling,
114
+ temporal_resolution=temporal_resolution,
110
115
  )
111
116
  return ds
112
117
 
@@ -116,6 +121,12 @@ class DISDRODB_Dataset_Accessor(DISDRODB_Base_Accessor):
116
121
 
117
122
  plot_nd(self._obj, **kwargs)
118
123
 
124
+ def plot_raw_and_filtered_spectra(self, **kwargs):
125
+ """Plot the raw and filtered spectra."""
126
+ from disdrodb.viz.plots import plot_raw_and_filtered_spectra
127
+
128
+ plot_raw_and_filtered_spectra(self._obj, **kwargs)
129
+
119
130
 
120
131
  @xr.register_dataarray_accessor("disdrodb")
121
132
  class DISDRODB_DataArray_Accessor(DISDRODB_Base_Accessor):
disdrodb/api/checks.py CHANGED
@@ -154,6 +154,16 @@ def check_rolling(rolling):
154
154
  raise TypeError("'rolling' must be a boolean.")
155
155
 
156
156
 
157
+ def check_temporal_resolution(temporal_resolution):
158
+ """Check temporal resolution validity."""
159
+ from disdrodb.utils.time import get_sampling_information
160
+
161
+ if not isinstance(temporal_resolution, str):
162
+ raise TypeError("'temporal_resolution' must be a string.")
163
+ # If correct, the follow should not raise error
164
+ sample_interval, rolling = get_sampling_information(temporal_resolution)
165
+
166
+
157
167
  def check_folder_partitioning(folder_partitioning):
158
168
  """
159
169
  Check if the given folder partitioning scheme is valid.
@@ -85,8 +85,6 @@ def create_l0_directory_structure(
85
85
  ``product = "L0A"`` will call ``run_l0a``.
86
86
  ``product = "L0B"`` will call ``run_l0b_nc``.
87
87
  """
88
- from disdrodb.configs import get_data_archive_dir, get_metadata_archive_dir
89
-
90
88
  # Retrieve the DISDRODB Metadata Archive directory
91
89
  data_archive_dir = get_data_archive_dir(data_archive_dir)
92
90
  metadata_archive_dir = get_metadata_archive_dir(metadata_archive_dir)
disdrodb/api/io.py CHANGED
@@ -40,6 +40,7 @@ from disdrodb.api.path import (
40
40
  define_station_dir,
41
41
  )
42
42
  from disdrodb.l0.l0_reader import define_readers_directory
43
+ from disdrodb.utils.dict import extract_product_kwargs
43
44
  from disdrodb.utils.directories import list_files
44
45
  from disdrodb.utils.logger import (
45
46
  log_info,
@@ -117,7 +118,7 @@ def filter_by_time(filepaths, start_time=None, end_time=None):
117
118
  start_time, end_time = check_start_end_time(start_time, end_time)
118
119
 
119
120
  # -------------------------------------------------------------------------.
120
- # - Retrieve start_time and end_time of GPM granules
121
+ # - Retrieve files start_time and end_time
121
122
  l_start_time, l_end_time = get_start_end_time_from_filepaths(filepaths)
122
123
 
123
124
  # -------------------------------------------------------------------------.
@@ -165,12 +166,9 @@ def find_files(
165
166
 
166
167
  Other Parameters
167
168
  ----------------
168
- sample_interval : int, optional
169
- The sampling interval in seconds of the product.
170
- It must be specified only for product L2E and L2M !
171
- rolling : bool, optional
172
- Whether the dataset has been resampled by aggregating or rolling.
173
- It must be specified only for product L2E and L2M !
169
+ temporal_resolution : str, optional
170
+ The temporal resolution of the product (e.g., "1MIN", "10MIN", "1H").
171
+ It must be specified only for product L1, L2E and L2M !
174
172
  model_name : str
175
173
  The model name of the statistical distribution for the DSD.
176
174
  It must be specified only for product L2M !
@@ -411,21 +409,19 @@ def open_dataset(
411
409
  The name of the station.
412
410
  product : str
413
411
  The name DISDRODB product.
414
- sample_interval : int, optional
415
- The sampling interval in seconds of the product.
416
- It must be specified only for product L2E and L2M !
417
- rolling : bool, optional
418
- Whether the dataset has been resampled by aggregating or rolling.
419
- It must be specified only for product L2E and L2M !
420
- model_name : str
421
- The model name of the statistical distribution for the DSD.
422
- It must be specified only for product L2M !
423
412
  debugging_mode : bool, optional
424
413
  If ``True``, it select maximum 3 files for debugging purposes.
425
414
  The default value is ``False``.
426
415
  data_archive_dir : str, optional
427
416
  The base directory of DISDRODB, expected in the format ``<...>/DISDRODB``.
428
417
  If not specified, the path specified in the DISDRODB active configuration will be used.
418
+ **product_kwargs : optional
419
+ DISDRODB product options
420
+ It must be specified only for product L1, L2E and L2M products !
421
+ For L1, L2E and L2M products, temporal_resolution is required
422
+ FOr L2M product, model_name is required.
423
+ **open_kwargs : optional
424
+ Additional keyword arguments passed to ``xarray.open_mfdataset()``.
429
425
 
430
426
  Returns
431
427
  -------
@@ -434,7 +430,8 @@ def open_dataset(
434
430
  """
435
431
  from disdrodb.l0.l0a_processing import read_l0a_dataframe
436
432
 
437
- product_kwargs = product_kwargs if product_kwargs else {}
433
+ # Extract product kwargs from open_kwargs
434
+ product_kwargs = extract_product_kwargs(open_kwargs, product=product)
438
435
 
439
436
  # List product files
440
437
  filepaths = find_files(
disdrodb/api/path.py CHANGED
@@ -336,13 +336,13 @@ def define_issue_filepath(
336
336
 
337
337
  def define_config_dir(product):
338
338
  """Define the config directory path of a given DISDRODB product."""
339
- from disdrodb import __root_path__
339
+ from disdrodb import package_dir
340
340
 
341
341
  if product.upper() in ["RAW", "L0A", "L0B"]:
342
342
  dir_name = "l0"
343
343
  else:
344
344
  raise NotImplementedError(f"Product {product} not implemented.")
345
- config_dir = os.path.join(__root_path__, "disdrodb", dir_name, "configs")
345
+ config_dir = os.path.join(package_dir, dir_name, "configs")
346
346
  return config_dir
347
347
 
348
348
 
@@ -448,12 +448,9 @@ def define_product_dir_tree(
448
448
  ----------
449
449
  product : str
450
450
  The DISDRODB product. See ``disdrodb.available_products()``.
451
- sample_interval : int, optional
452
- The sampling interval in seconds of the product.
453
- It must be specified only for product L2E and L2M !
454
- rolling : bool, optional
455
- Whether the dataset has been resampled by aggregating or rolling.
456
- It must be specified only for product L2E and L2M !
451
+ temporal_resolution : str, optional
452
+ The temporal resolution of the product.
453
+ It must be specified only for product L1, L2E and L2M !
457
454
  model_name : str
458
455
  The custom model name of the fitted statistical distribution.
459
456
  It must be specified only for product L2M !
@@ -463,28 +460,23 @@ def define_product_dir_tree(
463
460
  data_dir : str
464
461
  Station data directory path
465
462
  """
466
- from disdrodb.api.checks import check_product, check_product_kwargs, check_rolling, check_sample_interval
463
+ from disdrodb.api.checks import check_product, check_product_kwargs, check_temporal_resolution
467
464
 
468
465
  product = check_product(product)
469
466
  product_kwargs = check_product_kwargs(product, product_kwargs)
470
467
  if product.upper() == "RAW":
471
468
  return ""
472
- if product.upper() in ["L0A", "L0B", "L0C", "L1"]:
469
+ if product.upper() in ["L0A", "L0B", "L0C"]:
473
470
  return ""
474
- if product == "L2E":
475
- rolling = product_kwargs.get("rolling")
476
- sample_interval = product_kwargs.get("sample_interval")
477
- check_rolling(rolling)
478
- check_sample_interval(sample_interval)
479
- temporal_resolution = define_temporal_resolution(seconds=sample_interval, rolling=rolling)
480
- return os.path.join(temporal_resolution)
471
+ if product in ["L1", "L2E"]:
472
+ temporal_resolution = product_kwargs.get("temporal_resolution")
473
+ check_temporal_resolution(temporal_resolution)
474
+ return temporal_resolution
481
475
  # L2M if product == "L2M":
482
- rolling = product_kwargs.get("rolling")
483
- sample_interval = product_kwargs.get("sample_interval")
476
+ temporal_resolution = product_kwargs.get("temporal_resolution")
477
+ check_temporal_resolution(temporal_resolution)
484
478
  model_name = product_kwargs.get("model_name")
485
- check_rolling(rolling)
486
- check_sample_interval(sample_interval)
487
- temporal_resolution = define_temporal_resolution(seconds=sample_interval, rolling=rolling)
479
+
488
480
  return os.path.join(model_name, temporal_resolution)
489
481
 
490
482
 
@@ -611,12 +603,9 @@ def define_data_dir(
611
603
  If not specified, the path specified in the DISDRODB active configuration will be used.
612
604
  check_exists : bool, optional
613
605
  Whether to check if the directory exists. The default value is ``False``.
614
- sample_interval : int, optional
615
- The sampling interval in seconds of the product.
616
- It must be specified only for product L2E and L2M !
617
- rolling : bool, optional
618
- Whether the dataset has been resampled by aggregating or rolling.
619
- It must be specified only for product L2E and L2M !
606
+ temporal_resolution : str, optional
607
+ The temporal resolution of the product.
608
+ It must be specified only for product L1, L2E and L2M !
620
609
  model_name : str
621
610
  The name of the fitted statistical distribution for the DSD.
622
611
  It must be specified only for product L2M !
@@ -701,11 +690,8 @@ def define_filename(
701
690
  end_time : datetime.datatime, optional
702
691
  End time.
703
692
  Required if add_time_period = True.
704
- sample_interval : int, optional
705
- The sampling interval in seconds of the product.
706
- It must be specified only for product L0C, L1, L2E and L2M !
707
- rolling : bool, optional
708
- Whether the dataset has been resampled by aggregating or rolling.
693
+ temporal_resolution : str, optional
694
+ The temporal resolution of the product.
709
695
  It must be specified only for product L1, L2E and L2M !
710
696
  model_name : str
711
697
  The model name of the fitted statistical distribution for the DSD.
@@ -716,7 +702,7 @@ def define_filename(
716
702
  str
717
703
  L0B file name.
718
704
  """
719
- from disdrodb.api.checks import check_product, check_product_kwargs
705
+ from disdrodb.api.checks import check_product, check_product_kwargs, check_temporal_resolution
720
706
 
721
707
  product = check_product(product)
722
708
  product_kwargs = check_product_kwargs(product, product_kwargs)
@@ -729,16 +715,9 @@ def define_filename(
729
715
  product_name = f"{product}"
730
716
 
731
717
  # L0C ... sample interval known only per-file
732
- # L1 ... in future known a priori
733
- # if product in ["L1"]:
734
- # # TODO: HACK FOR CURRENT L0C and L1 log files in create_product_logs
735
- # sample_interval = product_kwargs.get("sample_interval", 0)
736
- # temporal_resolution = define_temporal_resolution(seconds=sample_interval, rolling=False)
737
- # product_name = f"{product}.{temporal_resolution}"
738
- if product in ["L2E", "L2M"]:
739
- rolling = product_kwargs.get("rolling")
740
- sample_interval = product_kwargs.get("sample_interval")
741
- temporal_resolution = define_temporal_resolution(seconds=sample_interval, rolling=rolling)
718
+ if product in ["L1", "L2E", "L2M"]:
719
+ temporal_resolution = product_kwargs.get("temporal_resolution")
720
+ check_temporal_resolution(temporal_resolution)
742
721
  product_name = f"{product}.{temporal_resolution}"
743
722
  if product in ["L2M"]:
744
723
  model_name = product_kwargs.get("model_name")
@@ -828,7 +807,7 @@ def define_l0b_filename(ds, campaign_name: str, station_name: str) -> str:
828
807
 
829
808
  def define_l0c_filename(ds, campaign_name: str, station_name: str) -> str:
830
809
  """Define L0C file name."""
831
- # TODO: add sample_interval as function argument s
810
+ # TODO: add sample_interval as function argument
832
811
  sample_interval = int(ensure_sample_interval_in_seconds(ds["sample_interval"]).data.item())
833
812
  temporal_resolution = define_temporal_resolution(sample_interval, rolling=False)
834
813
  starting_time, ending_time = get_file_start_end_time(ds)
@@ -839,37 +818,26 @@ def define_l0c_filename(ds, campaign_name: str, station_name: str) -> str:
839
818
  return filename
840
819
 
841
820
 
842
- def define_l1_filename(ds, campaign_name, station_name: str) -> str:
821
+ def define_l1_filename(ds, campaign_name, station_name: str, temporal_resolution: str) -> str:
843
822
  """Define L1 file name."""
844
- # TODO: add sample_interval and rolling as function argument
845
-
846
823
  starting_time, ending_time = get_file_start_end_time(ds)
847
- sample_interval = int(ensure_sample_interval_in_seconds(ds["sample_interval"]).data.item())
848
- temporal_resolution = define_temporal_resolution(sample_interval, rolling=False)
849
- starting_time, ending_time = get_file_start_end_time(ds)
850
- starting_time = starting_time.strftime("%Y%m%d%H%M%S")
851
- ending_time = ending_time.strftime("%Y%m%d%H%M%S")
852
- version = ARCHIVE_VERSION
853
- filename = f"L1.{temporal_resolution}.{campaign_name}.{station_name}.s{starting_time}.e{ending_time}.{version}.nc"
854
-
855
- # filename = define_filename(
856
- # product="L1",
857
- # campaign_name=campaign_name,
858
- # station_name=station_name,
859
- # # Filename options
860
- # start_time=starting_time,
861
- # end_time=ending_time,
862
- # add_version=True,
863
- # add_time_period=True,
864
- # add_extension=True,
865
- # # Product options
866
- # # sample_interval=sample_interval,
867
- # # rolling=rolling,
868
- # )
824
+ filename = define_filename(
825
+ product="L1",
826
+ campaign_name=campaign_name,
827
+ station_name=station_name,
828
+ # Filename options
829
+ start_time=starting_time,
830
+ end_time=ending_time,
831
+ add_version=True,
832
+ add_time_period=True,
833
+ add_extension=True,
834
+ # Product options
835
+ temporal_resolution=temporal_resolution,
836
+ )
869
837
  return filename
870
838
 
871
839
 
872
- def define_l2e_filename(ds, campaign_name: str, station_name: str, sample_interval: int, rolling: bool) -> str:
840
+ def define_l2e_filename(ds, campaign_name: str, station_name: str, temporal_resolution: str) -> str:
873
841
  """Define L2E file name."""
874
842
  starting_time, ending_time = get_file_start_end_time(ds)
875
843
  filename = define_filename(
@@ -883,8 +851,7 @@ def define_l2e_filename(ds, campaign_name: str, station_name: str, sample_interv
883
851
  add_time_period=True,
884
852
  add_extension=True,
885
853
  # Product options
886
- sample_interval=sample_interval,
887
- rolling=rolling,
854
+ temporal_resolution=temporal_resolution,
888
855
  )
889
856
  return filename
890
857
 
@@ -893,8 +860,7 @@ def define_l2m_filename(
893
860
  ds,
894
861
  campaign_name: str,
895
862
  station_name: str,
896
- sample_interval: int,
897
- rolling: bool,
863
+ temporal_resolution: str,
898
864
  model_name: str,
899
865
  ) -> str:
900
866
  """Define L2M file name."""
@@ -910,8 +876,7 @@ def define_l2m_filename(
910
876
  add_time_period=True,
911
877
  add_extension=True,
912
878
  # Product options
913
- sample_interval=sample_interval,
914
- rolling=rolling,
879
+ temporal_resolution=temporal_resolution,
915
880
  model_name=model_name,
916
881
  )
917
882
  return filename
disdrodb/api/search.py CHANGED
@@ -17,6 +17,7 @@ from disdrodb.api.path import (
17
17
  )
18
18
  from disdrodb.configs import get_data_archive_dir, get_metadata_archive_dir
19
19
  from disdrodb.constants import PRODUCTS_REQUIREMENTS
20
+ from disdrodb.utils.dict import extract_product_kwargs
20
21
  from disdrodb.utils.directories import contains_files, contains_netcdf_or_parquet_files, list_directories, list_files
21
22
  from disdrodb.utils.yaml import read_yaml
22
23
 
@@ -173,7 +174,15 @@ def list_station_names(
173
174
  #### Filtering utilities for available_stations
174
175
 
175
176
 
176
- def _finalize_output(list_info, return_tuple):
177
+ def _finalize_output(list_info, return_tuple, metadata_archive_dir, filter_kwargs):
178
+ # Filter stations if metadata filtering values are specified
179
+ if len(filter_kwargs) != 0:
180
+ list_info = select_stations_matching_metadata_values(
181
+ metadata_archive_dir=metadata_archive_dir,
182
+ list_info=list_info,
183
+ filter_kwargs=filter_kwargs,
184
+ )
185
+
177
186
  # - Return the (data_source, campaign_name, station_name) tuple
178
187
  if return_tuple:
179
188
  return list_info
@@ -192,7 +201,7 @@ def is_disdrodb_data_url_specified(metadata_filepath):
192
201
  return isinstance(disdrodb_data_url, str) and len(disdrodb_data_url) > 1
193
202
 
194
203
 
195
- def keep_list_info_with_disdrodb_data_url(metadata_archive_dir, list_info):
204
+ def select_stations_with_disdrodb_data_url(metadata_archive_dir, list_info):
196
205
  """Keep only the stations with disdrodb_data_url specified in the metadata file."""
197
206
  list_info_with_data = []
198
207
  for data_source, campaign_name, station_name in list_info:
@@ -209,7 +218,41 @@ def keep_list_info_with_disdrodb_data_url(metadata_archive_dir, list_info):
209
218
  return list_info_with_data
210
219
 
211
220
 
212
- def keep_list_info_elements_with_product_directory(data_archive_dir, product, list_info):
221
+ def _matches(metadata_value, expected_value):
222
+ """Return True if metadata_value matches expected_value."""
223
+ # Case 1: both lists → check any intersection
224
+ if isinstance(metadata_value, list) and isinstance(expected_value, list):
225
+ return any(v in metadata_value for v in expected_value)
226
+ # Case 2: metadata is list → check membership
227
+ if isinstance(metadata_value, list):
228
+ return expected_value in metadata_value
229
+ # Case 3: expected is list → check membership
230
+ if isinstance(expected_value, list):
231
+ return metadata_value in expected_value
232
+ # Case 4: both scalars → direct equality
233
+ return metadata_value == expected_value
234
+
235
+
236
+ def select_stations_matching_metadata_values(metadata_archive_dir, list_info, filter_kwargs):
237
+ """Keep only the stations with the specified metadata key matching the specified value."""
238
+ list_info_valid = []
239
+ for data_source, campaign_name, station_name in list_info:
240
+ # Define metadata filepath
241
+ metadata_filepath = define_metadata_filepath(
242
+ metadata_archive_dir=metadata_archive_dir,
243
+ data_source=data_source,
244
+ campaign_name=campaign_name,
245
+ station_name=station_name,
246
+ )
247
+ # Read metadata
248
+ metadata = read_yaml(metadata_filepath)
249
+ if np.all([_matches(metadata.get(k), v) for k, v in filter_kwargs.items()]):
250
+ list_info_valid.append((data_source, campaign_name, station_name))
251
+
252
+ return list_info_valid
253
+
254
+
255
+ def select_stations_with_product_directory(data_archive_dir, product, list_info):
213
256
  """Keep only the stations with the product directory."""
214
257
  list_info_with_product_directory = []
215
258
  for data_source, campaign_name, station_name in list_info:
@@ -228,7 +271,7 @@ def keep_list_info_elements_with_product_directory(data_archive_dir, product, li
228
271
  return list_info_with_product_directory
229
272
 
230
273
 
231
- def keep_list_info_elements_with_product_data(data_archive_dir, product, list_info, **product_kwargs):
274
+ def select_stations_with_product_data(data_archive_dir, product, list_info, **product_kwargs):
232
275
  """Keep only the stations with product data."""
233
276
  # Define file checking function
234
277
  checking_function = contains_files if product == "RAW" else contains_netcdf_or_parquet_files
@@ -265,7 +308,7 @@ def available_stations(
265
308
  invalid_fields_policy="raise",
266
309
  data_archive_dir=None,
267
310
  metadata_archive_dir=None,
268
- **product_kwargs,
311
+ **filter_kwargs,
269
312
  ):
270
313
  """
271
314
  Return stations information for which metadata or product data are available on disk.
@@ -289,7 +332,7 @@ def available_stations(
289
332
 
290
333
  If the DISDRODB product is specified,
291
334
  it lists the stations present in the local DISDRODB Data Archive given the specified filtering criteria.
292
- The default is is None.
335
+ The default is None.
293
336
 
294
337
  data_sources : str or sequence of str, optional
295
338
  One or more data source identifiers to filter stations by.
@@ -342,15 +385,15 @@ def available_stations(
342
385
  If None, the default metadata base directory is used. Default is None.
343
386
  **product_kwargs : dict, optional
344
387
  Additional arguments required for some products.
345
- For example, for the "L2E" product, you need to specify ``rolling`` and
346
- ``sample_interval``. For the "L2M" product, you need to specify also
347
- the ``model_name``.
388
+ It must be specified only for product L1, L2E and L2M products !
389
+ For L1, L2E and L2M products, ``temporal_resolution`` is required.
390
+ FOr L2M product, ``model_name`` is required.
348
391
 
349
392
  Returns
350
393
  -------
351
394
  list
352
395
  If ``return_tuple=True``, return a list of tuples ``(data_source, campaign_name, station_name)``.
353
- If ``return_tuple=True``,, return a list of station names.
396
+ If ``return_tuple=True``, return a list of station names.
354
397
 
355
398
  Examples
356
399
  --------
@@ -361,15 +404,18 @@ def available_stations(
361
404
  >>> # List stations with raw data available in the local DISDRODB Data Archive
362
405
  >>> raw_stations = available_stations(product="RAW", available_data=True)
363
406
  >>> # List stations of specific data sources
364
- >>> stations = available_stations(data_sources=["GPM", "EPFL"])
407
+ >>> stations = available_stations(data_sources=["NASA", "EPFL"])
365
408
  """
366
409
  # Retrieve DISDRODB Data and Metadata Archive directories
367
410
  metadata_archive_dir = get_metadata_archive_dir(metadata_archive_dir)
368
411
  product = check_product(product) if product is not None else None
369
412
  invalid_fields_policy = check_invalid_fields_policy(invalid_fields_policy)
370
413
 
414
+ # Extract product_kwargs from filter_kwargs
415
+ product_kwargs = extract_product_kwargs(filter_kwargs, product=product) if product is not None else {}
416
+
371
417
  # Retrieve available stations from the Metadata Archive
372
- # - Raise error if no stations availables !
418
+ # - Raise error if no stations available !
373
419
  list_info = list_station_names(
374
420
  metadata_archive_dir=metadata_archive_dir,
375
421
  data_sources=data_sources,
@@ -386,24 +432,34 @@ def available_stations(
386
432
  raise_error_if_empty=raise_error_if_empty,
387
433
  msg="No station available in the DISDRODB Metadata Archive.",
388
434
  )
389
- return _finalize_output(list_info, return_tuple=return_tuple)
435
+ return _finalize_output(
436
+ list_info,
437
+ return_tuple=return_tuple,
438
+ metadata_archive_dir=metadata_archive_dir,
439
+ filter_kwargs=filter_kwargs,
440
+ )
390
441
 
391
442
  # Return stations in the Metadata Archive with specified disdrodb_data_url
392
443
  if product is None and available_data:
393
- list_info = keep_list_info_with_disdrodb_data_url(metadata_archive_dir, list_info)
444
+ list_info = select_stations_with_disdrodb_data_url(metadata_archive_dir, list_info)
394
445
  _raise_an_error_if_no_stations(
395
446
  list_info,
396
447
  raise_error_if_empty=raise_error_if_empty,
397
448
  msg="No station has the disdrodb_data_url specified in the metadata.",
398
449
  )
399
- return _finalize_output(list_info, return_tuple=return_tuple)
450
+ return _finalize_output(
451
+ list_info,
452
+ return_tuple=return_tuple,
453
+ metadata_archive_dir=metadata_archive_dir,
454
+ filter_kwargs=filter_kwargs,
455
+ )
400
456
 
401
457
  # If product is specified, select stations available in the local DISDRODB Data Archive
402
458
  # - If available_data=False, search for station with the existing product directory (do not check for data)
403
459
  data_archive_dir = get_data_archive_dir(data_archive_dir)
404
460
  product = check_product(product)
405
461
  if not available_data:
406
- list_info = keep_list_info_elements_with_product_directory(
462
+ list_info = select_stations_with_product_directory(
407
463
  data_archive_dir=data_archive_dir,
408
464
  product=product,
409
465
  list_info=list_info,
@@ -413,11 +469,16 @@ def available_stations(
413
469
  raise_error_if_empty=raise_error_if_empty,
414
470
  msg=f"No station product {product} directory available in the local DISDRODB Data Archive.",
415
471
  )
416
- return _finalize_output(list_info, return_tuple=return_tuple)
472
+ return _finalize_output(
473
+ list_info,
474
+ return_tuple=return_tuple,
475
+ metadata_archive_dir=metadata_archive_dir,
476
+ filter_kwargs=filter_kwargs,
477
+ )
417
478
 
418
479
  # - If available_data=True, search for station with product data
419
480
  product_kwargs = check_product_kwargs(product, product_kwargs)
420
- list_info = keep_list_info_elements_with_product_data(
481
+ list_info = select_stations_with_product_data(
421
482
  data_archive_dir=data_archive_dir,
422
483
  product=product,
423
484
  list_info=list_info,
@@ -429,7 +490,12 @@ def available_stations(
429
490
  raise_error_if_empty=raise_error_if_empty,
430
491
  msg=f"No station has {product} {product_kwargs} data available in the local DISDRODB Data Archive.",
431
492
  )
432
- return _finalize_output(list_info, return_tuple=return_tuple)
493
+ return _finalize_output(
494
+ list_info,
495
+ return_tuple=return_tuple,
496
+ metadata_archive_dir=metadata_archive_dir,
497
+ filter_kwargs=filter_kwargs,
498
+ )
433
499
 
434
500
 
435
501
  def available_data_sources(
@@ -441,7 +507,7 @@ def available_data_sources(
441
507
  invalid_fields_policy="raise",
442
508
  data_archive_dir=None,
443
509
  metadata_archive_dir=None,
444
- **product_kwargs,
510
+ **kwargs,
445
511
  ):
446
512
  """Return data sources for which stations are available."""
447
513
  list_info = available_stations(
@@ -455,7 +521,7 @@ def available_data_sources(
455
521
  invalid_fields_policy=invalid_fields_policy,
456
522
  data_archive_dir=data_archive_dir,
457
523
  metadata_archive_dir=metadata_archive_dir,
458
- **product_kwargs,
524
+ **kwargs,
459
525
  )
460
526
  data_sources = [info[0] for info in list_info]
461
527
  data_sources = np.unique(data_sources).tolist()
@@ -471,7 +537,7 @@ def available_campaigns(
471
537
  invalid_fields_policy="raise",
472
538
  data_archive_dir=None,
473
539
  metadata_archive_dir=None,
474
- **product_kwargs,
540
+ **kwargs,
475
541
  ):
476
542
  """Return campaigns names for which stations are available."""
477
543
  list_info = available_stations(
@@ -485,7 +551,7 @@ def available_campaigns(
485
551
  invalid_fields_policy=invalid_fields_policy,
486
552
  data_archive_dir=data_archive_dir,
487
553
  metadata_archive_dir=metadata_archive_dir,
488
- **product_kwargs,
554
+ **kwargs,
489
555
  )
490
556
  campaign_names = [info[1] for info in list_info]
491
557
  campaign_names = np.unique(campaign_names).tolist()