예제 #1
0
def jarvis_march(points):
    if len(points) <= 3:
        return points

    p = (0, float('Inf'))
    x = float('Inf')
    for point in points:
        if point[1] < p[1]:
            p = point
        if point[0] < x:
            x = point[0]

    hull = [(x - 100, 0), p]
    hull_computed = False

    while not hull_computed:
        next_point = points[0]
        for point in points[1:]:
            if orient(hull[-2], hull[-1], point) > 0 and orient(hull[-1], next_point, point) <= 0:
                next_point = point
        if next_point == hull[1]:
            hull_computed = True
        else:
            hull.append(next_point)

    return hull[1:]
예제 #2
0
def grahams_scan(points):
    if len(points) <= 3:
        return points

    points.sort(key = psuedo_angle)

    p = (0, float('Inf'))
    for (i, point) in enumerate(points):
        if point[1] < p[1]:
            p = point
            start = i

    stack = []
    n = len(points)
    start -= n

    for i in range(start, start+n):
        while len(stack) > 1 and orient(stack[-2], stack[-1], points[i]) < 0:
            stack.pop()
        stack.append(points[i])

    while len(stack) > 2 and orient(stack[-2], stack[-1], stack[0]) < 0:
        stack.pop()

    return stack
예제 #3
0
def dac_helper(points, bad_dir):
    if len(points) < 3:
        return points

    if len(points) == 3:
        if orient(points[0], points[1], points[2]) == bad_dir:
            return [points[0], points[2]]
        else:
            return points

    m = len(points) // 2
    left = dac_helper(points[:m], bad_dir)
    right = dac_helper(points[m:], bad_dir)

    lp, rp = len(left) - 1, 0
    found_upper_tangent = False
    while not found_upper_tangent:
        if lp > 0 and orient(left[lp - 1], left[lp], right[rp]) == bad_dir:
            lp -= 1
        elif rp < len(right) - 1 and orient(left[lp], right[rp], right[rp + 1]) == bad_dir:
            rp += 1
        else:
            found_upper_tangent = True

    return left[:lp+1] + right[rp:]
예제 #4
0
def qh_helper(points, P, Q):
    if not points:
        return

    max_dist = float('-Inf')
    max_dist_point = None

    for point in points:
        dist = psuedo_distance(P.data, Q.data, point)
        if  dist > max_dist:
            max_dist = dist
            max_dist_point = point

    C = Node(max_dist_point)
    Q.next = C
    C.next = P

    left = []
    right = []

    for point in points:
        if orient(P.data, C.data, point) > 0:
            left.append(point)
        elif orient(C.data, Q.data, point) > 0:
            right.append(point)

    qh_helper(left, P, C)
    qh_helper(right, C, Q)
    return
예제 #5
0
def chans_algorithm_mod(points):
    if len(points) <= 3:
        return points

    p = (0, float('Inf'))
    x = float('Inf')
    for point in points:
        if point[1] < p[1]:
            p = point
        if point[0] < x:
            x = point[0]

    n = len(points)
    m = int(np.ceil(np.log2(np.log2(n))))
    i = 1
    success = False
    for i in range(m):
        h = 2 ** (2 ** (i+1))
        final_hull = [(x - 100, 0), p]

        r = int(np.ceil(n/h))
        mini_hulls = [None] * r
        start = 0
        end = h
        for j in range(r-1):
            mini_hulls[j] = grahams_scan(points[start:end])
            start = end
            end += h
        mini_hulls[-1] = grahams_scan(points[start:])

        for j in range(h):
            next_point = jarvis_binary_search(final_hull[-1], mini_hulls[0])
            for hull in mini_hulls[1:]:
                point = jarvis_binary_search(final_hull[-1], hull)
                if orient(final_hull[-2], final_hull[-1], point) > 0 and orient(final_hull[-1], next_point, point) <= 0:
                    next_point = point
            if next_point == final_hull[1]:
                success = True
                break
            else:
                final_hull.append(next_point)

        if success:
            break

        points = []
        for hull in mini_hulls:
            points += hull
        n = len(points)

    return final_hull[1:]
