disdrodb 0.2.0__py3-none-any.whl → 0.3.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 (315) hide show
  1. disdrodb/__init__.py +1 -1
  2. disdrodb/_config.py +1 -3
  3. disdrodb/_version.py +2 -2
  4. disdrodb/accessor/__init__.py +1 -1
  5. disdrodb/accessor/methods.py +18 -11
  6. disdrodb/api/checks.py +2 -4
  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 +15 -9
  11. disdrodb/api/path.py +1 -3
  12. disdrodb/cli/disdrodb_check_metadata_archive.py +2 -2
  13. disdrodb/cli/disdrodb_check_products_options.py +44 -0
  14. disdrodb/cli/disdrodb_create_summary.py +48 -22
  15. disdrodb/cli/disdrodb_create_summary_station.py +39 -18
  16. disdrodb/cli/disdrodb_data_archive_directory.py +1 -3
  17. disdrodb/cli/disdrodb_download_archive.py +45 -24
  18. disdrodb/cli/disdrodb_download_metadata_archive.py +27 -16
  19. disdrodb/cli/disdrodb_download_station.py +56 -26
  20. disdrodb/cli/disdrodb_initialize_station.py +40 -20
  21. disdrodb/cli/disdrodb_metadata_archive_directory.py +1 -3
  22. disdrodb/cli/disdrodb_open_data_archive.py +16 -11
  23. disdrodb/cli/disdrodb_open_logs_directory.py +29 -18
  24. disdrodb/cli/disdrodb_open_metadata_archive.py +25 -11
  25. disdrodb/cli/disdrodb_open_metadata_directory.py +32 -20
  26. disdrodb/cli/disdrodb_open_product_directory.py +38 -21
  27. disdrodb/cli/disdrodb_open_readers_directory.py +1 -3
  28. disdrodb/cli/disdrodb_run.py +189 -0
  29. disdrodb/cli/disdrodb_run_l0.py +55 -64
  30. disdrodb/cli/disdrodb_run_l0_station.py +47 -52
  31. disdrodb/cli/disdrodb_run_l0a.py +47 -45
  32. disdrodb/cli/disdrodb_run_l0a_station.py +38 -37
  33. disdrodb/cli/disdrodb_run_l0b.py +45 -45
  34. disdrodb/cli/disdrodb_run_l0b_station.py +37 -36
  35. disdrodb/cli/disdrodb_run_l0c.py +50 -47
  36. disdrodb/cli/disdrodb_run_l0c_station.py +41 -38
  37. disdrodb/cli/disdrodb_run_l1.py +49 -45
  38. disdrodb/cli/disdrodb_run_l1_station.py +40 -37
  39. disdrodb/cli/disdrodb_run_l2e.py +50 -45
  40. disdrodb/cli/disdrodb_run_l2e_station.py +41 -37
  41. disdrodb/cli/disdrodb_run_l2m.py +49 -45
  42. disdrodb/cli/disdrodb_run_l2m_station.py +40 -37
  43. disdrodb/cli/disdrodb_run_station.py +184 -0
  44. disdrodb/cli/disdrodb_upload_archive.py +45 -35
  45. disdrodb/cli/disdrodb_upload_station.py +39 -32
  46. disdrodb/configs.py +13 -8
  47. disdrodb/constants.py +4 -2
  48. disdrodb/data_transfer/__init__.py +1 -3
  49. disdrodb/data_transfer/download_data.py +38 -54
  50. disdrodb/data_transfer/upload_data.py +1 -3
  51. disdrodb/data_transfer/zenodo.py +1 -3
  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 +1 -14
  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 +17 -3
  74. disdrodb/etc/products/L2M/global.yaml +1 -1
  75. disdrodb/fall_velocity/__init__.py +46 -0
  76. disdrodb/fall_velocity/graupel.py +483 -0
  77. disdrodb/fall_velocity/hail.py +287 -0
  78. disdrodb/{l1/fall_velocity.py → fall_velocity/rain.py} +265 -50
  79. disdrodb/issue/__init__.py +1 -3
  80. disdrodb/issue/checks.py +3 -5
  81. disdrodb/issue/reader.py +1 -3
  82. disdrodb/issue/writer.py +1 -3
  83. disdrodb/l0/__init__.py +1 -1
  84. disdrodb/l0/check_configs.py +26 -17
  85. disdrodb/l0/check_standards.py +1 -3
  86. disdrodb/l0/configs/LPM/l0a_encodings.yml +0 -1
  87. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +0 -4
  88. disdrodb/l0/configs/LPM/l0b_encodings.yml +9 -9
  89. disdrodb/l0/configs/LPM/raw_data_format.yml +11 -11
  90. disdrodb/l0/configs/LPM_V0/bins_diameter.yml +103 -0
  91. disdrodb/l0/configs/LPM_V0/bins_velocity.yml +103 -0
  92. disdrodb/l0/configs/LPM_V0/l0a_encodings.yml +45 -0
  93. disdrodb/l0/configs/LPM_V0/l0b_cf_attrs.yml +180 -0
  94. disdrodb/l0/configs/LPM_V0/l0b_encodings.yml +410 -0
  95. disdrodb/l0/configs/LPM_V0/raw_data_format.yml +474 -0
  96. disdrodb/l0/configs/ODM470/bins_diameter.yml +643 -0
  97. disdrodb/l0/configs/ODM470/bins_velocity.yml +0 -0
  98. disdrodb/l0/configs/ODM470/l0a_encodings.yml +11 -0
  99. disdrodb/l0/configs/ODM470/l0b_cf_attrs.yml +46 -0
  100. disdrodb/l0/configs/ODM470/l0b_encodings.yml +106 -0
  101. disdrodb/l0/configs/ODM470/raw_data_format.yml +111 -0
  102. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  103. disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +8 -8
  104. disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +9 -9
  105. disdrodb/l0/l0_reader.py +1 -3
  106. disdrodb/l0/l0a_processing.py +7 -5
  107. disdrodb/l0/l0b_nc_processing.py +2 -4
  108. disdrodb/l0/l0b_processing.py +27 -22
  109. disdrodb/l0/l0c_processing.py +37 -11
  110. disdrodb/l0/manuals/LPM_V0.pdf +0 -0
  111. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +1 -1
  112. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +1 -1
  113. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +1 -1
  114. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +1 -1
  115. disdrodb/l0/readers/LPM/GERMANY/DWD.py +190 -12
  116. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +63 -14
  117. disdrodb/l0/readers/LPM/ITALY/GID_LPM_PI.py +279 -0
  118. disdrodb/l0/readers/LPM/ITALY/GID_LPM_T.py +279 -0
  119. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +3 -5
  120. disdrodb/l0/readers/LPM/KIT/CHWALA.py +1 -3
  121. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +1 -1
  122. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_RWANDA_LPM_NC.py +103 -0
  123. disdrodb/l0/readers/LPM/NORWAY/HAUKELISETER_LPM.py +214 -0
  124. disdrodb/l0/readers/LPM/NORWAY/NMBU_LPM.py +206 -0
  125. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +1 -3
  126. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +1 -3
  127. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +1 -3
  128. disdrodb/l0/readers/LPM/UK/DIVEN.py +1 -1
  129. disdrodb/l0/readers/LPM/UK/WITHWORTH_LPM.py +217 -0
  130. disdrodb/l0/readers/LPM/USA/CHARLESTON.py +227 -0
  131. disdrodb/l0/readers/{LPM → LPM_V0}/BELGIUM/ULIEGE.py +34 -52
  132. disdrodb/l0/readers/LPM_V0/ITALY/GID_LPM_V0.py +240 -0
  133. disdrodb/l0/readers/ODM470/OCEAN/OCEANRAIN.py +123 -0
  134. disdrodb/l0/readers/PARSIVEL/AUSTRALIA/MELBOURNE_2007_PARSIVEL.py +1 -1
  135. disdrodb/l0/readers/PARSIVEL/BASQUECOUNTRY/EUSKALMET_OTT.py +1 -1
  136. disdrodb/l0/readers/PARSIVEL/CHINA/CHONGQING.py +1 -3
  137. disdrodb/l0/readers/PARSIVEL/EPFL/ARCTIC_2021.py +1 -1
  138. disdrodb/l0/readers/PARSIVEL/EPFL/COMMON_2011.py +1 -1
  139. disdrodb/l0/readers/PARSIVEL/EPFL/DAVOS_2009_2011.py +1 -1
  140. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_2009.py +1 -1
  141. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2008.py +1 -1
  142. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2010.py +1 -1
  143. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2011.py +1 -1
  144. disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2012.py +1 -1
  145. disdrodb/l0/readers/PARSIVEL/EPFL/GENEPI_2007.py +1 -1
  146. disdrodb/l0/readers/PARSIVEL/EPFL/GRAND_ST_BERNARD_2007.py +1 -1
  147. disdrodb/l0/readers/PARSIVEL/EPFL/GRAND_ST_BERNARD_2007_2.py +1 -1
  148. disdrodb/l0/readers/PARSIVEL/EPFL/HPICONET_2010.py +1 -1
  149. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP2.py +1 -1
  150. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP3.py +1 -1
  151. disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP4.py +1 -1
  152. disdrodb/l0/readers/PARSIVEL/EPFL/LOCARNO_2018.py +1 -1
  153. disdrodb/l0/readers/PARSIVEL/EPFL/LOCARNO_2019.py +1 -1
  154. disdrodb/l0/readers/PARSIVEL/EPFL/PARADISO_2014.py +1 -1
  155. disdrodb/l0/readers/PARSIVEL/EPFL/PARSIVEL_2007.py +1 -1
  156. disdrodb/l0/readers/PARSIVEL/EPFL/PLATO_2019.py +1 -1
  157. disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019.py +1 -1
  158. disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019_WJF.py +1 -1
  159. disdrodb/l0/readers/PARSIVEL/EPFL/RIETHOLZBACH_2011.py +1 -1
  160. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2017.py +1 -1
  161. disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2019.py +1 -1
  162. disdrodb/l0/readers/PARSIVEL/EPFL/UNIL_2022.py +1 -1
  163. disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +1 -1
  164. disdrodb/l0/readers/PARSIVEL/KOREA/ICEPOP_MSC.py +159 -0
  165. disdrodb/l0/readers/PARSIVEL/NASA/LPVEX.py +26 -14
  166. disdrodb/l0/readers/PARSIVEL/NASA/MC3E.py +2 -2
  167. disdrodb/l0/readers/PARSIVEL/NCAR/CCOPE_2015.py +1 -1
  168. disdrodb/l0/readers/PARSIVEL/NCAR/OWLES_MIPS.py +1 -1
  169. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  170. disdrodb/l0/readers/PARSIVEL/NCAR/PLOWS_MIPS.py +1 -1
  171. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  172. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +1 -3
  173. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +1 -3
  174. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +1 -1
  175. disdrodb/l0/readers/PARSIVEL2/ARM/ARM_PARSIVEL2.py +1 -1
  176. disdrodb/l0/readers/PARSIVEL2/BASQUECOUNTRY/EUSKALMET_OTT2.py +2 -2
  177. disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +1 -3
  178. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +1 -1
  179. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +1 -1
  180. disdrodb/l0/readers/PARSIVEL2/CANADA/UQAM_NC.py +1 -1
  181. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +1 -1
  182. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_nc.py +1 -1
  183. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +1 -1
  184. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +1 -1
  185. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +1 -3
  186. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
  187. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -3
  188. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +4 -3
  189. disdrodb/l0/readers/PARSIVEL2/ITALY/GID_PARSIVEL2.py +1 -3
  190. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +5 -3
  191. disdrodb/l0/readers/PARSIVEL2/JAPAN/PRECIP.py +155 -0
  192. disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
  193. disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +1 -1
  194. disdrodb/l0/readers/PARSIVEL2/KOREA/ICEPOP_MSC.py +161 -0
  195. disdrodb/l0/readers/PARSIVEL2/{NASA/GCPEX.py → KOREA/ICEPOP_UCLM.py} +51 -31
  196. disdrodb/l0/readers/PARSIVEL2/MEXICO/OH_IIUNAM_nc.py +1 -1
  197. disdrodb/l0/readers/PARSIVEL2/MPI/BCO_PARSIVEL2.py +15 -8
  198. disdrodb/l0/readers/PARSIVEL2/MPI/BOWTIE.py +9 -4
  199. disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +31 -6
  200. disdrodb/l0/readers/PARSIVEL2/NASA/NSSTC.py +1 -1
  201. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -1
  202. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  203. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_MIPS.py +1 -1
  204. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +1 -1
  205. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +1 -1
  206. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +2 -2
  207. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +1 -1
  208. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +1 -1
  209. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P1.py +1 -3
  210. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  211. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +1 -1
  212. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +1 -1
  213. disdrodb/l0/readers/{PARSIVEL/NASA/PIERS.py → PARSIVEL2/NORWAY/UIB.py} +65 -31
  214. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PAGASA.py +7 -6
  215. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +1 -1
  216. disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +1 -1
  217. disdrodb/l0/readers/PARSIVEL2/SPAIN/GRANADA.py +1 -3
  218. disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +1 -1
  219. disdrodb/l0/readers/PARSIVEL2/SWEDEN/SMHI.py +1 -1
  220. disdrodb/l0/readers/PARSIVEL2/USA/CSU.py +138 -0
  221. disdrodb/l0/readers/PARSIVEL2/USA/CW3E.py +49 -22
  222. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +1 -3
  223. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +1 -3
  224. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +1 -1
  225. disdrodb/l0/readers/{PARSIVEL/NASA/IFLOODS.py → RD80/BRAZIL/ATTO_RD80.py} +50 -36
  226. disdrodb/l0/readers/RD80/BRAZIL/CHUVA_RD80.py +1 -3
  227. disdrodb/l0/readers/RD80/BRAZIL/GOAMAZON_RD80.py +1 -3
  228. disdrodb/l0/readers/RD80/NCAR/CINDY_2011_RD80.py +1 -3
  229. disdrodb/l0/readers/RD80/NCAR/RELAMPAGO_RD80.py +1 -3
  230. disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +1 -3
  231. disdrodb/l0/readers/{SW250 → SWS250}/BELGIUM/KMI.py +2 -4
  232. disdrodb/l0/readers/template_reader_raw_netcdf_data.py +1 -3
  233. disdrodb/l0/readers/template_reader_raw_text_data.py +1 -3
  234. disdrodb/l0/standards.py +4 -5
  235. disdrodb/l0/template_tools.py +1 -3
  236. disdrodb/l1/__init__.py +1 -1
  237. disdrodb/l1/classification.py +913 -0
  238. disdrodb/l1/processing.py +36 -106
  239. disdrodb/l1/resampling.py +8 -3
  240. disdrodb/l1_env/__init__.py +1 -1
  241. disdrodb/l1_env/routines.py +6 -6
  242. disdrodb/l2/__init__.py +1 -1
  243. disdrodb/l2/empirical_dsd.py +61 -31
  244. disdrodb/l2/processing.py +327 -62
  245. disdrodb/metadata/checks.py +1 -3
  246. disdrodb/metadata/download.py +4 -4
  247. disdrodb/metadata/geolocation.py +1 -3
  248. disdrodb/metadata/info.py +1 -3
  249. disdrodb/metadata/manipulation.py +1 -3
  250. disdrodb/metadata/reader.py +1 -3
  251. disdrodb/metadata/search.py +1 -3
  252. disdrodb/metadata/standards.py +1 -3
  253. disdrodb/metadata/writer.py +1 -3
  254. disdrodb/physics/__init__.py +17 -0
  255. disdrodb/physics/atmosphere.py +272 -0
  256. disdrodb/physics/water.py +130 -0
  257. disdrodb/physics/wrappers.py +62 -0
  258. disdrodb/psd/__init__.py +1 -1
  259. disdrodb/psd/fitting.py +22 -9
  260. disdrodb/psd/models.py +1 -1
  261. disdrodb/routines/__init__.py +5 -1
  262. disdrodb/routines/l0.py +28 -18
  263. disdrodb/routines/l1.py +8 -6
  264. disdrodb/routines/l2.py +8 -4
  265. disdrodb/routines/options.py +116 -71
  266. disdrodb/routines/options_validation.py +728 -0
  267. disdrodb/routines/wrappers.py +431 -11
  268. disdrodb/scattering/__init__.py +1 -1
  269. disdrodb/scattering/axis_ratio.py +9 -6
  270. disdrodb/scattering/permittivity.py +8 -8
  271. disdrodb/scattering/routines.py +32 -14
  272. disdrodb/summary/__init__.py +1 -1
  273. disdrodb/summary/routines.py +146 -86
  274. disdrodb/utils/__init__.py +1 -1
  275. disdrodb/utils/archiving.py +16 -9
  276. disdrodb/utils/attrs.py +4 -3
  277. disdrodb/utils/cli.py +8 -10
  278. disdrodb/utils/compression.py +13 -13
  279. disdrodb/utils/dask.py +33 -14
  280. disdrodb/utils/dataframe.py +1 -3
  281. disdrodb/utils/decorators.py +1 -3
  282. disdrodb/utils/dict.py +1 -1
  283. disdrodb/utils/directories.py +3 -5
  284. disdrodb/utils/encoding.py +2 -4
  285. disdrodb/utils/event.py +1 -1
  286. disdrodb/utils/list.py +1 -3
  287. disdrodb/utils/logger.py +1 -3
  288. disdrodb/utils/manipulations.py +182 -6
  289. disdrodb/utils/pydantic.py +80 -0
  290. disdrodb/utils/routines.py +1 -3
  291. disdrodb/utils/subsetting.py +1 -1
  292. disdrodb/utils/time.py +3 -2
  293. disdrodb/utils/warnings.py +1 -3
  294. disdrodb/utils/writer.py +1 -3
  295. disdrodb/utils/xarray.py +30 -3
  296. disdrodb/utils/yaml.py +1 -3
  297. disdrodb/viz/__init__.py +1 -1
  298. disdrodb/viz/plots.py +197 -21
  299. {disdrodb-0.2.0.dist-info → disdrodb-0.3.0.dist-info}/METADATA +2 -2
  300. disdrodb-0.3.0.dist-info/RECORD +358 -0
  301. {disdrodb-0.2.0.dist-info → disdrodb-0.3.0.dist-info}/entry_points.txt +3 -0
  302. disdrodb/etc/products/L1/1MIN.yaml +0 -13
  303. disdrodb/etc/products/L1/LPM/1MIN.yaml +0 -13
  304. disdrodb/etc/products/L1/PARSIVEL/1MIN.yaml +0 -13
  305. disdrodb/etc/products/L1/PARSIVEL2/1MIN.yaml +0 -13
  306. disdrodb/etc/products/L1/PWS100/1MIN.yaml +0 -13
  307. disdrodb/etc/products/L1/RD80/1MIN.yaml +0 -13
  308. disdrodb/etc/products/L1/SWS250/1MIN.yaml +0 -13
  309. disdrodb/etc/products/L2M/10MIN.yaml +0 -12
  310. disdrodb/l1/beard_model.py +0 -618
  311. disdrodb/l1/filters.py +0 -203
  312. disdrodb-0.2.0.dist-info/RECORD +0 -312
  313. {disdrodb-0.2.0.dist-info → disdrodb-0.3.0.dist-info}/WHEEL +0 -0
  314. {disdrodb-0.2.0.dist-info → disdrodb-0.3.0.dist-info}/licenses/LICENSE +0 -0
  315. {disdrodb-0.2.0.dist-info → disdrodb-0.3.0.dist-info}/top_level.txt +0 -0
