Skip to content

Commit 8c6d7bc

Browse files
authored
Merge pull request #144 from GeoOcean/WMS
Wms
2 parents 8ebee83 + d271913 commit 8c6d7bc

2 files changed

Lines changed: 112 additions & 22 deletions

File tree

bluemath_tk/additive/greensurge.py

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,92 @@
1313
from matplotlib.figure import Figure
1414
from matplotlib.path import Path
1515
from tqdm import tqdm
16+
from shapely.geometry import LineString, Point
1617

1718
from ..core.operations import get_degrees_from_uv
1819
from ..core.plotting.colors import hex_colors_land, hex_colors_water
1920
from ..core.plotting.utils import join_colormaps
2021

2122

23+
def add_forcing_edges_to_computational_domain(poly_clip, vert, tria):
24+
"""
25+
Add edges to the forcing mesh that align with the computational domain boundary.
26+
Parameters
27+
----------
28+
poly_clip : shapely.geometry.Polygon
29+
The polygon representing the computational domain boundary.
30+
vert : np.ndarray
31+
Array of shape (n_vertices, 2) containing the coordinates of the vertices in the forcing mesh.
32+
tria : np.ndarray
33+
Array of shape (n_triangles, 3) containing the indices of the vertices for each triangle in the forcing mesh.
34+
Returns
35+
-------
36+
vert_clean : np.ndarray
37+
Updated array of vertices including new intersection points.
38+
edges_clean : np.ndarray
39+
Updated array of edges including new edges along the computational domain boundary.
40+
"""
41+
42+
# Extract all edges from forcing mesh triangles
43+
edges_new_full = np.vstack([
44+
tria[:, [0, 1]],
45+
tria[:, [1, 2]],
46+
tria[:, [2, 0]]
47+
])
48+
edges_new = np.unique(np.sort(edges_new_full, axis=1), axis=0)
49+
50+
# Check which vertices are inside computational domain
51+
mask_node_in = np.array([poly_clip.contains(Point(x, y)) for x, y in vert])
52+
53+
# Classify edges by how many endpoints are inside
54+
count_inside = mask_node_in[edges_new].sum(axis=1)
55+
edges_both_inside = edges_new[count_inside == 2]
56+
edges_one_inside = edges_new[count_inside == 1]
57+
58+
# Clip edges at computational domain boundary
59+
edges_one_inside_new = edges_one_inside.copy()
60+
new_points = []
61+
62+
for i, (n1, n2) in enumerate(edges_one_inside):
63+
p1, p2 = vert[n1], vert[n2]
64+
inside1 = mask_node_in[n1]
65+
66+
line = LineString([p1, p2])
67+
inter = line.intersection(poly_clip.boundary)
68+
69+
if inter.is_empty:
70+
continue
71+
72+
# Handle multiple intersection points
73+
if inter.geom_type == "MultiPoint":
74+
points = list(inter.geoms)
75+
ref = p1 if inside1 else p2
76+
dists = [Point(ref).distance(pt) for pt in points]
77+
inter_pt = np.array(points[np.argmin(dists)].coords[0])
78+
else:
79+
inter_pt = np.array(inter.coords[0])
80+
81+
new_index = len(vert) + len(new_points)
82+
new_points.append(inter_pt)
83+
84+
if inside1:
85+
edges_one_inside_new[i, 1] = new_index
86+
else:
87+
edges_one_inside_new[i, 0] = new_index
88+
89+
# Update vertices with new intersection points
90+
if len(new_points) > 0:
91+
vert_update = np.vstack([vert, np.array(new_points)])
92+
93+
# Clean up: keep only used nodes
94+
edges_used = np.vstack([edges_both_inside, edges_one_inside_new])
95+
used_nodes = np.unique(edges_used.flatten())
96+
vert_clean = vert_update[used_nodes]
97+
mapping = {old_idx: new_idx for new_idx, old_idx in enumerate(used_nodes)}
98+
edges_clean = np.vectorize(mapping.get)(edges_used)
99+
100+
return vert_clean, edges_clean
101+
22102
def read_adcirc_grd(grd_file: str) -> Tuple[np.ndarray, np.ndarray, List[str]]:
23103
"""
24104
Reads the ADCIRC grid file and returns the node and element data.
@@ -376,9 +456,6 @@ def plot_greensurge_setup(
376456
ax.set_extent([*bnd], crs=ccrs.PlateCarree())
377457
plt.legend(loc="lower left", fontsize=10, markerscale=2.0)
378458
ax.set_title("GreenSurge Mesh Setup")
379-
gl = ax.gridlines(draw_labels=True)
380-
gl.top_labels = False
381-
gl.right_labels = False
382459

383460
return fig, ax
384461

bluemath_tk/tcs/vortex.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def vortex_model_grid(
216216
def vortex2delft_3D_FM_nc(
217217
mesh: xr.Dataset,
218218
ds_vortex: xr.Dataset,
219+
fill_value: float = 0,
219220
) -> xr.Dataset:
220221
"""
221222
Convert the vortex dataset to a Delft3D FM compatible netCDF forcing file.
@@ -232,6 +233,8 @@ def vortex2delft_3D_FM_nc(
232233
The name of the output netCDF file, default is "forcing_Tonga_vortex.nc".
233234
forcing_ext : str
234235
The extension for the forcing file, default is "GreenSurge_GFDcase_wind.ext".
236+
fill_value : float
237+
The fill value to use for missing data, default is 0.
235238
Returns
236239
-------
237240
xarray.Dataset
@@ -245,33 +248,43 @@ def vortex2delft_3D_FM_nc(
245248
lat_interp = xr.DataArray(latitude, dims="node")
246249
lon_interp = xr.DataArray(longitude, dims="node")
247250

248-
angle = np.deg2rad((270 - ds_vortex.Dir.values) % 360)
249-
W = ds_vortex.W.values
251+
W_node = ds_vortex.W.interp(
252+
lat=lat_interp,
253+
lon=lon_interp,
254+
method="nearest",
255+
)
256+
257+
Dir_node = ds_vortex.Dir.interp(
258+
lat=lat_interp,
259+
lon=lon_interp,
260+
method="nearest",
261+
)
262+
263+
p_node = ds_vortex.p.interp(
264+
lat=lat_interp,
265+
lon=lon_interp,
266+
method="nearest",
267+
)
268+
269+
angle = np.deg2rad((270 - Dir_node.values) % 360)
250270

251-
ds_vortex_interp = xr.Dataset(
271+
windx_node = (W_node.values * np.cos(angle)).astype(np.float32)
272+
windy_node = (W_node.values * np.sin(angle)).astype(np.float32)
273+
274+
forcing_dataset = xr.Dataset(
252275
{
253-
"windx": (
254-
("latitude", "longitude", "time"),
255-
(W * np.cos(angle)).astype(np.float32),
256-
),
257-
"windy": (
258-
("latitude", "longitude", "time"),
259-
(W * np.sin(angle)).astype(np.float32),
260-
),
261-
"airpressure": (
262-
("latitude", "longitude", "time"),
263-
ds_vortex.p.values.astype(np.float32),
264-
),
276+
"windx": (("node", "time"), np.nan_to_num(windx_node, nan=fill_value)),
277+
"windy": (("node", "time"), np.nan_to_num(windy_node, nan=fill_value)),
278+
"airpressure": (("node", "time"), np.nan_to_num(p_node.values.astype(np.float32), nan=fill_value)),
265279
},
266280
coords={
267-
"latitude": ds_vortex.lat.values,
268-
"longitude": ds_vortex.lon.values,
281+
"node": np.arange(lat_interp.size),
269282
"time": np.arange(n_time),
283+
"latitude": ("node", latitude),
284+
"longitude": ("node", longitude),
270285
},
271286
)
272287

273-
forcing_dataset = ds_vortex_interp.interp(latitude=lat_interp, longitude=lon_interp)
274-
275288
reference_date_str = (
276289
ds_vortex.time.values[0]
277290
.astype("M8[ms]")

0 commit comments

Comments
 (0)