disdrodb 0.2.1__py3-none-any.whl → 0.4.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 (313) hide show
  1. disdrodb/__init__.py +3 -1
  2. disdrodb/_config.py +2 -3
  3. disdrodb/_version.py +2 -2
  4. disdrodb/accessor/__init__.py +2 -1
  5. disdrodb/accessor/methods.py +10 -9
  6. disdrodb/api/checks.py +3 -7
  7. disdrodb/api/configs.py +1 -3
  8. disdrodb/api/create_directories.py +4 -6
  9. disdrodb/api/info.py +1 -3
  10. disdrodb/api/io.py +233 -32
  11. disdrodb/api/path.py +3 -7
  12. disdrodb/cli/disdrodb_check_metadata_archive.py +3 -2
  13. disdrodb/cli/disdrodb_check_products_options.py +45 -0
  14. disdrodb/cli/disdrodb_create_summary.py +54 -28
  15. disdrodb/cli/disdrodb_create_summary_station.py +41 -20
  16. disdrodb/cli/disdrodb_data_archive_directory.py +2 -3
  17. disdrodb/cli/disdrodb_download_archive.py +50 -30
  18. disdrodb/cli/disdrodb_download_metadata_archive.py +28 -16
  19. disdrodb/cli/disdrodb_download_station.py +58 -29
  20. disdrodb/cli/disdrodb_initialize_station.py +43 -23
  21. disdrodb/cli/disdrodb_metadata_archive_directory.py +2 -3
  22. disdrodb/cli/disdrodb_open_data_archive.py +17 -13
  23. disdrodb/cli/disdrodb_open_logs_directory.py +31 -21
  24. disdrodb/cli/disdrodb_open_metadata_archive.py +26 -13
  25. disdrodb/cli/disdrodb_open_metadata_directory.py +34 -23
  26. disdrodb/cli/disdrodb_open_product_directory.py +39 -23
  27. disdrodb/cli/disdrodb_open_readers_directory.py +2 -3
  28. disdrodb/cli/disdrodb_run.py +189 -0
  29. disdrodb/cli/disdrodb_run_l0.py +61 -70
  30. disdrodb/cli/disdrodb_run_l0_station.py +50 -55
  31. disdrodb/cli/disdrodb_run_l0a.py +53 -51
  32. disdrodb/cli/disdrodb_run_l0a_station.py +41 -40
  33. disdrodb/cli/disdrodb_run_l0b.py +51 -51
  34. disdrodb/cli/disdrodb_run_l0b_station.py +40 -39
  35. disdrodb/cli/disdrodb_run_l0c.py +56 -53
  36. disdrodb/cli/disdrodb_run_l0c_station.py +44 -41
  37. disdrodb/cli/disdrodb_run_l1.py +55 -51
  38. disdrodb/cli/disdrodb_run_l1_station.py +43 -40
  39. disdrodb/cli/disdrodb_run_l2e.py +56 -51
  40. disdrodb/cli/disdrodb_run_l2e_station.py +44 -40
  41. disdrodb/cli/disdrodb_run_l2m.py +55 -51
  42. disdrodb/cli/disdrodb_run_l2m_station.py +43 -40
  43. disdrodb/cli/disdrodb_run_station.py +184 -0
  44. disdrodb/cli/disdrodb_upload_archive.py +51 -42
  45. disdrodb/cli/disdrodb_upload_station.py +42 -36
  46. disdrodb/configs.py +20 -16
  47. disdrodb/constants.py +5 -2
  48. disdrodb/data_transfer/__init__.py +1 -3
  49. disdrodb/data_transfer/download_data.py +45 -61
  50. disdrodb/data_transfer/upload_data.py +7 -11
  51. disdrodb/data_transfer/zenodo.py +2 -4
  52. disdrodb/docs.py +1 -3
  53. disdrodb/etc/configs/attributes.yaml +52 -2
  54. disdrodb/etc/configs/encodings.yaml +45 -1
  55. disdrodb/etc/products/L0C/ODM470/global.yaml +5 -0
  56. disdrodb/etc/products/L0C/global.yaml +5 -0
  57. disdrodb/etc/products/L1/ODM470/global.yaml +6 -0
  58. disdrodb/etc/products/L1/global.yaml +0 -13
  59. disdrodb/etc/products/L2E/LPM/1MIN.yaml +1 -0
  60. disdrodb/etc/products/L2E/LPM/global.yaml +36 -0
  61. disdrodb/etc/products/L2E/LPM_V0/1MIN.yaml +1 -0
  62. disdrodb/etc/products/L2E/LPM_V0/global.yaml +36 -0
  63. disdrodb/etc/products/L2E/ODM470/1MIN.yaml +1 -0
  64. disdrodb/etc/products/L2E/ODM470/global.yaml +36 -0
  65. disdrodb/etc/products/L2E/PARSIVEL/1MIN.yaml +1 -0
  66. disdrodb/etc/products/L2E/PARSIVEL/global.yaml +36 -0
  67. disdrodb/etc/products/L2E/PARSIVEL2/1MIN.yaml +1 -0
  68. disdrodb/etc/products/L2E/PARSIVEL2/global.yaml +36 -0
  69. disdrodb/etc/products/L2E/PWS100/1MIN.yaml +1 -0
  70. disdrodb/etc/products/L2E/PWS100/global.yaml +36 -0
  71. disdrodb/etc/products/L2E/RD80/1MIN.yaml +19 -0
  72. disdrodb/etc/products/L2E/SWS250/1MIN.yaml +19 -0
  73. disdrodb/etc/products/L2E/global.yaml +16 -2
  74. disdrodb/fall_velocity/__init__.py +47 -0
  75. disdrodb/fall_velocity/graupel.py +484 -0
  76. disdrodb/fall_velocity/hail.py +288 -0
  77. disdrodb/{l1/fall_velocity.py → fall_velocity/rain.py} +265 -44
  78. disdrodb/issue/__init__.py +1 -3
  79. disdrodb/issue/checks.py +2 -3
  80. disdrodb/issue/reader.py +2 -3
  81. disdrodb/issue/writer.py +2 -5
  82. disdrodb/l0/__init__.py +2 -1
  83. disdrodb/l0/check_configs.py +36 -29
  84. disdrodb/l0/check_standards.py +1 -4
  85. disdrodb/l0/configs/LPM/l0a_encodings.yml +17 -17
  86. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +55 -55
  87. disdrodb/l0/configs/LPM/l0b_encodings.yml +17 -17
  88. disdrodb/l0/configs/LPM/raw_data_format.yml +17 -17
  89. disdrodb/l0/configs/LPM_V0/l0a_encodings.yml +2 -2
  90. disdrodb/l0/configs/LPM_V0/l0b_cf_attrs.yml +2 -2
  91. disdrodb/l0/configs/LPM_V0/l0b_encodings.yml +2 -2
  92. disdrodb/l0/configs/LPM_V0/raw_data_format.yml +2 -2
  93. disdrodb/l0/configs/ODM470/bins_diameter.yml +643 -0
  94. disdrodb/l0/configs/ODM470/bins_velocity.yml +0 -0
  95. disdrodb/l0/configs/ODM470/l0a_encodings.yml +11 -0
  96. disdrodb/l0/configs/ODM470/l0b_cf_attrs.yml +46 -0
  97. disdrodb/l0/configs/ODM470/l0b_encodings.yml +106 -0
  98. disdrodb/l0/configs/ODM470/raw_data_format.yml +111 -0
  99. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  100. disdrodb/l0/l0_reader.py +2 -3
  101. disdrodb/l0/l0a_processing.py +6 -8
  102. disdrodb/l0/l0b_nc_processing.py +3 -6
  103. disdrodb/l0/l0b_processing.py +2 -16
  104. disdrodb/l0/l0c_processing.py +29 -12
  105. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +2 -1
  106. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +18 -18
  107. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +18 -18
  108. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +18 -18
  109. disdrodb/l0/readers/LPM/GERMANY/DWD.py +244 -63
  110. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +65 -23
  111. disdrodb/l0/readers/LPM/ITALY/GID_LPM_AQ.py +277 -0
  112. disdrodb/l0/readers/LPM/ITALY/GID_LPM_PI.py +19 -18
  113. disdrodb/l0/readers/LPM/ITALY/GID_LPM_T.py +23 -19
  114. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +19 -21
  115. disdrodb/l0/readers/LPM/KIT/CHWALA.py +19 -20
  116. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +1 -1
  117. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_RWANDA_LPM_NC.py +18 -18
  118. disdrodb/l0/readers/LPM/NORWAY/HAUKELISETER_LPM.py +19 -20
  119. disdrodb/l0/readers/LPM/NORWAY/NMBU_LPM.py +19 -20
  120. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +19 -20
  121. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +19 -20
  122. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +19 -20
  123. disdrodb/l0/readers/LPM/UK/DIVEN.py +1 -1
  124. disdrodb/l0/readers/LPM/UK/WITHWORTH_LPM.py +19 -20
  125. disdrodb/l0/readers/LPM/USA/CHARLESTON.py +19 -20
  126. disdrodb/l0/readers/LPM/USA/DEVEX.py +255 -0
  127. disdrodb/l0/readers/LPM_V0/BELGIUM/ULIEGE.py +3 -5
  128. disdrodb/l0/readers/LPM_V0/ITALY/GID_LPM_V0.py +4 -3
  129. disdrodb/l0/readers/ODM470/OCEAN/OCEANRAIN.py +124 -0
  130. disdrodb/l0/readers/PARSIVEL/AUSTRALIA/MELBOURNE_2007_PARSIVEL.py +1 -1
  131. disdrodb/l0/readers/PARSIVEL/BASQUECOUNTRY/EUSKALMET_OTT.py +2 -1
  132. disdrodb/l0/readers/PARSIVEL/CHINA/CHONGQING.py +2 -3
  133. disdrodb/l0/readers/PARSIVEL/EPFL/ARCTIC_2021.py +2 -1
  134. disdrodb/l0/readers/PARSIVEL/EPFL/COMMON_2011.py +2 -1
  135. disdrodb/l0/readers/PARSIVEL/EPFL/DAVOS_2009_2011.py +2 -1
  136. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_2009.py +2 -1
  137. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2008.py +2 -1
  138. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2010.py +2 -1
  139. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2011.py +2 -1
  140. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2012.py +2 -1
  141. disdrodb/l0/readers/PARSIVEL/EPFL/GENEPI_2007.py +2 -1
  142. disdrodb/l0/readers/PARSIVEL/EPFL/GRAND_ST_BERNARD_2007.py +2 -1
  143. disdrodb/l0/readers/PARSIVEL/EPFL/GRAND_ST_BERNARD_2007_2.py +2 -1
  144. disdrodb/l0/readers/PARSIVEL/EPFL/HPICONET_2010.py +2 -1
  145. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP2.py +2 -1
  146. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP3.py +2 -1
  147. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP4.py +2 -1
  148. disdrodb/l0/readers/PARSIVEL/EPFL/LOCARNO_2018.py +1 -1
  149. disdrodb/l0/readers/PARSIVEL/EPFL/LOCARNO_2019.py +1 -1
  150. disdrodb/l0/readers/PARSIVEL/EPFL/PARADISO_2014.py +2 -1
  151. disdrodb/l0/readers/PARSIVEL/EPFL/PARSIVEL_2007.py +2 -1
  152. disdrodb/l0/readers/PARSIVEL/EPFL/PLATO_2019.py +1 -1
  153. disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019.py +2 -1
  154. disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019_WJF.py +2 -1
  155. disdrodb/l0/readers/PARSIVEL/EPFL/RIETHOLZBACH_2011.py +2 -1
  156. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2017.py +2 -1
  157. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2019.py +2 -1
  158. disdrodb/l0/readers/PARSIVEL/EPFL/UNIL_2022.py +2 -1
  159. disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +1 -1
  160. disdrodb/l0/readers/PARSIVEL/KOREA/ICEPOP_MSC.py +159 -0
  161. disdrodb/l0/readers/PARSIVEL/NASA/LPVEX.py +1 -1
  162. disdrodb/l0/readers/PARSIVEL/NASA/MC3E.py +1 -1
  163. disdrodb/l0/readers/PARSIVEL/NCAR/CCOPE_2015.py +1 -1
  164. disdrodb/l0/readers/PARSIVEL/NCAR/OWLES_MIPS.py +1 -1
  165. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  166. disdrodb/l0/readers/PARSIVEL/NCAR/PLOWS_MIPS.py +1 -1
  167. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  168. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +1 -3
  169. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +1 -3
  170. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +2 -1
  171. disdrodb/l0/readers/PARSIVEL2/ARM/ARM_PARSIVEL2.py +2 -1
  172. disdrodb/l0/readers/PARSIVEL2/BASQUECOUNTRY/EUSKALMET_OTT2.py +2 -1
  173. disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +2 -3
  174. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +1 -1
  175. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +1 -1
  176. disdrodb/l0/readers/PARSIVEL2/CANADA/UQAM_NC.py +1 -1
  177. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +1 -1
  178. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_nc.py +2 -1
  179. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +2 -1
  180. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +1 -1
  181. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +2 -3
  182. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +2 -2
  183. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -3
  184. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +4 -3
  185. disdrodb/l0/readers/PARSIVEL2/ITALY/GID_PARSIVEL2.py +1 -3
  186. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +6 -3
  187. disdrodb/l0/readers/PARSIVEL2/JAPAN/PRECIP.py +1 -1
  188. disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
  189. disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +1 -1
  190. disdrodb/l0/readers/PARSIVEL2/KOREA/ICEPOP_MSC.py +161 -0
  191. disdrodb/l0/readers/PARSIVEL2/KOREA/ICEPOP_UCLM.py +126 -0
  192. disdrodb/l0/readers/PARSIVEL2/MEXICO/OH_IIUNAM_nc.py +2 -1
  193. disdrodb/l0/readers/PARSIVEL2/MPI/BCO_PARSIVEL2.py +1 -1
  194. disdrodb/l0/readers/PARSIVEL2/MPI/BOWTIE.py +1 -1
  195. disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +3 -1
  196. disdrodb/l0/readers/PARSIVEL2/NASA/NSSTC.py +1 -1
  197. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +2 -1
  198. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  199. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_MIPS.py +1 -1
  200. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +2 -1
  201. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +2 -1
  202. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +1 -1
  203. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +1 -1
  204. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +1 -1
  205. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P1.py +2 -3
  206. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  207. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +2 -1
  208. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +1 -1
  209. disdrodb/l0/readers/PARSIVEL2/NORWAY/UIB.py +10 -2
  210. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PAGASA.py +2 -3
  211. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +1 -1
  212. disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +1 -1
  213. disdrodb/l0/readers/PARSIVEL2/SPAIN/GRANADA.py +2 -3
  214. disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +1 -1
  215. disdrodb/l0/readers/PARSIVEL2/SWEDEN/SMHI.py +2 -1
  216. disdrodb/l0/readers/PARSIVEL2/USA/CSU.py +1 -1
  217. disdrodb/l0/readers/PARSIVEL2/USA/CW3E.py +2 -1
  218. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +2 -3
  219. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +2 -3
  220. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +2 -1
  221. disdrodb/l0/readers/RD80/BRAZIL/ATTO_RD80.py +1 -3
  222. disdrodb/l0/readers/RD80/BRAZIL/CHUVA_RD80.py +1 -3
  223. disdrodb/l0/readers/RD80/BRAZIL/GOAMAZON_RD80.py +1 -3
  224. disdrodb/l0/readers/RD80/NCAR/CINDY_2011_RD80.py +1 -3
  225. disdrodb/l0/readers/RD80/NCAR/RELAMPAGO_RD80.py +1 -3
  226. disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +2 -3
  227. disdrodb/l0/readers/SWS250/BELGIUM/KMI.py +2 -3
  228. disdrodb/l0/readers/template_reader_raw_netcdf_data.py +2 -3
  229. disdrodb/l0/readers/template_reader_raw_text_data.py +2 -3
  230. disdrodb/l0/standards.py +4 -5
  231. disdrodb/l0/template_tools.py +7 -11
  232. disdrodb/l1/__init__.py +2 -1
  233. disdrodb/l1/classification.py +914 -0
  234. disdrodb/l1/processing.py +36 -106
  235. disdrodb/l1/resampling.py +13 -3
  236. disdrodb/l1_env/__init__.py +1 -1
  237. disdrodb/l1_env/routines.py +7 -6
  238. disdrodb/l2/__init__.py +2 -1
  239. disdrodb/l2/empirical_dsd.py +58 -31
  240. disdrodb/l2/processing.py +327 -61
  241. disdrodb/metadata/checks.py +10 -13
  242. disdrodb/metadata/download.py +5 -4
  243. disdrodb/metadata/geolocation.py +3 -4
  244. disdrodb/metadata/info.py +3 -5
  245. disdrodb/metadata/manipulation.py +1 -3
  246. disdrodb/metadata/reader.py +1 -3
  247. disdrodb/metadata/search.py +1 -4
  248. disdrodb/metadata/standards.py +1 -3
  249. disdrodb/metadata/writer.py +1 -3
  250. disdrodb/physics/__init__.py +17 -0
  251. disdrodb/physics/atmosphere.py +273 -0
  252. disdrodb/physics/water.py +131 -0
  253. disdrodb/physics/wrappers.py +63 -0
  254. disdrodb/psd/__init__.py +1 -2
  255. disdrodb/psd/fitting.py +23 -9
  256. disdrodb/psd/models.py +2 -1
  257. disdrodb/routines/__init__.py +6 -1
  258. disdrodb/routines/l0.py +39 -25
  259. disdrodb/routines/l1.py +23 -16
  260. disdrodb/routines/l2.py +12 -9
  261. disdrodb/routines/options.py +117 -73
  262. disdrodb/routines/options_validation.py +728 -0
  263. disdrodb/routines/wrappers.py +460 -40
  264. disdrodb/scattering/__init__.py +1 -2
  265. disdrodb/scattering/axis_ratio.py +6 -6
  266. disdrodb/scattering/permittivity.py +9 -8
  267. disdrodb/scattering/routines.py +33 -15
  268. disdrodb/summary/__init__.py +1 -1
  269. disdrodb/summary/routines.py +95 -30
  270. disdrodb/utils/__init__.py +1 -1
  271. disdrodb/utils/archiving.py +18 -10
  272. disdrodb/utils/attrs.py +7 -5
  273. disdrodb/utils/cli.py +8 -10
  274. disdrodb/utils/compression.py +10 -13
  275. disdrodb/utils/coords.py +45 -0
  276. disdrodb/utils/dask.py +7 -5
  277. disdrodb/utils/dataframe.py +5 -6
  278. disdrodb/utils/decorators.py +3 -4
  279. disdrodb/utils/dict.py +1 -1
  280. disdrodb/utils/directories.py +5 -7
  281. disdrodb/utils/encoding.py +4 -5
  282. disdrodb/utils/event.py +1 -1
  283. disdrodb/utils/list.py +1 -3
  284. disdrodb/utils/logger.py +1 -3
  285. disdrodb/utils/manipulations.py +175 -4
  286. disdrodb/utils/pydantic.py +81 -0
  287. disdrodb/utils/routines.py +2 -3
  288. disdrodb/utils/subsetting.py +1 -1
  289. disdrodb/utils/time.py +6 -4
  290. disdrodb/utils/warnings.py +2 -3
  291. disdrodb/utils/writer.py +5 -3
  292. disdrodb/utils/xarray.py +31 -3
  293. disdrodb/utils/yaml.py +1 -3
  294. disdrodb/viz/__init__.py +1 -1
  295. disdrodb/viz/plots.py +193 -18
  296. {disdrodb-0.2.1.dist-info → disdrodb-0.4.0.dist-info}/METADATA +5 -4
  297. disdrodb-0.4.0.dist-info/RECORD +361 -0
  298. {disdrodb-0.2.1.dist-info → disdrodb-0.4.0.dist-info}/entry_points.txt +3 -0
  299. disdrodb/etc/products/L1/1MIN.yaml +0 -13
  300. disdrodb/etc/products/L1/LPM/1MIN.yaml +0 -13
  301. disdrodb/etc/products/L1/LPM_V0/1MIN.yaml +0 -13
  302. disdrodb/etc/products/L1/PARSIVEL/1MIN.yaml +0 -13
  303. disdrodb/etc/products/L1/PARSIVEL2/1MIN.yaml +0 -13
  304. disdrodb/etc/products/L1/PWS100/1MIN.yaml +0 -13
  305. disdrodb/etc/products/L1/RD80/1MIN.yaml +0 -13
  306. disdrodb/etc/products/L1/SWS250/1MIN.yaml +0 -13
  307. disdrodb/etc/products/L2M/10MIN.yaml +0 -12
  308. disdrodb/l1/beard_model.py +0 -662
  309. disdrodb/l1/filters.py +0 -205
  310. disdrodb-0.2.1.dist-info/RECORD +0 -329
  311. {disdrodb-0.2.1.dist-info → disdrodb-0.4.0.dist-info}/WHEEL +0 -0
  312. {disdrodb-0.2.1.dist-info → disdrodb-0.4.0.dist-info}/licenses/LICENSE +0 -0
  313. {disdrodb-0.2.1.dist-info → disdrodb-0.4.0.dist-info}/top_level.txt +0 -0
