roms-tools 2.4.0__py3-none-any.whl → 2.6.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-with-xesmf.yml +16 -0
- roms_tools/__init__.py +1 -1
- roms_tools/analysis/roms_output.py +339 -234
- roms_tools/analysis/utils.py +137 -0
- roms_tools/plot.py +353 -214
- roms_tools/regrid.py +154 -9
- roms_tools/setup/boundary_forcing.py +51 -37
- roms_tools/setup/datasets.py +129 -74
- roms_tools/setup/grid.py +32 -33
- roms_tools/setup/initial_conditions.py +30 -37
- roms_tools/setup/nesting.py +238 -64
- roms_tools/setup/river_forcing.py +256 -86
- roms_tools/setup/surface_forcing.py +40 -28
- roms_tools/setup/tides.py +10 -13
- roms_tools/setup/topography.py +27 -4
- roms_tools/setup/utils.py +28 -12
- roms_tools/tests/test_analysis/test_roms_output.py +299 -80
- roms_tools/tests/test_regrid.py +85 -2
- roms_tools/tests/test_setup/test_boundary_forcing.py +63 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zattrs +3 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/.zmetadata +3 -1
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_ALT_CO2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/ALK_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_ALT_CO2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DIC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOCr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DON_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DONr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/DOPr_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Fe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/Lig_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NH4_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/NO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/O2_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/PO4_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/SiO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diatSi_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/diazP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spCaCO3_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spChl_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spFe_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/spP_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_boundary_forcing_from_climatology.zarr/zooC_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zattrs +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/.zmetadata +8 -7
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/abs_time/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/dust_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/iron_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nhy_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/nox_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_air_alt/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing.zarr/pco2_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zattrs +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/dust/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/iron/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nhy/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/nox/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/bgc_surface_forcing_from_climatology.zarr/pco2_air_alt/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_data/grid.zarr/angle/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/angle_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/f/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/h/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/lat_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/pm/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/f/0.0 +0 -0
- 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/lat_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pm/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pn/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zmetadata +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/salt/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zeta/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +27 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/nriver/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +27 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/nriver/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zattrs +1 -1
- roms_tools/tests/test_setup/test_initial_conditions.py +16 -0
- roms_tools/tests/test_setup/test_nesting.py +141 -104
- roms_tools/tests/test_setup/test_river_forcing.py +580 -266
- roms_tools/tests/test_setup/test_surface_forcing.py +47 -0
- roms_tools/tests/test_setup/test_validation.py +34 -2
- roms_tools/utils.py +11 -7
- roms_tools/vertical_coordinate.py +1 -0
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/METADATA +22 -11
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/RECORD +214 -206
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/WHEEL +1 -1
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info/licenses}/LICENSE +0 -0
- {roms_tools-2.4.0.dist-info → roms_tools-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import xarray as xr
|
|
2
2
|
import numpy as np
|
|
3
3
|
import matplotlib.pyplot as plt
|
|
4
|
+
from roms_tools.plot import _plot, _section_plot, _profile_plot, _line_plot
|
|
4
5
|
from roms_tools.utils import _load_data
|
|
6
|
+
from roms_tools.regrid import LateralRegridFromROMS, VerticalRegridFromROMS
|
|
5
7
|
from dataclasses import dataclass, field
|
|
6
8
|
from typing import Union, Optional
|
|
7
9
|
from pathlib import Path
|
|
8
|
-
import os
|
|
9
10
|
import re
|
|
10
11
|
import logging
|
|
11
12
|
from datetime import datetime, timedelta
|
|
12
13
|
from roms_tools import Grid
|
|
13
|
-
from roms_tools.plot import _plot, _section_plot, _profile_plot, _line_plot
|
|
14
14
|
from roms_tools.vertical_coordinate import (
|
|
15
15
|
compute_depth_coordinates,
|
|
16
16
|
)
|
|
17
|
+
from roms_tools.analysis.utils import _validate_plot_inputs, _generate_coordinate_range
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
@dataclass(
|
|
20
|
+
@dataclass(kw_only=True)
|
|
20
21
|
class ROMSOutput:
|
|
21
22
|
"""Represents ROMS model output.
|
|
22
23
|
|
|
@@ -25,41 +26,35 @@ class ROMSOutput:
|
|
|
25
26
|
grid : Grid
|
|
26
27
|
Object representing the grid information.
|
|
27
28
|
path : Union[str, Path, List[Union[str, Path]]]
|
|
28
|
-
|
|
29
|
-
type : str
|
|
30
|
-
Specifies the type of model output. Options are:
|
|
31
|
-
|
|
32
|
-
- "restart": for restart files.
|
|
33
|
-
- "average": for time-averaged files.
|
|
34
|
-
- "snapshot": for snapshot files.
|
|
35
|
-
|
|
29
|
+
Filename, or list of filenames with model output.
|
|
36
30
|
model_reference_date : datetime, optional
|
|
37
31
|
If not specified, this is inferred from metadata of the model output
|
|
38
32
|
If specified and does not coincide with metadata, a warning is raised.
|
|
33
|
+
adjust_depth_for_sea_surface_height : bool, optional
|
|
34
|
+
Whether to account for sea surface height variations when computing depth coordinates.
|
|
35
|
+
Defaults to `False`.
|
|
39
36
|
use_dask: bool, optional
|
|
40
37
|
Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
|
|
41
38
|
"""
|
|
42
39
|
|
|
43
40
|
grid: Grid
|
|
44
41
|
path: Union[str, Path]
|
|
45
|
-
type: Union[str, Path]
|
|
46
42
|
use_dask: bool = False
|
|
47
43
|
model_reference_date: Optional[datetime] = None
|
|
44
|
+
adjust_depth_for_sea_surface_height: Optional[bool] = False
|
|
48
45
|
ds: xr.Dataset = field(init=False, repr=False)
|
|
49
46
|
|
|
50
47
|
def __post_init__(self):
|
|
51
|
-
# Validate `type`
|
|
52
|
-
if self.type not in {"restart", "average", "snapshot"}:
|
|
53
|
-
raise ValueError(
|
|
54
|
-
f"Invalid type '{self.type}'. Must be one of 'restart', 'average', or 'snapshot'."
|
|
55
|
-
)
|
|
56
48
|
|
|
57
49
|
ds = self._load_model_output()
|
|
58
50
|
self._infer_model_reference_date_from_metadata(ds)
|
|
59
51
|
self._check_vertical_coordinate(ds)
|
|
60
52
|
ds = self._add_absolute_time(ds)
|
|
61
53
|
ds = self._add_lat_lon_coords(ds)
|
|
62
|
-
|
|
54
|
+
self.ds = ds
|
|
55
|
+
|
|
56
|
+
# Dataset for depth coordinates
|
|
57
|
+
self.ds_depth_coords = xr.Dataset()
|
|
63
58
|
|
|
64
59
|
def plot(
|
|
65
60
|
self,
|
|
@@ -68,11 +63,15 @@ class ROMSOutput:
|
|
|
68
63
|
s=None,
|
|
69
64
|
eta=None,
|
|
70
65
|
xi=None,
|
|
66
|
+
depth=None,
|
|
67
|
+
lat=None,
|
|
68
|
+
lon=None,
|
|
69
|
+
include_boundary=False,
|
|
71
70
|
depth_contours=False,
|
|
72
|
-
layer_contours=False,
|
|
73
71
|
ax=None,
|
|
72
|
+
save_path=None,
|
|
74
73
|
) -> None:
|
|
75
|
-
"""
|
|
74
|
+
"""Generate a plot of a ROMS output field for a specified vertical or horizontal
|
|
76
75
|
slice.
|
|
77
76
|
|
|
78
77
|
Parameters
|
|
@@ -85,26 +84,56 @@ class ROMSOutput:
|
|
|
85
84
|
|
|
86
85
|
time : int, optional
|
|
87
86
|
Index of the time dimension to plot. Default is 0.
|
|
87
|
+
|
|
88
88
|
s : int, optional
|
|
89
|
-
The index of the vertical layer (`s_rho`) to plot. If
|
|
90
|
-
will
|
|
89
|
+
The index of the vertical layer (`s_rho`) to plot. If specified, the plot
|
|
90
|
+
will display a horizontal slice at that layer. Cannot be used simultaneously
|
|
91
|
+
with `depth`. Default is None.
|
|
92
|
+
|
|
91
93
|
eta : int, optional
|
|
92
|
-
The eta-index to plot. Used for vertical sections or
|
|
93
|
-
|
|
94
|
+
The eta-index to plot. Used for generating vertical sections or plotting
|
|
95
|
+
horizontal slices along a constant eta-coordinate. Cannot be used simultaneously
|
|
96
|
+
with `lat` or `lon`, but can be combined with `xi`. Default is None.
|
|
97
|
+
|
|
94
98
|
xi : int, optional
|
|
95
|
-
The xi-index to plot. Used for vertical sections or
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
The xi-index to plot. Used for generating vertical sections or plotting
|
|
100
|
+
horizontal slices along a constant xi-coordinate. Cannot be used simultaneously
|
|
101
|
+
with `lat` or `lon`, but can be combined with `eta`. Default is None.
|
|
102
|
+
|
|
103
|
+
depth : float, optional
|
|
104
|
+
Depth (in meters) to plot a horizontal slice at a specific depth level.
|
|
105
|
+
If specified, the plot will interpolate the field to the given depth.
|
|
106
|
+
Cannot be used simultaneously with `s` or for fields that are inherently
|
|
107
|
+
2D (such as "zeta"). Default is None.
|
|
108
|
+
|
|
109
|
+
lat : float, optional
|
|
110
|
+
Latitude (in degrees) to plot a vertical section at a specific
|
|
111
|
+
latitude. This option is useful for generating zonal (west-east)
|
|
112
|
+
sections. Cannot be used simultaneously with `eta` or `xi`, bu can be
|
|
113
|
+
combined with `lon`. Default is None.
|
|
114
|
+
|
|
115
|
+
lon : float, optional
|
|
116
|
+
Longitude (in degrees) to plot a vertical section at a specific
|
|
117
|
+
longitude. This option is useful for generating meridional (south-north) sections.
|
|
118
|
+
Cannot be used simultaneously with `eta` or `xi`, but can be combined
|
|
119
|
+
with `lat`. Default is None.
|
|
120
|
+
|
|
121
|
+
include_boundary : bool, optional
|
|
122
|
+
Whether to include the outermost grid cells along the `eta`- and `xi`-boundaries in the plot.
|
|
123
|
+
In diagnostic ROMS output fields, these boundary cells are set to zero, so excluding them can improve visualization.
|
|
100
124
|
Default is False.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
|
|
126
|
+
depth_contours : bool, optional
|
|
127
|
+
If True, overlays contours representing lines of constant depth on the plot.
|
|
128
|
+
This option is only relevant when the `s` parameter is provided (i.e., not None).
|
|
129
|
+
By default, depth contours are not shown (False).
|
|
130
|
+
|
|
106
131
|
ax : matplotlib.axes.Axes, optional
|
|
107
|
-
The axes to plot on. If None, a new figure is created. Note that this argument does not work for horizontal plots
|
|
132
|
+
The axes to plot on. If None, a new figure is created. Note that this argument does not work for 2D horizontal plots. Default is None.
|
|
133
|
+
|
|
134
|
+
save_path : str, optional
|
|
135
|
+
Path to save the generated plot. If None, the plot is shown interactively.
|
|
136
|
+
Default is None.
|
|
108
137
|
|
|
109
138
|
Returns
|
|
110
139
|
-------
|
|
@@ -114,44 +143,86 @@ class ROMSOutput:
|
|
|
114
143
|
Raises
|
|
115
144
|
------
|
|
116
145
|
ValueError
|
|
117
|
-
If the specified `var_name` is not one of the valid options.
|
|
118
|
-
If the field specified by `var_name` is 3D and none of `s`, `eta`, or `
|
|
119
|
-
If the field specified by `var_name` is 2D and both `eta` and `xi` are specified.
|
|
146
|
+
- If the specified `var_name` is not one of the valid options.
|
|
147
|
+
- If the field specified by `var_name` is 3D and none of `s`, `eta`, `xi`, `depth`, `lat`, or `lon` are specified.
|
|
148
|
+
- If the field specified by `var_name` is 2D and both `eta` and `xi` or both `lat` and `lon` are specified.
|
|
149
|
+
- If conflicting dimensions are specified (e.g., specifying `eta`/`xi` with `lat`/`lon` or both `s` and `depth`).
|
|
150
|
+
- If more than two dimensions are specified for a 3D field.
|
|
151
|
+
- If `time` exceeds the bounds of the time dimension.
|
|
152
|
+
- If `time` is specified for a field that does not have a time dimension.
|
|
153
|
+
- If `eta` or `xi` indices are out of bounds.
|
|
154
|
+
- If `eta` or `xi` lie on the boundary when `include_boundary=False`.
|
|
120
155
|
"""
|
|
121
|
-
|
|
122
|
-
# Input checks
|
|
156
|
+
# Check if variable exists
|
|
123
157
|
if var_name not in self.ds:
|
|
124
|
-
raise ValueError(f"Variable '{var_name}' is not found in dataset.")
|
|
158
|
+
raise ValueError(f"Variable '{var_name}' is not found in the dataset.")
|
|
159
|
+
|
|
160
|
+
# Pick the variable
|
|
161
|
+
field = self.ds[var_name]
|
|
125
162
|
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
# Check and pick time
|
|
164
|
+
if "time" in field.dims:
|
|
165
|
+
if time >= len(field.time):
|
|
128
166
|
raise ValueError(
|
|
129
167
|
f"Invalid time index: The specified time index ({time}) exceeds the maximum index "
|
|
130
|
-
f"({len(
|
|
168
|
+
f"({len(field.time) - 1}) for the 'time' dimension."
|
|
131
169
|
)
|
|
132
|
-
field =
|
|
170
|
+
field = field.isel(time=time)
|
|
133
171
|
else:
|
|
134
172
|
if time > 0:
|
|
135
173
|
raise ValueError(
|
|
136
|
-
f"Invalid input: The
|
|
174
|
+
f"Invalid input: The field does not have a 'time' dimension, "
|
|
137
175
|
f"but a time index ({time}) greater than 0 was provided."
|
|
138
176
|
)
|
|
139
|
-
field = self.ds[var_name]
|
|
140
177
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
# Input checks
|
|
179
|
+
_validate_plot_inputs(field, s, eta, xi, depth, lat, lon, include_boundary)
|
|
180
|
+
|
|
181
|
+
# Get horizontal dimensions and grid location
|
|
182
|
+
horizontal_dims_dict = {
|
|
183
|
+
"rho": {"eta": "eta_rho", "xi": "xi_rho"},
|
|
184
|
+
"u": {"eta": "eta_rho", "xi": "xi_u"},
|
|
185
|
+
"v": {"eta": "eta_v", "xi": "xi_rho"},
|
|
186
|
+
}
|
|
187
|
+
for loc, horizontal_dims in horizontal_dims_dict.items():
|
|
188
|
+
if all(dim in field.dims for dim in horizontal_dims.values()):
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
# Convert relative to absolute indices
|
|
192
|
+
def _get_absolute_index(idx, field, dim_name):
|
|
193
|
+
index = field[dim_name].isel(**{dim_name: idx}).item()
|
|
194
|
+
return index
|
|
195
|
+
|
|
196
|
+
if eta is not None and eta < 0:
|
|
197
|
+
eta = _get_absolute_index(eta, field, horizontal_dims["eta"])
|
|
198
|
+
if xi is not None and xi < 0:
|
|
199
|
+
xi = _get_absolute_index(xi, field, horizontal_dims["xi"])
|
|
200
|
+
if s is not None and s < 0:
|
|
201
|
+
s = _get_absolute_index(s, field, "s_rho")
|
|
202
|
+
|
|
203
|
+
# Set spatial coordinates
|
|
204
|
+
lat_deg = self.grid.ds[f"lat_{loc}"]
|
|
205
|
+
lon_deg = self.grid.ds[f"lon_{loc}"]
|
|
206
|
+
if self.grid.straddle:
|
|
207
|
+
lon_deg = xr.where(lon_deg > 180, lon_deg - 360, lon_deg)
|
|
208
|
+
field = field.assign_coords({"lon": lon_deg, "lat": lat_deg})
|
|
150
209
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
210
|
+
# Mask the field
|
|
211
|
+
mask = self.grid.ds[f"mask_{loc}"]
|
|
212
|
+
field = field.where(mask)
|
|
213
|
+
|
|
214
|
+
# Assign eta and xi as coordinates
|
|
215
|
+
coords_to_assign = {dim: field[dim] for dim in horizontal_dims.values()}
|
|
216
|
+
field = field.assign_coords(**coords_to_assign)
|
|
217
|
+
|
|
218
|
+
# Remove horizontal boundary if desired
|
|
219
|
+
slice_dict = {
|
|
220
|
+
"rho": {"eta_rho": slice(1, -1), "xi_rho": slice(1, -1)},
|
|
221
|
+
"u": {"eta_rho": slice(1, -1), "xi_u": slice(1, -1)},
|
|
222
|
+
"v": {"eta_v": slice(1, -1), "xi_rho": slice(1, -1)},
|
|
223
|
+
}
|
|
224
|
+
if not include_boundary:
|
|
225
|
+
field = field.isel(**slice_dict[loc])
|
|
155
226
|
|
|
156
227
|
# Load the data
|
|
157
228
|
if self.use_dask:
|
|
@@ -160,106 +231,156 @@ class ROMSOutput:
|
|
|
160
231
|
with ProgressBar():
|
|
161
232
|
field.load()
|
|
162
233
|
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
234
|
+
# Compute layer depth for 3D fields when depth contours are requested or no vertical layer is specified.
|
|
235
|
+
compute_layer_depth = len(field.dims) > 2 and (depth_contours or s is None)
|
|
236
|
+
if compute_layer_depth:
|
|
237
|
+
if eta is not None or xi is not None:
|
|
238
|
+
# Computing depth coordinates directly for the slice in question is more efficient
|
|
239
|
+
# than using .ds_depth_coords, which computes depth coordinates for full field
|
|
240
|
+
if self.adjust_depth_for_sea_surface_height:
|
|
241
|
+
zeta = self.ds.zeta.isel(time=time)
|
|
242
|
+
else:
|
|
243
|
+
zeta = 0
|
|
244
|
+
if compute_layer_depth:
|
|
245
|
+
layer_depth = compute_depth_coordinates(
|
|
246
|
+
self.grid.ds,
|
|
247
|
+
zeta,
|
|
248
|
+
depth_type="layer",
|
|
249
|
+
location=loc,
|
|
250
|
+
eta=eta,
|
|
251
|
+
xi=xi,
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
self._get_depth_coordinates(depth_type="layer", locations=[loc])
|
|
255
|
+
layer_depth = self.ds_depth_coords[f"layer_depth_{loc}"]
|
|
256
|
+
if self.adjust_depth_for_sea_surface_height:
|
|
257
|
+
layer_depth = layer_depth.isel(time=time)
|
|
258
|
+
|
|
259
|
+
if not include_boundary:
|
|
260
|
+
# Apply valid slices only for dimensions that exist in layer_depth.dims
|
|
261
|
+
layer_depth = layer_depth.isel(
|
|
262
|
+
**{
|
|
263
|
+
dim: s
|
|
264
|
+
for dim, s in slice_dict.get(loc, {}).items()
|
|
265
|
+
if dim in layer_depth.dims
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
layer_depth.load()
|
|
179
269
|
|
|
180
|
-
|
|
270
|
+
# Prepare figure title
|
|
271
|
+
formatted_time = np.datetime_as_string(field.abs_time.values, unit="m")
|
|
272
|
+
title = f"time: {formatted_time}"
|
|
181
273
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
274
|
+
# Slice the field horizontally as desired
|
|
275
|
+
def _slice_along_dimension(field, title, dim_name, idx):
|
|
276
|
+
field = field.sel(**{dim_name: idx})
|
|
277
|
+
title = title + f", {dim_name} = {idx}"
|
|
278
|
+
return field, title
|
|
185
279
|
|
|
186
|
-
if
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
self.ds.zeta.isel(time=time),
|
|
190
|
-
depth_type="layer",
|
|
191
|
-
location=loc,
|
|
192
|
-
eta=eta,
|
|
193
|
-
xi=xi,
|
|
280
|
+
if eta is not None:
|
|
281
|
+
field, title = _slice_along_dimension(
|
|
282
|
+
field, title, horizontal_dims["eta"], eta
|
|
194
283
|
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
interface_depth = compute_depth_coordinates(
|
|
199
|
-
self.grid.ds,
|
|
200
|
-
self.ds.zeta.isel(time=time),
|
|
201
|
-
depth_type="interface",
|
|
202
|
-
location=loc,
|
|
203
|
-
eta=eta,
|
|
204
|
-
xi=xi,
|
|
284
|
+
if xi is not None:
|
|
285
|
+
field, title = _slice_along_dimension(
|
|
286
|
+
field, title, horizontal_dims["xi"], xi
|
|
205
287
|
)
|
|
206
|
-
if s is not None:
|
|
207
|
-
interface_depth = interface_depth.isel(s_w=s)
|
|
208
|
-
|
|
209
|
-
# Slice the field as desired
|
|
210
|
-
title = field.long_name
|
|
211
288
|
if s is not None:
|
|
212
|
-
title = title
|
|
213
|
-
|
|
289
|
+
field, title = _slice_along_dimension(field, title, "s_rho", s)
|
|
290
|
+
if compute_layer_depth:
|
|
291
|
+
layer_depth = layer_depth.isel(s_rho=s)
|
|
214
292
|
else:
|
|
215
293
|
depth_contours = False
|
|
216
294
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
295
|
+
# Regrid laterally
|
|
296
|
+
if lat is not None or lon is not None:
|
|
297
|
+
|
|
298
|
+
if lat is not None:
|
|
299
|
+
lats = [lat]
|
|
300
|
+
title = title + f", lat = {lat}°N"
|
|
222
301
|
else:
|
|
223
|
-
|
|
224
|
-
|
|
302
|
+
resolution = self._infer_nominal_horizontal_resolution()
|
|
303
|
+
lats = _generate_coordinate_range(
|
|
304
|
+
field.lat.min().values, field.lat.max().values, resolution
|
|
225
305
|
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if eta is not None:
|
|
229
|
-
field, mask, title = _process_dimension(
|
|
230
|
-
field,
|
|
231
|
-
mask,
|
|
232
|
-
"eta_rho" if "eta_rho" in field.dims else "eta_v",
|
|
233
|
-
field.eta_rho if "eta_rho" in field.dims else field.eta_v,
|
|
234
|
-
eta,
|
|
235
|
-
title,
|
|
236
|
-
)
|
|
306
|
+
lats = xr.DataArray(lats, dims=["lat"], attrs={"units": "°N"})
|
|
237
307
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
)
|
|
308
|
+
if lon is not None:
|
|
309
|
+
lons = [lon]
|
|
310
|
+
title = title + f", lon = {lon}°E"
|
|
311
|
+
else:
|
|
312
|
+
resolution = self._infer_nominal_horizontal_resolution(lat)
|
|
313
|
+
lons = _generate_coordinate_range(
|
|
314
|
+
field.lon.min().values, field.lon.max().values, resolution
|
|
315
|
+
)
|
|
316
|
+
lons = xr.DataArray(lons, dims=["lon"], attrs={"units": "°E"})
|
|
247
317
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
318
|
+
target_coords = {"lat": lats, "lon": lons}
|
|
319
|
+
lateral_regrid = LateralRegridFromROMS(field, target_coords)
|
|
320
|
+
field = lateral_regrid.apply(field).squeeze()
|
|
321
|
+
if compute_layer_depth:
|
|
322
|
+
layer_depth = lateral_regrid.apply(layer_depth).squeeze()
|
|
251
323
|
|
|
324
|
+
# Assign depth as coordinate
|
|
252
325
|
if compute_layer_depth:
|
|
253
326
|
field = field.assign_coords({"layer_depth": layer_depth})
|
|
254
327
|
|
|
328
|
+
def _remove_edge_nans(field, xdim, layer_depth=None):
|
|
329
|
+
"""Removes NaNs from the edges along the specified dimension."""
|
|
330
|
+
if xdim in field.dims:
|
|
331
|
+
if layer_depth is not None:
|
|
332
|
+
nan_mask = layer_depth.isnull().sum(
|
|
333
|
+
dim=[dim for dim in layer_depth.dims if dim != xdim]
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
nan_mask = field.isnull().sum(
|
|
337
|
+
dim=[dim for dim in field.dims if dim != xdim]
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Find the valid indices where the sum of the nans is 0
|
|
341
|
+
valid_indices = np.where(nan_mask.values == 0)[0]
|
|
342
|
+
|
|
343
|
+
if len(valid_indices) > 0:
|
|
344
|
+
first_valid = valid_indices[0]
|
|
345
|
+
last_valid = valid_indices[-1]
|
|
346
|
+
|
|
347
|
+
field = field.isel({xdim: slice(first_valid, last_valid + 1)})
|
|
348
|
+
if layer_depth is not None:
|
|
349
|
+
layer_depth = layer_depth.isel(
|
|
350
|
+
{xdim: slice(first_valid, last_valid + 1)}
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return field, layer_depth
|
|
354
|
+
|
|
355
|
+
if lat is not None:
|
|
356
|
+
field, layer_depth = _remove_edge_nans(
|
|
357
|
+
field, "lon", layer_depth if "layer_depth" in locals() else None
|
|
358
|
+
)
|
|
359
|
+
if lon is not None:
|
|
360
|
+
field, layer_depth = _remove_edge_nans(
|
|
361
|
+
field, "lat", layer_depth if "layer_depth" in locals() else None
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Regrid vertically
|
|
365
|
+
if depth is not None:
|
|
366
|
+
vertical_regrid = VerticalRegridFromROMS(self.ds)
|
|
367
|
+
# Save attributes before vertical regridding
|
|
368
|
+
attrs = field.attrs
|
|
369
|
+
field = vertical_regrid.apply(
|
|
370
|
+
field, layer_depth, np.array([depth])
|
|
371
|
+
).squeeze()
|
|
372
|
+
# Reset attributes
|
|
373
|
+
field.attrs = attrs
|
|
374
|
+
title = title + f", depth = {depth}m"
|
|
375
|
+
|
|
255
376
|
# Choose colorbar
|
|
256
377
|
if var_name in ["u", "v", "w", "ubar", "vbar", "zeta"]:
|
|
257
|
-
vmax = max(field.
|
|
378
|
+
vmax = max(field.max().values, -field.min().values)
|
|
258
379
|
vmin = -vmax
|
|
259
380
|
cmap = plt.colormaps.get_cmap("RdBu_r")
|
|
260
381
|
else:
|
|
261
|
-
vmax = field.
|
|
262
|
-
vmin = field.
|
|
382
|
+
vmax = field.max().values
|
|
383
|
+
vmin = field.min().values
|
|
263
384
|
if var_name in ["temp", "salt"]:
|
|
264
385
|
cmap = plt.colormaps.get_cmap("YlOrRd")
|
|
265
386
|
else:
|
|
@@ -268,40 +389,34 @@ class ROMSOutput:
|
|
|
268
389
|
kwargs = {"vmax": vmax, "vmin": vmin, "cmap": cmap}
|
|
269
390
|
|
|
270
391
|
# Plotting
|
|
271
|
-
if eta is None and xi is None:
|
|
272
|
-
_plot(
|
|
273
|
-
field=field
|
|
392
|
+
if (eta is None and xi is None) and (lat is None and lon is None):
|
|
393
|
+
fig = _plot(
|
|
394
|
+
field=field,
|
|
274
395
|
depth_contours=depth_contours,
|
|
275
396
|
title=title,
|
|
276
397
|
kwargs=kwargs,
|
|
277
|
-
c=
|
|
398
|
+
c=None,
|
|
278
399
|
)
|
|
279
400
|
else:
|
|
280
401
|
if len(field.dims) == 2:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
# restrict number of layer_contours to 10 for the sake of plot clearity
|
|
285
|
-
nr_layers = len(interface_depth["s_w"])
|
|
286
|
-
selected_layers = np.linspace(
|
|
287
|
-
0, nr_layers - 1, min(nr_layers, 10), dtype=int
|
|
288
|
-
)
|
|
289
|
-
interface_depth = interface_depth.isel(s_w=selected_layers)
|
|
290
|
-
_section_plot(
|
|
291
|
-
field.where(mask),
|
|
292
|
-
interface_depth=interface_depth,
|
|
402
|
+
fig = _section_plot(
|
|
403
|
+
field,
|
|
404
|
+
interface_depth=None,
|
|
293
405
|
title=title,
|
|
294
406
|
kwargs=kwargs,
|
|
295
407
|
ax=ax,
|
|
296
408
|
)
|
|
297
409
|
else:
|
|
298
410
|
if "s_rho" in field.dims:
|
|
299
|
-
_profile_plot(field
|
|
411
|
+
fig = _profile_plot(field, title=title, ax=ax)
|
|
300
412
|
else:
|
|
301
|
-
_line_plot(field
|
|
413
|
+
fig = _line_plot(field, title=title, ax=ax)
|
|
414
|
+
|
|
415
|
+
if save_path:
|
|
416
|
+
plt.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
302
417
|
|
|
303
|
-
def
|
|
304
|
-
"""
|
|
418
|
+
def _get_depth_coordinates(self, depth_type="layer", locations=["rho"]):
|
|
419
|
+
"""Ensure depth coordinates are stored for a given location and depth type.
|
|
305
420
|
|
|
306
421
|
Calculates vertical depth coordinates (layer or interface) for specified locations (e.g., rho, u, v points)
|
|
307
422
|
and updates them in the dataset (`self.ds`).
|
|
@@ -321,61 +436,44 @@ class ROMSOutput:
|
|
|
321
436
|
|
|
322
437
|
Updates
|
|
323
438
|
-------
|
|
324
|
-
self.
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
439
|
+
self.ds_depth_coords : xarray.Dataset
|
|
440
|
+
|
|
441
|
+
Raises
|
|
442
|
+
------
|
|
443
|
+
ValueError
|
|
444
|
+
If `adjust_depth_for_sea_surface_height` is enabled but `zeta` is missing from `self.ds`.
|
|
329
445
|
|
|
330
446
|
Notes
|
|
331
447
|
-----
|
|
332
|
-
This method
|
|
448
|
+
- This method relies on the `compute_depth_coordinates` function to perform calculations.
|
|
449
|
+
- If `adjust_depth_for_sea_surface_height` is `True`, the method accounts for variations
|
|
450
|
+
in sea surface height (`zeta`).
|
|
333
451
|
"""
|
|
334
452
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def _load_model_output(self) -> xr.Dataset:
|
|
341
|
-
"""Load the model output based on the type."""
|
|
342
|
-
if isinstance(self.path, list):
|
|
343
|
-
filetype = "list"
|
|
344
|
-
force_combine_nested = True
|
|
345
|
-
# Check if all items in the list are files
|
|
346
|
-
if not all(Path(item).is_file() for item in self.path):
|
|
347
|
-
raise FileNotFoundError(
|
|
348
|
-
"All items in the provided list must be valid files."
|
|
453
|
+
if self.adjust_depth_for_sea_surface_height:
|
|
454
|
+
if "zeta" not in self.ds:
|
|
455
|
+
raise ValueError(
|
|
456
|
+
"`zeta` is required in provided ROMS output when `adjust_depth_for_sea_surface_height` is enabled."
|
|
349
457
|
)
|
|
350
|
-
|
|
351
|
-
filetype = "file"
|
|
352
|
-
force_combine_nested = False
|
|
353
|
-
elif Path(self.path).is_dir():
|
|
354
|
-
filetype = "dir"
|
|
355
|
-
force_combine_nested = True
|
|
458
|
+
zeta = self.ds.zeta
|
|
356
459
|
else:
|
|
357
|
-
|
|
358
|
-
f"The specified path '{self.path}' is neither a file, nor a list of files, nor a directory."
|
|
359
|
-
)
|
|
460
|
+
zeta = 0
|
|
360
461
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
filename = _validate_and_set_filenames(self.path, filetype, "his")
|
|
369
|
-
else:
|
|
370
|
-
raise ValueError(f"Unsupported type '{self.type}'.")
|
|
462
|
+
for location in locations:
|
|
463
|
+
self.ds_depth_coords[
|
|
464
|
+
f"{depth_type}_depth_{location}"
|
|
465
|
+
] = compute_depth_coordinates(self.grid.ds, zeta, depth_type, location)
|
|
466
|
+
|
|
467
|
+
def _load_model_output(self) -> xr.Dataset:
|
|
468
|
+
"""Load the model output."""
|
|
371
469
|
|
|
372
470
|
# Load the dataset
|
|
373
471
|
ds = _load_data(
|
|
374
|
-
|
|
472
|
+
self.path,
|
|
375
473
|
dim_names={"time": "time"},
|
|
376
474
|
use_dask=self.use_dask,
|
|
377
|
-
time_chunking=
|
|
378
|
-
force_combine_nested=
|
|
475
|
+
time_chunking=True,
|
|
476
|
+
force_combine_nested=True,
|
|
379
477
|
)
|
|
380
478
|
|
|
381
479
|
return ds
|
|
@@ -419,7 +517,7 @@ class ROMSOutput:
|
|
|
419
517
|
)
|
|
420
518
|
else:
|
|
421
519
|
# Set the model reference date if not already set
|
|
422
|
-
|
|
520
|
+
self.model_reference_date = inferred_date
|
|
423
521
|
else:
|
|
424
522
|
# Handle case where no match is found
|
|
425
523
|
if hasattr(self, "model_reference_date") and self.model_reference_date:
|
|
@@ -552,39 +650,46 @@ class ROMSOutput:
|
|
|
552
650
|
|
|
553
651
|
return ds
|
|
554
652
|
|
|
653
|
+
def _infer_nominal_horizontal_resolution(self, lat=None):
|
|
654
|
+
"""Estimate the nominal horizontal resolution of the grid in degrees at a
|
|
655
|
+
specified latitude.
|
|
555
656
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
type and checks for the presence of a string in the filename.
|
|
657
|
+
This method calculates the nominal horizontal resolution of the grid by first
|
|
658
|
+
determining the average grid spacing in meters. The spacing is then converted
|
|
659
|
+
to degrees, accounting for the Earth's curvature, and the latitude where the
|
|
660
|
+
resolution is being computed.
|
|
561
661
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
662
|
+
Parameters
|
|
663
|
+
----------
|
|
664
|
+
lat : float, optional
|
|
665
|
+
Latitude (in degrees) at which to estimate the horizontal resolution.
|
|
666
|
+
If not provided, the resolution is calculated at the average latitude of
|
|
667
|
+
the grid (`lat_rho`).
|
|
668
|
+
|
|
669
|
+
Returns
|
|
670
|
+
-------
|
|
671
|
+
float
|
|
672
|
+
The estimated horizontal resolution in degrees, adjusted for the Earth's curvature.
|
|
673
|
+
"""
|
|
674
|
+
# Earth radius in meters
|
|
675
|
+
r_earth = 6371315.0
|
|
676
|
+
|
|
677
|
+
if lat is None:
|
|
678
|
+
# Center latitude in degrees
|
|
679
|
+
lat = (self.grid.ds.lat_rho.max() + self.grid.ds.lat_rho.min()) / 2
|
|
680
|
+
|
|
681
|
+
# Convert latitude to radians
|
|
682
|
+
lat_rad = np.deg2rad(lat)
|
|
683
|
+
|
|
684
|
+
# Mean resolution in meters
|
|
685
|
+
resolution_in_m = (
|
|
686
|
+
(1 / self.grid.ds.pm).mean() + (1 / self.grid.ds.pn).mean()
|
|
687
|
+
) / 2
|
|
688
|
+
|
|
689
|
+
# Meters per degree at the equator
|
|
690
|
+
meters_per_degree = 2 * np.pi * r_earth / 360
|
|
691
|
+
|
|
692
|
+
# Correct for latitude by multiplying by cos(latitude) for longitude
|
|
693
|
+
resolution_in_degrees = resolution_in_m / (meters_per_degree * np.cos(lat_rad))
|
|
589
694
|
|
|
590
|
-
|
|
695
|
+
return resolution_in_degrees
|