예제 #6
0
def grahams_scan(points, ak=False):
    """
    Compute the convex hull of points using Graham's Scan.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, _, _, _, _ = akl_toussaint(points)

    # First we determine the lowest point in the pointset
    # Break ties by taking the point with smaller x-coord
    p = (0, float('Inf'))
    for point in points:
        if point[1] < p[1]:
            p = point
        elif point[1] == p[1]:
            if point[0] < p[0]:
                p = point

    # Shoot a ray horizontally to the right from p
    # Sort the points in order of increasing angle from the ray
    points.sort(key=lambda x: pseudo_angle(p, x))

    stack = [points[0], points[1], points[2]]
    for point in points[3:]:
        # Remove points on the current hull until there is a left turn
        # with respect to the last two points on the hull and the current point
        while len(stack) > 1 and orient(stack[-2], stack[-1], point) < 0:
            stack.pop()
        stack.append(point)

    return stack
예제 #7
0
def jarvis_march(points, ak=False):
    """
    Compute the convex hull of points using the Jarvis March algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, _, p, _, x = akl_toussaint(points)
        x = x[0]
    else:
        # Find the lowest point in the pointset.
        p = (0, float('Inf'))
        x = float('Inf')
        for point in points:
            if point[1] < p[1]:
                p = point
            if point[0] < x:
                x = point[0]

    # The hull is initialized with a sentinel point that is effectively very
    # far to the left. The first actual point is the lowest point that was
    # computed above.
    hull = [(x - 100, 0), p]
    hull_computed = False

    # Look for the next point on the hull. Essentially looking for the point
    # that cause the least amount of "turning" when compared to the previous
    # edge. This ensures, all other points will be within the hull. The loop
    # ends when we get back to p.
    while not hull_computed:
        next_point = points[0]
        for point in points[1:]:
            if orient(hull[-2], hull[-1], point) > 0 and orient(
                    hull[-1], next_point, point) <= 0:
                next_point = point
        if next_point == hull[1]:
            hull_computed = True
        else:
            hull.append(next_point)

    return hull[1:]  # Return the hull minus the sentinel point.
예제 #8
0
def andrews_monotone_chain(points):
    if len(points) <= 3:
        return points

    points.sort(key = lambda x : x[0])

    upper = []
    for point in points:
        while len(upper) > 1 and orient(upper[-2], upper[-1], point) > 0:
            upper.pop()
        upper.append(point)

    upper = upper[::-1]

    lower = []
    for point in points:
        while len(lower) > 1 and orient(lower[-2], lower[-1], point) < 0:
            lower.pop()
        lower.append(point)

    return upper[1:] + lower[1:]
예제 #9
0
def qh_helper(points, hull, P, Q):
    """
    Recursive helper method to find next point on the hull.
    P and Q represent an edge on the current hull.
    points is all points in between P and Q with respect to x-coordinate.
    This method updates the convex hull and returns.
    """
    if not points:
        return

    max_dist = float('-Inf')
    max_dist_point = None

    # Find the point that is the farthest distance from the line formed P and Q.
    for point in points:
        dist = pseudo_distance(P.value, Q.value, point)
        if dist > max_dist:
            max_dist = dist
            max_dist_point = point

    # The point that is the farther distance is clearly on the convex hull.
    # We add it between P and Q.
    C = dllistnode(max_dist_point)
    hull.insertnode(C, Q)

    # Separate the points into those between P and C and those between C and Q.
    # Discard all points below either of these edges since they obviously
    # will not be on the hull.
    left = []
    right = []
    for point in points:
        if orient(P.value, C.value, point) > 0:
            left.append(point)
        elif orient(C.value, Q.value, point) > 0:
            right.append(point)

    # Recursively compute the rest of the hull and return.
    qh_helper(left, hull, P, C)
    qh_helper(right, hull, C, Q)
    return
