Skip to content

Commit 44dfb00

Browse files
committed
Add method to get convex hull from a concave polygon
1 parent e767238 commit 44dfb00

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
setup(
1111
name="pyEddyTracker",
12+
python_requires=">=3.7",
1213
version=versioneer.get_version(),
1314
cmdclass=versioneer.get_cmdclass(),
1415
description="Py-Eddy-Tracker libraries",

src/py_eddy_tracker/observations/observation.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
close_center,
5959
get_pixel_in_regular,
6060
winding_number_poly,
61+
convexs,
6162
)
6263

6364
logger = logging.getLogger("pet")
@@ -101,9 +102,7 @@ def shifted_ellipsoid_degrees_mask2(lon0, lat0, lon1, lat1, minor=1.5, major=1.5
101102

102103
class EddiesObservations(object):
103104
"""
104-
Class to hold eddy properties *amplitude* and counts of
105-
*local maxima/minima* within a closed region of a sea level anomaly field.
106-
105+
Class to store eddy observations.
107106
"""
108107

109108
__slots__ = (
@@ -1606,6 +1605,17 @@ def last_obs(self):
16061605
m[:-1][self["n"][1:] == 0] = True
16071606
return self.extract_with_mask(m)
16081607

1608+
def is_convex(self, intern=False):
1609+
"""
1610+
Get flag of eddy convexity
1611+
1612+
:param bool intern: If true use speed contour instead of effective contour
1613+
:return: true if contour is convex
1614+
:rtype: array[bool]
1615+
"""
1616+
xname, yname = self.intern(intern)
1617+
return convexs(self[xname], self[yname])
1618+
16091619
def inside(self, x, y, intern=False):
16101620
"""
16111621
True for each postion inside an eddy
@@ -1769,9 +1779,9 @@ def insidepoly(x_p, y_p, x_c, y_c):
17691779
x_c_max, y_c_max = x_c[i].max(), y_c[i].max()
17701780
v = create_vertice(x_c[i], y_c[i])
17711781
for j in range(nb_p):
1772-
x, y = x_p[j], y_p[j]
17731782
if flag[j]:
17741783
continue
1784+
x, y = x_p[j], y_p[j]
17751785
if x > x_c_min and x < x_c_max and y > y_c_min and y < y_c_max:
17761786
if winding_number_poly(x, y, v) != 0:
17771787
flag[j] = True

src/py_eddy_tracker/poly.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,101 @@ def poly_area(x, y):
8888
return abs(p_area) * 0.5
8989

9090

91+
@njit(cache=True)
92+
def convexs(x, y):
93+
"""
94+
Check if polygons are convex
95+
96+
:param array[float] x:
97+
:param array[float] y:
98+
:return: True if convex
99+
:rtype: array[bool]
100+
"""
101+
nb_poly = x.shape[0]
102+
flag = empty(nb_poly, dtype=numba_types.bool_)
103+
for i in range(nb_poly):
104+
flag[i] = convex(x[i], y[i])
105+
return flag
106+
107+
108+
@njit(cache=True)
109+
def convex(x, y):
110+
"""
111+
Check if polygon is convex
112+
113+
:param array[float] x:
114+
:param array[float] y:
115+
:return: True if convex
116+
:rtype: bool
117+
"""
118+
nb = x.shape[0]
119+
x0, y0, x1, y1, x2, y2 = x[-2], y[-2], x[-1], y[-1], x[1], y[1]
120+
# if first is left it must be always left if it's right it must be always right
121+
ref = is_left(x0, y0, x1, y1, x2, y2)
122+
# We skip 0 because it's same than -1
123+
# We skip 1 because we tested previously
124+
for i in range(2, nb):
125+
# shift position
126+
x0, y0, x1, y1 = x1, y1, x2, y2
127+
x2, y2 = x[i], y[i]
128+
# test
129+
if ref != is_left(x0, y0, x1, y1, x2, y2):
130+
return False
131+
return True
132+
133+
134+
@njit(cache=True)
135+
def get_convex_hull(x, y):
136+
"""
137+
Get convex polygon which enclosed current polygon
138+
139+
Work only if contour is describe anti-clockwise
140+
141+
:param array[float] x:
142+
:param array[float] y:
143+
:return: a convex polygon
144+
:rtype: array,array
145+
"""
146+
nb = x.shape[0] - 1
147+
indices = list()
148+
# leftmost point
149+
i_first = x[:-1].argmin()
150+
indices.append(i_first)
151+
i_next = (i_first + 1) % nb
152+
# Will define bounds line
153+
x0, y0, x1, y1 = x[i_first], y[i_first], x[i_next], y[i_next]
154+
xf, yf = x0, y0
155+
# we will check if no point are right
156+
while True:
157+
i_test = (i_next + 1) % nb
158+
# value to test
159+
xt, yt = x[i_test], y[i_test]
160+
# We will test all the position until we touch first one,
161+
# If all next position are on the left we keep x1, y1
162+
# if not we will replace by xt,yt which are more outter
163+
while is_left(x0, y0, x1, y1, xt, yt):
164+
i_test += 1
165+
i_test %= nb
166+
if i_test == i_first:
167+
x0, y0 = x1, y1
168+
indices.append(i_next)
169+
i_next += 1
170+
i_next %= nb
171+
x1, y1 = x[i_next], y[i_next]
172+
break
173+
xt, yt = x[i_test], y[i_test]
174+
if i_test != i_first:
175+
i_next = i_test
176+
x1, y1 = x[i_next], y[i_next]
177+
if i_next == (i_first - 1) % nb:
178+
if is_left(x0, y0, x1, y1, xf, yf):
179+
indices.append(i_next)
180+
break
181+
indices.append(i_first)
182+
indices = array(indices)
183+
return x[indices], y[indices]
184+
185+
91186
@njit(cache=True)
92187
def winding_number_poly(x, y, xy_poly):
93188
"""
@@ -155,7 +250,7 @@ def winding_number_grid_in_poly(x_1d, y_1d, i_x0, i_x1, x_size, i_y0, xy_poly):
155250

156251

157252
@njit(cache=True, fastmath=True)
158-
def close_center(x0, y0, x1, y1, delta=.1):
253+
def close_center(x0, y0, x1, y1, delta=0.1):
159254
"""
160255
Compute an overlap with circle parameter and return a percentage
161256

tests/test_poly.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from py_eddy_tracker.poly import poly_area_vertice, fit_circle
1+
from py_eddy_tracker.poly import poly_area_vertice, fit_circle, convex, get_convex_hull
22
from numpy import array, pi
33
from pytest import approx
44

55
# Vertices for next test
66
V = array(((2, 2, 3, 3, 2), (-10, -9, -9, -10, -10)))
7+
V_concave = array(((2, 2, 2.5, 3, 3, 2), (-10, -9, -9.5, -9, -10, -10)))
78

89

910
def test_poly_area():
@@ -16,3 +17,14 @@ def test_fit_circle():
1617
assert y0 == approx(-9.5, rel=1e-10)
1718
assert r == approx(2 ** 0.5 / 2, rel=1e-10)
1819
assert err == approx((1 - 2 / pi) * 100, rel=1e-10)
20+
21+
22+
def test_convex():
23+
assert convex(*V) is True
24+
assert convex(*V[::-1]) is True
25+
assert convex(*V_concave) is False
26+
assert convex(*V_concave[::-1]) is False
27+
28+
29+
def test_convex_hull():
30+
assert convex(*get_convex_hull(*V_concave)) is True

0 commit comments

Comments
 (0)