roms-tools 1.6.2__py3-none-any.whl → 2.0.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 (283) hide show
  1. ci/environment.yml +1 -1
  2. roms_tools/__init__.py +1 -0
  3. roms_tools/_version.py +1 -1
  4. roms_tools/setup/boundary_forcing.py +266 -256
  5. roms_tools/setup/datasets.py +986 -231
  6. roms_tools/setup/download.py +41 -15
  7. roms_tools/setup/grid.py +561 -512
  8. roms_tools/setup/initial_conditions.py +162 -106
  9. roms_tools/setup/mask.py +69 -0
  10. roms_tools/setup/plot.py +81 -23
  11. roms_tools/setup/regrid.py +4 -2
  12. roms_tools/setup/river_forcing.py +589 -0
  13. roms_tools/setup/surface_forcing.py +21 -130
  14. roms_tools/setup/tides.py +15 -79
  15. roms_tools/setup/topography.py +92 -128
  16. roms_tools/setup/utils.py +307 -25
  17. roms_tools/setup/vertical_coordinate.py +5 -16
  18. roms_tools/tests/test_setup/test_boundary_forcing.py +10 -7
  19. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zattrs +1 -1
  20. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zmetadata +157 -130
  21. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/.zattrs +1 -1
  22. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_north/.zattrs +1 -1
  23. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_south/.zattrs +1 -1
  24. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/.zattrs +1 -1
  25. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/.zattrs +1 -1
  26. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_north/.zattrs +1 -1
  27. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_south/.zattrs +1 -1
  28. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/.zattrs +1 -1
  29. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/.zattrs +1 -1
  30. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_north/.zattrs +1 -1
  31. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_south/.zattrs +1 -1
  32. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/.zattrs +1 -1
  33. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/.zattrs +1 -1
  34. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_north/.zattrs +1 -1
  35. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_south/.zattrs +1 -1
  36. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/.zattrs +1 -1
  37. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/.zattrs +1 -1
  38. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_north/.zattrs +1 -1
  39. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_south/.zattrs +1 -1
  40. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/.zattrs +1 -1
  41. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/.zattrs +1 -1
  42. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_north/.zattrs +1 -1
  43. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_south/.zattrs +1 -1
  44. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/.zattrs +1 -1
  45. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/.zattrs +1 -1
  46. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_north/.zattrs +1 -1
  47. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_south/.zattrs +1 -1
  48. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/.zattrs +1 -1
  49. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/.zattrs +1 -1
  50. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_north/.zattrs +1 -1
  51. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_south/.zattrs +1 -1
  52. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/.zattrs +1 -1
  53. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/.zattrs +1 -1
  54. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_north/.zattrs +1 -1
  55. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_south/.zattrs +1 -1
  56. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/.zattrs +1 -1
  57. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/.zattrs +1 -1
  58. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_north/.zattrs +1 -1
  59. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_south/.zattrs +1 -1
  60. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/.zattrs +1 -1
  61. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/.zattrs +1 -1
  62. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_north/.zattrs +1 -1
  63. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_south/.zattrs +1 -1
  64. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/.zattrs +1 -1
  65. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/.zattrs +1 -1
  66. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_north/.zattrs +1 -1
  67. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_south/.zattrs +1 -1
  68. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/.zattrs +1 -1
  69. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/.zattrs +1 -1
  70. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/.zattrs +1 -1
  71. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_south/.zattrs +1 -1
  72. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/.zattrs +1 -1
  73. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/.zattrs +1 -1
  74. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_north/.zattrs +1 -1
  75. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_south/.zattrs +1 -1
  76. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/.zattrs +1 -1
  77. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/.zattrs +1 -1
  78. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_north/.zattrs +1 -1
  79. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_south/.zattrs +1 -1
  80. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/.zattrs +1 -1
  81. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/.zattrs +1 -1
  82. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_north/.zattrs +1 -1
  83. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_south/.zattrs +1 -1
  84. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/.zattrs +1 -1
  85. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/.zattrs +1 -1
  86. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_north/.zattrs +1 -1
  87. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_south/.zattrs +1 -1
  88. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/.zattrs +1 -1
  89. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/abs_time/.zattrs +1 -0
  90. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/bry_time/.zattrs +1 -1
  91. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/.zattrs +1 -1
  92. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/.zattrs +1 -1
  93. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_south/.zattrs +1 -1
  94. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/.zattrs +1 -1
  95. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/.zattrs +1 -1
  96. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/.zattrs +1 -1
  97. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_south/.zattrs +1 -1
  98. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/.zattrs +1 -1
  99. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/.zattrs +1 -1
  100. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/.zattrs +1 -1
  101. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_south/.zattrs +1 -1
  102. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/.zattrs +1 -1
  103. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/.zattrs +1 -1
  104. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/.zattrs +1 -1
  105. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_south/.zattrs +1 -1
  106. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/.zattrs +1 -1
  107. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/.zattrs +1 -1
  108. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/.zattrs +1 -1
  109. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_south/.zattrs +1 -1
  110. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/.zattrs +1 -1
  111. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/.zattrs +1 -1
  112. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/.zattrs +1 -1
  113. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_south/.zattrs +1 -1
  114. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/.zattrs +1 -1
  115. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/.zattrs +1 -1
  116. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/.zattrs +1 -1
  117. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_south/.zattrs +1 -1
  118. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/.zattrs +1 -1
  119. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/.zattrs +1 -1
  120. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/.zattrs +1 -1
  121. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_south/.zattrs +1 -1
  122. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/.zattrs +1 -1
  123. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/.zattrs +1 -1
  124. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/.zattrs +1 -1
  125. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_south/.zattrs +1 -1
  126. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/.zattrs +1 -1
  127. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/.zarray +20 -0
  128. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/.zattrs +6 -0
  129. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/0 +0 -0
  130. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/.zattrs +1 -1
  131. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/.zattrs +1 -1
  132. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_south/.zattrs +1 -1
  133. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/.zattrs +1 -1
  134. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/.zattrs +1 -1
  135. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/.zattrs +1 -1
  136. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_south/.zattrs +1 -1
  137. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/.zattrs +1 -1
  138. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/.zattrs +1 -1
  139. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/.zattrs +1 -1
  140. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_south/.zattrs +1 -1
  141. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/.zattrs +1 -1
  142. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/.zattrs +1 -1
  143. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/.zattrs +1 -1
  144. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_south/.zattrs +1 -1
  145. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/.zattrs +1 -1
  146. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/.zattrs +1 -1
  147. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/.zattrs +1 -1
  148. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_south/.zattrs +1 -1
  149. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/.zattrs +1 -1
  150. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/.zattrs +1 -1
  151. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/.zattrs +1 -1
  152. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_south/.zattrs +1 -1
  153. roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/.zattrs +1 -1
  154. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zattrs +1 -1
  155. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zmetadata +39 -12
  156. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/abs_time/.zattrs +1 -0
  157. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust/.zattrs +1 -1
  158. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust_time/.zattrs +1 -1
  159. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron/.zattrs +1 -1
  160. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron_time/.zattrs +1 -1
  161. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/.zarray +20 -0
  162. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/.zattrs +6 -0
  163. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/0 +0 -0
  164. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy/.zattrs +1 -1
  165. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy_time/.zattrs +1 -1
  166. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox/.zattrs +1 -1
  167. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox_time/.zattrs +1 -1
  168. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air/.zattrs +1 -1
  169. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air_alt/.zattrs +1 -1
  170. roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_time/.zattrs +1 -1
  171. roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +0 -1
  172. roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +56 -201
  173. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_r/.zattrs +1 -1
  174. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_w/.zattrs +1 -1
  175. roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_rho → sigma_r}/.zarray +2 -6
  176. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/.zattrs +7 -0
  177. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/0 +0 -0
  178. roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_u → sigma_w}/.zarray +2 -6
  179. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zattrs +7 -0
  180. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/0 +0 -0
  181. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -2
  182. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +58 -203
  183. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_r/.zattrs +1 -1
  184. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_w/.zattrs +1 -1
  185. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/.zattrs +1 -1
  186. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
  187. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
  188. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
  189. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
  190. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
  191. roms_tools/tests/test_setup/test_data/{grid.zarr/interface_depth_v → grid_that_straddles_dateline.zarr/sigma_r}/.zarray +2 -6
  192. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zattrs +7 -0
  193. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/0 +0 -0
  194. roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_rho → grid_that_straddles_dateline.zarr/sigma_w}/.zarray +2 -6
  195. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zattrs +7 -0
  196. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/0 +0 -0
  197. roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zattrs +3 -0
  198. roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zgroup +3 -0
  199. roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zmetadata +214 -0
  200. roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/.zarray +20 -0
  201. roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/.zattrs +8 -0
  202. roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/0 +0 -0
  203. roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/.zarray +20 -0
  204. roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/.zattrs +6 -0
  205. roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/0 +0 -0
  206. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/.zarray +24 -0
  207. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/.zattrs +6 -0
  208. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/0 +0 -0
  209. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/.zarray +20 -0
  210. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/.zattrs +8 -0
  211. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/0 +0 -0
  212. roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_v → river_forcing.zarr/river_tracer}/.zarray +4 -4
  213. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/.zattrs +10 -0
  214. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/0.0.0 +0 -0
  215. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/.zarray +22 -0
  216. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/.zattrs +9 -0
  217. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/0.0 +0 -0
  218. roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_u → river_forcing.zarr/tracer_name}/.zarray +2 -6
  219. roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/.zattrs +6 -0
  220. roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/0 +0 -0
  221. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zattrs +1 -0
  222. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zgroup +3 -0
  223. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +185 -0
  224. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/.zarray +20 -0
  225. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/.zattrs +8 -0
  226. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/0 +0 -0
  227. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/.zarray +24 -0
  228. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/.zattrs +6 -0
  229. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/0 +0 -0
  230. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/.zarray +20 -0
  231. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/.zattrs +7 -0
  232. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/0 +0 -0
  233. roms_tools/tests/test_setup/test_data/{grid_that_straddles_dateline.zarr/interface_depth_v → river_forcing_no_climatology.zarr/river_tracer}/.zarray +4 -4
  234. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +10 -0
  235. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/0.0.0 +0 -0
  236. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/.zarray +22 -0
  237. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/.zattrs +9 -0
  238. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/0.0 +0 -0
  239. roms_tools/tests/test_setup/test_data/{grid_that_straddles_dateline.zarr/interface_depth_u → river_forcing_no_climatology.zarr/tracer_name}/.zarray +2 -6
  240. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/.zattrs +6 -0
  241. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/0 +0 -0
  242. roms_tools/tests/test_setup/test_grid.py +110 -12
  243. roms_tools/tests/test_setup/test_initial_conditions.py +2 -3
  244. roms_tools/tests/test_setup/test_river_forcing.py +367 -0
  245. roms_tools/tests/test_setup/test_surface_forcing.py +2 -24
  246. roms_tools/tests/test_setup/test_tides.py +2 -3
  247. roms_tools/tests/test_setup/test_topography.py +106 -1
  248. roms_tools/tests/test_setup/test_validation.py +4 -0
  249. roms_tools/utils.py +12 -10
  250. {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/LICENSE +1 -1
  251. {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/METADATA +6 -5
  252. {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/RECORD +254 -225
  253. {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/WHEEL +1 -1
  254. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zattrs +0 -9
  255. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/0.0.0 +0 -0
  256. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zattrs +0 -9
  257. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/0.0.0 +0 -0
  258. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zattrs +0 -9
  259. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/0.0.0 +0 -0
  260. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/.zattrs +0 -9
  261. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/0.0.0 +0 -0
  262. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zattrs +0 -9
  263. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/0.0.0 +0 -0
  264. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zattrs +0 -9
  265. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/0.0.0 +0 -0
  266. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zarray +0 -24
  267. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zattrs +0 -9
  268. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/0.0.0 +0 -0
  269. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zattrs +0 -9
  270. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/0.0.0 +0 -0
  271. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zattrs +0 -9
  272. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/0.0.0 +0 -0
  273. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zarray +0 -24
  274. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zattrs +0 -9
  275. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/0.0.0 +0 -0
  276. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zarray +0 -24
  277. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zattrs +0 -9
  278. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/0.0.0 +0 -0
  279. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zarray +0 -24
  280. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zattrs +0 -9
  281. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/0.0.0 +0 -0
  282. roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
  283. {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/grid.py CHANGED
@@ -1,4 +1,6 @@
1
+ import time
1
2
  import copy
3
+ import logging
2
4
  from dataclasses import dataclass, field, asdict
3
5
 
4
6
  import numpy as np
@@ -6,14 +8,17 @@ import xarray as xr
6
8
  import matplotlib.pyplot as plt
7
9
  import yaml
8
10
  import importlib.metadata
9
-
10
- from typing import Union
11
- from roms_tools.setup.topography import _add_topography_and_mask, _add_velocity_masks
12
- from roms_tools.setup.plot import _plot, _section_plot, _profile_plot, _line_plot
13
- from roms_tools.setup.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
11
+ from typing import Dict, Union, List
12
+ from roms_tools.setup.topography import _add_topography
13
+ from roms_tools.setup.mask import _add_mask, _add_velocity_masks
14
+ from roms_tools.setup.plot import _plot, _section_plot
15
+ from roms_tools.setup.utils import (
16
+ interpolate_from_rho_to_u,
17
+ interpolate_from_rho_to_v,
18
+ get_target_coords,
19
+ )
14
20
  from roms_tools.setup.vertical_coordinate import sigma_stretch, compute_depth
15
21
  from roms_tools.setup.utils import extract_single_value, save_datasets
16
- import logging
17
22
  from pathlib import Path
18
23
 
19
24
  RADIUS_OF_EARTH = 6371315.0 # in m
@@ -21,9 +26,15 @@ RADIUS_OF_EARTH = 6371315.0 # in m
21
26
 
22
27
  @dataclass(frozen=True, kw_only=True)
23
28
  class Grid:
24
- """A single ROMS grid.
29
+ """A single ROMS grid, used for creating, plotting, and then saving a new ROMS
30
+ domain grid.
25
31
 
26
- Used for creating, plotting, and then saving a new ROMS domain grid.
32
+ The grid generation consists of four steps:
33
+
34
+ 1. Creating the horizontal grid
35
+ 2. Creating the mask
36
+ 3. Generating the topography
37
+ 4. Preparing the vertical coordinate system
27
38
 
28
39
  Parameters
29
40
  ----------
@@ -43,6 +54,15 @@ class Grid:
43
54
  Rotation of grid x-direction from lines of constant latitude, measured in degrees.
44
55
  Positive values represent a counterclockwise rotation.
45
56
  The default is 0, which means that the x-direction of the grid is aligned with lines of constant latitude.
57
+ topography_source : Dict[str, Union[str, Path]], optional
58
+ Dictionary specifying the source of the topography data:
59
+
60
+ - "name" (str): The name of the topography data source (e.g., "SRTM15").
61
+ - "path" (Union[str, Path, List[Union[str, Path]]]): The path to the raw data file. Can be a string or a Path object.
62
+
63
+ The default is "ETOPO5", which does not require a path.
64
+ hmin : float, optional
65
+ The minimum ocean depth (in meters). The default is 5.0.
46
66
  N : int, optional
47
67
  The number of vertical levels. The default is 100.
48
68
  theta_s : float, optional
@@ -51,11 +71,8 @@ class Grid:
51
71
  The bottom control parameter. Must satisfy 0 < theta_b <= 4. The default is 2.0.
52
72
  hc : float, optional
53
73
  The critical depth (in meters). The default is 300.0.
54
- topography_source : str, optional
55
- Specifies the data source to use for the topography. Options are
56
- "ETOPO5". The default is "ETOPO5".
57
- hmin : float, optional
58
- The minimum ocean depth (in meters). The default is 5.0.
74
+ verbose: bool, optional
75
+ Indicates whether to print grid generation steps with timing. Defaults to False.
59
76
 
60
77
  Raises
61
78
  ------
@@ -74,161 +91,166 @@ class Grid:
74
91
  theta_s: float = 5.0
75
92
  theta_b: float = 2.0
76
93
  hc: float = 300.0
77
- topography_source: str = "ETOPO5"
94
+ topography_source: Dict[str, Union[str, Path, List[Union[str, Path]]]] = None
78
95
  hmin: float = 5.0
96
+ verbose: bool = False
79
97
  ds: xr.Dataset = field(init=False, repr=False)
80
98
  straddle: bool = field(init=False, repr=False)
81
99
 
82
100
  def __post_init__(self):
83
- ds = _make_grid_ds(
84
- nx=self.nx,
85
- ny=self.ny,
86
- size_x=self.size_x,
87
- size_y=self.size_y,
88
- center_lon=self.center_lon,
89
- center_lat=self.center_lat,
90
- rot=self.rot,
91
- )
92
- # Calling object.__setattr__ is ugly but apparently this really is the best (current) way to combine __post_init__ with a frozen dataclass
93
- # see https://stackoverflow.com/questions/53756788/how-to-set-the-value-of-dataclass-field-in-post-init-when-frozen-true
94
- object.__setattr__(self, "ds", ds)
95
101
 
96
- # Update self.ds with topography and mask information
97
- self.update_topography_and_mask(
98
- topography_source=self.topography_source,
99
- hmin=self.hmin,
100
- )
102
+ self._input_checks()
103
+
104
+ # Horizontal grid
105
+ self._create_horizontal_grid()
101
106
 
102
107
  # Check if the Greenwich meridian goes through the domain.
103
108
  self._straddle()
104
109
 
105
- object.__setattr__(self, "ds", ds)
110
+ # Mask
111
+ self._create_mask(verbose=self.verbose)
106
112
 
107
- # Update the grid by adding grid variables that are coarsened versions of the original grid variables
113
+ # Coarsen the dataset if needed
108
114
  self._coarsen()
109
115
 
116
+ # Topography and mask
117
+ self.update_topography(
118
+ topography_source=self.topography_source,
119
+ hmin=self.hmin,
120
+ verbose=self.verbose,
121
+ )
122
+
123
+ # Vertical coordinate system
110
124
  self.update_vertical_coordinate(
111
- N=self.N, theta_s=self.theta_s, theta_b=self.theta_b, hc=self.hc
125
+ N=self.N,
126
+ theta_s=self.theta_s,
127
+ theta_b=self.theta_b,
128
+ hc=self.hc,
129
+ verbose=self.verbose,
112
130
  )
113
131
 
114
- def update_topography_and_mask(self, hmin, topography_source="ETOPO5") -> None:
115
- """Update the grid dataset by adding or overwriting the topography and land/sea
116
- mask.
132
+ def _input_checks(self):
133
+ if self.topography_source is None:
134
+ object.__setattr__(self, "topography_source", {"name": "ETOPO5"})
117
135
 
118
- This method processes the topography data and generates a land/sea mask.
119
- It applies several steps, including interpolating topography, smoothing
120
- the topography over the entire domain and locally, and filling in enclosed basins. The
121
- processed topography and mask are added to the grid's dataset as new variables.
136
+ if "name" not in self.topography_source:
137
+ raise ValueError(
138
+ "`topography_source` must include a 'name' key specifying the data source."
139
+ )
122
140
 
123
- Parameters
124
- ----------
125
- hmin : float
126
- The minimum ocean depth (in meters).
127
- topography_source : str
128
- Specifies the data source to use for the topography. Options are
129
- "ETOPO5". Default is "ETOPO5".
141
+ if self.topography_source["name"] != "ETOPO5":
142
+ if "path" not in self.topography_source:
143
+ raise ValueError(
144
+ "`topography_source` must include a 'path' key when the 'name' is not 'ETOPO5'."
145
+ )
130
146
 
131
- Returns
132
- -------
133
- None
134
- This method modifies the dataset in place and does not return a value.
135
- """
147
+ def _create_mask(self, verbose=False) -> None:
148
+
149
+ if verbose:
150
+ start_time = time.time()
151
+ logging.info("=== Creating the mask ===")
152
+ ds = _add_mask(self.ds)
153
+
154
+ if verbose:
155
+ logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
156
+ logging.info(
157
+ "========================================================================================================"
158
+ )
136
159
 
137
- ds = _add_topography_and_mask(self.ds, topography_source, hmin)
138
- # Assign the updated dataset back to the frozen dataclass
139
160
  object.__setattr__(self, "ds", ds)
140
- object.__setattr__(self, "topography_source", topography_source)
141
- object.__setattr__(self, "hmin", hmin)
142
161
 
143
- def _straddle(self) -> None:
144
- """Check if the Greenwich meridian goes through the domain.
162
+ def update_topography(
163
+ self, topography_source=None, hmin=None, verbose=False
164
+ ) -> None:
165
+ """Update the grid dataset with processed topography.
145
166
 
146
- This method sets the `straddle` attribute to `True` if the Greenwich meridian
147
- (0° longitude) intersects the domain defined by `lon_rho`. Otherwise, it sets
148
- the `straddle` attribute to `False`.
167
+ This method performs several key operations, including regridding the topography, smoothing the
168
+ topography over the entire domain and locally.
169
+ The processed topography is then added to the grid's dataset.
149
170
 
150
- The check is based on whether the longitudinal differences between adjacent
151
- points exceed 300 degrees, indicating a potential wraparound of longitude.
152
- """
171
+ Parameters
172
+ ----------
173
+ topography_source : dict, optional
174
+ A dictionary specifying the source of the topography data. The dictionary should
175
+ contain the following keys:
176
+ - "name" (str): The name of the topography data source (e.g., "SRTM15").
177
+ - "path" (Union[str, Path): The path to the raw data file.
153
178
 
154
- if (
155
- np.abs(self.ds.lon_rho.diff("xi_rho")).max() > 300
156
- or np.abs(self.ds.lon_rho.diff("eta_rho")).max() > 300
157
- ):
158
- object.__setattr__(self, "straddle", True)
159
- else:
160
- object.__setattr__(self, "straddle", False)
179
+ If not provided, `topography_source` will remain unchanged (i.e., the existing value will not be overwritten).
161
180
 
162
- def _coarsen(self):
163
- """Update the grid by adding grid variables that are coarsened versions of the
164
- original fine-resoluion grid variables. The coarsening is by a factor of two.
181
+ hmin : float, optional
182
+ The minimum ocean depth (in meters).
183
+ If not provided, `hmin` will remain unchanged (i.e., the existing value will not be overwritten).
165
184
 
166
- The specific variables being coarsened are:
167
- - `lon_rho` -> `lon_coarse`: Longitude at rho points.
168
- - `lat_rho` -> `lat_coarse`: Latitude at rho points.
169
- - `angle` -> `angle_coarse`: Angle between the xi axis and true east.
170
- - `mask_rho` -> `mask_coarse`: Land/sea mask at rho points.
185
+ verbose : bool, optional
186
+ If True, the method will print detailed information about the grid generation process,
187
+ including the timing of each step. Defaults to False.
171
188
 
172
189
  Returns
173
190
  -------
174
191
  None
175
-
176
- Modifies
177
- --------
178
- self.ds : xr.Dataset
179
- The dataset attribute of the Grid instance is updated with the new coarser variables.
192
+ This method updates the internal dataset (`self.ds`) in place by adding or overwriting the
193
+ topography variable. It does not return any value.
180
194
  """
181
- d = {
182
- "angle": "angle_coarse",
183
- "mask_rho": "mask_coarse",
184
- "lat_rho": "lat_coarse",
185
- "lon_rho": "lon_coarse",
186
- }
187
195
 
188
- for fine_var, coarse_var in d.items():
189
- fine_field = self.ds[fine_var]
190
- if self.straddle and fine_var == "lon_rho":
191
- fine_field = xr.where(fine_field > 180, fine_field - 360, fine_field)
196
+ topography_source = topography_source or self.topography_source
197
+ hmin = hmin or self.hmin
192
198
 
193
- coarse_field = _f2c(fine_field)
194
- if fine_var == "lon_rho":
195
- coarse_field = xr.where(
196
- coarse_field < 0, coarse_field + 360, coarse_field
197
- )
198
- if coarse_var in ["lon_coarse", "lat_coarse"]:
199
- ds = self.ds.assign_coords({coarse_var: coarse_field})
200
- object.__setattr__(self, "ds", ds)
201
- else:
202
- self.ds[coarse_var] = coarse_field
199
+ # Extract target coordinates for processing
200
+ target_coords = get_target_coords(self)
201
+
202
+ # If verbose is enabled, start the timer and print the start message
203
+ if verbose:
204
+ start_time = time.time()
205
+ logging.info(
206
+ f"=== Generating the topography using {topography_source['name']} data and hmin = {hmin} meters ==="
207
+ )
203
208
 
204
- self.ds["mask_coarse"] = xr.where(self.ds["mask_coarse"] > 0.5, 1, 0).astype(
205
- np.int32
209
+ # Add topography and mask to the dataset
210
+ ds = _add_topography(
211
+ ds=self.ds,
212
+ target_coords=target_coords,
213
+ topography_source=topography_source,
214
+ hmin=hmin,
215
+ verbose=verbose,
206
216
  )
207
217
 
208
- for fine_var, coarse_var in d.items():
209
- self.ds[coarse_var].attrs[
210
- "long_name"
211
- ] = f"{self.ds[fine_var].attrs['long_name']} on coarsened grid"
212
- self.ds[coarse_var].attrs["units"] = self.ds[fine_var].attrs["units"]
218
+ # If verbose is enabled, print elapsed time and a separator
219
+ if verbose:
220
+ logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
221
+ logging.info(
222
+ "========================================================================================================"
223
+ )
213
224
 
214
- def update_vertical_coordinate(self, N, theta_s, theta_b, hc) -> None:
225
+ # Update the grid's dataset and related attributes
226
+ object.__setattr__(self, "ds", ds)
227
+ object.__setattr__(self, "topography_source", topography_source)
228
+ object.__setattr__(self, "hmin", hmin)
229
+
230
+ def update_vertical_coordinate(
231
+ self, N=None, theta_s=None, theta_b=None, hc=None, verbose=False
232
+ ) -> None:
215
233
  """Create vertical coordinate variables for the ROMS grid.
216
234
 
217
- This method computes the S-coordinate stretching curves and depths
218
- at various grid points (rho, u, v) using the specified parameters.
219
- The computed depths and stretching curves are added to the dataset
220
- as new coordinates, along with their corresponding attributes.
235
+ This method computes the S-coordinate stretching curves at rho- and
236
+ w-points.
221
237
 
222
238
  Parameters
223
239
  ----------
224
240
  N : int
225
241
  Number of vertical levels.
242
+ If not provided, `N` will remain unchanged (i.e., the existing value will not be overwritten).
226
243
  theta_s : float
227
244
  S-coordinate surface control parameter.
245
+ If not provided, `theta_s` will remain unchanged (i.e., the existing value will not be overwritten).
228
246
  theta_b : float
229
247
  S-coordinate bottom control parameter.
248
+ If not provided, `theta_b` will remain unchanged (i.e., the existing value will not be overwritten).
230
249
  hc : float
231
250
  Critical depth (m) used in ROMS vertical coordinate stretching.
251
+ If not provided, `hc` will remain unchanged (i.e., the existing value will not be overwritten).
252
+ verbose: bool, optional
253
+ Indicates whether to print vertical coordinate generation steps with timing. Defaults to False.
232
254
 
233
255
  Returns
234
256
  -------
@@ -236,6 +258,17 @@ class Grid:
236
258
  This method modifies the dataset in place by adding vertical coordinate variables.
237
259
  """
238
260
 
261
+ N = N or self.N
262
+ theta_s = theta_s or self.theta_s
263
+ theta_b = theta_b or self.theta_b
264
+ hc = hc or self.hc
265
+
266
+ if verbose:
267
+ start_time = time.time()
268
+ logging.info(
269
+ f"=== Preparing the vertical coordinate system using N = {N}, theta_s = {theta_s}, theta_b = {theta_b}, hc = {hc} ==="
270
+ )
271
+
239
272
  ds = self.ds
240
273
  # need to drop vertical coordinates because they could cause conflict if N changed
241
274
  vars_to_drop = [
@@ -245,6 +278,8 @@ class Grid:
245
278
  "interface_depth_rho",
246
279
  "interface_depth_u",
247
280
  "interface_depth_v",
281
+ "sigma_r",
282
+ "sigma_w",
248
283
  "Cs_w",
249
284
  "Cs_r",
250
285
  ]
@@ -253,74 +288,117 @@ class Grid:
253
288
  if var in ds.variables:
254
289
  ds = ds.drop_vars(var)
255
290
 
256
- h = ds.h
257
-
258
291
  cs_r, sigma_r = sigma_stretch(theta_s, theta_b, N, "r")
259
- zr = compute_depth(h * 0, h, hc, cs_r, sigma_r)
260
292
  cs_w, sigma_w = sigma_stretch(theta_s, theta_b, N, "w")
261
- zw = compute_depth(h * 0, h, hc, cs_w, sigma_w)
293
+
294
+ ds["sigma_r"] = sigma_r.astype(np.float32)
295
+ ds["sigma_r"].attrs[
296
+ "long_name"
297
+ ] = "Fractional vertical stretching coordinate at rho-points"
298
+ ds["sigma_r"].attrs["units"] = "nondimensional"
262
299
 
263
300
  ds["Cs_r"] = cs_r.astype(np.float32)
264
- ds["Cs_r"].attrs["long_name"] = "S-coordinate stretching curves at rho-points"
301
+ ds["Cs_r"].attrs["long_name"] = "Vertical stretching function at rho-points"
265
302
  ds["Cs_r"].attrs["units"] = "nondimensional"
266
303
 
304
+ ds["sigma_w"] = sigma_w.astype(np.float32)
305
+ ds["sigma_w"].attrs[
306
+ "long_name"
307
+ ] = "Fractional vertical stretching coordinate at w-points"
308
+ ds["sigma_w"].attrs["units"] = "nondimensional"
309
+
267
310
  ds["Cs_w"] = cs_w.astype(np.float32)
268
- ds["Cs_w"].attrs["long_name"] = "S-coordinate stretching curves at w-points"
311
+ ds["Cs_w"].attrs["long_name"] = "Vertical stretching function at w-points"
269
312
  ds["Cs_w"].attrs["units"] = "nondimensional"
270
313
 
271
314
  ds.attrs["theta_s"] = np.float32(theta_s)
272
315
  ds.attrs["theta_b"] = np.float32(theta_b)
273
316
  ds.attrs["hc"] = np.float32(hc)
274
317
 
275
- depth = -zr
276
- depth.attrs["long_name"] = "Layer depth at rho-points"
277
- depth.attrs["units"] = "m"
318
+ if verbose:
319
+ logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
320
+ logging.info(
321
+ "========================================================================================================"
322
+ )
278
323
 
279
- depth_u = interpolate_from_rho_to_u(depth)
280
- depth_u.attrs["long_name"] = "Layer depth at u-points"
281
- depth_u.attrs["units"] = "m"
324
+ object.__setattr__(self, "ds", ds)
325
+ object.__setattr__(self, "theta_s", theta_s)
326
+ object.__setattr__(self, "theta_b", theta_b)
327
+ object.__setattr__(self, "hc", hc)
328
+ object.__setattr__(self, "N", N)
282
329
 
283
- depth_v = interpolate_from_rho_to_v(depth)
284
- depth_v.attrs["long_name"] = "Layer depth at v-points"
285
- depth_v.attrs["units"] = "m"
330
+ def _straddle(self) -> None:
331
+ """Check if the Greenwich meridian goes through the domain.
286
332
 
287
- interface_depth = -zw
288
- interface_depth.attrs["long_name"] = "Interface depth at rho-points"
289
- interface_depth.attrs["units"] = "m"
333
+ This method sets the `straddle` attribute to `True` if the Greenwich meridian
334
+ (0° longitude) intersects the domain defined by `lon_rho`. Otherwise, it sets
335
+ the `straddle` attribute to `False`.
290
336
 
291
- interface_depth_u = interpolate_from_rho_to_u(interface_depth)
292
- interface_depth_u.attrs["long_name"] = "Interface depth at u-points"
293
- interface_depth_u.attrs["units"] = "m"
337
+ The check is based on whether the longitudinal differences between adjacent
338
+ points exceed 300 degrees, indicating a potential wraparound of longitude.
339
+ """
294
340
 
295
- interface_depth_v = interpolate_from_rho_to_v(interface_depth)
296
- interface_depth_v.attrs["long_name"] = "Interface depth at v-points"
297
- interface_depth_v.attrs["units"] = "m"
341
+ if (
342
+ np.abs(self.ds.lon_rho.diff("xi_rho")).max() > 300
343
+ or np.abs(self.ds.lon_rho.diff("eta_rho")).max() > 300
344
+ ):
345
+ object.__setattr__(self, "straddle", True)
346
+ else:
347
+ object.__setattr__(self, "straddle", False)
298
348
 
299
- ds = ds.assign_coords(
300
- {
301
- "layer_depth_rho": depth.astype(np.float32),
302
- "layer_depth_u": depth_u.astype(np.float32),
303
- "layer_depth_v": depth_v.astype(np.float32),
304
- "interface_depth_rho": interface_depth.astype(np.float32),
305
- "interface_depth_u": interface_depth_u.astype(np.float32),
306
- "interface_depth_v": interface_depth_v.astype(np.float32),
307
- }
308
- )
309
- ds = ds.drop_vars(["eta_rho", "xi_rho"])
349
+ def _coarsen(self):
350
+ """Update the grid by adding grid variables that are coarsened versions of the
351
+ original fine-resoluion grid variables. The coarsening is by a factor of two.
352
+
353
+ The specific variables being coarsened are:
354
+ - `lon_rho` -> `lon_coarse`: Longitude at rho points.
355
+ - `lat_rho` -> `lat_coarse`: Latitude at rho points.
356
+ - `angle` -> `angle_coarse`: Angle between the xi axis and true east.
357
+ - `mask_rho` -> `mask_coarse`: Land/sea mask at rho points.
358
+ """
359
+ d = {
360
+ "angle": "angle_coarse",
361
+ "mask_rho": "mask_coarse",
362
+ "lat_rho": "lat_coarse",
363
+ "lon_rho": "lon_coarse",
364
+ }
365
+
366
+ ds = self.ds
367
+
368
+ for fine_var, coarse_var in d.items():
369
+ fine_field = ds[fine_var]
370
+ if self.straddle and fine_var == "lon_rho":
371
+ fine_field = xr.where(fine_field > 180, fine_field - 360, fine_field)
372
+
373
+ coarse_field = _f2c(fine_field)
374
+ if fine_var == "lon_rho":
375
+ coarse_field = xr.where(
376
+ coarse_field < 0, coarse_field + 360, coarse_field
377
+ )
378
+ if coarse_var in ["lon_coarse", "lat_coarse"]:
379
+ ds = ds.assign_coords({coarse_var: coarse_field})
380
+ else:
381
+ ds[coarse_var] = coarse_field
382
+
383
+ ds["mask_coarse"] = xr.where(ds["mask_coarse"] > 0.5, 1, 0).astype(np.int32)
384
+
385
+ for fine_var, coarse_var in d.items():
386
+ ds[coarse_var].attrs[
387
+ "long_name"
388
+ ] = f"{ds[fine_var].attrs['long_name']} on coarsened grid"
389
+ ds[coarse_var].attrs["units"] = ds[fine_var].attrs["units"]
310
390
 
311
391
  object.__setattr__(self, "ds", ds)
312
- object.__setattr__(self, "theta_s", theta_s)
313
- object.__setattr__(self, "theta_b", theta_b)
314
- object.__setattr__(self, "hc", hc)
315
- object.__setattr__(self, "N", N)
316
392
 
317
- def plot(self, bathymetry: bool = False) -> None:
393
+ def plot(self, bathymetry: bool = False, title: str = None) -> None:
318
394
  """Plot the grid.
319
395
 
320
396
  Parameters
321
397
  ----------
322
398
  bathymetry : bool
323
399
  Whether or not to plot the bathymetry. Default is False.
400
+ title : str, optional
401
+ The title of the plot. If not provided, it will be set to a default.
324
402
 
325
403
  Returns
326
404
  -------
@@ -329,6 +407,8 @@ class Grid:
329
407
  """
330
408
 
331
409
  if bathymetry:
410
+ if title is None:
411
+ title = "ROMS grid and bathymetry"
332
412
  field = self.ds.h.where(self.ds.mask_rho)
333
413
  field = field.assign_coords(
334
414
  {"lon": self.ds.lon_rho, "lat": self.ds.lat_rho}
@@ -344,28 +424,24 @@ class Grid:
344
424
  self.ds,
345
425
  field=field,
346
426
  straddle=self.straddle,
427
+ title=title,
347
428
  kwargs=kwargs,
348
429
  )
349
430
  else:
350
- _plot(self.ds, straddle=self.straddle)
431
+ if title is None:
432
+ title = "ROMS grid"
433
+ _plot(self.ds, straddle=self.straddle, title=title)
351
434
 
352
435
  def plot_vertical_coordinate(
353
- self, varname="layer_depth_rho", s=None, eta=None, xi=None, ax=None
436
+ self,
437
+ s=None,
438
+ eta=None,
439
+ xi=None,
354
440
  ) -> None:
355
- """Plot the vertical coordinate system for a given eta-, xi-, or s-slice.
441
+ """Plot the layer depth for a given eta-, xi-, or s-slice.
356
442
 
357
443
  Parameters
358
444
  ----------
359
- varname : str, optional
360
- The vertical coordinate field to plot. Options include:
361
-
362
- - "layer_depth_rho": Layer depth at rho-points.
363
- - "layer_depth_u": Layer depth at u-points.
364
- - "layer_depth_v": Layer depth at v-points.
365
- - "interface_depth_rho": Interface depth at rho-points.
366
- - "interface_depth_u": Interface depth at u-points.
367
- - "interface_depth_v": Interface depth at v-points.
368
-
369
445
  s: int, optional
370
446
  The s-index to plot. Default is None.
371
447
  eta : int, optional
@@ -383,105 +459,67 @@ class Grid:
383
459
  Raises
384
460
  ------
385
461
  ValueError
386
- If the specified varname is not one of the valid options.
387
- If none of s, eta, xi are specified.
462
+ If not exactly one of s, eta, xi is specified.
388
463
  """
389
464
 
390
- if not any([s is not None, eta is not None, xi is not None]):
391
- raise ValueError("At least one of s, eta, or xi must be specified.")
465
+ title = "Layer depth at rho-points"
392
466
 
393
- self.ds[varname].load()
394
- field = self.ds[varname].squeeze()
467
+ if sum(s is not None for s in [s, eta, xi]) != 1:
468
+ raise ValueError("Exactly one of s, eta, or xi must be specified.")
395
469
 
396
- if all(dim in field.dims for dim in ["eta_rho", "xi_rho"]):
397
- interface_depth = self.ds.interface_depth_rho
398
- field = field.where(self.ds.mask_rho)
399
- field = field.assign_coords(
400
- {"lon": self.ds.lon_rho, "lat": self.ds.lat_rho}
401
- )
402
- elif all(dim in field.dims for dim in ["eta_rho", "xi_u"]):
403
- interface_depth = self.ds.interface_depth_u
404
- field = field.where(self.ds.mask_u)
405
- field = field.assign_coords({"lon": self.ds.lon_u, "lat": self.ds.lat_u})
406
- elif all(dim in field.dims for dim in ["eta_v", "xi_rho"]):
407
- interface_depth = self.ds.interface_depth_v
408
- field = field.where(self.ds.mask_v)
409
- field = field.assign_coords({"lon": self.ds.lon_v, "lat": self.ds.lat_v})
410
-
411
- # slice the field as desired
412
- title = field.long_name
413
- if s is not None:
414
- if "s_rho" in field.dims:
415
- title = title + f", s_rho = {field.s_rho[s].item()}"
416
- field = field.isel(s_rho=s)
417
- elif "s_w" in field.dims:
418
- title = title + f", s_w = {field.s_w[s].item()}"
419
- field = field.isel(s_w=s)
420
- else:
421
- raise ValueError(
422
- f"None of the expected dimensions (s_rho, s_w) found in ds[{varname}]."
423
- )
470
+ h = self.ds["h"]
471
+ h = h.assign_coords({"lon": self.ds.lon_rho, "lat": self.ds.lat_rho})
424
472
 
473
+ # slice the bathymetry as desired
425
474
  if eta is not None:
426
- if "eta_rho" in field.dims:
427
- title = title + f", eta_rho = {field.eta_rho[eta].item()}"
428
- field = field.isel(eta_rho=eta)
429
- interface_depth = interface_depth.isel(eta_rho=eta)
430
- elif "eta_v" in field.dims:
431
- title = title + f", eta_v = {field.eta_v[eta].item()}"
432
- field = field.isel(eta_v=eta)
433
- interface_depth = interface_depth.isel(eta_v=eta)
434
- else:
435
- raise ValueError(
436
- f"None of the expected dimensions (eta_rho, eta_v) found in ds[{varname}]."
437
- )
475
+ title = title + f", eta_rho = {h.eta_rho[eta].item()}"
476
+ h = h.isel(eta_rho=eta)
438
477
  if xi is not None:
439
- if "xi_rho" in field.dims:
440
- title = title + f", xi_rho = {field.xi_rho[xi].item()}"
441
- field = field.isel(xi_rho=xi)
442
- interface_depth = interface_depth.isel(xi_rho=xi)
443
- elif "xi_u" in field.dims:
444
- title = title + f", xi_u = {field.xi_u[xi].item()}"
445
- field = field.isel(xi_u=xi)
446
- interface_depth = interface_depth.isel(xi_u=xi)
447
- else:
448
- raise ValueError(
449
- f"None of the expected dimensions (xi_rho, xi_u) found in ds[{varname}]."
450
- )
478
+ title = title + f", xi_rho = {h.xi_rho[xi].item()}"
479
+ h = h.isel(xi_rho=xi)
451
480
 
452
481
  if eta is None and xi is None:
453
- vmax = field.max().values
454
- vmin = field.min().values
482
+ layer_depth = compute_depth(0, h, self.hc, self.ds.Cs_r, self.ds.sigma_r)
483
+ title = title + f", s_rho = {layer_depth.s_rho[s].item()}"
484
+ layer_depth = layer_depth.isel(s_rho=s)
485
+
486
+ layer_depth.attrs["long_name"] = "Layer depth"
487
+ layer_depth.attrs["units"] = "m"
488
+
489
+ vmax = layer_depth.max().values
490
+ vmin = layer_depth.min().values
455
491
  cmap = plt.colormaps.get_cmap("YlGnBu")
456
492
  cmap.set_bad(color="gray")
457
493
  kwargs = {"vmax": vmax, "vmin": vmin, "cmap": cmap}
458
494
 
459
495
  _plot(
460
496
  self.ds,
461
- field=field,
497
+ field=layer_depth.where(self.ds.mask_rho),
462
498
  straddle=self.straddle,
463
499
  depth_contours=False,
464
500
  title=title,
465
501
  kwargs=kwargs,
466
502
  )
467
503
  else:
468
- if len(field.dims) == 2:
469
- cmap = plt.colormaps.get_cmap("YlGnBu")
470
- cmap.set_bad(color="gray")
471
- kwargs = {"vmax": 0.0, "vmin": 0.0, "cmap": cmap, "add_colorbar": False}
472
-
473
- _section_plot(
474
- xr.zeros_like(field),
475
- interface_depth=interface_depth,
476
- title=title,
477
- kwargs=kwargs,
478
- ax=ax,
479
- )
480
- else:
481
- if "s_rho" in field.dims or "s_w" in field.dims:
482
- _profile_plot(field, title=title, ax=ax)
483
- else:
484
- _line_plot(field, title=title, ax=ax)
504
+ layer_depth = compute_depth(0, h, self.hc, self.ds.Cs_r, self.ds.sigma_r)
505
+ layer_depth.attrs["long_name"] = "Layer depth"
506
+ layer_depth.attrs["units"] = "m"
507
+ field = xr.zeros_like(layer_depth)
508
+ field = field.assign_coords({"layer_depth": layer_depth})
509
+
510
+ interface_depth = compute_depth(
511
+ 0, h, self.hc, self.ds.Cs_w, self.ds.sigma_w
512
+ )
513
+ cmap = plt.colormaps.get_cmap("YlGnBu")
514
+ cmap.set_bad(color="gray")
515
+ kwargs = {"vmax": 0.0, "vmin": 0.0, "cmap": cmap, "add_colorbar": False}
516
+
517
+ _section_plot(
518
+ field=field,
519
+ interface_depth=interface_depth,
520
+ title=title,
521
+ kwargs=kwargs,
522
+ )
485
523
 
486
524
  def save(
487
525
  self, filepath: Union[str, Path], np_eta: int = None, np_xi: int = None
@@ -532,13 +570,15 @@ class Grid:
532
570
  return saved_filenames
533
571
 
534
572
  @classmethod
535
- def from_file(cls, filepath: Union[str, Path]) -> "Grid":
573
+ def from_file(cls, filepath: Union[str, Path], verbose: bool = False) -> "Grid":
536
574
  """Create a Grid instance from an existing file.
537
575
 
538
576
  Parameters
539
577
  ----------
540
578
  filepath : Union[str, Path]
541
579
  Path to the file containing the grid information.
580
+ verbose: bool, optional
581
+ Indicates whether to print grid generation steps with timing. Defaults to False.
542
582
 
543
583
  Returns
544
584
  -------
@@ -584,13 +624,14 @@ class Grid:
584
624
 
585
625
  # Update vertical coordinate if necessary
586
626
  if not all(var in grid.ds for var in ["Cs_r", "Cs_w"]):
627
+ logging.warning("Vertical coordinates (Cs_r, Cs_w) not found in grid file.")
587
628
  N = 100
588
629
  theta_s = 5.0
589
630
  theta_b = 2.0
590
631
  hc = 300.0
591
632
 
592
633
  grid.update_vertical_coordinate(
593
- N=N, theta_s=theta_s, theta_b=theta_b, hc=hc
634
+ N=N, theta_s=theta_s, theta_b=theta_b, hc=hc, verbose=verbose
594
635
  )
595
636
  else:
596
637
  object.__setattr__(grid, "theta_s", ds.attrs["theta_s"].item())
@@ -639,7 +680,10 @@ class Grid:
639
680
  "hmin",
640
681
  ]:
641
682
  if attr in ds.attrs:
642
- a = ds.attrs[attr]
683
+ if attr == "topography_source":
684
+ a = {"name": ds.attrs[attr]}
685
+ else:
686
+ a = ds.attrs[attr]
643
687
  else:
644
688
  a = None
645
689
  object.__setattr__(grid, attr, a)
@@ -661,6 +705,7 @@ class Grid:
661
705
  data = asdict(self)
662
706
  data.pop("ds", None)
663
707
  data.pop("straddle", None)
708
+ data.pop("verbose", None)
664
709
 
665
710
  # Include the version of roms-tools
666
711
  try:
@@ -681,13 +726,15 @@ class Grid:
681
726
  yaml.dump(yaml_data, file, default_flow_style=False, sort_keys=False)
682
727
 
683
728
  @classmethod
684
- def from_yaml(cls, filepath: Union[str, Path]) -> "Grid":
729
+ def from_yaml(cls, filepath: Union[str, Path], verbose: bool = False) -> "Grid":
685
730
  """Create an instance of the class from a YAML file.
686
731
 
687
732
  Parameters
688
733
  ----------
689
734
  filepath : Union[str, Path]
690
735
  The path to the YAML file from which the parameters will be read.
736
+ verbose: bool, optional
737
+ Indicates whether to print grid generation steps with timing. Defaults to False.
691
738
 
692
739
  Returns
693
740
  -------
@@ -733,8 +780,7 @@ class Grid:
733
780
 
734
781
  if grid_data is None:
735
782
  raise ValueError("No Grid configuration found in the YAML file.")
736
-
737
- return cls(**grid_data)
783
+ return cls(**grid_data, verbose=verbose)
738
784
 
739
785
  # override __repr__ method to only print attributes that are actually set
740
786
  def __repr__(self) -> str:
@@ -747,150 +793,263 @@ class Grid:
747
793
  attr_str = ", ".join(f"{k}={v!r}" for k, v in attr_dict.items())
748
794
  return f"{cls_name}({attr_str})"
749
795
 
796
+ def _create_horizontal_grid(self) -> xr.Dataset():
797
+ if self.verbose:
798
+ start_time = time.time()
799
+ logging.info("=== Creating the horizontal grid ===")
750
800
 
751
- def _make_grid_ds(
752
- nx: int,
753
- ny: int,
754
- size_x: float,
755
- size_y: float,
756
- center_lon: float,
757
- center_lat: float,
758
- rot: float,
759
- ) -> xr.Dataset:
760
- _raise_if_domain_size_too_large(size_x, size_y)
761
-
762
- initial_lon_lat_vars = _make_initial_lon_lat_ds(size_x, size_y, nx, ny)
763
-
764
- # rotate coordinate system
765
- rotated_lon_lat_vars = _rotate(*initial_lon_lat_vars, rot)
766
-
767
- # translate coordinate system
768
- translated_lon_lat_vars = _translate(*rotated_lon_lat_vars, center_lat, center_lon)
769
- lon, lat, lonu, latu, lonv, latv, lonq, latq = translated_lon_lat_vars
770
-
771
- # compute 1/dx and 1/dy
772
- pm, pn = _compute_coordinate_metrics(lon, lonu, latu, lonv, latv)
773
-
774
- # compute angle of local grid positive x-axis relative to east
775
- ang = _compute_angle(lon, lonu, latu, lonq)
776
-
777
- # make sure lons are in [0, 360] range
778
- lon[lon < 0] = lon[lon < 0] + 2 * np.pi
779
- lonu[lonu < 0] = lonu[lonu < 0] + 2 * np.pi
780
- lonv[lonv < 0] = lonv[lonv < 0] + 2 * np.pi
781
- lonq[lonq < 0] = lonq[lonq < 0] + 2 * np.pi
782
-
783
- ds = _create_grid_ds(
784
- lon,
785
- lat,
786
- lonu,
787
- latu,
788
- lonv,
789
- latv,
790
- lonq,
791
- latq,
792
- pm,
793
- pn,
794
- ang,
795
- rot,
796
- center_lon,
797
- center_lat,
798
- )
801
+ self._raise_if_domain_size_too_large()
799
802
 
800
- ds = _add_global_metadata(ds, size_x, size_y, center_lon, center_lat, rot)
803
+ coords = self._make_initial_lon_lat_ds()
801
804
 
802
- return ds
805
+ # rotate coordinate system
806
+ coords = _rotate(coords, self.rot)
803
807
 
808
+ # translate coordinate system
809
+ coords = _translate(coords, self.center_lat, self.center_lon)
804
810
 
805
- def _raise_if_domain_size_too_large(size_x, size_y):
806
- threshold = 20000
807
- if size_x > threshold or size_y > threshold:
808
- raise ValueError("Domain size has to be smaller than %g km" % threshold)
811
+ # compute 1/dx and 1/dy
812
+ coords["pm"], coords["pn"] = _compute_coordinate_metrics(coords)
809
813
 
814
+ # compute angle of local grid positive x-axis relative to east
815
+ coords["angle"] = _compute_angle(coords)
810
816
 
811
- def _make_initial_lon_lat_ds(size_x, size_y, nx, ny):
812
- # Mercator projection around the equator
817
+ # make sure lons are in [0, 360] range
818
+ for lon in ["lon", "lonu", "lonv", "lonq"]:
819
+ coords[lon][coords[lon] < 0] = coords[lon][coords[lon] < 0] + 2 * np.pi
813
820
 
814
- # initially define the domain to be longer in x-direction (dimension "length")
815
- # than in y-direction (dimension "width") to keep grid distortion minimal
816
- if size_y > size_x:
817
- domain_length, domain_width = size_y * 1e3, size_x * 1e3 # in m
818
- nl, nw = ny, nx
819
- else:
820
- domain_length, domain_width = size_x * 1e3, size_y * 1e3 # in m
821
- nl, nw = nx, ny
821
+ ds = self._create_grid_ds(coords)
822
822
 
823
- domain_length_in_degrees = domain_length / RADIUS_OF_EARTH
824
- domain_width_in_degrees = domain_width / RADIUS_OF_EARTH
823
+ ds = self._add_global_metadata(ds)
825
824
 
826
- # 1d array describing the longitudes at cell centers
827
- x = np.arange(-0.5, nl + 1.5, 1)
828
- lon_array_1d_in_degrees = (
829
- domain_length_in_degrees * x / nl - domain_length_in_degrees / 2
830
- )
831
- # 1d array describing the longitudes at cell corners (or vorticity points "q")
832
- xq = np.arange(-1, nl + 2, 1)
833
- lonq_array_1d_in_degrees_q = (
834
- domain_length_in_degrees * xq / nl - domain_length_in_degrees / 2
835
- )
825
+ if self.verbose:
826
+ logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
827
+ logging.info(
828
+ "========================================================================================================"
829
+ )
836
830
 
837
- # convert degrees latitude to y-coordinate using Mercator projection
838
- y1 = np.log(np.tan(np.pi / 4 - domain_width_in_degrees / 4))
839
- y2 = np.log(np.tan(np.pi / 4 + domain_width_in_degrees / 4))
831
+ object.__setattr__(self, "ds", ds)
840
832
 
841
- # linearly space points in y-space
842
- y = (y2 - y1) * np.arange(-0.5, nw + 1.5, 1) / nw + y1
843
- yq = (y2 - y1) * np.arange(-1, nw + 2) / nw + y1
833
+ def _add_global_metadata(self, ds):
844
834
 
845
- # inverse Mercator projections
846
- lat_array_1d_in_degrees = np.arctan(np.sinh(y))
847
- latq_array_1d_in_degrees = np.arctan(np.sinh(yq))
835
+ ds["spherical"] = xr.DataArray(np.array("T", dtype="S1"))
836
+ ds["spherical"].attrs["Long_name"] = "Grid type logical switch"
837
+ ds["spherical"].attrs["option_T"] = "spherical"
848
838
 
849
- # 2d grid at cell centers
850
- lon, lat = np.meshgrid(lon_array_1d_in_degrees, lat_array_1d_in_degrees)
851
- # 2d grid at cell corners
852
- lonq, latq = np.meshgrid(lonq_array_1d_in_degrees_q, latq_array_1d_in_degrees)
839
+ ds.attrs["title"] = "ROMS grid created by ROMS-Tools"
853
840
 
854
- if size_y > size_x:
855
- # Rotate grid by 90 degrees because until here the grid has been defined
856
- # to be longer in x-direction than in y-direction
841
+ # Include the version of roms-tools
842
+ try:
843
+ roms_tools_version = importlib.metadata.version("roms-tools")
844
+ except importlib.metadata.PackageNotFoundError:
845
+ roms_tools_version = "unknown"
857
846
 
858
- lon, lat = _rot_sphere(lon, lat, 90)
859
- lonq, latq = _rot_sphere(lonq, latq, 90)
847
+ ds.attrs["roms_tools_version"] = roms_tools_version
848
+ ds.attrs["size_x"] = self.size_x
849
+ ds.attrs["size_y"] = self.size_y
850
+ ds.attrs["center_lon"] = self.center_lon
851
+ ds.attrs["center_lat"] = self.center_lat
852
+ ds.attrs["rot"] = self.rot
860
853
 
861
- lon = np.transpose(np.flip(lon, 0))
862
- lat = np.transpose(np.flip(lat, 0))
863
- lonq = np.transpose(np.flip(lonq, 0))
864
- latq = np.transpose(np.flip(latq, 0))
854
+ return ds
865
855
 
866
- # infer longitudes and latitudes at u- and v-points
867
- lonu = 0.5 * (lon[:, :-1] + lon[:, 1:])
868
- latu = 0.5 * (lat[:, :-1] + lat[:, 1:])
869
- lonv = 0.5 * (lon[:-1, :] + lon[1:, :])
870
- latv = 0.5 * (lat[:-1, :] + lat[1:, :])
856
+ def _raise_if_domain_size_too_large(self):
857
+ threshold = 20000
858
+ if self.size_x > threshold or self.size_y > threshold:
859
+ raise ValueError(
860
+ f"Domain size exceeds the allowable limit of {threshold} km. "
861
+ f"Received dimensions: size_x = {self.size_x} km, size_y = {self.size_y} km. "
862
+ "Please reduce the domain size to meet the threshold."
863
+ )
871
864
 
872
- # TODO wrap up into temporary container Dataset object?
873
- return lon, lat, lonu, latu, lonv, latv, lonq, latq
865
+ def _make_initial_lon_lat_ds(self):
866
+ # Mercator projection around the equator
867
+
868
+ # initially define the domain to be longer in x-direction (dimension "length")
869
+ # than in y-direction (dimension "width") to keep grid distortion minimal
870
+ if self.size_y > self.size_x:
871
+ domain_length, domain_width = self.size_y * 1e3, self.size_x * 1e3 # in m
872
+ nl, nw = self.ny, self.nx
873
+ else:
874
+ domain_length, domain_width = self.size_x * 1e3, self.size_y * 1e3 # in m
875
+ nl, nw = self.nx, self.ny
874
876
 
877
+ domain_length_in_degrees = domain_length / RADIUS_OF_EARTH
878
+ domain_width_in_degrees = domain_width / RADIUS_OF_EARTH
875
879
 
876
- def _rotate(lon, lat, lonu, latu, lonv, latv, lonq, latq, rot):
880
+ # 1d array describing the longitudes at cell centers
881
+ x = np.arange(-0.5, nl + 1.5, 1)
882
+ lon_array_1d_in_degrees = (
883
+ domain_length_in_degrees * x / nl - domain_length_in_degrees / 2
884
+ )
885
+ # 1d array describing the longitudes at cell corners (or vorticity points "q")
886
+ xq = np.arange(-1, nl + 2, 1)
887
+ lonq_array_1d_in_degrees_q = (
888
+ domain_length_in_degrees * xq / nl - domain_length_in_degrees / 2
889
+ )
890
+
891
+ # convert degrees latitude to y-coordinate using Mercator projection
892
+ y1 = np.log(np.tan(np.pi / 4 - domain_width_in_degrees / 4))
893
+ y2 = np.log(np.tan(np.pi / 4 + domain_width_in_degrees / 4))
894
+
895
+ # linearly space points in y-space
896
+ y = (y2 - y1) * np.arange(-0.5, nw + 1.5, 1) / nw + y1
897
+ yq = (y2 - y1) * np.arange(-1, nw + 2) / nw + y1
898
+
899
+ # inverse Mercator projections
900
+ lat_array_1d_in_degrees = np.arctan(np.sinh(y))
901
+ latq_array_1d_in_degrees = np.arctan(np.sinh(yq))
902
+
903
+ # 2d grid at cell centers
904
+ lon, lat = np.meshgrid(lon_array_1d_in_degrees, lat_array_1d_in_degrees)
905
+ # 2d grid at cell corners
906
+ lonq, latq = np.meshgrid(lonq_array_1d_in_degrees_q, latq_array_1d_in_degrees)
907
+
908
+ if self.size_y > self.size_x:
909
+ # Rotate grid by 90 degrees because until here the grid has been defined
910
+ # to be longer in x-direction than in y-direction
911
+
912
+ lon, lat = _rot_sphere(lon, lat, 90)
913
+ lonq, latq = _rot_sphere(lonq, latq, 90)
914
+
915
+ lon = np.transpose(np.flip(lon, 0))
916
+ lat = np.transpose(np.flip(lat, 0))
917
+ lonq = np.transpose(np.flip(lonq, 0))
918
+ latq = np.transpose(np.flip(latq, 0))
919
+
920
+ # infer longitudes and latitudes at u- and v-points
921
+ lonu = 0.5 * (lon[:, :-1] + lon[:, 1:])
922
+ latu = 0.5 * (lat[:, :-1] + lat[:, 1:])
923
+ lonv = 0.5 * (lon[:-1, :] + lon[1:, :])
924
+ latv = 0.5 * (lat[:-1, :] + lat[1:, :])
925
+
926
+ coords = {
927
+ "lon": lon,
928
+ "lat": lat,
929
+ "lonu": lonu,
930
+ "latu": latu,
931
+ "lonv": lonv,
932
+ "latv": latv,
933
+ "lonq": lonq,
934
+ "latq": latq,
935
+ }
936
+
937
+ return coords
938
+
939
+ def _create_grid_ds(self, coords):
940
+
941
+ ds = xr.Dataset()
942
+
943
+ lon_rho = xr.Variable(
944
+ data=coords["lon"] * 180 / np.pi,
945
+ dims=["eta_rho", "xi_rho"],
946
+ attrs={"long_name": "longitude of rho-points", "units": "degrees East"},
947
+ )
948
+ lat_rho = xr.Variable(
949
+ data=coords["lat"] * 180 / np.pi,
950
+ dims=["eta_rho", "xi_rho"],
951
+ attrs={"long_name": "latitude of rho-points", "units": "degrees North"},
952
+ )
953
+ lon_u = xr.Variable(
954
+ data=coords["lonu"] * 180 / np.pi,
955
+ dims=["eta_rho", "xi_u"],
956
+ attrs={"long_name": "longitude of u-points", "units": "degrees East"},
957
+ )
958
+ lat_u = xr.Variable(
959
+ data=coords["latu"] * 180 / np.pi,
960
+ dims=["eta_rho", "xi_u"],
961
+ attrs={"long_name": "latitude of u-points", "units": "degrees North"},
962
+ )
963
+ lon_v = xr.Variable(
964
+ data=coords["lonv"] * 180 / np.pi,
965
+ dims=["eta_v", "xi_rho"],
966
+ attrs={"long_name": "longitude of v-points", "units": "degrees East"},
967
+ )
968
+ lat_v = xr.Variable(
969
+ data=coords["latv"] * 180 / np.pi,
970
+ dims=["eta_v", "xi_rho"],
971
+ attrs={"long_name": "latitude of v-points", "units": "degrees North"},
972
+ )
973
+ # lon_q = xr.Variable(
974
+ # data=coords["lonq"] * 180 / np.pi,
975
+ # dims=["eta_psi", "xi_psi"],
976
+ # attrs={"long_name": "longitude of psi-points", "units": "degrees East"},
977
+ # )
978
+ # lat_q = xr.Variable(
979
+ # data=coords["latq"] * 180 / np.pi,
980
+ # dims=["eta_psi", "xi_psi"],
981
+ # attrs={"long_name": "latitude of psi-points", "units": "degrees North"},
982
+ # )
983
+
984
+ ds = ds.assign_coords(
985
+ {
986
+ "lat_rho": lat_rho,
987
+ "lon_rho": lon_rho,
988
+ "lat_u": lat_u,
989
+ "lon_u": lon_u,
990
+ "lat_v": lat_v,
991
+ "lon_v": lon_v,
992
+ # "lat_psi": lat_q,
993
+ # "lon_psi": lon_q,
994
+ }
995
+ )
996
+
997
+ ds["angle"] = xr.Variable(
998
+ data=coords["angle"],
999
+ dims=["eta_rho", "xi_rho"],
1000
+ attrs={"long_name": "Angle between xi axis and east", "units": "radians"},
1001
+ )
1002
+
1003
+ # Coriolis frequency
1004
+ f0 = 4 * np.pi * np.sin(coords["lat"]) / (24 * 3600)
1005
+
1006
+ ds["f"] = xr.Variable(
1007
+ data=f0,
1008
+ dims=["eta_rho", "xi_rho"],
1009
+ attrs={
1010
+ "long_name": "Coriolis parameter at rho-points",
1011
+ "units": "second-1",
1012
+ },
1013
+ )
1014
+
1015
+ ds["pm"] = xr.Variable(
1016
+ data=coords["pm"],
1017
+ dims=["eta_rho", "xi_rho"],
1018
+ attrs={
1019
+ "long_name": "Curvilinear coordinate metric in xi-direction",
1020
+ "units": "meter-1",
1021
+ },
1022
+ )
1023
+ ds["pn"] = xr.Variable(
1024
+ data=coords["pn"],
1025
+ dims=["eta_rho", "xi_rho"],
1026
+ attrs={
1027
+ "long_name": "Curvilinear coordinate metric in eta-direction",
1028
+ "units": "meter-1",
1029
+ },
1030
+ )
1031
+
1032
+ return ds
1033
+
1034
+
1035
+ def _rotate(coords, rot):
877
1036
  """Rotate grid counterclockwise relative to surface of Earth by rot degrees."""
878
1037
 
879
- (lon, lat) = _rot_sphere(lon, lat, rot)
880
- (lonu, latu) = _rot_sphere(lonu, latu, rot)
881
- (lonv, latv) = _rot_sphere(lonv, latv, rot)
882
- (lonq, latq) = _rot_sphere(lonq, latq, rot)
1038
+ (coords["lon"], coords["lat"]) = _rot_sphere(coords["lon"], coords["lat"], rot)
1039
+ (coords["lonu"], coords["latu"]) = _rot_sphere(coords["lonu"], coords["latu"], rot)
1040
+ (coords["lonv"], coords["latv"]) = _rot_sphere(coords["lonv"], coords["latv"], rot)
1041
+ (coords["lonq"], coords["latq"]) = _rot_sphere(coords["lonq"], coords["latq"], rot)
883
1042
 
884
- return lon, lat, lonu, latu, lonv, latv, lonq, latq
1043
+ return coords
885
1044
 
886
1045
 
887
- def _translate(lon, lat, lonu, latu, lonv, latv, lonq, latq, tra_lat, tra_lon):
1046
+ def _translate(coords, tra_lat, tra_lon):
888
1047
  """Translate grid so that the centre lies at the position (tra_lat, tra_lon)"""
889
1048
 
890
- (lon, lat) = _tra_sphere(lon, lat, tra_lat)
891
- (lonu, latu) = _tra_sphere(lonu, latu, tra_lat)
892
- (lonv, latv) = _tra_sphere(lonv, latv, tra_lat)
893
- (lonq, latq) = _tra_sphere(lonq, latq, tra_lat)
1049
+ (lon, lat) = _tra_sphere(coords["lon"], coords["lat"], tra_lat)
1050
+ (lonu, latu) = _tra_sphere(coords["lonu"], coords["latu"], tra_lat)
1051
+ (lonv, latv) = _tra_sphere(coords["lonv"], coords["latv"], tra_lat)
1052
+ (lonq, latq) = _tra_sphere(coords["lonq"], coords["latq"], tra_lat)
894
1053
 
895
1054
  lon = lon + tra_lon * np.pi / 180
896
1055
  lonu = lonu + tra_lon * np.pi / 180
@@ -902,7 +1061,18 @@ def _translate(lon, lat, lonu, latu, lonv, latv, lonq, latq, tra_lat, tra_lon):
902
1061
  lonv[lonv < -np.pi] = lonv[lonv < -np.pi] + 2 * np.pi
903
1062
  lonq[lonq < -np.pi] = lonq[lonq < -np.pi] + 2 * np.pi
904
1063
 
905
- return lon, lat, lonu, latu, lonv, latv, lonq, latq
1064
+ coords = {
1065
+ "lon": lon,
1066
+ "lat": lat,
1067
+ "lonu": lonu,
1068
+ "latu": latu,
1069
+ "lonv": lonv,
1070
+ "latv": latv,
1071
+ "lonq": lonq,
1072
+ "latq": latq,
1073
+ }
1074
+
1075
+ return coords
906
1076
 
907
1077
 
908
1078
  def _rot_sphere(lon, lat, rot):
@@ -1013,21 +1183,31 @@ def _tra_sphere(lon, lat, tra):
1013
1183
  return (lon, lat)
1014
1184
 
1015
1185
 
1016
- def _compute_coordinate_metrics(lon, lonu, latu, lonv, latv):
1186
+ def _compute_coordinate_metrics(coords):
1017
1187
  """Compute the curvilinear coordinate metrics pn and pm, defined as 1/grid
1018
1188
  spacing."""
1019
1189
 
1020
1190
  # pm = 1/dx
1021
- pmu = gc_dist(lonu[:, :-1], latu[:, :-1], lonu[:, 1:], latu[:, 1:])
1022
- pm = 0 * lon
1191
+ pmu = gc_dist(
1192
+ coords["lonu"][:, :-1],
1193
+ coords["latu"][:, :-1],
1194
+ coords["lonu"][:, 1:],
1195
+ coords["latu"][:, 1:],
1196
+ )
1197
+ pm = 0 * coords["lon"]
1023
1198
  pm[:, 1:-1] = pmu
1024
1199
  pm[:, 0] = pm[:, 1]
1025
1200
  pm[:, -1] = pm[:, -2]
1026
1201
  pm = 1 / pm
1027
1202
 
1028
1203
  # pn = 1/dy
1029
- pnv = gc_dist(lonv[:-1, :], latv[:-1, :], lonv[1:, :], latv[1:, :])
1030
- pn = 0 * lon
1204
+ pnv = gc_dist(
1205
+ coords["lonv"][:-1, :],
1206
+ coords["latv"][:-1, :],
1207
+ coords["lonv"][1:, :],
1208
+ coords["latv"][1:, :],
1209
+ )
1210
+ pn = 0 * coords["lon"]
1031
1211
  pn[1:-1, :] = pnv
1032
1212
  pn[0, :] = pn[1, :]
1033
1213
  pn[-1, :] = pn[-2, :]
@@ -1055,16 +1235,16 @@ def gc_dist(lon1, lat1, lon2, lat2):
1055
1235
  return dis
1056
1236
 
1057
1237
 
1058
- def _compute_angle(lon, lonu, latu, lonq):
1238
+ def _compute_angle(coords):
1059
1239
  """Compute angles of local grid positive x-axis relative to east."""
1060
1240
 
1061
- dellat = latu[:, 1:] - latu[:, :-1]
1062
- dellon = lonu[:, 1:] - lonu[:, :-1]
1241
+ dellat = coords["latu"][:, 1:] - coords["latu"][:, :-1]
1242
+ dellon = coords["lonu"][:, 1:] - coords["lonu"][:, :-1]
1063
1243
  dellon[dellon > np.pi] = dellon[dellon > np.pi] - 2 * np.pi
1064
1244
  dellon[dellon < -np.pi] = dellon[dellon < -np.pi] + 2 * np.pi
1065
- dellon = dellon * np.cos(0.5 * (latu[:, 1:] + latu[:, :-1]))
1245
+ dellon = dellon * np.cos(0.5 * (coords["latu"][:, 1:] + coords["latu"][:, :-1]))
1066
1246
 
1067
- ang = copy.copy(lon)
1247
+ ang = copy.copy(coords["lon"])
1068
1248
  ang_s = np.arctan(dellat / (dellon + 1e-16))
1069
1249
  ang_s[(dellon < 0) & (dellat < 0)] = ang_s[(dellon < 0) & (dellat < 0)] - np.pi
1070
1250
  ang_s[(dellon < 0) & (dellat >= 0)] = ang_s[(dellon < 0) & (dellat >= 0)] + np.pi
@@ -1078,137 +1258,6 @@ def _compute_angle(lon, lonu, latu, lonq):
1078
1258
  return ang
1079
1259
 
1080
1260
 
1081
- def _create_grid_ds(
1082
- lon,
1083
- lat,
1084
- lonu,
1085
- latu,
1086
- lonv,
1087
- latv,
1088
- lonq,
1089
- latq,
1090
- pm,
1091
- pn,
1092
- angle,
1093
- rot,
1094
- center_lon,
1095
- center_lat,
1096
- ):
1097
- ds = xr.Dataset()
1098
-
1099
- lon_rho = xr.Variable(
1100
- data=lon * 180 / np.pi,
1101
- dims=["eta_rho", "xi_rho"],
1102
- attrs={"long_name": "longitude of rho-points", "units": "degrees East"},
1103
- )
1104
- lat_rho = xr.Variable(
1105
- data=lat * 180 / np.pi,
1106
- dims=["eta_rho", "xi_rho"],
1107
- attrs={"long_name": "latitude of rho-points", "units": "degrees North"},
1108
- )
1109
- lon_u = xr.Variable(
1110
- data=lonu * 180 / np.pi,
1111
- dims=["eta_rho", "xi_u"],
1112
- attrs={"long_name": "longitude of u-points", "units": "degrees East"},
1113
- )
1114
- lat_u = xr.Variable(
1115
- data=latu * 180 / np.pi,
1116
- dims=["eta_rho", "xi_u"],
1117
- attrs={"long_name": "latitude of u-points", "units": "degrees North"},
1118
- )
1119
- lon_v = xr.Variable(
1120
- data=lonv * 180 / np.pi,
1121
- dims=["eta_v", "xi_rho"],
1122
- attrs={"long_name": "longitude of v-points", "units": "degrees East"},
1123
- )
1124
- lat_v = xr.Variable(
1125
- data=latv * 180 / np.pi,
1126
- dims=["eta_v", "xi_rho"],
1127
- attrs={"long_name": "latitude of v-points", "units": "degrees North"},
1128
- )
1129
- # lon_q = xr.Variable(
1130
- # data=lonq * 180 / np.pi,
1131
- # dims=["eta_psi", "xi_psi"],
1132
- # attrs={"long_name": "longitude of psi-points", "units": "degrees East"},
1133
- # )
1134
- # lat_q = xr.Variable(
1135
- # data=latq * 180 / np.pi,
1136
- # dims=["eta_psi", "xi_psi"],
1137
- # attrs={"long_name": "latitude of psi-points", "units": "degrees North"},
1138
- # )
1139
-
1140
- ds = ds.assign_coords(
1141
- {
1142
- "lat_rho": lat_rho,
1143
- "lon_rho": lon_rho,
1144
- "lat_u": lat_u,
1145
- "lon_u": lon_u,
1146
- "lat_v": lat_v,
1147
- "lon_v": lon_v,
1148
- # "lat_psi": lat_q,
1149
- # "lon_psi": lon_q,
1150
- }
1151
- )
1152
-
1153
- ds["angle"] = xr.Variable(
1154
- data=angle,
1155
- dims=["eta_rho", "xi_rho"],
1156
- attrs={"long_name": "Angle between xi axis and east", "units": "radians"},
1157
- )
1158
-
1159
- # Coriolis frequency
1160
- f0 = 4 * np.pi * np.sin(lat) / (24 * 3600)
1161
-
1162
- ds["f"] = xr.Variable(
1163
- data=f0,
1164
- dims=["eta_rho", "xi_rho"],
1165
- attrs={"long_name": "Coriolis parameter at rho-points", "units": "second-1"},
1166
- )
1167
-
1168
- ds["pm"] = xr.Variable(
1169
- data=pm,
1170
- dims=["eta_rho", "xi_rho"],
1171
- attrs={
1172
- "long_name": "Curvilinear coordinate metric in xi-direction",
1173
- "units": "meter-1",
1174
- },
1175
- )
1176
- ds["pn"] = xr.Variable(
1177
- data=pn,
1178
- dims=["eta_rho", "xi_rho"],
1179
- attrs={
1180
- "long_name": "Curvilinear coordinate metric in eta-direction",
1181
- "units": "meter-1",
1182
- },
1183
- )
1184
-
1185
- return ds
1186
-
1187
-
1188
- def _add_global_metadata(ds, size_x, size_y, center_lon, center_lat, rot):
1189
-
1190
- ds["spherical"] = xr.DataArray(np.array("T", dtype="S1"))
1191
- ds["spherical"].attrs["Long_name"] = "Grid type logical switch"
1192
- ds["spherical"].attrs["option_T"] = "spherical"
1193
-
1194
- ds.attrs["title"] = "ROMS grid created by ROMS-Tools"
1195
-
1196
- # Include the version of roms-tools
1197
- try:
1198
- roms_tools_version = importlib.metadata.version("roms-tools")
1199
- except importlib.metadata.PackageNotFoundError:
1200
- roms_tools_version = "unknown"
1201
-
1202
- ds.attrs["roms_tools_version"] = roms_tools_version
1203
- ds.attrs["size_x"] = size_x
1204
- ds.attrs["size_y"] = size_y
1205
- ds.attrs["center_lon"] = center_lon
1206
- ds.attrs["center_lat"] = center_lat
1207
- ds.attrs["rot"] = rot
1208
-
1209
- return ds
1210
-
1211
-
1212
1261
  def _f2c(f):
1213
1262
  """Coarsen input xarray DataArray f in both x- and y-direction.
1214
1263