예제 #10
0
def andrews_monotone_chain(points, ak=False):
    """
    Compute the convex hull of points using Andrew's Monotone Chain Algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, _, _, _, _ = akl_toussaint(points)

    # Sort the points based on increasing x coordinate.
    points.sort(key=lambda x: x[0])

    # Compute the upper hull
    upper = []
    for point in points:
        # Remove points on the current hull until there is a right turn
        # with respect to the last two points on the hull and the current point
        while len(upper) > 1 and orient(upper[-2], upper[-1], point) > 0:
            upper.pop()
        upper.append(point)

    upper = upper[::-1]  # Reverse upper hull since we want left turns

    # Computer the lower hull
    lower = []
    for point in points:
        # Remove points on the current hull until there is a left turn
        # with respect to the last two points on the hull and the current point
        while len(lower) > 1 and orient(lower[-2], lower[-1], point) < 0:
            lower.pop()
        lower.append(point)

    # Concatenate the two hulls to form the complete hull
    # Note they will both have the leftmost and rightmost points
    # Thus each of them ignores one of the points.
    return upper[1:] + lower[1:]
예제 #11
0
def dac_helper(points, bad_dir):
    """
    Recursive helper method to compute a partial hull of points
    bad_dir determines which direction we DON'T want when computing the hull
        1 means we don't want left turns
        -1 means we don't want right turns
    Returns a list of points on the partial hull.
    """
    if len(points) < 3:
        return points

    if len(points) == 3:
        # Check to see if the middle point is inside the hull
        if orient(points[0], points[1], points[2]) == bad_dir:
            return [points[0], points[2]]
        else:
            return points

    m = len(points) // 2
    # Recursively compute partial hulls on the left and right half of the points
    left = dac_helper(points[:m], bad_dir)
    right = dac_helper(points[m:], bad_dir)

    lp, rp = len(left) - 1, 0
    found_tangent = False
    # Look for the tangent line that has both partial hulls on the same side.
    # Walk a test line between the two hulls until it is found
    while not found_tangent:
        if lp > 0 and orient(left[lp - 1], left[lp], right[rp]) == bad_dir:
            lp -= 1
        elif rp < len(right) - 1 and orient(left[lp], right[rp],
                                            right[rp + 1]) == bad_dir:
            rp += 1
        else:
            found_tangent = True

    # Remove points below the tangent line, concatenate, and return the hull
    return left[:lp + 1] + right[rp:]
예제 #12
0
def quickhull(points, ak=False):
    """
    Compute the convex hull of points using the Quickhull algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, _, _, rightmost, leftmost = akl_toussaint(points)
    else:
        # Find the leftmost and rightmost points in the set.
        leftmost = (float('Inf'), 0)
        rightmost = (float('-Inf'), 0)
        for point in points:
            if point[0] < leftmost[0]:
                leftmost = point
            if point[0] > rightmost[0]:
                rightmost = point

    # The leftmost and rightmost points are guaranteed to be on the hull.
    # We form a circular linked list that will represent this "initial" hull.
    hull = dllist([leftmost, rightmost])
    L = hull.first
    R = hull.last

    # Separate the points into two groups. Those that fall above the current
    # hull and those that fall below.
    top = []
    bot = []
    for point in points:
        side = orient(L.value, R.value, point)
        if side > 0:
            top.append(point)
        elif side < 0:
            bot.append(point)

    # Recursively compute the upper and lower hulls
    qh_helper(top, hull, L, R)
    qh_helper(bot, hull, R, L)

    return list(hull)
예제 #13
0
def quickhull(points):
    if len(points) <= 3:
        return points

    leftmost = (float('Inf'), 0)
    rightmost = (float('-Inf'), 0)
    for point in points:
        if point[0] < leftmost[0]:
            leftmost = point
        if point[0] > rightmost[0]:
            rightmost = point

    L = Node(leftmost)
    R = Node(rightmost)
    L.next = R
    R.next = L

    top = []
    bot = []

    for point in points:
        side = orient(L.data, R.data, point)
        if side > 0:
            top.append(point)
        elif side < 0:
            bot.append(point)

    qh_helper(top, L, R)
    qh_helper(bot, R, L)

    hull = [L.data]
    start = L
    L = L.next
    while L is not start:
        hull.append(L.data)
        L = L.next

    return hull
예제 #14
0
def jarvis_binary_search(q, hull):
    """
    This is a helper method for Chan's algorithm.
    It utilizes binary search to find a point on the hull such that the support
    line through q and this point will have the points on the hull to the left.
    This point is returned.
    """
    if len(hull) == 1:
        return hull[0]

    n = len(hull)
    i = 0
    k = n - 1
    found_point = False

    # A giant case analysis to find the correct point
    # Will possibly detail how this all works at a later date.
    while not found_point:
        m = (i + k) // 2
        if i == k:
            found_point = True
        elif q == hull[i]:
            i += 1
            found_point = True
        elif q == hull[k]:
            i = (k + 1) % n
            found_point = True
        elif i == m:
            if orient(q, hull[i], hull[i + 1]) < 0:
                i = k
            found_point = True
        elif orient(q, hull[i], hull[i + 1]) >= 0 and orient(
                q, hull[k], hull[(k + 1) % n]) < 0:
            found_point = True
        elif orient(q, hull[i], hull[i + 1]) < 0 and orient(
                q, hull[k], hull[(k + 1) % n]) >= 0:
            if orient(q, hull[m], hull[m + 1]) >= 0:
                k = m
            else:
                i = m
        elif orient(q, hull[i], hull[i + 1]) < 0 and orient(
                q, hull[k], hull[(k + 1) % n]) < 0:
            if orient(q, hull[m], hull[m + 1]) >= 0:
                k = m
            elif orient(q, hull[i], hull[m]) >= 0:
                k = m
            else:
                i = m
        else:
            if orient(q, hull[m], hull[m + 1]) < 0:
                i = m
            elif orient(q, hull[i], hull[m]) < 0:
                k = m
            else:
                i = m

    return hull[i]
