Skip to content

Commit 71a12b8

Browse files
author
keith roberts
authored
adding in edge stuff and SDF (#9)
* adding in edge stuff and SDF
1 parent 6fd7c3d commit 71a12b8

File tree

11 files changed

+372
-7
lines changed

11 files changed

+372
-7
lines changed

oceanmesh/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from oceanmesh.geodata import DEM, Geodata, Shoreline
2-
from oceanmesh.edgefx import Grid
2+
from oceanmesh.grid import Grid
33
from oceanmesh.edgefx import distance_sizing_function
4+
from oceanmesh.signed_distance_function import signed_distance_function
5+
from oceanmesh.edges import get_poly_edges, draw_edges
6+
from .inpoly import inpoly
47

58
__all__ = [
69
"SizeFunction",
@@ -9,4 +12,8 @@
912
"DEM",
1013
"Shoreline",
1114
"distance_sizing_function",
15+
"signed_distance_function",
16+
"get_poly_edges",
17+
"draw_edges",
18+
"inpoly",
1219
]

oceanmesh/edgefx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy
22
import skfmm
33

4-
from .Grid import Grid
4+
from .grid import Grid
55

66
__all__ = ["distance_sizing_function"]
77

oceanmesh/edges.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib import collections as mc
4+
5+
6+
nan = np.nan
7+
8+
9+
def get_poly_edges(poly):
10+
"""Given a winded polygon represented as a set of ascending line segments
11+
with separated features indicated by nans, this function calculates
12+
the edges of the polygon such that each edge indexes the start and end
13+
coordinates of each line segment of the polygon.
14+
15+
Parameters
16+
----------
17+
poly: array-like, float
18+
A 2D array of point coordinates with features sepearated by NaNs
19+
20+
Returns
21+
-------
22+
edges: array-like, int
23+
A 2D array of integers containing indexes into the `poly` array.
24+
25+
"""
26+
ix = np.argwhere(np.isnan(poly[:, 0]))
27+
ix = np.insert(ix, 0, 0)
28+
29+
edges = []
30+
for s in range(len(ix) - 1):
31+
col1 = np.arange(ix[s], ix[s + 1] - 1)
32+
col2 = np.arange(ix[s] + 1, ix[s + 1])
33+
tmp = np.vstack((col1, col2)).T
34+
tmp = np.append(tmp, [[ix[s + 1] - 1, ix[s] + 1]], axis=0)
35+
edges.append(tmp)
36+
return np.concatenate(edges, axis=0)
37+
38+
39+
def draw_edges(poly, edges):
40+
"""Visualizes the polygon as a bunch of line segments
41+
42+
Parameters
43+
----------
44+
poly: array-like, float
45+
A 2D array of point coordinates with features sepearated by NaNs.
46+
edges: array-like, int
47+
A 2D array of integers indexing into the `poly` array.
48+
49+
Returns
50+
-------
51+
None
52+
53+
"""
54+
lines = []
55+
for edge in edges:
56+
lines.append([poly[edge[0]], poly[edge[1]]])
57+
lc = mc.LineCollection(lines, linewidths=2)
58+
fig, ax = plt.subplots()
59+
ax.add_collection(lc)
60+
ax.autoscale()
61+
plt.show()
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
class Grid:
99
"""Abstracts a structured grid along with
10-
operations (e.g., min, project, etc.), stores data
11-
`values` defined at each grid point.
10+
primitive operations (e.g., min, project, etc.) and
11+
stores data `values` defined at each grid point.
1212
1313
Parameters
1414
----------
@@ -200,7 +200,7 @@ def project(self, grid2):
200200
new_values[new_values == FILL] = grid2.values[new_values == FILL]
201201
return Grid(bbox=grid2.bbox, grid_spacing=grid2.grid_spacing, values=new_values)
202202

203-
def plot(self, hold=False):
203+
def plot(self, hold=False, vmin=0.0, vmax=0.1):
204204
"""Visualize the values in :obj:`Grid`
205205
206206
Parameters
@@ -222,7 +222,7 @@ def plot(self, hold=False):
222222
x, y = self.create_grid()
223223

224224
fig, ax = plt.subplots()
225-
ax.pcolor(x, y, self.values, vmin=0.0, vmax=0.1, shading="auto")
225+
ax.pcolor(x, y, self.values, vmin=vmin, vmax=vmax, shading="auto")
226226
ax.axis("equal")
227227
if hold is False:
228228
plt.show()

oceanmesh/inpoly.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import numpy as np
2+
from numba import jit
3+
4+
5+
def inpoly(vert, node, edge=None, ftol=4.9485e-14):
6+
"""A port of Darren Engwirda's `inpoly` routine into Python.
7+
8+
Returns the "inside/outside" status for a set of vertices VERT and
9+
a polygon {NODE,EDGE} embedded in a two-dimensional plane. General
10+
non-convex and multiply-connected polygonal regions can be handled.
11+
12+
13+
Parameters
14+
----------
15+
vert: array-like
16+
VERT is an N-by-2 array of XY coordinates to query.
17+
18+
node: array-like
19+
NODE is an M-by-2 array of polygon vertices.
20+
21+
edge: array-like, optional
22+
EDGE is a P-by-2 array of edge indexing. Each
23+
row in EDGE represents an edge of the polygon, such that
24+
node(edge(KK,1),:) and node(edge(KK,2),:) are the coord-
25+
inates of the endpoints of the KK-TH edge. If the argum-
26+
ent EDGE is omitted it assumed that the vertices in NODE
27+
are connected in ascending order.
28+
29+
ftol: float, otpional
30+
FTOL is a floating-point tolerance for boundary comparisons.
31+
By default, FTOL = EPS ^ 0.85.
32+
33+
34+
Returns:
35+
--------
36+
STAT: array-like
37+
STAT is an associated N-by-1 logical array,
38+
with STAT(II) = TRUE if VERT(II,:) is an interior point.
39+
40+
BNDS: array-like
41+
N-by-1 logical array BNDS, with BNDS(II) = TRUE if VERT(II,:)
42+
lies "on" a boundary segment,
43+
44+
45+
Notes
46+
-----
47+
48+
ALL CREDIT GOES TO THE ORIGINAL AUTHOR: Darren Engwirda
49+
50+
Darren Engwirda : 2017 --
51+
Email : de2363@columbia.edu
52+
Last updated : 31/03/2020
53+
54+
55+
Author of port:
56+
57+
Keith J. Roberts (2020)
58+
Email: krober@usp.br
59+
Date: 2020-13-09
60+
61+
"""
62+
63+
nnod = len(node)
64+
nvrt = len(vert)
65+
66+
# create edge if not supplied
67+
if edge is None:
68+
edge = np.vstack((np.arange(0, nnod - 1), np.arange(1, nnod))).T
69+
edge = np.concatenate((edge, [[nnod - 1, 0]]))
70+
71+
STAT = np.zeros((nvrt))
72+
BNDS = np.zeros((nvrt))
73+
74+
# prune points using bbox
75+
mask = (
76+
(vert[:, 0] >= np.nanmin(node[:, 0]))
77+
& (vert[:, 0] <= np.nanmax(node[:, 0]))
78+
& (vert[:, 1] >= np.nanmin(node[:, 1]))
79+
& (vert[:, 1] <= np.nanmax(node[:, 1]))
80+
)
81+
82+
vert = vert[mask]
83+
84+
# flip to ensure y-axis is the `long` axis
85+
vmin = np.amin(vert, axis=0)
86+
vmax = np.amax(vert, axis=0)
87+
ddxy = vmax - vmin
88+
89+
lbar = np.sum(ddxy) / 2.0
90+
91+
if ddxy[0] > ddxy[1]:
92+
vert = vert[:, [1, 0]]
93+
node = node[:, [1, 0]]
94+
95+
# sort points via y-value
96+
swap = node[edge[:, 1], 1] < node[edge[:, 0], 1]
97+
98+
tmp = edge[swap]
99+
edge[swap, :] = tmp[:, [1, 0]]
100+
101+
ivec = np.argsort(vert[:, 1])
102+
vert = vert[ivec]
103+
104+
t_stat, t_bnds = _inpoly(vert, node, edge, ftol, lbar)
105+
106+
stat = np.ones(len(vert))
107+
bnds = np.ones(len(vert))
108+
stat[ivec] = t_stat
109+
bnds[ivec] = t_bnds
110+
111+
STAT[mask] = stat
112+
BNDS[mask] = bnds
113+
114+
return STAT, BNDS
115+
116+
117+
@jit(nopython=True)
118+
def _inpoly(vert, node, edge, ftol, lbar):
119+
120+
feps = ftol * lbar ** 2
121+
veps = ftol * lbar ** 1
122+
123+
_nvrt = len(vert)
124+
_nedge = len(edge)
125+
126+
_stat = np.zeros((_nvrt))
127+
_bnds = np.zeros((_nvrt))
128+
129+
# loop over polygon edges
130+
for epos in range(_nedge):
131+
132+
inod = edge[epos, 0]
133+
jnod = edge[epos, 1]
134+
135+
# calc. edge bounding-box
136+
yone = node[inod, 1]
137+
ytwo = node[jnod, 1]
138+
xone = node[inod, 0]
139+
xtwo = node[jnod, 0]
140+
141+
xmin = np.minimum(xone, xtwo)
142+
xmax = np.maximum(xone, xtwo)
143+
144+
xmax += veps
145+
146+
ymin = yone - veps
147+
ymax = ytwo + veps
148+
149+
ydel = ytwo - yone
150+
xdel = xtwo - xone
151+
152+
# find top VERT[:,1]<YONE
153+
ilow = 0 # for python
154+
iupp = _nvrt
155+
156+
while ilow < iupp - 1: # binary search
157+
imid = int(ilow + np.floor((iupp - ilow) / 2))
158+
if vert[imid, 1] < ymin:
159+
ilow = imid
160+
else:
161+
iupp = imid
162+
163+
if vert[ilow, 1] >= ymin:
164+
ilow -= 1
165+
166+
# calc. edge-intersection
167+
for jpos in range(ilow + 1, _nvrt):
168+
169+
if _bnds[jpos]:
170+
continue
171+
172+
xpos = vert[jpos, 0]
173+
ypos = vert[jpos, 1]
174+
175+
if ypos <= ymax:
176+
if xpos >= xmin:
177+
if xpos <= xmax:
178+
# compute crossing number
179+
mul1 = ydel * (xpos - xone)
180+
mul2 = xdel * (ypos - yone)
181+
if feps >= np.abs(mul2 - mul1):
182+
# BNDS -- approx. on edge
183+
_bnds[jpos] = 1
184+
_stat[jpos] = 1
185+
elif (ypos == yone) & (xpos == xone):
186+
# BNDS -- match about ONE
187+
_bnds[jpos] = 1
188+
_stat[jpos] = 1
189+
elif (ypos == ytwo) & (xpos == xtwo):
190+
# BNDS -- match about TWO
191+
_bnds[jpos] = 1
192+
_stat[jpos] = 1
193+
elif mul1 < mul2:
194+
if (ypos >= yone) & (ypos < ytwo):
195+
# advance crossing number
196+
_stat[jpos] = 1 - _stat[jpos]
197+
198+
else:
199+
if (ypos >= yone) & (ypos < ytwo):
200+
# advance crossing number
201+
_stat[jpos] = 1 - _stat[jpos]
202+
else:
203+
# done -- due to the sort
204+
break
205+
return _stat, _bnds
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import numpy
2+
import skfmm
3+
4+
from .grid import Grid
5+
from .inpoly import inpoly
6+
from . import edges
7+
8+
__all__ = ["signed_distance_function"]
9+
10+
11+
def signed_distance_function(shoreline):
12+
"""Takes a `shoreline` object containing segments representing islands and mainland boundaries
13+
and calculates a signed distance function with it (assuming the polygons are all closed)
14+
15+
Parameters
16+
----------
17+
18+
Returns
19+
-------
20+
21+
"""
22+
23+
print("Building a signed distance function...")
24+
grid = Grid(bbox=shoreline.bbox, grid_spacing=shoreline.h0)
25+
phi = numpy.ones(shape=(grid.nx, grid.ny))
26+
lon, lat = grid.create_grid()
27+
poly = numpy.vstack((shoreline.inner, shoreline.mainland))
28+
indices = grid.find_indices(poly, lon, lat)
29+
phi[indices] = -1.0
30+
# call Fast Marching Method
31+
print("Calculating the distance...")
32+
dis = skfmm.distance(phi, grid.grid_spacing)
33+
# now sign it
34+
e = edges.get_poly_edges(poly)
35+
print("Signing the distance...")
36+
lon = numpy.reshape(lon, -1)
37+
lat = numpy.reshape(lat, -1)
38+
qry = numpy.vstack((lon, lat)).T
39+
40+
inside, _ = inpoly(qry, poly, e)
41+
42+
grid.values = dis * inside.reshape((dis.shape))
43+
grid.build_interpolant()
44+
return grid

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ netcdf4
55
pyshp
66
Pillow==7.1.0
77
scikit-fmm==2019.1.30
8+
numba

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
"matplotlib",
2929
"Pillow",
3030
"scikit-fmm",
31+
"numba",
3132
],
3233
)

0 commit comments

Comments
 (0)