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.
- ci/environment.yml +1 -1
- roms_tools/__init__.py +1 -0
- roms_tools/_version.py +1 -1
- roms_tools/setup/boundary_forcing.py +266 -256
- roms_tools/setup/datasets.py +986 -231
- roms_tools/setup/download.py +41 -15
- roms_tools/setup/grid.py +561 -512
- roms_tools/setup/initial_conditions.py +162 -106
- roms_tools/setup/mask.py +69 -0
- roms_tools/setup/plot.py +81 -23
- roms_tools/setup/regrid.py +4 -2
- roms_tools/setup/river_forcing.py +589 -0
- roms_tools/setup/surface_forcing.py +21 -130
- roms_tools/setup/tides.py +15 -79
- roms_tools/setup/topography.py +92 -128
- roms_tools/setup/utils.py +307 -25
- roms_tools/setup/vertical_coordinate.py +5 -16
- roms_tools/tests/test_setup/test_boundary_forcing.py +10 -7
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zmetadata +157 -130
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/abs_time/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/bry_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/month/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_south/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zmetadata +39 -12
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/abs_time/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/month/0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air_alt/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +0 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +56 -201
- roms_tools/tests/test_setup/test_data/grid.zarr/Cs_r/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/Cs_w/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_rho → sigma_r}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_u → sigma_w}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +58 -203
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_r/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_w/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/interface_depth_v → grid_that_straddles_dateline.zarr/sigma_r}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_rho → grid_that_straddles_dateline.zarr/sigma_w}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zattrs +3 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zgroup +3 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/.zmetadata +214 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/abs_time/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/month/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/.zarray +24 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_time/0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_v → river_forcing.zarr/river_tracer}/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/.zattrs +10 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/.zarray +22 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/.zattrs +9 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_volume/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_u → river_forcing.zarr/tracer_name}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zgroup +3 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +185 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/abs_time/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/.zarray +24 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_time/0 +0 -0
- 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
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +10 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/.zarray +22 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/.zattrs +9 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_volume/0.0 +0 -0
- 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
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/0 +0 -0
- roms_tools/tests/test_setup/test_grid.py +110 -12
- roms_tools/tests/test_setup/test_initial_conditions.py +2 -3
- roms_tools/tests/test_setup/test_river_forcing.py +367 -0
- roms_tools/tests/test_setup/test_surface_forcing.py +2 -24
- roms_tools/tests/test_setup/test_tides.py +2 -3
- roms_tools/tests/test_setup/test_topography.py +106 -1
- roms_tools/tests/test_setup/test_validation.py +4 -0
- roms_tools/utils.py +12 -10
- {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/LICENSE +1 -1
- {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/METADATA +6 -5
- {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/RECORD +254 -225
- {roms_tools-1.6.2.dist-info → roms_tools-2.0.0.dist-info}/WHEEL +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
- {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
|
|
11
|
-
from roms_tools.setup.
|
|
12
|
-
from roms_tools.setup.plot import _plot, _section_plot
|
|
13
|
-
from roms_tools.setup.utils import
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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 =
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
110
|
+
# Mask
|
|
111
|
+
self._create_mask(verbose=self.verbose)
|
|
106
112
|
|
|
107
|
-
#
|
|
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,
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
def _input_checks(self):
|
|
133
|
+
if self.topography_source is None:
|
|
134
|
+
object.__setattr__(self, "topography_source", {"name": "ETOPO5"})
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
144
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
218
|
-
|
|
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
|
-
|
|
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"] = "
|
|
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"] = "
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
318
|
+
if verbose:
|
|
319
|
+
logging.info(f"Total time: {time.time() - start_time:.3f} seconds")
|
|
320
|
+
logging.info(
|
|
321
|
+
"========================================================================================================"
|
|
322
|
+
)
|
|
278
323
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
depth_v.attrs["units"] = "m"
|
|
330
|
+
def _straddle(self) -> None:
|
|
331
|
+
"""Check if the Greenwich meridian goes through the domain.
|
|
286
332
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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,
|
|
436
|
+
self,
|
|
437
|
+
s=None,
|
|
438
|
+
eta=None,
|
|
439
|
+
xi=None,
|
|
354
440
|
) -> None:
|
|
355
|
-
"""Plot the
|
|
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
|
|
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
|
-
|
|
391
|
-
raise ValueError("At least one of s, eta, or xi must be specified.")
|
|
465
|
+
title = "Layer depth at rho-points"
|
|
392
466
|
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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=
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
803
|
+
coords = self._make_initial_lon_lat_ds()
|
|
801
804
|
|
|
802
|
-
|
|
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
|
-
|
|
806
|
-
|
|
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
|
-
|
|
812
|
-
|
|
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
|
-
|
|
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
|
-
|
|
824
|
-
domain_width_in_degrees = domain_width / RADIUS_OF_EARTH
|
|
823
|
+
ds = self._add_global_metadata(ds)
|
|
825
824
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
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
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
|
|
873
|
-
|
|
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
|
-
|
|
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
|
|
1043
|
+
return coords
|
|
885
1044
|
|
|
886
1045
|
|
|
887
|
-
def _translate(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1022
|
-
|
|
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(
|
|
1030
|
-
|
|
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(
|
|
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
|
|