Skip to content

Commit c6d7c4a

Browse files
committed
[EJP] Plot update CRS clean bro
1 parent e9f52a7 commit c6d7c4a

File tree

1 file changed

+86
-123
lines changed

1 file changed

+86
-123
lines changed

bluemath_tk/topo_bathy/mesh_utils.py

Lines changed: 86 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@
1818
from shapely.geometry import Polygon, mapping
1919
from shapely.ops import transform
2020
from shapely.vectorized import contains
21+
import cartopy.crs as ccrs
2122

2223

23-
def plot_mesh_edge(msh_t: jigsaw_msh_t, ax: Axes = None, **kwargs) -> Axes:
24+
def plot_mesh_edge(
25+
msh_t: jigsaw_msh_t,
26+
ax = None ,
27+
to_geo=None,
28+
**kwargs
29+
) -> None:
2430
"""
2531
Plots the edges of a triangular mesh on a given set of axes.
2632
@@ -32,75 +38,87 @@ def plot_mesh_edge(msh_t: jigsaw_msh_t, ax: Axes = None, **kwargs) -> Axes:
3238
- 'tria3['index']' containing the indices of the triangles
3339
ax : Axes, optional
3440
The axes to plot on. If None, a new plot is created. Default is None.
41+
to_geo : callable, optional
42+
A function to transform (x, y) coordinates from projected to geographic CRS.
3543
**kwargs : keyword arguments, optional
3644
Additional keyword arguments passed to the `triplot` function.
3745
These can be used to customize the plot (e.g., color, line style).
3846
39-
Returns
40-
-------
41-
ax : Axes
42-
The axes object with the plotted mesh edges.
47+
4348
"""
4449

45-
crd = msh_t.vert2["coord"]
50+
crd = np.array(msh_t.vert2["coord"], copy=True)
4651
cnn = msh_t.tria3["index"]
4752

48-
if ax is None:
49-
_fig, ax = plt.subplots()
50-
ax.triplot(crd[:, 0], crd[:, 1], cnn, **kwargs)
51-
ax.set_title("Mesh Design Criteria")
52-
ax.set_xlabel("X UTM")
53-
ax.set_ylabel("Y UTM")
53+
if to_geo is not None:
54+
crd[:, 0], crd[:, 1] = to_geo(crd[:, 0], crd[:, 1])
55+
bnd = [crd[:, 0].min(), crd[:, 0].max(), crd[:, 1].min(), crd[:, 1].max()]
5456

55-
return ax
57+
ax.triplot(crd[:, 0], crd[:, 1], cnn, **kwargs)
58+
ax.set_extent([*bnd], crs=ccrs.PlateCarree())
59+
gl = ax.gridlines(draw_labels=True)
60+
gl.top_labels = False
61+
gl.right_labels = False
5662

5763

5864
def plot_mesh_vals(
5965
msh_t: jigsaw_msh_t,
6066
ax: Axes = None,
6167
colorbar: bool = True,
62-
clim: tuple = None,
68+
clim: Tuple[float, float] = None,
69+
to_geo: callable = None,
6370
**kwargs,
6471
) -> Axes:
6572
"""
66-
Plots the mesh values on a triangular mesh.
73+
Plots the mesh values on a triangular mesh, with optional transformation
74+
from UTM to geographic coordinates.
6775
6876
Parameters
6977
----------
7078
msh_t : jigsaw_msh_t
71-
An object containing the mesh data. It must have:
72-
- 'vert2['coord']' containing the coordinates of the mesh vertices.
73-
- 'tria3['index']' containing the indices of the triangles.
74-
- 'value' containing the mesh values to be plotted.
75-
ax : Axes, optional
76-
The axes to plot on. If None, a new plot is created. Default is None.
79+
An object containing the mesh data. Must include:
80+
- vert2['coord']: coordinates of mesh vertices (N, 2)
81+
- tria3['index']: triangle connectivity (M, 3)
82+
- value: values to plot (length M or Mx1)
83+
ax : matplotlib Axes, optional
84+
Axes to draw on. If None, a new one is created.
7785
colorbar : bool, optional
78-
Whether to display the colorbar. Default is True.
86+
If True, show colorbar. Default is True.
7987
clim : tuple, optional
80-
The limits for the color scale. If None, the limits are automatically
81-
determined from the data. Default is None.
82-
**kwargs : keyword arguments, optional
83-
Additional keyword arguments passed to the `tricontourf` function.
84-
These can be used to customize the plot (e.g., color, line style).
88+
Color limits (vmin, vmax). If None, autoscale.
89+
to_geo : callable, optional
90+
Function to transform (x, y) in projected coordinates to (lon, lat),
91+
**kwargs : additional keyword args for tricontourf
8592
8693
Returns
8794
-------
88-
ax : Axes
89-
The axes object with the plotted mesh values.
95+
ax : matplotlib Axes
96+
The axes with the plot.
9097
"""
9198

