Example #1
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
Example #2
0
def divide_and_conquer(points, ak=False):
    """
    Compute the convex hull of points using the Divide and Conquer algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

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

    # Sort the points by increasing x-coordinate
    points.sort(key=lambda x: x[0])
    upper = dac_helper(points, 1)  # Compute upper hull
    lower = dac_helper(points, -1)  # Compute lower hull

    upper = upper[::-1]  # Reverse to get left turns
    return upper[1:] + lower[1:]  # Concatenate and return complete hull
Example #3
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.
Example #4
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)
Example #5
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:]
Example #6
0
def symmetric_hull(points, ak=False):
    """
    Compute the convex hull of points using the SymmetricHull algorithm.
    Returns a list of points on the hull.
    """
    if len(points) <= 3:
        return points

    if ak:
        points, p_top, p_bot, p_right, p_left = akl_toussaint(points)
    else:
        # Search for endpoints
        p_top = (0, float('-Inf'))
        p_bot = (0, float('Inf'))
        p_right = (float('-Inf'), 0)
        p_left = (float('Inf'), 0)
        for point in points:
            if p_top[1] < point[1]:
                p_top = point
            if p_bot[1] > point[1]:
                p_bot = point
            if p_right[0] < point[0]:
                p_right = point
            if p_left[0] > point[0]:
                p_left = point

    # Lexicographic sort by y-coordinate
    points.sort(key=lambda x: (x[1], x[0]))

    # Intialize stacks
    t1 = [(p_top, float('Inf'))]
    t2 = [(p_top, float('-Inf'))]
    t3 = [(p_bot, float('Inf'))]
    t4 = [(p_bot, float('-Inf'))]

    # Q3 and Q4
    stop_right = False
    stop_left = False
    for point in points:
        if not stop_right or not stop_left:
            # Q4 case
            if point[0] > p_bot[0] and not stop_right:
                t = t4
                x_compare = lambda p, t: True if p[0] > t[-1][0][0] else False
                s_compare = lambda s, t: True if s < t[-1][1] else False
                if point[0] == p_right[0]:
                    stop_right = True

            # Q3 case
            elif point[0] <= p_bot[0] and not stop_left:
                t = t3
                x_compare = lambda p, t: True if p[0] < t[-1][0][0] else False
                s_compare = lambda s, t: True if s > t[-1][1] else False
                if point[0] == p_left[0]:
                    stop_left = True
            else:
                continue

            # Ensure the new point adheres to monotonic slope increase/decrease
            if x_compare(point, t):
                slope = (point[1] - t[-1][0][1]) / (point[0] - t[-1][0][0])
                while s_compare(slope, t):
                    t.pop()
                    slope = (point[1] - t[-1][0][1]) / (point[0] - t[-1][0][0])
                t.append((point, slope))
        else:
            break

    # Q1 and Q2
    stop_right = False
    stop_left = False
    for point in reversed(points):
        if not stop_right or not stop_left:
            # Q1 case
            if point[0] > p_top[0] and not stop_right:
                t = t1
                x_compare = lambda p, t: True if p[0] > t[-1][0][0] else False
                s_compare = lambda s, t: True if s > t[-1][1] else False
                if point[0] == p_right[0]:
                    stop_right = True

            # Q2 case
            elif point[0] <= p_bot[0] and not stop_left:
                t = t2
                x_compare = lambda p, t: True if p[0] < t[-1][0][0] else False
                s_compare = lambda s, t: True if s < t[-1][1] else False
                if point[0] == p_left[0]:
                    stop_left = True
            else:
                continue

            # Ensure the new point adheres to monotonic slope increase/decrease
            if x_compare(point, t):
                slope = (point[1] - t[-1][0][1]) / (point[0] - t[-1][0][0])
                while s_compare(slope, t):
                    t.pop()
                    slope = (point[1] - t[-1][0][1]) / (point[0] - t[-1][0][0])
                t.append((point, slope))
        else:
            break

    # Concatenate hulls, remove slopes, and return
    hull = t1[::-1] + t2[1:-1] + t3[::-1] + t4[1:-1]
    ret = [x[0] for x in hull]
    return ret
Example #7
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