disdrodb/l2/processing.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -----------------------------------------------------------------------------.
2
- # Copyright (c) 2021-2023 DISDRODB developers
2
+ # Copyright (c) 2021-2026 DISDRODB developers
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
5
5
  # it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
19
19
  import numpy as np
20
20
  import xarray as xr
21
21
 
22
- from disdrodb.constants import DIAMETER_DIMENSION
23
- from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity
22
+ from disdrodb.constants import DIAMETER_DIMENSION, METEOROLOGICAL_VARIABLES, VELOCITY_DIMENSION
23
+ from disdrodb.fall_velocity import get_rain_fall_velocity, get_rain_fall_velocity_from_ds
24
24
  from disdrodb.l1_env.routines import load_env_dataset
25
25
  from disdrodb.l2.empirical_dsd import (
26
26
  BINS_METRICS,
@@ -29,55 +29,23 @@ from disdrodb.l2.empirical_dsd import (
29
29
  compute_spectrum_parameters,
30
30
  get_drop_number_concentration,
31
31
  get_effective_sampling_area,
32
+ get_effective_sampling_interval,
32
33
  get_kinetic_energy_variables_from_drop_number,
34
+ get_min_max_diameter,
33
35
  get_rain_accumulation,
34
36
  get_rain_rate_from_drop_number,
35
37
  )
36
38
  from disdrodb.psd import create_psd, estimate_model_parameters
37
39
  from disdrodb.psd.fitting import compute_gof_stats
38
40
  from disdrodb.utils.decorators import check_pytmatrix_availability
39
- from disdrodb.utils.time import ensure_sample_interval_in_seconds
41
+ from disdrodb.utils.manipulations import (
42
+ define_diameter_array,
43
+ filter_diameter_bins,
44
+ filter_velocity_bins,
45
+ )
40
46
  from disdrodb.utils.writer import finalize_product
41
47
 
42
48
 
43
- def define_diameter_array(diameter_min=0, diameter_max=10, diameter_spacing=0.05):
44
- """
45
- Define an array of diameters and their corresponding bin properties.
46
-
47
- Parameters
48
- ----------
49
- diameter_min : float, optional
50
- The minimum diameter value. The default value is 0 mm.
51
- diameter_max : float, optional
52
- The maximum diameter value. The default value is 10 mm.
53
- diameter_spacing : float, optional
54
- The spacing between diameter values. The default value is 0.05 mm.
55
-
56
- Returns
57
- -------
58
- xr.DataArray
59
- A DataArray containing the center of each diameter bin, with coordinates for
60
- the bin width, lower bound, upper bound, and center.
61
-
62
- """
63
- diameters_bounds = np.arange(diameter_min, diameter_max + diameter_spacing / 2, step=diameter_spacing)
64
- diameters_bin_lower = diameters_bounds[:-1]
65
- diameters_bin_upper = diameters_bounds[1:]
66
- diameters_bin_width = diameters_bin_upper - diameters_bin_lower
67
- diameters_bin_center = diameters_bin_lower + diameters_bin_width / 2
68
- da = xr.DataArray(
69
- diameters_bin_center,
70
- dims="diameter_bin_center",
71
- coords={
72
- "diameter_bin_width": ("diameter_bin_center", diameters_bin_width),
73
- "diameter_bin_lower": ("diameter_bin_center", diameters_bin_lower),
74
- "diameter_bin_upper": ("diameter_bin_center", diameters_bin_upper),
75
- "diameter_bin_center": ("diameter_bin_center", diameters_bin_center),
76
- },
77
- )
78
- return da
79
-
80
-
81
49
  def define_velocity_array(ds):
82
50
  """
83
51
  Create the fall velocity DataArray using various methods.
@@ -99,7 +67,7 @@ def define_velocity_array(ds):
99
67
  if "velocity_bin_center" in ds.dims:
100
68
  velocity = xr.Dataset(
101
69
  {
102
- "fall_velocity": xr.ones_like(drop_number) * ds["fall_velocity"],
70
+ "theoretical_velocity": xr.ones_like(drop_number) * ds["fall_velocity"],
103
71
  "measured_velocity": xr.ones_like(drop_number) * ds["velocity_bin_center"],
104
72
  },
105
73
  ).to_array(dim="velocity_method")
@@ -108,12 +76,183 @@ def define_velocity_array(ds):
108
76
  return velocity
109
77
 
110
78
 
79
+ ####--------------------------------------------------------------------------
80
+ #### Extract drop spectrum
81
+
82
+
83
+ def retrieve_drop_spectrum(
84
+ ds,
85
+ ds_env,
86
+ above_velocity_fraction=None,
87
+ above_velocity_tolerance=None,
88
+ below_velocity_fraction=None,
89
+ below_velocity_tolerance=None,
90
+ maintain_drops_smaller_than=1,
91
+ maintain_drops_slower_than=2.5,
92
+ maintain_smallest_drops=False,
93
+ remove_splashing_drops=True,
94
+ fall_velocity_model="Beard1976",
95
+ ):
96
+ """Retrieve the drop spectrum from the DISDRODB L1 product."""
97
+ from disdrodb.fall_velocity.rain import get_rain_fall_velocity
98
+
99
+ # Retrieve spectrum
100
+ raw_spectrum = ds["raw_drop_number"].copy()
101
+
102
+ # Retrieve coordinates
103
+ diameter_upper = raw_spectrum["diameter_bin_upper"]
104
+ diameter_lower = raw_spectrum["diameter_bin_lower"]
105
+ velocity_upper = raw_spectrum["velocity_bin_upper"]
106
+
107
+ # Retrieve rainfall mask
108
+ raindrop_fall_velocity_upper = get_rain_fall_velocity(
109
+ diameter=diameter_upper,
110
+ model=fall_velocity_model,
111
+ ds_env=ds_env,
112
+ )
113
+ raindrop_fall_velocity_lower = get_rain_fall_velocity(
114
+ diameter=diameter_lower,
115
+ model=fall_velocity_model,
116
+ ds_env=ds_env,
117
+ )
118
+ rain_mask = define_rain_spectrum_mask(
119
+ drop_number=raw_spectrum,
120
+ fall_velocity_lower=raindrop_fall_velocity_lower,
121
+ fall_velocity_upper=raindrop_fall_velocity_upper,
122
+ above_velocity_fraction=above_velocity_fraction,
123
+ above_velocity_tolerance=above_velocity_tolerance,
124
+ below_velocity_fraction=below_velocity_fraction,
125
+ below_velocity_tolerance=below_velocity_tolerance,
126
+ maintain_drops_smaller_than=maintain_drops_smaller_than,
127
+ maintain_drops_slower_than=maintain_drops_slower_than,
128
+ maintain_smallest_drops=maintain_smallest_drops,
129
+ )
130
+
131
+ # Set to 0 spectrum not classified as liquid or mixed
132
+ if "precipitation_type" in ds:
133
+ raw_spectrum = xr.where(ds["precipitation_type"].isin([0, 2]), raw_spectrum, 0)
134
+
135
+ # Retrieve drop spectrum
136
+ # - Liquid + Mixed
137
+ drop_spectrum = raw_spectrum.where(rain_mask, 0)
138
+
139
+ # Optionally mask area affected by splashing
140
+ if remove_splashing_drops and "flag_splashing" in ds:
141
+ flag_splashing = ds["flag_splashing"]
142
+ splash_mask = (diameter_lower >= 0.0) & (diameter_upper <= 6) & (velocity_upper <= 0.6)
143
+
144
+ drop_spectrum = xr.where(flag_splashing == 1, drop_spectrum.where(~splash_mask, 0), drop_spectrum)
145
+ return drop_spectrum
146
+
147
+
148
+ def define_rain_spectrum_mask(
149
+ drop_number,
150
+ fall_velocity_lower,
151
+ fall_velocity_upper,
152
+ above_velocity_fraction=None,
153
+ above_velocity_tolerance=None,
154
+ below_velocity_fraction=None,
155
+ below_velocity_tolerance=None,
156
+ maintain_drops_smaller_than=1, # 1, # 2
157
+ maintain_drops_slower_than=2.5, # 2.5, # 3
158
+ maintain_smallest_drops=False,
159
+ ):
160
+ """Define a mask for the drop spectrum based on fall velocity thresholds.
161
+
162
+ Parameters
163
+ ----------
164
+ drop_number : xarray.DataArray
165
+ Array of drop counts per diameter and velocity bins.
166
+ fall_velocity_lower : array-like
167
+ The expected terminal fall velocities lower bound for rain drops of given size interval.
168
+ fall_velocity_upper : array-like
169
+ The expected terminal fall velocities upper bound for rain drops of given size interval.
170
+ above_velocity_fraction : float, optional
171
+ Fraction of terminal fall velocity above which rain drops are considered too fast.
172
+ Either specify ``above_velocity_fraction`` or ``above_velocity_tolerance``.
173
+ above_velocity_tolerance : float, optional
174
+ Absolute tolerance above which rain drops terminal fall velocities are considered too fast.
175
+ Either specify ``above_velocity_fraction`` or ``above_velocity_tolerance``.
176
+ below_velocity_fraction : float, optional
177
+ Fraction of terminal fall velocity below which rain drops are considered too slow.
178
+ Either specify ``below_velocity_fraction`` or ``below_velocity_tolerance``.
179
+ below_velocity_tolerance : float, optional
180
+ Absolute tolerance below which rain drops terminal fall velocities are considered too slow.
181
+ Either specify ``below_velocity_fraction`` or ``below_velocity_tolerance``.
182
+ maintain_smallest : bool, optional
183
+ If True, ensures that the small rain drops in the spectrum are retained in the mask.
184
+ The smallest rain drops are characterized by ``maintain_drops_smaller_than``
185
+ and ``maintain_drops_slower_than`` arguments.
186
+ Defaults to False.
187
+ maintain_drops_smaller_than : float, optional
188
+ The diameter threshold to use for keeping the smallest rain drop.
189
+ Defaults to 1 mm.
190
+ maintain_drops_slower_than : float, optional
191
+ The fall velocity threshold to use for keeping the smallest rain drops.
192
+ Defaults to 2.5 m/s.
193
+
194
+ Returns
195
+ -------
196
+ xarray.DataArray
197
+ A boolean mask array indicating valid bins according to the specified criteria.
198
+
199
+ """
200
+ # Ensure it creates a 2D mask if the fall_velocity does not vary over time
201
+ if "time" in drop_number.dims and "time" not in fall_velocity_lower.dims:
202
+ drop_number = drop_number.isel(time=0)
203
+
204
+ # Check arguments
205
+ if above_velocity_fraction is not None and above_velocity_tolerance is not None:
206
+ raise ValueError("Either specify 'above_velocity_fraction' or 'above_velocity_tolerance'.")
207
+ if below_velocity_fraction is not None and below_velocity_tolerance is not None:
208
+ raise ValueError("Either specify 'below_velocity_fraction' or 'below_velocity_tolerance'.")
209
+
210
+ # Define above/below velocity thresholds
211
+ if above_velocity_fraction is not None:
212
+ above_fall_velocity = fall_velocity_upper * (1 + above_velocity_fraction)
213
+ elif above_velocity_tolerance is not None:
214
+ above_fall_velocity = fall_velocity_upper + above_velocity_tolerance
215
+ else:
216
+ above_fall_velocity = np.inf
217
+
218
+ if below_velocity_fraction is not None:
219
+ below_fall_velocity = fall_velocity_lower * (1 - below_velocity_fraction)
220
+ elif below_velocity_tolerance is not None:
221
+ below_fall_velocity = fall_velocity_lower - below_velocity_tolerance
222
+ else:
223
+ below_fall_velocity = 0
224
+
225
+ # Define velocity 2D array
226
+ velocity_lower = xr.ones_like(drop_number) * drop_number["velocity_bin_lower"]
227
+ velocity_upper = xr.ones_like(drop_number) * drop_number["velocity_bin_upper"]
228
+
229
+ # Define mask
230
+ mask = np.logical_and(
231
+ velocity_upper > below_fall_velocity,
232
+ velocity_lower < above_fall_velocity,
233
+ )
234
+
235
+ # Maintant smallest drops
236
+ if maintain_smallest_drops:
237
+ mask_smallest = np.logical_and(
238
+ drop_number["diameter_bin_upper"] <= maintain_drops_smaller_than,
239
+ drop_number["velocity_bin_upper"] <= maintain_drops_slower_than,
240
+ )
241
+ mask = np.logical_or(mask, mask_smallest)
242
+
243
+ return mask
244
+
245
+
111
246
  ####--------------------------------------------------------------------------
112
247
  #### Timesteps filtering functions
113
248
 
114
249
 
115
250
  def select_timesteps_with_drops(ds, minimum_ndrops=0):
116
251
  """Select timesteps with at least the specified number of drops."""
252
+ # If not a unique time dimension, skip subsetting
253
+ if ds["N"].dims != ("time",):
254
+ return ds
255
+ # Otherwise subset time dimension
117
256
  valid_timesteps = ds["N"].to_numpy() >= minimum_ndrops
118
257
  if not valid_timesteps.any().item():
119
258
  raise ValueError(f"No timesteps with N >= {minimum_ndrops}.")
@@ -124,6 +263,10 @@ def select_timesteps_with_drops(ds, minimum_ndrops=0):
124
263
 
125
264
  def select_timesteps_with_minimum_nbins(ds, minimum_nbins):
126
265
  """Select timesteps with at least the specified number of diameter bins with drops."""
266
+ # If not a unique time dimension, skip subsetting
267
+ if ds["Nbins"].dims != ("time",):
268
+ return ds
269
+ # Otherwise subset time dimension
127
270
  if minimum_nbins == 0:
128
271
  return ds
129
272
  valid_timesteps = ds["Nbins"].to_numpy() >= minimum_nbins
@@ -167,7 +310,7 @@ def check_l2e_input_dataset(ds):
167
310
  from disdrodb.scattering import RADAR_OPTIONS
168
311
 
169
312
  # Check minimum required variables, coordinates and dimensions are presents
170
- required_variables = ["drop_number", "fall_velocity"]
313
+ required_variables = ["raw_drop_number"]
171
314
  required_coords = [
172
315
  "diameter_bin_center",
173
316
  "diameter_bin_width",
@@ -194,9 +337,25 @@ def generate_l2e(
194
337
  ds_env=None,
195
338
  compute_spectra=False,
196
339
  compute_percentage_contribution=False,
340
+ # Filtering options
197
341
  minimum_ndrops=1,
198
342
  minimum_nbins=1,
199
343
  minimum_rain_rate=0.01,
344
+ minimum_diameter=0,
345
+ maximum_diameter=10,
346
+ minimum_velocity=0,
347
+ maximum_velocity=12,
348
+ keep_mixed_precipitation=False,
349
+ # Spectrum filtering options
350
+ fall_velocity_model="Beard1976",
351
+ above_velocity_fraction=0.5,
352
+ above_velocity_tolerance=None,
353
+ below_velocity_fraction=0.5,
354
+ below_velocity_tolerance=None,
355
+ maintain_drops_smaller_than=1, # 2
356
+ maintain_drops_slower_than=2.5, # 3
357
+ maintain_smallest_drops=True,
358
+ remove_splashing_drops=True,
200
359
  ):
201
360
  """Generate the DISDRODB L2E dataset from the DISDRODB L1 dataset.
202
361
 
@@ -205,15 +364,40 @@ def generate_l2e(
205
364
  ds : xarray.Dataset
206
365
  DISDRODB L1 dataset.
207
366
  Alternatively, a xarray dataset with at least:
208
-
209
- - variables: drop_number, fall_velocity
367
+ - variables: raw_drop_number
210
368
  - dimension: DIAMETER_DIMENSION
211
369
  - coordinates: diameter_bin_center, diameter_bin_width, sample_interval
212
370
  - attributes: sensor_name
213
-
214
371
  ds_env : xarray.Dataset, optional
215
372
  Environmental dataset used for fall velocity and water density estimates.
216
373
  If None, a default environment dataset will be loaded.
374
+ fall_velocity_model : str, optional
375
+ Model name to estimate drop fall velocity.
376
+ The default method is ``"Beard1976"``.
377
+ minimum_diameter : float, optional
378
+ Minimum diameter for filtering. The default value is 0 mm.
379
+ maximum_diameter : float, optional
380
+ Maximum diameter for filtering. The default value is 10 mm.
381
+ minimum_velocity : float, optional
382
+ Minimum velocity for filtering. The default value is 0 m/s.
383
+ maximum_velocity : float, optional
384
+ Maximum velocity for filtering. The default value is 12 m/s.
385
+ above_velocity_fraction : float, optional
386
+ Fraction of drops above velocity threshold. The default value is 0.5.
387
+ above_velocity_tolerance : float or None, optional
388
+ Tolerance for above velocity filtering. The default value is ``None``.
389
+ below_velocity_fraction : float, optional
390
+ Fraction of drops below velocity threshold. The default value is 0.5.
391
+ below_velocity_tolerance : float or None, optional
392
+ Tolerance for below velocity filtering. The default value is ``None``.
393
+ maintain_drops_smaller_than : float, optional
394
+ Threshold for small diameter drops. The default value is 1.
395
+ maintain_drops_slower_than : float, optional
396
+ Threshold for small velocity drops. The default value is 2.5.
397
+ maintain_smallest_drops : bool, optional
398
+ Whether to maintain the smallest drops. The default value is ``True``.
399
+ remove_splashing_drops: bool, optional
400
+ Whether to mask splashing drops. The default value is ``True``.
217
401
 
218
402
  Returns
219
403
  -------
@@ -223,6 +407,67 @@ def generate_l2e(
223
407
  # Check and prepapre input dataset
224
408
  ds = check_l2e_input_dataset(ds)
225
409
 
410
+ # Select only dry and rainy timesteps
411
+ if "precipitation_type" in ds:
412
+ if keep_mixed_precipitation: # class 4
413
+ ds = ds.isel(time=ds["precipitation_type"].isin([-1, 0, 4]), drop=True)
414
+ else:
415
+ ds = ds.isel(time=ds["precipitation_type"].isin([-1, 0]), drop=True)
416
+
417
+ # Determine if the velocity dimension is available
418
+ has_velocity_dimension = VELOCITY_DIMENSION in ds.dims
419
+
420
+ # - Filter diameter bins
421
+ ds = filter_diameter_bins(ds=ds, minimum_diameter=minimum_diameter, maximum_diameter=maximum_diameter)
422
+ # - Filter velocity bins
423
+ if has_velocity_dimension:
424
+ ds = filter_velocity_bins(ds=ds, minimum_velocity=minimum_velocity, maximum_velocity=maximum_velocity)
425
+
426
+ # -------------------------------------------------------------------------------------------
427
+ # Compute fall velocity
428
+ ds["fall_velocity"] = get_rain_fall_velocity_from_ds(ds=ds, ds_env=ds_env, model=fall_velocity_model)
429
+
430
+ # -------------------------------------------------------
431
+ # Retrieve filtered spectrum and drop counts (summing over velocity dimension if present)
432
+ if has_velocity_dimension:
433
+ drop_number = retrieve_drop_spectrum(
434
+ ds=ds,
435
+ ds_env=ds_env,
436
+ above_velocity_fraction=above_velocity_fraction,
437
+ above_velocity_tolerance=above_velocity_tolerance,
438
+ below_velocity_fraction=below_velocity_fraction,
439
+ below_velocity_tolerance=below_velocity_tolerance,
440
+ maintain_drops_smaller_than=maintain_drops_smaller_than,
441
+ maintain_drops_slower_than=maintain_drops_slower_than,
442
+ maintain_smallest_drops=maintain_smallest_drops,
443
+ remove_splashing_drops=remove_splashing_drops,
444
+ fall_velocity_model=fall_velocity_model,
445
+ )
446
+ drop_counts = drop_number.sum(dim=VELOCITY_DIMENSION) # 1D (diameter)
447
+ drop_counts_raw = ds["raw_drop_number"].sum(dim=VELOCITY_DIMENSION) # 1D (diameter)
448
+ else:
449
+ drop_number = ds["raw_drop_number"] # no filtering applied
450
+ drop_counts = ds["raw_drop_number"] # 1D (diameter)
451
+ drop_counts_raw = ds["raw_drop_number"]
452
+
453
+ ds["drop_number"] = drop_number
454
+ ds["drop_counts"] = drop_counts
455
+
456
+ # -------------------------------------------------------
457
+ # Compute drop statistics
458
+ # - Compute minimum and max drop diameter observed
459
+ min_drop_diameter, max_drop_diameter = get_min_max_diameter(drop_counts)
460
+
461
+ # - Add rain drop statistics
462
+ ds["Dmin"] = min_drop_diameter
463
+ ds["Dmax"] = max_drop_diameter
464
+ ds["N"] = drop_counts.sum(dim=DIAMETER_DIMENSION)
465
+ ds["Nraw"] = drop_counts_raw.sum(dim=DIAMETER_DIMENSION)
466
+ ds["Nremoved"] = ds["Nraw"] - ds["N"]
467
+
468
+ # - Add bins statistics
469
+ ds = add_bins_metrics(ds)
470
+
226
471
  # -------------------------------------------------------
227
472
  # Initialize L2E dataset
228
473
  ds_l2 = xr.Dataset()
@@ -235,9 +480,6 @@ def generate_l2e(
235
480
  # Select timesteps with at least the specified number of drops
236
481
  ds = select_timesteps_with_drops(ds, minimum_ndrops=minimum_ndrops)
237
482
 
238
- # Add bins metrics to resampled data if missing
239
- ds = add_bins_metrics(ds)
240
-
241
483
  # Remove timesteps with not enough bins with drops
242
484
  ds = select_timesteps_with_minimum_nbins(ds, minimum_nbins=minimum_nbins)
243
485
 
@@ -256,24 +498,41 @@ def generate_l2e(
256
498
  diameter = ds["diameter_bin_center"] / 1000 # m
257
499
  diameter_bin_width = ds["diameter_bin_width"] # mm
258
500
  drop_number = ds["drop_number"]
259
- sample_interval = ensure_sample_interval_in_seconds(ds["sample_interval"]) # s
260
501
 
261
- # Compute sampling area [m2]
502
+ # Retrieve effective sampling interval [s]
503
+ sample_interval = get_effective_sampling_interval(ds, sensor_name=sensor_name) # s
504
+
505
+ # Retrieve effective sampling area [m2]
262
506
  sampling_area = get_effective_sampling_area(sensor_name=sensor_name, diameter=diameter) # m2
263
507
 
264
508
  # Copy relevant L1 variables to L2 product
265
509
  variables = [
510
+ # L1 inputs
511
+ "sample_interval",
512
+ "fall_velocity",
266
513
  "raw_drop_number", # 2D V x D
267
514
  "drop_number", # 2D V x D
515
+ # Drop statistics
268
516
  "drop_counts", # 1D D
269
- "sample_interval",
270
517
  "N",
271
518
  "Nremoved",
519
+ "Nraw",
272
520
  "Dmin",
273
521
  "Dmax",
274
- "fall_velocity",
522
+ # L0C QC
523
+ "qc_time",
524
+ # L1 flags and variables
275
525
  "qc_resampling",
276
- "time_qc",
526
+ "precipitation_type",
527
+ "hydrometeor_type",
528
+ "n_margin_fallers",
529
+ "n_splashing",
530
+ "flag_graupel",
531
+ "flag_hail",
532
+ "flag_spikes",
533
+ "flag_splashing",
534
+ "flag_wind_artefacts",
535
+ *METEOROLOGICAL_VARIABLES,
277
536
  ]
278
537
 
279
538
  variables = [var for var in variables if var in ds]
@@ -289,6 +548,7 @@ def generate_l2e(
289
548
  # -------------------------------------------------------------------------------------------
290
549
  # Define velocity array with dimension 'velocity_method'
291
550
  velocity = define_velocity_array(ds)
551
+ velocity = velocity.fillna(0)
292
552
 
293
553
  # Compute drop number concentration (Nt) [#/m3/mm]
294
554
  drop_number_concentration = get_drop_number_concentration(
@@ -398,7 +658,7 @@ def check_l2m_input_dataset(ds):
398
658
  if "drop_number_concentration" not in ds:
399
659
  if "drop_number" in ds:
400
660
  check_l2e_input_dataset(ds)
401
- sample_interval = ensure_sample_interval_in_seconds(ds["sample_interval"])
661
+ sample_interval = get_effective_sampling_interval(ds, sensor_name=ds.attrs["sensor_name"])
402
662
  sampling_area = get_effective_sampling_area(
403
663
  sensor_name=ds.attrs["sensor_name"],
404
664
  diameter=ds["diameter_bin_center"] / 1000,
@@ -503,7 +763,7 @@ def generate_l2m(
503
763
 
504
764
  # Retrieve measurement interval
505
765
  # - If dataset is opened with decode_timedelta=False, sample_interval is already in seconds !
506
- sample_interval = ensure_sample_interval_in_seconds(ds["sample_interval"])
766
+ sample_interval = get_effective_sampling_interval(ds, sensor_name=ds.attrs["sensor_name"])
507
767
 
508
768
  # Select timesteps with at least the specified number of drops
509
769
  ds = select_timesteps_with_drops(ds, minimum_ndrops=minimum_ndrops)
@@ -549,7 +809,7 @@ def generate_l2m(
549
809
  drop_number_concentration = psd(diameter)
550
810
 
551
811
  # Retrieve fall velocity for each new diameter bin
552
- velocity = get_raindrop_fall_velocity(diameter=diameter, model=fall_velocity_model, ds_env=ds_env) # mm
812
+ velocity = get_rain_fall_velocity(diameter=diameter, model=fall_velocity_model, ds_env=ds_env) # mm
553
813
 
554
814
  # Compute integral parameters
555
815
  ds_params = compute_integral_parameters(
@@ -576,9 +836,15 @@ def generate_l2m(
576
836
 
577
837
  # Add empirical drop_number_concentration and fall velocity
578
838
  # - To reuse output dataset to create another L2M dataset or to compute other GOF metrics
579
- ds_params["drop_number_concentration"] = ds["drop_number_concentration"]
580
- ds_params["fall_velocity"] = ds["fall_velocity"]
581
- ds_params["N"] = ds["N"]
839
+ # Copy relevant L1 variables to L2 product
840
+ variables = [
841
+ "drop_number_concentration",
842
+ "fall_velocity",
843
+ "N",
844
+ *METEOROLOGICAL_VARIABLES,
845
+ ]
846
+ variables = [var for var in variables if var in ds]
847
+ ds_params.update(ds[variables])
582
848
  ds_params.update(ds[BINS_METRICS])
583
849
 
584
850
  #### ----------------------------------------------------------------------------.
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env python3
2
-
3
1
  # -----------------------------------------------------------------------------.
4
- # Copyright (c) 2021-2023 DISDRODB developers
2
+ # Copyright (c) 2021-2026 DISDRODB developers
5
3
  #
6
4
  # This program is free software: you can redistribute it and/or modify
7
5
  # it under the terms of the GNU General Public License as published by
@@ -19,7 +17,6 @@
19
17
  """Check metadata."""
20
18
 
21
19
  import os
22
- from typing import Optional, Union
23
20
 
24
21
  import numpy as np
25
22
 
@@ -192,7 +189,7 @@ def check_station_metadata_geolocation(metadata, raise_error_if_unknown=True) ->
192
189
  _check_lonlat_validity(longitude=longitude, latitude=latitude, raise_error_if_unknown=raise_error_if_unknown)
193
190
 
194
191
 
195
- def check_metadata_archive_geolocation(metadata_archive_dir: Optional[str] = None, raise_error_if_unknown=True):
192
+ def check_metadata_archive_geolocation(metadata_archive_dir: str | None = None, raise_error_if_unknown=True):
196
193
  """Check the metadata files have missing or wrong geolocation..
197
194
 
198
195
  Parameters
@@ -261,7 +258,7 @@ def check_station_metadata(data_source, campaign_name, station_name, metadata_ar
261
258
  check_metadata_reader(metadata)
262
259
 
263
260
 
264
- def check_metadata_archive(metadata_archive_dir: Optional[str] = None, raise_error=False):
261
+ def check_metadata_archive(metadata_archive_dir: str | None = None, raise_error=False):
265
262
  """Check the archive metadata compliance.
266
263
 
267
264
  Parameters
@@ -336,7 +333,7 @@ def identify_missing_metadata_coords(metadata_filepaths: str) -> None:
336
333
  check_station_metadata_geolocation(metadata)
337
334
 
338
335
 
339
- def identify_empty_metadata_keys(metadata_filepaths: list, keys: Union[str, list]) -> None:
336
+ def identify_empty_metadata_keys(metadata_filepaths: list, keys: str | list) -> None:
340
337
  """Identify empty metadata keys.
341
338
 
342
339
  Parameters
@@ -360,7 +357,7 @@ def identify_empty_metadata_keys(metadata_filepaths: list, keys: Union[str, list
360
357
  #### Metadata Archive Utilities
361
358
 
362
359
 
363
- def check_metadata_archive_keys(metadata_archive_dir: Optional[str] = None) -> bool:
360
+ def check_metadata_archive_keys(metadata_archive_dir: str | None = None) -> bool:
364
361
  """Check that all metadata files have valid keys.
365
362
 
366
363
  Parameters
@@ -407,7 +404,7 @@ def check_metadata_archive_keys(metadata_archive_dir: Optional[str] = None) -> b
407
404
  return is_valid
408
405
 
409
406
 
410
- def check_metadata_archive_campaign_name(metadata_archive_dir: Optional[str] = None) -> bool:
407
+ def check_metadata_archive_campaign_name(metadata_archive_dir: str | None = None) -> bool:
411
408
  """Check metadata ``campaign_name``.
412
409
 
413
410
  Parameters
@@ -453,7 +450,7 @@ def check_metadata_archive_campaign_name(metadata_archive_dir: Optional[str] = N
453
450
  return is_valid
454
451
 
455
452
 
456
- def check_metadata_archive_data_source(metadata_archive_dir: Optional[str] = None) -> bool:
453
+ def check_metadata_archive_data_source(metadata_archive_dir: str | None = None) -> bool:
457
454
  """Check metadata ``data_source``.
458
455
 
459
456
  Parameters
@@ -499,7 +496,7 @@ def check_metadata_archive_data_source(metadata_archive_dir: Optional[str] = Non
499
496
  return is_valid
500
497
 
501
498
 
502
- def check_metadata_archive_sensor_name(metadata_archive_dir: Optional[str] = None) -> bool:
499
+ def check_metadata_archive_sensor_name(metadata_archive_dir: str | None = None) -> bool:
503
500
  """Check metadata ``sensor_name``.
504
501
 
505
502
  Parameters
@@ -545,7 +542,7 @@ def check_metadata_archive_sensor_name(metadata_archive_dir: Optional[str] = Non
545
542
  return is_valid
546
543
 
547
544
 
548
- def check_metadata_archive_station_name(metadata_archive_dir: Optional[str] = None) -> bool:
545
+ def check_metadata_archive_station_name(metadata_archive_dir: str | None = None) -> bool:
549
546
  """Check metadata ``station_name``.
550
547
 
551
548
  Parameters
@@ -591,7 +588,7 @@ def check_metadata_archive_station_name(metadata_archive_dir: Optional[str] = No
591
588
  return is_valid
592
589
 
593
590
 
594
- def check_metadata_archive_reader(metadata_archive_dir: Optional[str] = None) -> bool:
591
+ def check_metadata_archive_reader(metadata_archive_dir: str | None = None) -> bool:
595
592
  """Check if the ``reader`` key is available and there is the associated reader.
596
593
 
597
594
  Parameters
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env python3
2
-
3
1
  # -----------------------------------------------------------------------------.
4
- # Copyright (c) 2021-2023 DISDRODB developers
2
+ # Copyright (c) 2021-2026 DISDRODB developers
5
3
  #
6
4
  # This program is free software: you can redistribute it and/or modify
7
5
  # it under the terms of the GNU General Public License as published by
@@ -17,12 +15,15 @@
17
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
16
  # -----------------------------------------------------------------------------.
19
17
  """Routine to download the DISDRODB Metadata Archive from GitHub."""
18
+
20
19
  import io
21
20
  import os
22
21
  import shutil
23
22
  import urllib.request
24
23
  import zipfile
25
24
 
25
+ from disdrodb.utils.directories import remove_file_or_directories
26
+
26
27
 
27
28
  def download_metadata_archive(directory_path, force=False):
28
29
  """Download the DISDRODB Metadata Archive to the specified directory.
@@ -64,7 +65,7 @@ def download_metadata_archive(directory_path, force=False):
64
65
  # Handle existing target directory
65
66
  if os.path.exists(target_dir):
66
67
  if force:
67
- shutil.rmtree(target_dir)
68
+ remove_file_or_directories(target_dir)
68
69
  else:
69
70
  raise FileExistsError(
70
71
  f"A DISDRODB Metadata Archive already exists at '{target_dir}'. Use force=True to update it.",