roms-tools 1.7.0__py3-none-any.whl → 2.1.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.
- roms_tools/__init__.py +2 -1
- roms_tools/setup/boundary_forcing.py +246 -146
- roms_tools/setup/datasets.py +229 -69
- roms_tools/setup/download.py +13 -17
- roms_tools/setup/grid.py +777 -614
- roms_tools/setup/initial_conditions.py +168 -32
- roms_tools/setup/mask.py +115 -0
- roms_tools/setup/nesting.py +575 -0
- roms_tools/setup/plot.py +218 -63
- roms_tools/setup/regrid.py +4 -2
- roms_tools/setup/river_forcing.py +125 -29
- roms_tools/setup/surface_forcing.py +31 -25
- roms_tools/setup/tides.py +29 -14
- roms_tools/setup/topography.py +250 -153
- roms_tools/setup/utils.py +174 -44
- roms_tools/setup/vertical_coordinate.py +5 -16
- roms_tools/tests/test_setup/test_boundary_forcing.py +10 -5
- 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/{layer_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/sigma_w/.zarray +20 -0
- 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_that_straddles_dateline.zarr/sigma_r/.zarray +20 -0
- 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_that_straddles_dateline.zarr/sigma_w/.zarray +20 -0
- 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_no_climatology.zarr/.zmetadata +2 -3
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/.zarray +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zmetadata +5 -6
- roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zarray +2 -2
- roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_tracer/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zarray +2 -2
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_name/0 +0 -0
- roms_tools/tests/test_setup/test_datasets.py +2 -2
- roms_tools/tests/test_setup/test_grid.py +110 -12
- roms_tools/tests/test_setup/test_initial_conditions.py +2 -1
- roms_tools/tests/test_setup/test_nesting.py +489 -0
- roms_tools/tests/test_setup/test_river_forcing.py +53 -15
- roms_tools/tests/test_setup/test_surface_forcing.py +3 -22
- roms_tools/tests/test_setup/test_tides.py +2 -1
- roms_tools/tests/test_setup/test_topography.py +106 -1
- roms_tools/tests/test_setup/test_validation.py +2 -2
- {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/LICENSE +1 -1
- {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/METADATA +9 -4
- {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/RECORD +85 -108
- {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/WHEEL +1 -1
- roms_tools/_version.py +0 -2
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zarray +0 -24
- 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/.zarray +0 -24
- 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/.zarray +0 -24
- 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/.zarray +0 -24
- 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/.zarray +0 -24
- 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/.zarray +0 -24
- 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/.zarray +0 -24
- 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_data/river_forcing.zarr/river_tracer/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/0 +0 -0
- roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zgroup +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/0 +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/0 +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/0 +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/0 +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/0.0 +0 -0
- /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zattrs +0 -0
- {roms_tools-1.7.0.dist-info → roms_tools-2.1.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/topography.py
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
1
3
|
import xarray as xr
|
|
2
4
|
import numpy as np
|
|
3
5
|
import gcm_filters
|
|
4
|
-
from
|
|
5
|
-
from scipy.ndimage import label
|
|
6
|
-
from roms_tools.setup.download import fetch_topo
|
|
7
|
-
from roms_tools.setup.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
|
|
6
|
+
from roms_tools.setup.utils import handle_boundaries
|
|
8
7
|
import warnings
|
|
9
8
|
from itertools import count
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
from roms_tools.setup.datasets import ETOPO5Dataset, SRTM15Dataset
|
|
10
|
+
from roms_tools.setup.regrid import LateralRegrid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _add_topography(
|
|
14
|
+
ds,
|
|
15
|
+
target_coords,
|
|
16
|
+
topography_source,
|
|
17
|
+
hmin,
|
|
18
|
+
smooth_factor=8.0,
|
|
19
|
+
rmax=0.2,
|
|
20
|
+
verbose=False,
|
|
14
21
|
) -> xr.Dataset:
|
|
15
|
-
"""Adds topography
|
|
16
|
-
topography source.
|
|
17
|
-
|
|
18
|
-
This function performs the following operations:
|
|
19
|
-
1. Interpolates topography data onto the desired grid.
|
|
20
|
-
2. Applies a mask based on ocean depth.
|
|
21
|
-
3. Smooths the topography globally to reduce grid-scale instabilities.
|
|
22
|
-
4. Fills enclosed basins with land.
|
|
23
|
-
5. Smooths the topography locally to ensure the steepness ratio satisfies the rmax criterion.
|
|
24
|
-
6. Adds topography metadata.
|
|
22
|
+
"""Adds topography to the dataset based on the provided topography source.
|
|
25
23
|
|
|
26
24
|
Parameters
|
|
27
25
|
----------
|
|
28
26
|
ds : xr.Dataset
|
|
29
|
-
The dataset to which topography
|
|
30
|
-
topography_source : str
|
|
31
|
-
|
|
27
|
+
The dataset to which topography will be added.
|
|
28
|
+
topography_source : Dict[str, Union[str, Path]], optional
|
|
29
|
+
Dictionary specifying the source of the topography data:
|
|
30
|
+
|
|
31
|
+
- "name" (str): The name of the topography data source (e.g., "SRTM15").
|
|
32
|
+
- "path" (Union[str, Path, List[Union[str, Path]]]): The path to the raw data file. Can be a string or a Path object.
|
|
33
|
+
|
|
34
|
+
The default is "ETOPO5", which does not require a path.
|
|
32
35
|
hmin : float
|
|
33
36
|
The minimum allowable depth for the topography.
|
|
34
37
|
smooth_factor : float, optional
|
|
@@ -39,89 +42,142 @@ def _add_topography_and_mask(
|
|
|
39
42
|
The maximum allowable steepness ratio for the topography smoothing.
|
|
40
43
|
This parameter controls the local smoothing of the topography. Smaller values result in
|
|
41
44
|
smoother topography, while larger values preserve more detail. The default is 0.2.
|
|
45
|
+
verbose: bool, optional
|
|
46
|
+
Indicates whether to print topography generation steps with timing. Defaults to False.
|
|
42
47
|
|
|
43
48
|
Returns
|
|
44
49
|
-------
|
|
45
50
|
xr.Dataset
|
|
46
|
-
|
|
51
|
+
Updated dataset with added topography and metadata.
|
|
47
52
|
"""
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
if verbose:
|
|
55
|
+
start_time = time.time()
|
|
56
|
+
data = _get_topography_data(topography_source)
|
|
57
|
+
if verbose:
|
|
58
|
+
logging.info(
|
|
59
|
+
f"Reading the topography data: {time.time() - start_time:.3f} seconds"
|
|
60
|
+
)
|
|
51
61
|
|
|
52
62
|
# interpolate topography onto desired grid
|
|
53
|
-
hraw = _make_raw_topography(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Mask is obtained by finding locations where ocean depth is positive
|
|
57
|
-
mask = xr.where(hraw > 0, 1.0, 0.0)
|
|
63
|
+
hraw = _make_raw_topography(data, target_coords, verbose=verbose)
|
|
64
|
+
nan_check(hraw)
|
|
58
65
|
|
|
59
66
|
# smooth topography domain-wide with Gaussian kernel to avoid grid scale instabilities
|
|
67
|
+
if verbose:
|
|
68
|
+
start_time = time.time()
|
|
60
69
|
hraw = _smooth_topography_globally(hraw, smooth_factor)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# adjust mask boundaries by copying values from adjacent cells
|
|
66
|
-
mask = _handle_boundaries(mask)
|
|
67
|
-
|
|
68
|
-
ds["mask_rho"] = xr.DataArray(mask.astype(np.int32), dims=("eta_rho", "xi_rho"))
|
|
69
|
-
ds["mask_rho"].attrs = {
|
|
70
|
-
"long_name": "Mask at rho-points",
|
|
71
|
-
"units": "land/water (0/1)",
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
ds = _add_velocity_masks(ds)
|
|
70
|
+
if verbose:
|
|
71
|
+
logging.info(
|
|
72
|
+
f"Smoothing the topography globally: {time.time() - start_time:.3f} seconds"
|
|
73
|
+
)
|
|
75
74
|
|
|
76
75
|
# smooth topography locally to satisfy r < rmax
|
|
76
|
+
if verbose:
|
|
77
|
+
start_time = time.time()
|
|
78
|
+
# inserting hraw * mask_rho into this function eliminates any inconsistencies between
|
|
79
|
+
# the land according to the topography and the land according to the mask; land points
|
|
80
|
+
# will always be set to hmin
|
|
77
81
|
ds["h"] = _smooth_topography_locally(hraw * ds["mask_rho"], hmin, rmax)
|
|
78
82
|
ds["h"].attrs = {
|
|
79
|
-
"long_name": "
|
|
83
|
+
"long_name": "Bathymetry at rho-points",
|
|
80
84
|
"units": "meter",
|
|
81
85
|
}
|
|
86
|
+
if verbose:
|
|
87
|
+
logging.info(
|
|
88
|
+
f"Smoothing the topography locally: {time.time() - start_time:.3f} seconds"
|
|
89
|
+
)
|
|
82
90
|
|
|
83
91
|
ds = _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax)
|
|
84
92
|
|
|
85
93
|
return ds
|
|
86
94
|
|
|
87
95
|
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
90
|
-
height values onto the desired grid."""
|
|
96
|
+
def _get_topography_data(source):
|
|
97
|
+
"""Load topography data based on the specified source.
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
source : dict
|
|
102
|
+
A dictionary containing the source details (e.g., "name" and "path").
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
[
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
data : object
|
|
107
|
+
The loaded topography dataset (ETOPO5 or SRTM15).
|
|
108
|
+
"""
|
|
109
|
+
kwargs = {"use_dask": False}
|
|
110
|
+
|
|
111
|
+
if source["name"] == "ETOPO5":
|
|
112
|
+
if "path" in source.keys():
|
|
113
|
+
kwargs["filename"] = source["path"]
|
|
114
|
+
data = ETOPO5Dataset(**kwargs)
|
|
115
|
+
elif source["name"] == "SRTM15":
|
|
116
|
+
kwargs["filename"] = source["path"]
|
|
117
|
+
data = SRTM15Dataset(**kwargs)
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
'Only "ETOPO5" and "SRTM15" are valid options for topography_source["name"].'
|
|
107
121
|
)
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
return data
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _make_raw_topography(
|
|
127
|
+
data, target_coords, method="linear", verbose=False
|
|
128
|
+
) -> xr.DataArray:
|
|
129
|
+
"""Regrid topography data to match target coordinates.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
data : object
|
|
134
|
+
The dataset object containing the topography data.
|
|
135
|
+
target_coords : object
|
|
136
|
+
The target coordinates to which the data will be regridded.
|
|
137
|
+
method : str, optional
|
|
138
|
+
The regridding method to use, by default "linear".
|
|
139
|
+
verbose : bool, optional
|
|
140
|
+
If True, logs the time taken for regridding, by default False.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
xr.DataArray
|
|
145
|
+
The regridded topography data with the sign flipped (bathymetry positive).
|
|
146
|
+
"""
|
|
147
|
+
data.choose_subdomain(target_coords, buffer_points=3, verbose=verbose)
|
|
148
|
+
|
|
149
|
+
if verbose:
|
|
150
|
+
start_time = time.time()
|
|
151
|
+
lateral_regrid = LateralRegrid(target_coords, data.dim_names)
|
|
152
|
+
hraw = lateral_regrid.apply(data.ds[data.var_names["topo"]], method=method)
|
|
153
|
+
if verbose:
|
|
154
|
+
logging.info(
|
|
155
|
+
f"Regridding the topography: {time.time() - start_time:.3f} seconds"
|
|
113
156
|
)
|
|
114
157
|
|
|
115
|
-
#
|
|
116
|
-
hraw =
|
|
158
|
+
# flip sign so that bathmetry is positive
|
|
159
|
+
hraw = -hraw
|
|
117
160
|
|
|
118
161
|
return hraw
|
|
119
162
|
|
|
120
163
|
|
|
121
164
|
def _smooth_topography_globally(hraw, factor) -> xr.DataArray:
|
|
165
|
+
"""Apply global smoothing to the topography using a Gaussian filter.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
hraw : xr.DataArray
|
|
170
|
+
The raw topography data to be smoothed.
|
|
171
|
+
factor : float
|
|
172
|
+
The smoothing factor (controls the width of the Gaussian filter).
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
xr.DataArray
|
|
177
|
+
The smoothed topography data.
|
|
178
|
+
"""
|
|
122
179
|
# since GCM-Filters assumes periodic domain, we extend the domain by one grid cell in each dimension
|
|
123
180
|
# and set that margin to land
|
|
124
|
-
|
|
125
181
|
mask = xr.ones_like(hraw)
|
|
126
182
|
margin_mask = xr.concat([mask, 0 * mask.isel(eta_rho=-1)], dim="eta_rho")
|
|
127
183
|
margin_mask = xr.concat(
|
|
@@ -148,30 +204,28 @@ def _smooth_topography_globally(hraw, factor) -> xr.DataArray:
|
|
|
148
204
|
return hsmooth
|
|
149
205
|
|
|
150
206
|
|
|
151
|
-
def
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
# Label connected regions in the mask
|
|
155
|
-
reg, nreg = label(mask)
|
|
156
|
-
# Find the largest region
|
|
157
|
-
lint = 0
|
|
158
|
-
lreg = 0
|
|
159
|
-
for ireg in range(nreg):
|
|
160
|
-
int_ = np.sum(reg == ireg)
|
|
161
|
-
if int_ > lint and mask[reg == ireg].sum() > 0:
|
|
162
|
-
lreg = ireg
|
|
163
|
-
lint = int_
|
|
164
|
-
|
|
165
|
-
# Remove regions other than the largest one
|
|
166
|
-
for ireg in range(nreg):
|
|
167
|
-
if ireg != lreg:
|
|
168
|
-
mask[reg == ireg] = 0
|
|
207
|
+
def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
208
|
+
"""Smooths topography locally to ensure the slope (r-factor) is below the specified
|
|
209
|
+
threshold.
|
|
169
210
|
|
|
170
|
-
|
|
211
|
+
This function applies a logarithmic transformation to the topography and iteratively smooths
|
|
212
|
+
it in four directions (eta, xi, and two diagonals) until the maximum slope parameter (r) is
|
|
213
|
+
below `rmax`. A threshold `hmin` is applied to prevent values from going below a minimum height.
|
|
171
214
|
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
h : xarray.DataArray
|
|
218
|
+
The topography data to be smoothed.
|
|
219
|
+
hmin : float, optional
|
|
220
|
+
The minimum height threshold. Default is 5.
|
|
221
|
+
rmax : float, optional
|
|
222
|
+
The maximum allowable slope parameter (r-factor). Default is 0.2.
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
xarray.DataArray
|
|
227
|
+
The smoothed topography data.
|
|
228
|
+
"""
|
|
175
229
|
# Compute rmax_log
|
|
176
230
|
if rmax > 0.0:
|
|
177
231
|
rmax_log = np.log((1.0 + rmax * 0.9) / (1.0 - rmax * 0.9))
|
|
@@ -181,65 +235,90 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
|
181
235
|
# Apply hmin threshold
|
|
182
236
|
h = xr.where(h < hmin, hmin, h)
|
|
183
237
|
|
|
184
|
-
#
|
|
238
|
+
# Perform logarithmic transformation of the height field
|
|
185
239
|
h_log = np.log(h / hmin)
|
|
186
240
|
|
|
187
|
-
|
|
188
|
-
|
|
241
|
+
# Constants for smoothing
|
|
242
|
+
smoothing_factor_1 = 1.0 / 6
|
|
243
|
+
smoothing_factor_2 = 0.25
|
|
189
244
|
|
|
245
|
+
# Iterate until convergence
|
|
190
246
|
for iter in count():
|
|
191
|
-
# Compute gradients
|
|
247
|
+
# Compute gradients and smoothing for eta, xi, and diagonal directions
|
|
192
248
|
|
|
193
|
-
# in eta-direction
|
|
194
|
-
|
|
195
|
-
|
|
249
|
+
# Gradient in eta-direction
|
|
250
|
+
delta_eta = h_log.diff("eta_rho").isel(xi_rho=slice(1, -1))
|
|
251
|
+
abs_eta_gradient = np.abs(delta_eta)
|
|
196
252
|
with warnings.catch_warnings():
|
|
197
253
|
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
254
|
+
eta_correction = xr.where(
|
|
255
|
+
abs_eta_gradient < rmax_log,
|
|
256
|
+
0,
|
|
257
|
+
delta_eta * (1 - rmax_log / abs_eta_gradient),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Gradient in xi-direction
|
|
261
|
+
delta_xi = h_log.diff("xi_rho").isel(eta_rho=slice(1, -1))
|
|
262
|
+
abs_xi_gradient = np.abs(delta_xi)
|
|
203
263
|
with warnings.catch_warnings():
|
|
204
264
|
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
265
|
+
xi_correction = xr.where(
|
|
266
|
+
abs_xi_gradient < rmax_log,
|
|
267
|
+
0,
|
|
268
|
+
delta_xi * (1 - rmax_log / abs_xi_gradient),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Gradient in first diagonal direction
|
|
272
|
+
delta_diag_1 = (h_log - h_log.shift(eta_rho=1, xi_rho=1)).isel(
|
|
209
273
|
eta_rho=slice(1, None), xi_rho=slice(1, None)
|
|
210
274
|
)
|
|
211
|
-
|
|
275
|
+
abs_diag_1_gradient = np.abs(delta_diag_1)
|
|
212
276
|
with warnings.catch_warnings():
|
|
213
277
|
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
278
|
+
diag_1_correction = xr.where(
|
|
279
|
+
abs_diag_1_gradient < rmax_log,
|
|
280
|
+
0,
|
|
281
|
+
delta_diag_1 * (1 - rmax_log / abs_diag_1_gradient),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Gradient in second diagonal direction
|
|
285
|
+
delta_diag_2 = (h_log.shift(eta_rho=1) - h_log.shift(xi_rho=1)).isel(
|
|
218
286
|
eta_rho=slice(1, None), xi_rho=slice(1, None)
|
|
219
287
|
)
|
|
220
|
-
|
|
288
|
+
abs_diag_2_gradient = np.abs(delta_diag_2)
|
|
221
289
|
with warnings.catch_warnings():
|
|
222
290
|
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
223
|
-
|
|
291
|
+
diag_2_correction = xr.where(
|
|
292
|
+
abs_diag_2_gradient < rmax_log,
|
|
293
|
+
0,
|
|
294
|
+
delta_diag_2 * (1 - rmax_log / abs_diag_2_gradient),
|
|
295
|
+
)
|
|
224
296
|
|
|
225
297
|
# Update h_log in domain interior
|
|
226
|
-
h_log[1:-1, 1:-1] +=
|
|
227
|
-
|
|
228
|
-
-
|
|
229
|
-
+
|
|
230
|
-
-
|
|
231
|
-
+
|
|
298
|
+
h_log[1:-1, 1:-1] += smoothing_factor_1 * (
|
|
299
|
+
eta_correction[1:, :]
|
|
300
|
+
- eta_correction[:-1, :]
|
|
301
|
+
+ xi_correction[:, 1:]
|
|
302
|
+
- xi_correction[:, :-1]
|
|
303
|
+
+ smoothing_factor_2
|
|
304
|
+
* (
|
|
305
|
+
diag_1_correction[1:, 1:]
|
|
306
|
+
- diag_1_correction[:-1, :-1]
|
|
307
|
+
+ diag_2_correction[:-1, 1:]
|
|
308
|
+
- diag_2_correction[1:, :-1]
|
|
309
|
+
)
|
|
232
310
|
)
|
|
233
311
|
|
|
234
312
|
# No gradient at the domain boundaries
|
|
235
|
-
h_log =
|
|
313
|
+
h_log = handle_boundaries(h_log)
|
|
236
314
|
|
|
237
|
-
#
|
|
315
|
+
# Recompute the topography after smoothing
|
|
238
316
|
h = hmin * np.exp(h_log)
|
|
317
|
+
|
|
239
318
|
# Apply hmin threshold again
|
|
240
319
|
h = xr.where(h < hmin, hmin, h)
|
|
241
320
|
|
|
242
|
-
#
|
|
321
|
+
# Compute maximum slope parameter r
|
|
243
322
|
r_eta, r_xi = _compute_rfactor(h)
|
|
244
323
|
rmax0 = np.max([r_eta.max(), r_xi.max()])
|
|
245
324
|
if rmax0 < rmax:
|
|
@@ -248,32 +327,24 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
|
248
327
|
return h
|
|
249
328
|
|
|
250
329
|
|
|
251
|
-
def
|
|
252
|
-
"""
|
|
330
|
+
def _compute_rfactor(h):
|
|
331
|
+
"""Computes the slope parameter (r-factor) in both horizontal directions.
|
|
332
|
+
|
|
333
|
+
The r-factor is calculated as |Δh| / (2h) in the eta and xi directions:
|
|
334
|
+
- r_eta = |h_i - h_{i-1}| / (h_i + h_{i+1})
|
|
335
|
+
- r_xi = |h_i - h_{i-1}| / (h_i + h_{i+1})
|
|
253
336
|
|
|
254
337
|
Parameters
|
|
255
338
|
----------
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
need to be adjusted.
|
|
339
|
+
h : xarray.DataArray
|
|
340
|
+
The topography data.
|
|
259
341
|
|
|
260
342
|
Returns
|
|
261
343
|
-------
|
|
262
|
-
|
|
263
|
-
|
|
344
|
+
tuple of xarray.DataArray
|
|
345
|
+
r_eta : r-factor in the eta direction.
|
|
346
|
+
r_xi : r-factor in the xi direction.
|
|
264
347
|
"""
|
|
265
|
-
|
|
266
|
-
field[0, :] = field[1, :]
|
|
267
|
-
field[-1, :] = field[-2, :]
|
|
268
|
-
field[:, 0] = field[:, 1]
|
|
269
|
-
field[:, -1] = field[:, -2]
|
|
270
|
-
|
|
271
|
-
return field
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
def _compute_rfactor(h):
|
|
275
|
-
"""Computes slope parameter (or r-factor) r = |Delta h| / 2h in both horizontal grid
|
|
276
|
-
directions."""
|
|
277
348
|
# compute r_{i-1/2} = |h_i - h_{i-1}| / (h_i + h_{i+1})
|
|
278
349
|
r_eta = np.abs(h.diff("eta_rho")) / (h + h.shift(eta_rho=1)).isel(
|
|
279
350
|
eta_rho=slice(1, None)
|
|
@@ -286,23 +357,49 @@ def _compute_rfactor(h):
|
|
|
286
357
|
|
|
287
358
|
|
|
288
359
|
def _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax):
|
|
289
|
-
|
|
360
|
+
"""Adds topography metadata to the dataset.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
ds : xarray.Dataset
|
|
365
|
+
Dataset to update.
|
|
366
|
+
topography_source : dict
|
|
367
|
+
Dictionary with topography source information (requires 'name' key).
|
|
368
|
+
smooth_factor : float
|
|
369
|
+
Smoothing factor (unused in this function).
|
|
370
|
+
hmin : float
|
|
371
|
+
Minimum height threshold for smoothing.
|
|
372
|
+
rmax : float
|
|
373
|
+
Maximum slope parameter (unused in this function).
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
xarray.Dataset
|
|
378
|
+
Updated dataset with added metadata.
|
|
379
|
+
"""
|
|
380
|
+
ds.attrs["topography_source"] = topography_source["name"]
|
|
290
381
|
ds.attrs["hmin"] = hmin
|
|
291
382
|
|
|
292
383
|
return ds
|
|
293
384
|
|
|
294
385
|
|
|
295
|
-
def
|
|
386
|
+
def nan_check(hraw):
|
|
387
|
+
"""Checks for NaN values in the topography data.
|
|
296
388
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
ds["mask_v"] = interpolate_from_rho_to_v(
|
|
302
|
-
ds["mask_rho"], method="multiplicative"
|
|
303
|
-
).astype(np.int32)
|
|
304
|
-
|
|
305
|
-
ds["mask_u"].attrs = {"long_name": "Mask at u-points", "units": "land/water (0/1)"}
|
|
306
|
-
ds["mask_v"].attrs = {"long_name": "Mask at v-points", "units": "land/water (0/1)"}
|
|
389
|
+
Parameters
|
|
390
|
+
----------
|
|
391
|
+
hraw : xarray.DataArray
|
|
392
|
+
Input topography data to check for NaN values.
|
|
307
393
|
|
|
308
|
-
|
|
394
|
+
Raises
|
|
395
|
+
------
|
|
396
|
+
ValueError
|
|
397
|
+
If NaN values are found in the data, raises an error with a descriptive message.
|
|
398
|
+
"""
|
|
399
|
+
error_message = (
|
|
400
|
+
"NaN values found in regridded topography. This likely occurs because the ROMS grid, including "
|
|
401
|
+
"a small safety margin for interpolation, is not fully contained within the topography dataset's longitude/latitude range. Please ensure that the "
|
|
402
|
+
"dataset covers the entire area required by the ROMS grid."
|
|
403
|
+
)
|
|
404
|
+
if hraw.isnull().any().values:
|
|
405
|
+
raise ValueError(error_message)
|