예제 #15
0
def chans_algorithm_mod(points, ak=False):
    """
    Compute the convex hull using a modified version of Chan's Algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, _, p, _, x = akl_toussaint(points)
        x = x[0]
    else:
        # Find the lowest point in the pointset.
        p = (0, float('Inf'))
        x = float('Inf')
        for point in points:
            if point[1] < p[1]:
                p = point
            if point[0] < x:
                x = point[0]

    n = len(points)
    # Max number of iterations needed for the algorithm to succeed
    m = int(np.ceil(np.log2(np.log2(n))))
    i = 1
    success = False
    for i in range(m):
        h = 2**(2**(i + 1))  # the "guess" for how many points on the hull.
        final_hull = [(x - 100, 0), p]  # hull has sentinel and p to start

        r = int(np.ceil(n / h))  # number of mini-hulls to compute
        mini_hulls = [None] * r
        start = 0
        end = h
        # Compute all mini hulls using graham's scan
        for j in range(r - 1):
            mini_hulls[j] = grahams_scan(points[start:end])
            start = end
            end += h
        mini_hulls[-1] = grahams_scan(points[start:])

        # For each mini-hull, find the correct representative point such that
        # the line through the last point on the hull and the representative is
        # tangent to the mini hull. This is done through a binary search.
        # Then we perform a jarvis march on all of these possible points to
        # find the correct one.
        for j in range(h):
            next_point = jarvis_binary_search(final_hull[-1], mini_hulls[0])
            for hull in mini_hulls[1:]:
                point = jarvis_binary_search(final_hull[-1], hull)
                if orient(final_hull[-2],
                          final_hull[-1], point) > 0 and orient(
                              final_hull[-1], next_point, point) <= 0:
                    next_point = point
            if next_point == final_hull[1]:
                success = True
                break
            else:
                final_hull.append(next_point)

        if success:
            break

        # This is an optimization. Clearly points iwthin each mini hull will
        # never be on the final hull. Thus for the next iteration, we only
        # consider the points on each mini hull.
        points = []
        for hull in mini_hulls:
            points += hull
        n = len(points)

    return final_hull[1:]  # return hull minus the sentinel
예제 #16
0
def jarvis_binary_search(q, hull):
    if len(hull) == 1:
        return hull[0]

    n = len(hull)
    i = 0
    k = n - 1
    found_point = False

    while not found_point:
        m = (i + k) // 2
        if i == k:
            found_point = True
        elif q == hull[i]:
            i += 1
            found_point = True
        elif q == hull[k]:
            i = (k + 1) % n
            found_point = True
        elif i == m:
            if orient(q, hull[i], hull[i+1]) < 0:
                i = k
            found_point = True
        elif orient(q, hull[i], hull[i+1]) >= 0 and orient(q, hull[k], hull[(k+1)%n]) < 0:
            found_point = True
        elif orient(q, hull[i], hull[i+1]) < 0 and orient(q, hull[k], hull[(k+1)%n]) >= 0:
            if orient(q, hull[m], hull[m+1]) >= 0:
                k = m
            else:
                i = m
        elif orient(q, hull[i], hull[i+1]) < 0 and orient(q, hull[k], hull[(k+1)%n]) < 0:
            if orient(q, hull[m], hull[m+1]) >= 0:
                k = m
            elif orient(q, hull[i], hull[m]) >= 0:
                k = m
            else:
                i = m
        else:
            if orient(q, hull[m], hull[m+1]) < 0:
                i = m
            elif orient(q, hull[i], hull[m]) < 0:
                k = m
            else:
                i = m

    return hull[i]