92-
crd = msh_t.vert2["coord"]
99+
# Copy coordinates to avoid modifying original mesh
100+
crd = np.array(msh_t.vert2["coord"], copy=True)
93101
cnn = msh_t.tria3["index"]
94102
val = msh_t.value.flatten()
95103

104+
# Transform to geographic coordinates if needed
105+
if to_geo is not None:
106+
crd[:, 0], crd[:, 1] = to_geo(crd[:, 0], crd[:, 1])
107+
96108
if ax is None:
97-
_fig, ax = plt.subplots()
109+
_, ax = plt.subplots()
110+
98111
mappable = ax.tricontourf(crd[:, 0], crd[:, 1], cnn, val, **kwargs)
112+
99113
if colorbar:
100114
if clim is not None:
101115
mappable.set_clim(*clim)
102-
_cb = plt.colorbar(mappable, ax=ax)
116+
cbar = plt.colorbar(mappable, ax=ax)
117+
cbar.set_label("Mesh spacing conditioning (m)")
103118

119+
gl = ax.gridlines(draw_labels=True)
120+
gl.top_labels = False
121+
gl.right_labels = False
104122
return ax
105123

106124

@@ -138,19 +156,18 @@ def plot_bathymetry(rasters_path: List[str], polygon: Polygon, ax: Axes) -> Axes
138156
height, width = data[0].shape
139157
cols, rows = np.meshgrid(np.arange(width), np.arange(height))
140158
xs, ys = rasterio.transform.xy(transform, rows, cols)
141-
159+
142160
im = ax.imshow(
143-
data[0], cmap="terrain", extent=(np.min(xs), np.max(xs), np.min(ys), np.max(ys))
161+
data[0], cmap="gist_earth", extent=(np.min(xs), np.max(xs), np.min(ys), np.max(ys))
144162
)
145163
cbar = plt.colorbar(im, ax=ax)
146164
cbar.set_label("Depth (m)")
147-
ax.set_xlabel("Longitude")
148-
ax.set_ylabel("Latitude")
149165
ax.set_title("Raster")
150166

151167
ax.plot(x_polygon, y_polygon, color="red", linewidth=1)
152-
ax.axis("equal")
153-
168+
gl = ax.gridlines(draw_labels=True)
169+
gl.top_labels = False
170+
gl.right_labels = False
154171
return ax
155172

156173

