bluemesh2d 0.1.1.dev0__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.
- bluemesh2d/_version.py +24 -0
- bluemesh2d/aabb_tree/findball.py +121 -0
- bluemesh2d/aabb_tree/findtria.py +299 -0
- bluemesh2d/aabb_tree/maketree.py +147 -0
- bluemesh2d/aabb_tree/mapvert.py +72 -0
- bluemesh2d/aabb_tree/queryset.py +83 -0
- bluemesh2d/aabb_tree/scantree.py +124 -0
- bluemesh2d/dependencies.py +83 -0
- bluemesh2d/feedback.py +142 -0
- bluemesh2d/geom_util/boundary_util.py +216 -0
- bluemesh2d/geom_util/getiso.py +194 -0
- bluemesh2d/geom_util/poly_util.py +795 -0
- bluemesh2d/geom_util/proj_util.py +250 -0
- bluemesh2d/geomesh_util/border_util.py +283 -0
- bluemesh2d/geomesh_util/depth_field.py +333 -0
- bluemesh2d/geomesh_util/grd_util.py +1000 -0
- bluemesh2d/geomesh_util/interpolation_mesh.py +318 -0
- bluemesh2d/geomesh_util/merge_circumcenters.py +268 -0
- bluemesh2d/geomesh_util/water_polygon.py +406 -0
- bluemesh2d/hfun_util/build_hfun.py +589 -0
- bluemesh2d/hfun_util/hfun_dispersion.py +96 -0
- bluemesh2d/hfun_util/lfshfn.py +110 -0
- bluemesh2d/hfun_util/limhfn.py +65 -0
- bluemesh2d/hfun_util/make_constant_hfun.py +32 -0
- bluemesh2d/hfun_util/make_depth_hfun.py +51 -0
- bluemesh2d/hfun_util/smooth_and_precomput.py +188 -0
- bluemesh2d/hfun_util/trihfn.py +84 -0
- bluemesh2d/hjac_util/limgrad.py +105 -0
- bluemesh2d/mesh_ball/cdtbal1.py +38 -0
- bluemesh2d/mesh_ball/cdtbal2.py +104 -0
- bluemesh2d/mesh_ball/inv_2x2.py +61 -0
- bluemesh2d/mesh_ball/inv_3x3.py +72 -0
- bluemesh2d/mesh_ball/pwrbal2.py +134 -0
- bluemesh2d/mesh_ball/tribal2.py +27 -0
- bluemesh2d/mesh_cost/relhfn.py +62 -0
- bluemesh2d/mesh_cost/triang.py +59 -0
- bluemesh2d/mesh_cost/triarea.py +51 -0
- bluemesh2d/mesh_cost/trideg.py +44 -0
- bluemesh2d/mesh_cost/triscr.py +43 -0
- bluemesh2d/mesh_file/bnd_util.py +664 -0
- bluemesh2d/mesh_file/loadmsh.py +165 -0
- bluemesh2d/mesh_file/ugrid.py +308 -0
- bluemesh2d/mesh_util/cfmtri.py +118 -0
- bluemesh2d/mesh_util/deltri.py +131 -0
- bluemesh2d/mesh_util/idxtri.py +53 -0
- bluemesh2d/mesh_util/isfeat.py +90 -0
- bluemesh2d/mesh_util/minlen.py +46 -0
- bluemesh2d/mesh_util/setset.py +49 -0
- bluemesh2d/mesh_util/tricon.py +90 -0
- bluemesh2d/mesh_util/tridiv.py +187 -0
- bluemesh2d/meshgen.py +202 -0
- bluemesh2d/ortho_merge/constants.py +27 -0
- bluemesh2d/ortho_merge/geometry.py +64 -0
- bluemesh2d/ortho_merge/ortho_merge_iter.py +812 -0
- bluemesh2d/ortho_merge/orthogonalize.py +2989 -0
- bluemesh2d/pipeline.py +277 -0
- bluemesh2d/poly_data/airfoil.msh +958 -0
- bluemesh2d/poly_data/channel.msh +212 -0
- bluemesh2d/poly_data/islands.msh +13819 -0
- bluemesh2d/poly_data/lake.msh +612 -0
- bluemesh2d/poly_data/river.msh +690 -0
- bluemesh2d/poly_test/inpoly.py +105 -0
- bluemesh2d/poly_test/inpoly_mat.py +104 -0
- bluemesh2d/refine.py +972 -0
- bluemesh2d/smood.py +623 -0
- bluemesh2d/smooth.py +522 -0
- bluemesh2d/tricost.py +426 -0
- bluemesh2d/tridemo.py +723 -0
- bluemesh2d/triread.py +53 -0
- bluemesh2d-0.1.1.dev0.dist-info/METADATA +139 -0
- bluemesh2d-0.1.1.dev0.dist-info/RECORD +74 -0
- bluemesh2d-0.1.1.dev0.dist-info/WHEEL +5 -0
- bluemesh2d-0.1.1.dev0.dist-info/licenses/LICENSE +674 -0
- bluemesh2d-0.1.1.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Iso-contour extraction from structured 2D scalar fields.
|
|
2
|
+
|
|
3
|
+
Utilities for extracting contour polylines and polygon regions from gridded
|
|
4
|
+
data when building mesh domains from bathymetry or other scalar fields."""
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.interpolate import griddata
|
|
8
|
+
from shapely.geometry import Polygon
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def getiso(xpos, ypos, zdat, ilev, filt=0.0):
|
|
12
|
+
"""An iso-contour from a structured 2D dataset.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
xpos, ypos : ndarray of shape (N, M)
|
|
17
|
+
Grid coordinates (same shape as ``zdat``).
|
|
18
|
+
zdat : ndarray of shape (N, M)
|
|
19
|
+
Scalar field values.
|
|
20
|
+
ilev : float
|
|
21
|
+
Iso-contour level.
|
|
22
|
+
filt : float, optional
|
|
23
|
+
Minimum bounding-box extent in x and y for a contour segment to be
|
|
24
|
+
kept. Segments smaller than ``filt`` in either direction are dropped.
|
|
25
|
+
Default is 0 (keep all).
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
node : ndarray of shape (K, 2)
|
|
30
|
+
Vertex coordinates of contour polylines.
|
|
31
|
+
edge : ndarray of shape (E, 2), dtype int
|
|
32
|
+
Edge list (vertex indices) defining the contour PSLG.
|
|
33
|
+
|
|
34
|
+
References
|
|
35
|
+
----------
|
|
36
|
+
Translation of the MESH2D function ``getiso``.
|
|
37
|
+
Original MATLAB source: https://github.com/dengwirda/mesh2d
|
|
38
|
+
"""
|
|
39
|
+
fig, ax = plt.subplots()
|
|
40
|
+
cs = ax.contour(xpos, ypos, zdat, levels=[ilev])
|
|
41
|
+
|
|
42
|
+
# Matplotlib < 3.9 exposes line contours via cs.collections; >= 3.9 uses
|
|
43
|
+
# cs.allsegs (collections was removed in 3.10).
|
|
44
|
+
try:
|
|
45
|
+
collections = cs.collections
|
|
46
|
+
except AttributeError:
|
|
47
|
+
class DummyCollection:
|
|
48
|
+
def __init__(self, segs):
|
|
49
|
+
self.paths = [type("PathLike", (), {"vertices": seg})() for seg in segs]
|
|
50
|
+
|
|
51
|
+
def get_paths(self):
|
|
52
|
+
return self.paths
|
|
53
|
+
|
|
54
|
+
collections = [DummyCollection(segs) for segs in cs.allsegs if segs]
|
|
55
|
+
|
|
56
|
+
plt.close(fig)
|
|
57
|
+
|
|
58
|
+
node = []
|
|
59
|
+
edge = []
|
|
60
|
+
for collection in collections:
|
|
61
|
+
for path in collection.get_paths():
|
|
62
|
+
ppts = path.vertices
|
|
63
|
+
numc = ppts.shape[0]
|
|
64
|
+
|
|
65
|
+
pmin = ppts.min(axis=0)
|
|
66
|
+
pmax = ppts.max(axis=0)
|
|
67
|
+
pdel = pmax - pmin
|
|
68
|
+
|
|
69
|
+
if np.min(pdel) >= filt:
|
|
70
|
+
if np.allclose(ppts[0], ppts[-1]):
|
|
71
|
+
enew = np.vstack(
|
|
72
|
+
[
|
|
73
|
+
np.column_stack(
|
|
74
|
+
[np.arange(0, numc - 1), np.arange(1, numc)]
|
|
75
|
+
),
|
|
76
|
+
[numc - 1, 0],
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
enew = np.column_stack([np.arange(0, numc - 1), np.arange(1, numc)])
|
|
81
|
+
|
|
82
|
+
offset = len(node)
|
|
83
|
+
enew = enew + offset
|
|
84
|
+
|
|
85
|
+
node.extend(ppts.tolist())
|
|
86
|
+
edge.extend(enew.tolist())
|
|
87
|
+
|
|
88
|
+
node = np.array(node)
|
|
89
|
+
edge = np.array(edge, dtype=int)
|
|
90
|
+
|
|
91
|
+
return node, edge
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def getiso_polygon(x, y, z, zmax=None, grid_res=None):
|
|
95
|
+
"""Polygon regions from a 2D scalar field by thresholding.
|
|
96
|
+
|
|
97
|
+
Builds filled contours for regions where ``z <= zmax`` (or the zero level
|
|
98
|
+
when ``zmax`` is ``None``) and returns valid Shapely polygons with holes
|
|
99
|
+
resolved.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
x, y : ndarray
|
|
104
|
+
1D or 2D arrays defining grid coordinates (must match ``z``).
|
|
105
|
+
z : ndarray
|
|
106
|
+
Scalar field values.
|
|
107
|
+
zmax : float, optional
|
|
108
|
+
Threshold value. Polygons enclose regions where ``z <= zmax``. If
|
|
109
|
+
``None``, the zero level is used.
|
|
110
|
+
grid_res : int, optional
|
|
111
|
+
Grid resolution used when ``x`` and ``y`` are 1D scattered points.
|
|
112
|
+
If ``None``, inferred from ``len(z)``.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
polygons : list of shapely.geometry.Polygon or None
|
|
117
|
+
Polygons sorted by area with hole geometry attached. ``None`` if no
|
|
118
|
+
valid region is found.
|
|
119
|
+
|
|
120
|
+
Notes
|
|
121
|
+
-----
|
|
122
|
+
Filled-contour paths are compound: one path holds the outer boundary plus
|
|
123
|
+
hole rings as separate closed sub-rings. ``Path.to_polygons()`` splits
|
|
124
|
+
those sub-rings; using ``path.vertices`` alone would concatenate rings
|
|
125
|
+
into an invalid polygon.
|
|
126
|
+
|
|
127
|
+
References
|
|
128
|
+
----------
|
|
129
|
+
Translation of the MESH2D function ``getiso``.
|
|
130
|
+
Original MATLAB source: https://github.com/dengwirda/mesh2d
|
|
131
|
+
"""
|
|
132
|
+
if x.ndim == 1 and y.ndim == 1 and z.ndim == 1:
|
|
133
|
+
if grid_res is None:
|
|
134
|
+
grid_res = int(np.sqrt(len(z)))
|
|
135
|
+
|
|
136
|
+
dx = (np.max(x) - np.min(x)) / (grid_res - 1)
|
|
137
|
+
dy = (np.max(y) - np.min(y)) / (grid_res - 1)
|
|
138
|
+
|
|
139
|
+
xi = np.arange(np.min(x) - dx / 2, np.max(x) + dx / 2 + dx, dx)
|
|
140
|
+
yi = np.arange(np.min(y) - dy / 2, np.max(y) + dy / 2 + dy, dy)
|
|
141
|
+
|
|
142
|
+
X, Y = np.meshgrid(xi, yi)
|
|
143
|
+
|
|
144
|
+
Z = griddata((x, y), z, (X, Y), method="linear")
|
|
145
|
+
|
|
146
|
+
z = np.nan_to_num(Z, nan=np.nanmedian(z))
|
|
147
|
+
else:
|
|
148
|
+
X, Y = x, y
|
|
149
|
+
|
|
150
|
+
if z.ndim == 2 and X.ndim == 1 and Y.ndim == 1:
|
|
151
|
+
X, Y = np.meshgrid(X, Y)
|
|
152
|
+
|
|
153
|
+
if X.shape != Y.shape or X.shape != z.shape:
|
|
154
|
+
raise ValueError("Inconsistent array shapes between x, y, z.")
|
|
155
|
+
|
|
156
|
+
new_mask = np.full(z.shape, 1)
|
|
157
|
+
if zmax is not None:
|
|
158
|
+
new_mask[z > zmax] = -1
|
|
159
|
+
|
|
160
|
+
fig, ax = plt.subplots()
|
|
161
|
+
cs = ax.contourf(X, Y, new_mask, levels=[0, 1])
|
|
162
|
+
|
|
163
|
+
# Matplotlib < 3.9: one PathCollection per level (cs.collections, removed
|
|
164
|
+
# in 3.10). >= 3.9: cs.get_paths() returns one compound path per level.
|
|
165
|
+
try:
|
|
166
|
+
paths = [p for c in cs.collections for p in c.get_paths()]
|
|
167
|
+
except AttributeError:
|
|
168
|
+
paths = list(cs.get_paths())
|
|
169
|
+
|
|
170
|
+
plt.close(fig)
|
|
171
|
+
|
|
172
|
+
polygons = []
|
|
173
|
+
for path in paths:
|
|
174
|
+
for pts in path.to_polygons():
|
|
175
|
+
pts = np.asarray(pts)
|
|
176
|
+
if pts.shape[0] < 4:
|
|
177
|
+
continue
|
|
178
|
+
poly = Polygon(pts)
|
|
179
|
+
if poly.is_valid and not poly.is_empty and poly.area > 0:
|
|
180
|
+
polygons.append(poly)
|
|
181
|
+
|
|
182
|
+
if not polygons:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
polygons.sort(key=lambda p: p.area, reverse=True)
|
|
186
|
+
|
|
187
|
+
final_polys = []
|
|
188
|
+
while polygons:
|
|
189
|
+
outer = polygons.pop(0)
|
|
190
|
+
holes = [p.exterior.coords for p in polygons if p.within(outer)]
|
|
191
|
+
polygons = [p for p in polygons if not p.within(outer)]
|
|
192
|
+
final_polys.append(Polygon(outer.exterior.coords, holes))
|
|
193
|
+
|
|
194
|
+
return final_polys
|