@@ -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,13 +15,16 @@
17
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
16
  # -----------------------------------------------------------------------------.
19
17
  """DISDRODB reader for DWD stations."""
18
+ import glob
20
19
  import os
20
+ from pathlib import Path
21
21
 
22
22
  import numpy as np
23
23
  import pandas as pd
24
24
 
25
25
  from disdrodb.l0.l0_reader import is_documented_by, reader_generic_docstring
26
26
  from disdrodb.l0.l0a_processing import read_raw_text_file
27
+ from disdrodb.utils.logger import log_error, log_warning
27
28
 
28
29
  # Assign column names
29
30
  COLUMNS = [
@@ -104,14 +105,90 @@ COLUMNS = [
104
105
  "raw_drop_number",
105
106
  ]
106
107
 
108
+ ####------------------------------------------------------------------------.
109
+ #### SYNOP utilities
110
+
111
+
112
+ def _reindex_to_custom_frequency(df, freq="1min"):
113
+ # Interpolate to 1 min
114
+ t_start = df.index.min()
115
+ t_end = df.index.max()
116
+ timesteps = pd.date_range(
117
+ start=t_start,
118
+ end=t_end,
119
+ freq=freq,
120
+ )
121
+ return df.reindex(timesteps)
122
+
123
+
124
+ def interpolate_wind_direction(wind_direction, limit=None):
125
+ """Interpolate NaN values in a 1-min wind direction series.
126
+
127
+ Use circular (vector) interpolation.
128
+
129
+ Parameters
130
+ ----------
131
+ wind_direction : pandas.Series
132
+ Wind direction in degrees with DateTimeIndex.
133
+ limit : int or None
134
+ Max number of consecutive NaNs to fill.
135
+
136
+ Returns
137
+ -------
138
+ pandas.Series
139
+ Wind direction with NaNs interpolated.
140
+ """
141
+ wind_direction = wind_direction.copy()
142
+ wind_direction.index = pd.to_datetime(wind_direction.index)
143
+
144
+ # Convert to radians
145
+ theta = np.deg2rad(wind_direction)
146
+
147
+ # Vector components
148
+ u = np.cos(theta)
149
+ v = np.sin(theta)
150
+
151
+ df_vec = pd.DataFrame({"u": u, "v": v}, index=wind_direction.index)
152
+
153
+ # Interpolate ONLY NaNs
154
+ df_vec_i = df_vec.interpolate(
155
+ method="time",
156
+ limit=limit,
157
+ )
158
+
159
+ # Back to degrees
160
+ dir_i = np.rad2deg(np.arctan2(df_vec_i["v"], df_vec_i["u"]))
161
+ dir_i = (dir_i + 360) % 360
162
+
163
+ dir_i = (dir_i / 10).round() * 10
164
+ return pd.Series(dir_i, index=wind_direction.index, name=wind_direction.name)
107
165
 
108
- def read_synop_file(filepath, logger):
166
+
167
+ def retrieve_synop_filepaths(df, filepath):
168
+ """Retrieve SYNOP files relevant for a LPM file."""
169
+ # Retrieve relevant info to list required synop files
170
+ filename = os.path.basename(filepath)
171
+ station_id = filename.split("_")[1]
172
+ date = df["time"].dt.date.iloc[0]
173
+ synop_base_dir = Path(filepath).parents[3] / "SYNOP"
174
+ synop_filepaths = []
175
+ for d in [date, date + pd.Timedelta(days=1)]:
176
+ y = d.strftime("%Y")
177
+ m = d.strftime("%m")
178
+ ymd = d.strftime("%Y%m%d")
179
+ fname_pattern = f"synop10min_{station_id}_{ymd}*1.0days.dat"
180
+ glob_pattern = os.path.join(synop_base_dir, y, m, fname_pattern)
181
+ synop_filepaths.append(*glob.glob(glob_pattern))
182
+ return synop_filepaths
183
+
184
+
185
+ def read_synop_file(filepath, logger=None):
109
186
  """Read SYNOP 10 min file."""
110
187
  ##------------------------------------------------------------------------.
111
188
  #### Define column names
112
189
  column_names = [
113
190
  "time",
114
- "temperature_2m",
191
+ "air_temperature",
115
192
  "relative_humidity",
116
193
  "precipitation_accumulated_10min",
117
194
  "total_cloud_cover",
@@ -169,6 +246,99 @@ def read_synop_file(filepath, logger):
169
246
  return df
170
247
 
171
248
 
249
+ def _add_nan_synop_variables(df, logger, msg):
250
+ """Guarantee SYNOP columns on LPM df output."""
251
+ # Define SYNOP vars to be always present
252
+ synop_vars = [
253
+ "air_temperature",
254
+ "relative_humidity",
255
+ "wind_speed",
256
+ "wind_direction",
257
+ ]
258
+ # Add SYNOP vars columns
259
+ log_warning(logger=logger, msg=msg)
260
+ for v in synop_vars:
261
+ df[v] = np.nan
262
+ return df
263
+
264
+
265
+ def add_synop_information(df, filepath, logger):
266
+ """Add SYNOP (10-min) meteorological data to an LPM (1-min) dataframe.
267
+
268
+ LPM files contains timesteps: 00:00-23.59
269
+ SYNOP files contains timesteps: 00:00-23.50
270
+
271
+ To interpolate SYNOP data between 00:00-23.59 we need also next-day SYNOP file
272
+
273
+ Always returns a dataframe with SYNOP columns present.
274
+ """
275
+ # Drop duplicate timesteps from input LPM dataframe
276
+ df = df.drop_duplicates(subset="time", keep="first").sort_values("time")
277
+
278
+ # Retrieve date
279
+ date = df["time"].iloc[0].date()
280
+
281
+ # --------------------------------------------------------------------
282
+ # Retrieve required SYNOP files
283
+ synop_filepaths = retrieve_synop_filepaths(df=df, filepath=filepath)
284
+
285
+ # If no SYNOP files available
286
+ if not synop_filepaths:
287
+ msg = f"No SYNOP files available for {date}"
288
+ return _add_nan_synop_variables(df, logger=logger, msg=msg)
289
+
290
+ # Read relevant SYNOP files
291
+ synop_dfs = []
292
+ for f in synop_filepaths:
293
+ try:
294
+ synop_dfs.append(read_synop_file(f))
295
+ except Exception as e:
296
+ log_error(logger=logger, msg=f"Failed to read SYNOP file {f}. Error: {e!s}")
297
+
298
+ if not synop_dfs:
299
+ msg = f"No valid SYNOP data could be read for {date}"
300
+ return _add_nan_synop_variables(df, logger=logger, msg=msg)
301
+
302
+ # Concatenate SYNOP files into unique dataframe
303
+ df_synop_10min = pd.concat(synop_dfs, ignore_index=True)
304
+
305
+ # --------------------------------------------------------------------
306
+ # Subset SYNOP file
307
+ tmin = df["time"].min() - pd.Timedelta(minutes=10)
308
+ tmax = df["time"].max() + pd.Timedelta(minutes=10)
309
+ df_synop_10min = df_synop_10min[(df_synop_10min["time"] >= tmin) & (df_synop_10min["time"] <= tmax)]
310
+ if df_synop_10min.empty:
311
+ msg = f"No SYNOP data available for {date}"
312
+ return _add_nan_synop_variables(df, logger=logger, msg=msg)
313
+
314
+ # Drop time duplicates if present
315
+ df_synop_10min = df_synop_10min.drop_duplicates(subset="time", keep="first")
316
+ df_synop_10min = df_synop_10min.drop(columns=["total_cloud_cover", "precipitation_accumulated_10min"])
317
+ # Reindex SYNOP 10 min file to 1 min
318
+ df_synop_10min = df_synop_10min.set_index("time") # set time column as index
319
+ df_synop_10min = df_synop_10min.astype(float) # cast column to float
320
+ df_synop_1min = _reindex_to_custom_frequency(df_synop_10min, freq="1min")
321
+ # Interpolate variables
322
+ df_synop_1min["wind_direction"] = interpolate_wind_direction(df_synop_1min["wind_direction"], limit=9)
323
+ variables = ["air_temperature", "relative_humidity", "wind_speed"]
324
+ df_synop_1min[variables] = df_synop_1min[variables].interpolate(method="time", limit=9)
325
+ df_synop_1min = df_synop_1min.reset_index().rename(columns={"index": "time"})
326
+ # Merge data
327
+ df_synop_1min = df_synop_1min.drop_duplicates(subset="time", keep="first").sort_values("time")
328
+ df_merged = pd.merge_asof(
329
+ df,
330
+ df_synop_1min,
331
+ on="time",
332
+ direction="nearest", # or "backward" / "forward"
333
+ tolerance=pd.Timedelta("0min"),
334
+ )
335
+ return df_merged
336
+
337
+
338
+ ####-------------------------------------------------------------------------.
339
+ #### LPM parsers
340
+
341
+
172
342
  def parse_format_v1(df):
173
343
  """Parse DWD format v1."""
174
344
  raise NotImplementedError
@@ -433,7 +603,7 @@ def reader(
433
603
  ):
434
604
  """Reader."""
435
605
  ##------------------------------------------------------------------------.
436
- #### - Define raw data headers
606
+ #### Define raw data headers
437
607
  column_names = ["TO_PARSE"]
438
608
 
439
609
  ##------------------------------------------------------------------------.
@@ -481,11 +651,19 @@ def reader(
481
651
  )
482
652
  ##------------------------------------------------------------------------.
483
653
  #### Adapt the dataframe to adhere to DISDRODB L0 standards
654
+ # Read LPM raw data
484
655
  filename = os.path.basename(filepath)
485
656
  if filename.startswith("3_"):
486
- return parse_format_v3(df)
487
- if filename.startswith("2_"):
488
- return parse_format_v2(df)
489
- if filename.startswith("1_"):
490
- return parse_format_v1(df)
491
- raise ValueError(f"Not implemented parser for DWD {filepath} data format.")
657
+ df = parse_format_v3(df)
658
+ elif filename.startswith("2_"):
659
+ df = parse_format_v2(df)
660
+ elif filename.startswith("1_"):
661
+ df = parse_format_v1(df)
662
+ else:
663
+ raise ValueError(f"Not implemented parser for DWD {filepath} data format.")
664
+
665
+ # Add SYNOP data if available
666
+ df = add_synop_information(df=df, filepath=filepath, logger=logger)
667
+
668
+ # Return dataframe
669
+ return df
@@ -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,10 +15,14 @@
17
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
16
  # -----------------------------------------------------------------------------.
19
17
  """DISDRODB reader for GID LPM sensors not measuring wind."""
18
+ import os
19
+
20
+ import numpy as np
20
21
  import pandas as pd
21
22
 
22
23
  from disdrodb.l0.l0_reader import is_documented_by, reader_generic_docstring
23
24
  from disdrodb.l0.l0a_processing import read_raw_text_file
25
+ from disdrodb.utils.logger import log_warning
24
26
 
25
27
 
26
28
  @is_documented_by(reader_generic_docstring)
@@ -31,7 +33,7 @@ def reader(
31
33
  """Reader."""
32
34
  ##------------------------------------------------------------------------.
33
35
  #### - Define raw data headers
34
- column_names = ["TO_BE_SPLITTED"]
36
+ column_names = ["TO_PARSE"]
35
37
 
36
38
  ##------------------------------------------------------------------------.
37
39
  #### Define reader options
@@ -50,6 +52,9 @@ def reader(
50
52
  # - Number of rows to be skipped at the beginning of the file
51
53
  reader_kwargs["skiprows"] = None
52
54
 
55
+ # - Define encoding
56
+ reader_kwargs["encoding"] = "latin"
57
+
53
58
  # - Define behaviour when encountering bad lines
54
59
  reader_kwargs["on_bad_lines"] = "skip"
55
60
 
@@ -79,14 +84,22 @@ def reader(
79
84
 
80
85
  ##------------------------------------------------------------------------.
81
86
  #### Adapt the dataframe to adhere to DISDRODB L0 standards
82
- # Count number of delimiters to identify valid rows
83
- df = df[df["TO_BE_SPLITTED"].str.count(";") == 519]
87
+ # Raise error if empty file
88
+ if len(df) == 0:
89
+ raise ValueError(f"{filepath} is empty.")
90
+
91
+ # Select only rows with expected number of delimiters
92
+ df = df[df["TO_PARSE"].str.count(";").isin([519, 520])]
93
+
94
+ # Check there are still valid rows
95
+ if len(df) == 0:
96
+ raise ValueError(f"No valid rows in {filepath}.")
84
97
 
85
98
  # Split by ; delimiter (before raw drop number)
86
- df = df["TO_BE_SPLITTED"].str.split(";", expand=True, n=79)
99
+ df = df["TO_PARSE"].str.split(";", expand=True, n=79)
87
100
 
88
101
  # Assign column names
89
- column_names = [
102
+ names = [
90
103
  "start_identifier",
91
104
  "device_address",
92
105
  "sensor_serial_number",
@@ -168,14 +181,50 @@ def reader(
168
181
  "number_particles_class_9_internal_data",
169
182
  "raw_drop_number",
170
183
  ]
171
- df.columns = column_names
184
+ df.columns = names
172
185
 
173
186
  # Remove checksum from raw_drop_number
174
- df["raw_drop_number"] = df["raw_drop_number"].str.rsplit(";", n=1, expand=True)[0]
175
-
176
- # Define datetime "time" column
177
- df["time"] = df["sensor_date"] + "-" + df["sensor_time"]
178
- df["time"] = pd.to_datetime(df["time"], format="%d.%m.%y-%H:%M:%S", errors="coerce")
187
+ df["raw_drop_number"] = df["raw_drop_number"].str.strip(";").str.rsplit(";", n=1, expand=True)[0]
188
+
189
+ # Identify rows with bad sensor date (compared to file name)
190
+ filename = os.path.basename(filepath)
191
+ file_date_str = filename[0:8]
192
+ idx_bad_date = df["sensor_date"] != pd.to_datetime(file_date_str, format="%Y%m%d").strftime("%d.%m.%y")
193
+
194
+ # If all rows have bad sensor_date, use the one of the file name
195
+ if idx_bad_date.all():
196
+ # - Infer and define "time" column
197
+ start_time_str = filename[0:10]
198
+ start_time = pd.to_datetime(start_time_str, format="%Y%m%d%H")
199
+
200
+ # - Define timedelta based on sensor_time
201
+ # --> Add +24h to subsequent times when time resets
202
+ dt = pd.to_timedelta(df["sensor_time"]).to_numpy().astype("m8[s]")
203
+ rollover_indices = np.where(np.diff(dt) < np.timedelta64(0, "s"))[0]
204
+ if rollover_indices.size > 0:
205
+ for idx in rollover_indices:
206
+ dt[idx + 1 :] += np.timedelta64(24, "h")
207
+ dt = dt - dt[0]
208
+
209
+ # - Define approximate time
210
+ df["time"] = start_time + dt
211
+
212
+ # - Keep rows where time increment is between 00 and 59 minutes
213
+ valid_rows = dt <= np.timedelta64(3540, "s")
214
+ df = df[valid_rows]
215
+
216
+ # If only some rows have bad sensor date, just drop such bad rows
217
+ else:
218
+ if idx_bad_date.any():
219
+ # Remove rows with bad dates
220
+ bad_dates = df[idx_bad_date]["sensor_date"].unique().tolist()
221
+ df = df[~idx_bad_date]
222
+ msg = f"{filepath} contain rows with the following unexpected sensor_date values {bad_dates}"
223
+ log_warning(msg=msg, logger=logger)
224
+
225
+ # Define datetime "time" column
226
+ df["time"] = df["sensor_date"] + "-" + df["sensor_time"]
227
+ df["time"] = pd.to_datetime(df["time"], format="%d.%m.%y-%H:%M:%S", errors="coerce")
179
228
 
180
229
  # Drop row if start_identifier different than 00
181
230
  df = df[df["start_identifier"].astype(str) == "00"]
@@ -0,0 +1,279 @@
1
+ # -----------------------------------------------------------------------------.
2
+ # Copyright (c) 2021-2026 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
+ """DISDRODB reader for GID LPM sensor TC-PI with incorrect reported time."""
18
+ import pandas as pd
19
+
20
+ from disdrodb.l0.l0_reader import is_documented_by, reader_generic_docstring
21
+ from disdrodb.l0.l0a_processing import read_raw_text_file
22
+ from disdrodb.utils.logger import log_error
23
+
24
+
25
+ def read_txt_file(file, filename, logger):
26
+ """Parse for TC-PI LPM file."""
27
+ #### - Define raw data headers
28
+ column_names = ["TO_PARSE"]
29
+
30
+ ##------------------------------------------------------------------------.
31
+ #### Define reader options
32
+ # - For more info: https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
33
+ reader_kwargs = {}
34
+
35
+ # - Define delimiter
36
+ reader_kwargs["delimiter"] = "\\n"
37
+
38
+ # - Avoid first column to become df index !!!
39
+ reader_kwargs["index_col"] = False
40
+
41
+ # Since column names are expected to be passed explicitly, header is set to None
42
+ reader_kwargs["header"] = None
43
+
44
+ # - Number of rows to be skipped at the beginning of the file
45
+ reader_kwargs["skiprows"] = 1
46
+
47
+ # - Define behaviour when encountering bad lines
48
+ reader_kwargs["on_bad_lines"] = "skip"
49
+
50
+ # - Define reader engine
51
+ # - C engine is faster
52
+ # - Python engine is more feature-complete
53
+ reader_kwargs["engine"] = "python"
54
+
55
+ # - Define on-the-fly decompression of on-disk data
56
+ # - Available: gzip, bz2, zip
57
+ reader_kwargs["compression"] = "infer"
58
+
59
+ # - Strings to recognize as NA/NaN and replace with standard NA flags
60
+ # - Already included: '#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN',
61
+ # '-NaN', '-nan', '1.#IND', '1.#QNAN', '<NA>', 'N/A',
62
+ # 'NA', 'NULL', 'NaN', 'n/a', 'nan', 'null'
63
+ reader_kwargs["na_values"] = ["na", "", "error"]
64
+
65
+ ##------------------------------------------------------------------------.
66
+ #### Read the data
67
+ df = read_raw_text_file(
68
+ filepath=file,
69
+ column_names=column_names,
70
+ reader_kwargs=reader_kwargs,
71
+ logger=logger,
72
+ )
73
+
74
+ ##------------------------------------------------------------------------.
75
+ #### Adapt the dataframe to adhere to DISDRODB L0 standards
76
+ # Raise error if empty file
77
+ if len(df) == 0:
78
+ raise ValueError(f"{filename} is empty.")
79
+
80
+ # Select only rows with expected number of delimiters
81
+ df = df[df["TO_PARSE"].str.count(" ") == 526]
82
+
83
+ # Check there are still valid rows
84
+ if len(df) == 0:
85
+ raise ValueError(f"No valid rows in {filename}.")
86
+
87
+ # Split by ; delimiter (before raw drop number)
88
+ df = df["TO_PARSE"].str.split(" ", expand=True, n=82)
89
+
90
+ # Assign column names
91
+ names = [
92
+ "date",
93
+ "time",
94
+ "unknown",
95
+ "start_identifier",
96
+ "device_address",
97
+ "sensor_serial_number",
98
+ "sensor_date",
99
+ "sensor_time",
100
+ "weather_code_synop_4677_5min",
101
+ "weather_code_synop_4680_5min",
102
+ "weather_code_metar_4678_5min",
103
+ "precipitation_rate_5min",
104
+ "weather_code_synop_4677",
105
+ "weather_code_synop_4680",
106
+ "weather_code_metar_4678",
107
+ "precipitation_rate",
108
+ "rainfall_rate",
109
+ "snowfall_rate",
110
+ "precipitation_accumulated",
111
+ "mor_visibility",
112
+ "reflectivity",
113
+ "quality_index",
114
+ "max_hail_diameter",
115
+ "laser_status",
116
+ "static_signal_status",
117
+ "laser_temperature_analog_status",
118
+ "laser_temperature_digital_status",
119
+ "laser_current_analog_status",
120
+ "laser_current_digital_status",
121
+ "sensor_voltage_supply_status",
122
+ "current_heating_pane_transmitter_head_status",
123
+ "current_heating_pane_receiver_head_status",
124
+ "temperature_sensor_status",
125
+ "current_heating_voltage_supply_status",
126
+ "current_heating_house_status",
127
+ "current_heating_heads_status",
128
+ "current_heating_carriers_status",
129
+ "control_output_laser_power_status",
130
+ "reserved_status",
131
+ "temperature_interior",
132
+ "laser_temperature",
133
+ "laser_current_average",
134
+ "control_voltage",
135
+ "optical_control_voltage_output",
136
+ "sensor_voltage_supply",
137
+ "current_heating_pane_transmitter_head",
138
+ "current_heating_pane_receiver_head",
139
+ "temperature_ambient",
140
+ "current_heating_voltage_supply",
141
+ "current_heating_house",
142
+ "current_heating_heads",
143
+ "current_heating_carriers",
144
+ "number_particles",
145
+ "number_particles_internal_data",
146
+ "number_particles_min_speed",
147
+ "number_particles_min_speed_internal_data",
148
+ "number_particles_max_speed",
149
+ "number_particles_max_speed_internal_data",
150
+ "number_particles_min_diameter",
151
+ "number_particles_min_diameter_internal_data",
152
+ "number_particles_no_hydrometeor",
153
+ "number_particles_no_hydrometeor_internal_data",
154
+ "number_particles_unknown_classification",
155
+ "number_particles_unknown_classification_internal_data",
156
+ "number_particles_class_1",
157
+ "number_particles_class_1_internal_data",
158
+ "number_particles_class_2",
159
+ "number_particles_class_2_internal_data",
160
+ "number_particles_class_3",
161
+ "number_particles_class_3_internal_data",
162
+ "number_particles_class_4",
163
+ "number_particles_class_4_internal_data",
164
+ "number_particles_class_5",
165
+ "number_particles_class_5_internal_data",
166
+ "number_particles_class_6",
167
+ "number_particles_class_6_internal_data",
168
+ "number_particles_class_7",
169
+ "number_particles_class_7_internal_data",
170
+ "number_particles_class_8",
171
+ "number_particles_class_8_internal_data",
172
+ "number_particles_class_9",
173
+ "number_particles_class_9_internal_data",
174
+ "TO_BE_FURTHER_PROCESSED",
175
+ ]
176
+ df.columns = names
177
+
178
+ # Define datetime "time" column
179
+ df["time"] = df["date"] + " " + df["time"]
180
+ df["time"] = pd.to_datetime(df["time"], format="%Y-%m-%d %H:%M:%S", errors="coerce")
181
+
182
+ # Drop row if start_identifier different than 00
183
+ df = df[df["start_identifier"].astype(str) == "00"]
184
+
185
+ # Extract the last variables remained in raw_drop_number
186
+ df_parsed = df["TO_BE_FURTHER_PROCESSED"].str.rsplit(" ", n=5, expand=True)
187
+ df_parsed.columns = [
188
+ "raw_drop_number",
189
+ "air_temperature",
190
+ "relative_humidity",
191
+ "wind_speed",
192
+ "wind_direction",
193
+ "checksum",
194
+ ]
195
+
196
+ # Assign columns to the original dataframe
197
+ df[df_parsed.columns] = df_parsed
198
+
199
+ # Drop rows with invalid raw_drop_number
200
+ # --> 440 value # 22x20
201
+ df = df[df["raw_drop_number"].astype(str).str.len() == 1759]
202
+
203
+ # Drop columns not agreeing with DISDRODB L0 standards
204
+ columns_to_drop = [
205
+ "start_identifier",
206
+ "device_address",
207
+ "sensor_serial_number",
208
+ "sensor_date",
209
+ "sensor_time",
210
+ "date",
211
+ "unknown",
212
+ "TO_BE_FURTHER_PROCESSED",
213
+ "air_temperature",
214
+ "relative_humidity",
215
+ "wind_speed",
216
+ "wind_direction",
217
+ "checksum",
218
+ ]
219
+ df = df.drop(columns=columns_to_drop)
220
+ return df
221
+
222
+
223
+ @is_documented_by(reader_generic_docstring)
224
+ def reader(
225
+ filepath,
226
+ logger=None,
227
+ ):
228
+ """Reader."""
229
+ import zipfile
230
+
231
+ ##------------------------------------------------------------------------.
232
+ # filename = os.path.basename(filepath)
233
+ # return read_txt_file(file=filepath, filename=filename, logger=logger)
234
+
235
+ # ---------------------------------------------------------------------.
236
+ #### Iterate over all files (aka timesteps) in the daily zip archive
237
+ # - Each file contain a single timestep !
238
+ # list_df = []
239
+ # with tempfile.TemporaryDirectory() as temp_dir:
240
+ # # Extract all files
241
+ # unzip_file_on_terminal(filepath, temp_dir)
242
+
243
+ # # Walk through extracted files
244
+ # for root, _, files in os.walk(temp_dir):
245
+ # for filename in sorted(files):
246
+ # if filename.endswith(".txt"):
247
+ # full_path = os.path.join(root, filename)
248
+ # try:
249
+ # df = read_txt_file(file=full_path, filename=filename, logger=logger)
250
+ # if df is not None:
251
+ # list_df.append(df)
252
+ # except Exception as e:
253
+ # msg = f"An error occurred while reading {filename}: {e}"
254
+ # log_error(logger=logger, msg=msg, verbose=True)
255
+
256
+ list_df = []
257
+ with zipfile.ZipFile(filepath, "r") as zip_ref:
258
+ filenames = sorted(zip_ref.namelist())
259
+ for filename in filenames:
260
+ if filename.endswith(".txt"):
261
+ # Open file
262
+ with zip_ref.open(filename) as file:
263
+ try:
264
+ df = read_txt_file(file=file, filename=filename, logger=logger)
265
+ if df is not None:
266
+ list_df.append(df)
267
+ except Exception as e:
268
+ msg = f"An error occurred while reading {filename}. The error is: {e}"
269
+ log_error(logger=logger, msg=msg, verbose=True)
270
+
271
+ # Check the zip file contains at least some non.empty files
272
+ if len(list_df) == 0:
273
+ raise ValueError(f"{filepath} contains only empty files!")
274
+
275
+ # Concatenate all dataframes into a single one
276
+ df = pd.concat(list_df)
277
+
278
+ # ---------------------------------------------------------------------.
279
+ return df