@@ -271,53 +288,7 @@ def clip_bati_manning(
271288
dest.write(out_image)
272289

273290

274-
def plot_poly(largest_polygon: Polygon, final_polygon: Polygon) -> Axes:
275-
"""
276-
Plots two polygons on a map: the largest polygon and the final polygon.
277-
278-
Parameters
279-
----------
280-
largest_polygon : Polygon
281-
The largest polygon to plot.
282-
final_polygon : Polygon
283-
The final polygon to plot.
284-
285-
Returns
286-
-------
287-
ax : Axes
288-
The axes object with the plotted polygons.
289-
"""
290-
291-
exterior_points = list(largest_polygon.exterior.coords)
292-
interior_points = [list(interior.coords) for interior in largest_polygon.interiors]
293-
all_points = exterior_points + [
294-
point for island in interior_points for point in island
295-
]
296-
297-
exterior_points_1 = list(final_polygon.exterior.coords)
298-
interior_points_1 = [list(interior.coords) for interior in final_polygon.interiors]
299-
all_points_1 = exterior_points_1 + [
300-
point for island in interior_points_1 for point in island
301-
]
302-
303-
x1, y1 = zip(*all_points_1)
304-
x, y = zip(*all_points)
305-
xx, yy = largest_polygon.exterior.xy
306-
307-
_fig, ax = plt.subplots()
308-
ax.fill(xx, yy, alpha=0.5, fc="lightgrey", ec="black")
309-
ax.scatter(x, y, color="red", s=1, label="Initial Polygon Points")
310-
ax.scatter(x1, y1, color="black", s=1, label="Final Polygon Points")
311-
ax.axis("equal")
312-
ax.set_title("Polygon Domain")
313-
ax.set_xlabel("Longitude")
314-
ax.set_ylabel("Latitude")
315-
ax.legend()
316-
317-
return ax
318-
319-
320-
def plot_boundaries(mesh: jigsaw_msh_t, ax: Axes) -> Axes:
291+
def plot_boundaries(mesh: jigsaw_msh_t, ax: Axes, to_geo=None) -> Axes:
321292
"""
322293
Plots the boundaries of a mesh, including ocean, interior (islands), and land areas.
323294
@@ -327,38 +298,30 @@ def plot_boundaries(mesh: jigsaw_msh_t, ax: Axes) -> Axes:
327298
The mesh object containing the mesh data and boundaries.
328299
ax : Axes
329300
The axes on which to plot the boundaries.
330-
331-
Returns
332-
-------
333-
ax : Axes
334-
The axes object with the plotted boundaries.
301+
to_geo : callable, optional
302+
A function to transform coordinates from projected to geographic CRS.
335303
"""
336304

337-
plot_mesh_edge(mesh.msh_t, ax=ax, lw=0.2, color="c")
305+
plot_mesh_edge(mesh.msh_t,to_geo=to_geo, ax=ax, color="gray", lw=0.5)
338306

339-
try:
340-
mesh.boundaries.ocean().plot(ax=ax, color="b", label="Ocean")
341-
except Exception as e:
342-
print(f"No Ocean boundaries available. Error: {e}")
343-
try:
344-
mesh.boundaries.interior().plot(ax=ax, color="g", label="Islands")
345-
except Exception as e:
346-
print(f"No Islands boundaries available. Error: {e}")
347-
try:
348-
mesh.boundaries.land().plot(ax=ax, color="r", label="Land")
349-
except Exception as e:
350-
print(f"No Land boundaries available. Error: {e}")
307+
def plot_boundary(gdf, color, label):
308+
try:
309+
if to_geo:
310+
gdf = gdf.copy()
311+
gdf["geometry"] = gdf["geometry"].apply(lambda geom: transform(to_geo, geom))
312+
gdf.plot(ax=ax, color=color, label=label)
313+
except Exception as e:
314+
print(f"No {label} boundaries available. Error: {e}")
351315

352-
ax.legend()
353-
ax.axis("equal")
354-
ax.set_title("Mesh Boundaries")
355-
ax.set_xlabel("X UTM")
356-
ax.set_ylabel("Y UTM")
316+
plot_boundary(mesh.boundaries.ocean(), color="b", label="Ocean")
317+
plot_boundary(mesh.boundaries.interior(), color="g", label="Islands")
318+
plot_boundary(mesh.boundaries.land(), color="r", label="Land")
357319

358-
return ax
320+
ax.set_title("Mesh Boundaries")
321+
ax.legend()
359322

360323

361-
def plot_bathymetry_interp(mesh: jigsaw_msh_t, ax: Axes) -> Axes:
324+
def plot_bathymetry_interp(mesh: jigsaw_msh_t,to_geo, ax: Axes) -> Axes:
362325
"""
363326
Plots the interpolated bathymetry data on a mesh.
364327
@@ -368,30 +331,30 @@ def plot_bathymetry_interp(mesh: jigsaw_msh_t, ax: Axes) -> Axes:
368331
The mesh object containing the bathymetry values and mesh structure.
369332
ax : Axes
370333
The axes on which to plot the interpolated bathymetry.
371-
372-
Returns
373-
-------
374-
ax : Axes
375-
The axes object with the plotted interpolated bathymetry.
334+
to_geo : callable
335+
A function to transform coordinates from projected to geographic CRS.
376336
"""
337+
crd = np.array(mesh.msh_t.vert2["coord"], copy=True)
338+
339+
if to_geo is not None:
340+
crd[:, 0], crd[:, 1] = to_geo(crd[:, 0], crd[:, 1])
341+
bnd = [crd[:, 0].min(), crd[:, 0].max(), crd[:, 1].min(), crd[:, 1].max()]
377342

378343
im = ax.tricontourf(
379344
Triangulation(
380-
mesh.msh_t.vert2["coord"][:, 0],
381-
mesh.msh_t.vert2["coord"][:, 1],
345+
crd[:, 0],
346+
crd[:, 1],
382347
triangles=mesh.msh_t.tria3["index"],
383348
),
384349
mesh.msh_t.value.flatten(),
385350
)
386351
ax.set_title("Interpolated Bathymetry")
387-
ax.axis("equal")
388-
ax.set_xlabel("X UTM")
389-
ax.set_ylabel("Y UTM")
352+
gl = ax.gridlines(draw_labels=True)
353+
gl.top_labels = False
354+
gl.right_labels = False
390355
cbar = plt.colorbar(im, ax=ax)
391356
cbar.set_label("Depth (m)")
392-
393-
return ax
394-
357+
ax.set_extent([*bnd], crs=ccrs.PlateCarree())
395358

396359
def simply_polygon(base_shape: Polygon, simpl_UTM: float, project) -> Polygon:
397360
"""

0 commit comments

Comments
 (0)