-
-
Save tixxit/252229 to your computer and use it in GitHub Desktop.
# Chan's Convex Hull O(n log h) - Tom Switzer <[email protected]> | |
TURN_LEFT, TURN_RIGHT, TURN_NONE = (1, -1, 0) | |
def turn(p, q, r): | |
"""Returns -1, 0, 1 if p,q,r forms a right, straight, or left turn.""" | |
return cmp((q[0] - p[0])*(r[1] - p[1]) - (r[0] - p[0])*(q[1] - p[1]), 0) | |
def _keep_left(hull, r): | |
while len(hull) > 1 and turn(hull[-2], hull[-1], r) != TURN_LEFT: | |
hull.pop() | |
return (not len(hull) or hull[-1] != r) and hull.append(r) or hull | |
def _graham_scan(points): | |
"""Returns points on convex hull of an array of points in CCW order.""" | |
points.sort() | |
lh = reduce(_keep_left, points, []) | |
uh = reduce(_keep_left, reversed(points), []) | |
return lh.extend(uh[i] for i in xrange(1, len(uh) - 1)) or lh | |
def _rtangent(hull, p): | |
"""Return the index of the point in hull that the right tangent line from p | |
to hull touches. | |
""" | |
l, r = 0, len(hull) | |
l_prev = turn(p, hull[0], hull[-1]) | |
l_next = turn(p, hull[0], hull[(l + 1) % r]) | |
while l < r: | |
c = (l + r) / 2 | |
c_prev = turn(p, hull[c], hull[(c - 1) % len(hull)]) | |
c_next = turn(p, hull[c], hull[(c + 1) % len(hull)]) | |
c_side = turn(p, hull[l], hull[c]) | |
if c_prev != TURN_RIGHT and c_next != TURN_RIGHT: | |
return c | |
elif c_side == TURN_LEFT and (l_next == TURN_RIGHT or | |
l_prev == l_next) or \ | |
c_side == TURN_RIGHT and c_prev == TURN_RIGHT: | |
r = c # Tangent touches left chain | |
else: | |
l = c + 1 # Tangent touches right chain | |
l_prev = -c_next # Switch sides | |
l_next = turn(p, hull[l], hull[(l + 1) % len(hull)]) | |
return l | |
def _min_hull_pt_pair(hulls): | |
"""Returns the hull, point index pair that is minimal.""" | |
h, p = 0, 0 | |
for i in xrange(len(hulls)): | |
j = min(xrange(len(hulls[i])), key=lambda j: hulls[i][j]) | |
if hulls[i][j] < hulls[h][p]: | |
h, p = i, j | |
return (h, p) | |
def _next_hull_pt_pair(hulls, pair): | |
""" | |
Returns the (hull, point) index pair of the next point in the convex | |
hull. | |
""" | |
p = hulls[pair[0]][pair[1]] | |
next = (pair[0], (pair[1] + 1) % len(hulls[pair[0]])) | |
for h in (i for i in xrange(len(hulls)) if i != pair[0]): | |
s = _rtangent(hulls[h], p) | |
q, r = hulls[next[0]][next[1]], hulls[h][s] | |
t = turn(p, q, r) | |
if t == TURN_RIGHT or t == TURN_NONE and _dist(p, r) > _dist(p, q): | |
next = (h, s) | |
return next | |
def convex_hull(pts): | |
"""Returns the points on the convex hull of pts in CCW order.""" | |
for m in (1 << (1 << t) for t in xrange(len(pts))): | |
hulls = [_graham_scan(pts[i:i + m]) for i in xrange(0, len(pts), m)] | |
hull = [_min_hull_pt_pair(hulls)] | |
for throw_away in xrange(m): | |
p = _next_hull_pt_pair(hulls, hull[-1]) | |
if p == hull[0]: | |
return [hulls[h][i] for h, i in hull] | |
hull.append(p) |
Hi Tom
I've found a problem in your code, on line 40: 'l' can grow to the len(hull) here and then subsequent lines will fail with an index error. I am not sure what the behaviour should be if l == len(hull). The problem might be very specific to my set of points I want to find a convex hull for, but if you want, I can email it. (list of 352 points).
Or alternatively use my 'gist' here: https://gist.github.com/mdunschen/2ce8ba071a1c6c768d5d
@mdunschen Have you found out the solution?
def _rtangent(hull, p):
"""Return the index of the point in hull that the right tangent line from p
to hull touches.
"""
l, r = 0, len(hull)
l_prev = turn(p, hull[0], hull[-1])
l_next = turn(p, hull[0], hull[(l + 1) % r])
c = r // 2
while l < r:
c_prev = turn(p, hull[c], hull[(c - 1) % len(hull)])
c_next = turn(p, hull[c], hull[(c + 1) % len(hull)])
c_side = turn(p, hull[l], hull[c])
if c_prev != TURN_RIGHT and c_next != TURN_RIGHT:
return c
elif c_side == TURN_LEFT and (l_next == TURN_RIGHT or l_prev == l_next) or \
c_side == TURN_RIGHT and c_prev == TURN_RIGHT:
r = c # Tangent touches left chain
c = (l + r) // 2
else:
l = c + 1
l_prev = -c_next # Switch sides
l_next = turn(p, hull[l], hull[(l + 1) % len(hull)])
return l
Hi Tom
I've found a problem in your code, on line 40: 'l' can grow to the len(hull) here and then subsequent lines will fail with an index error. I am not sure what the behaviour should be if l == len(hull). The problem might be very specific to my set of points I want to find a convex hull for, but if you want, I can email it. (list of 352 points).
Or alternatively use my 'gist' here: https://gist.github.com/mdunschen/2ce8ba071a1c6c768d5d
I had the same problem, with this for instance
hull = []
hull += [[997.00, 38.00]]
hull += [[469.00, 854.00]]
hull += [[23.00, 902.00]]
hull += [[355.00, 588.00]]
p = [1019.00, 4.00]
_rtangent(hull,p)
and noticed that when it is the case, it seems that the function should always return 1. Also, I used this function in C. Python and C do not agree on -1%n, python will see it as 1 while C will see it as -1 so to be safe the following line
c_prev = turn(p, hull[c], hull[(c - 1) % len(hull)])
can be changed like this
c_prev = turn(p, hull[c], hull[abs((c - 1) % len(hull))])
Good one dude, really helpful for understanding chans algorithm. Thanks