71 lines
1.9 KiB
Python
71 lines
1.9 KiB
Python
from collections import namedtuple
|
|
from itertools import chain
|
|
from operator import itemgetter
|
|
|
|
|
|
__all__ = ['bounding_box', 'convex_hull', 'Point', 'Rect']
|
|
|
|
|
|
Point = namedtuple('Point', ['x', 'y'])
|
|
Rect = namedtuple('Rect', ['left', 'top', 'width', 'height'])
|
|
|
|
|
|
def bounding_box(locations):
|
|
"""Computes the bounding box of an iterable of (x, y) coordinates.
|
|
|
|
Args:
|
|
locations: iterable of (x, y) tuples.
|
|
|
|
Returns:
|
|
`Rect`: Coordinates of the bounding box.
|
|
"""
|
|
x_values = list(map(itemgetter(0), locations))
|
|
x_min, x_max = min(x_values), max(x_values)
|
|
y_values = list(map(itemgetter(1), locations))
|
|
y_min, y_max = min(y_values), max(y_values)
|
|
return Rect(x_min, y_min, x_max - x_min, y_max - y_min)
|
|
|
|
|
|
def convex_hull(points):
|
|
"""Computes the convex hull of an iterable of (x, y) coordinates.
|
|
|
|
Args:
|
|
points: iterable of (x, y) tuples.
|
|
|
|
Returns:
|
|
`list`: instances of `Point` - vertices of the convex hull in
|
|
counter-clockwise order, starting from the vertex with the
|
|
lexicographically smallest coordinates.
|
|
|
|
Andrew's monotone chain algorithm. O(n log n) complexity.
|
|
https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
|
|
"""
|
|
|
|
def is_not_clockwise(p0, p1, p2):
|
|
return 0 <= (
|
|
(p1[0] - p0[0]) * (p2[1] - p0[1]) -
|
|
(p1[1] - p0[1]) * (p2[0] - p0[0])
|
|
)
|
|
|
|
def go(points_):
|
|
res = []
|
|
for p in points_:
|
|
while 1 < len(res) and is_not_clockwise(res[-2], res[-1], p):
|
|
res.pop()
|
|
res.append(p)
|
|
|
|
# The last point in each list is the first point in the other list
|
|
res.pop()
|
|
|
|
return res
|
|
|
|
# Discard duplicates and sort by x then y
|
|
points = sorted(set(points))
|
|
|
|
# Algorithm needs at least two points
|
|
hull = (
|
|
points if len(points) < 2 else chain(go(points), go(reversed(points)))
|
|
)
|
|
|
|
return list(map(Point._make, hull))
|