BerGeo/h2/quick_hull.py

107 lines
2.4 KiB
Python
Raw Normal View History

2018-09-20 12:50:16 +00:00
import random
from collections import namedtuple
import matplotlib.pyplot as plt
from enum import Enum, auto
from math import atan2, degrees, tau, pi, acos, sqrt
Point = namedtuple('Point', 'x y')
class Side(Enum):
ON = auto()
ABOVE = auto()
BELOW = auto()
def display(points, hull):
x = [point.x for point in points]
y = [point.y for point in points]
h_x = [point.x for point in hull]
h_y = [point.y for point in hull]
plt.plot(h_x, h_y, 'ro-')
plt.scatter(x, y)
plt.show()
def gen_point():
a = random.uniform(1, 5)
b = random.uniform(1, 5)
x_i = random.uniform(1, 5)
p_i = Point(x_i, a * x_i + b)
return p_i
def sidedness(p1, p2, p3, eps=0.0000001):
y = p1.y * (p3.x - p2.x)
x = p1.x
a = (p3.y - p2.y)
b = p3.y * (p3.x - p2.x) - a * p3.x
line = a*x+b
if y - eps < line < y + eps:
return [Side.ON, y - line]
elif y > line:
return [Side.ABOVE, y - line]
return [Side.BELOW, y - line]
def prune_below_find_highest(points: set, line_segment, side = Side.BELOW, lel=max):
min_pt, max_pt = line_segment
above_pts = []
for point in points:
sidedness_res = sidedness(min_pt, max_pt, point)
if sidedness_res[0] != side:
above_pts.append(point)
highest_above = lel(above_pts, key=lambda pt: pt[1])
return above_pts, highest_above
def find_line_segment(points):
min_pt = min(points, key=lambda pt: pt.x)
max_pt = max(points, key=lambda pt: pt.x)
return min_pt, max_pt
def quick_hull(points, side=Side.BELOW, lel=max, hull=None):
if hull is None:
hull = set()
print(len(points))
if len(points) <= 2:
return hull
min_pt, max_pt = find_line_segment(points)
above_points, max_dist_pt = prune_below_find_highest(points, (min_pt, max_pt), side, lel)
left_side = prune_below_find_highest(points, (min_pt, max_dist_pt), side, lel)[0]
right_side = prune_below_find_highest(points, (max_dist_pt, max_pt), side, lel)[0]
hull.add(max_dist_pt)
quick_hull(left_side, side, lel, hull=hull).union(quick_hull(right_side, side, lel, hull=hull))
return hull
points = {Point(2, 5), Point(3, 1), Point(3, 4), Point(9, 4), Point(1, 5), Point(5, 7)}
#points = {gen_point() for _ in range(10)}
line_segment = find_line_segment(points)
uh_hull = quick_hull(points)
lh_hull = quick_hull(points, Side.ABOVE, min)
hull = uh_hull.union(lh_hull)
display(points, hull)