#!/usr/bin/env python
"""convexhull.py
Calculate the convex hull of a set of n 2D-points in O(n log n) time.
Taken from Berg et al., Computational Geometry, Springer-Verlag, 1997.
Prints output as EPS file.
When run from the command line it generates a random set of points
inside a square of given length and finds the convex hull for those,
printing the result as an EPS file.
Usage:
convexhull.py <numPoints> <squareLength> <outFile>
Dinu C. Gherman
"""
import sys, string, random
######################################################################
# Helpers
######################################################################
def _myDet(p, q, r):
"""Calc. determinant of a special matrix with three 2D points.
The sign, "-" or "+", determines the side, right or left,
respectivly, on which the point r lies, when measured against
a directed vector from p to q.
"""
# We use Sarrus' Rule to calculate the determinant.
# (could also use the Numeric package...)
sum1 = q[0]*r[1] + p[0]*q[1] + r[0]*p[1]
sum2 = q[0]*p[1] + r[0]*q[1] + p[0]*r[1]
return sum1 - sum2
def _isRightTurn((p, q, r)):
"Do the vectors pq:qr form a right turn, or not?"
assert p != q and q != r and p != r
if _myDet(p, q, r) < 0:
return 1
else:
return 0
def _isPointInPolygon(r, P):
"Is point r inside a given polygon P?"
# We assume the polygon is a list of points, listed clockwise!
for i in xrange(len(P[:-1])):
p, q = P[i], P[i+1]
if not _isRightTurn((p, q, r)):
return 0 # Out!
return 1 # It's within!
def _makeRandomData(numPoints=10, sqrLength=100, addCornerPoints=0):
"Generate a list of random points within a square."
# Fill a square with random points.
min, max = 0, sqrLength
P = []
for i in xrange(numPoints):
rand = random.randint
x = rand(min+1, max-1)
y = rand(min+1, max-1)
P.append((x, y))
# Add some "outmost" corner points.
if addCornerPoints != 0:
P = P + [(min, min), (max, max), (min, max), (max, min)]
return P
######################################################################
# Output
######################################################################
epsHeader = """%%!PS-Adobe-2.0 EPSF-2.0
%%%%BoundingBox: %d %d %d %d
/r 2 def %% radius
/circle %% circle, x, y, r --> -
{
0 360 arc %% draw circle
} def
/cross %% cross, x, y --> -
{
0 360 arc %% draw cross hair
} def
1 setlinewidth %% thin line
newpath %% open page
0 setgray %% black color
"""
def saveAsEps(P, H, boxSize, path):
"Save some points and their convex hull into an EPS file."
# Save header.
f = open(path, 'w')
f.write(epsHeader % (0, 0, boxSize, boxSize))
format = "%3d %3d"
# Save the convex hull as a connected path.
if H:
f.write("%s moveto\n" % format % H[0])
for p in H:
f.write("%s lineto\n" % format % p)
f.write("%s lineto\n" % format % H[0])
f.write("stroke\n\n")
# Save the whole list of points as individual dots.
for p in P:
f.write("%s r circle\n" % format % p)
f.write("stroke\n")
# Save footer.
f.write("\nshowpage\n")
######################################################################
# Public interface
######################################################################
def convexHull(P):
"Calculate the convex hull of a set of points."
# Get a local list copy of the points and sort them lexically.
points = map(None, P)
points.sort()
# Build upper half of the hull.
upper = [points[0], points[1]]
for p in points[2:]:
upper.append(p)
while len(upper) > 2 and not _isRightTurn(upper[-3:]):
del upper[-2]
# Build lower half of the hull.
points.reverse()
lower = [points[0], points[1]]
for p in points[2:]:
lower.append(p)
while len(lower) > 2 and not _isRightTurn(lower[-3:]):
del lower[-2]
# Remove duplicates.
del lower[0]
del lower[-1]
# Concatenate both halfs and return.
return tuple(upper + lower)
######################################################################
# Test
######################################################################
def test():
a = 200
p = _makeRandomData(30, a, 0)
c = convexHull(p)
saveAsEps(p, c, a, file)
######################################################################
if __name__ == '__main__':
try:
numPoints = string.atoi(sys.argv[1])
squareLength = string.atoi(sys.argv[2])
path = sys.argv[3]
except IndexError:
numPoints = 30
squareLength = 200
path = "sample.eps"
p = _makeRandomData(numPoints, squareLength, addCornerPoints=0)
c = convexHull(p)
saveAsEps(p, c, squareLength, path)