[postgis-users] Minimum Bounding Circle - was Roeck Test
Bruce Rindahl
rindahl at lrcwe.com
Wed Oct 29 10:42:27 PDT 2008
The following is a procedure for a Minimum Bounding Circle of a
geometry. It works with polygon geometry but it should also work with
others. Anyone who is interested please test and comment. This was
fun :-)
Bruce Rindahl
-- Function: mbc(geometry)
-- DROP FUNCTION mbc(geometry);
CREATE OR REPLACE FUNCTION mbc(geometry)
RETURNS geometry AS
$BODY$
DECLARE
hull GEOMETRY;
ring GEOMETRY;
center GEOMETRY;
radius DOUBLE PRECISION;
dist DOUBLE PRECISION;
d DOUBLE PRECISION;
idx1 integer;
idx2 integer;
l1 GEOMETRY;
l2 GEOMETRY;
p1 GEOMETRY;
p2 GEOMETRY;
a1 DOUBLE PRECISION;
a2 DOUBLE PRECISION;
BEGIN
-- First compute the ConvexHull of the geometry
hull = convexHull($1);
-- convert the hull perimeter to a linestring so we can manipulate
individual points
ring = exteriorRing(hull);
-- Compute the diameter of the the convex hull
-- The method is from Shamos (1978)
-- The idea is to "roll" the polygon along the ground and find the
maximum height of the bounding box
-- Start with the side from the last point to the first point.
p1 = PointN(ring,NumPoints(ring)-1);
-- Set distance check to 0
dist = 0;
FOR i in 1 .. (NumPoints(ring)-1)
LOOP
-- Compute the azimuth of the current side
a1 = azimuth(p1,PointN(ring,i));
-- Rotate the polygon by 90 degrees + the azimuth this makes
the side horizontal
-- and compute the height of the bounding box
d = ymax(box3d(rotate(ring,pi()/2+a1))) -
ymin(box3d(rotate(ring,pi()/2+a1)));
-- Check the distance and update if larger
IF (d > dist) THEN
dist = d;
idx1 = i;
END IF;
-- Drag the current vertex along for the next side
p1 = PointN(ring,i);
END LOOP;
-- The diameter (maximum distance between vertexes)
-- is from either point idx1 or idx1-1. Compute the points at those
indexes
p1 = PointN(ring,idx1);
IF (idx1 = 1) then
p2 = PointN(ring,NumPoints(ring)-1);
ELSE
p2 = PointN(ring,idx1-1);
END IF;
-- Now find the vertex furthest from p1 or p2. The will be the
-- other end of the diameter
dist = 0;
FOR j in 1 .. (NumPoints(ring)-1)
LOOP
IF (distance(PointN(ring,j),p1) > dist) THEN
dist = distance(PointN(ring,j),p1);
idx2 = j;
END IF;
IF (distance(PointN(ring,j),p2) > dist) THEN
dist = distance(PointN(ring,j),p2);
idx2 = j;
END IF;
END LOOP;
-- idx2 now has the point index of the other end of the diameter
-- Compute which is the starting point - p1 or p2
IF (distance(PointN(ring,idx2),p2) > distance(PointN(ring,idx2),p1))
THEN
idx1 = idx1 - 1;
END IF;
IF (idx1 = 0) THEN
idx1 = idx1 + NumPoints(ring) - 1;
END IF;
-- We now have the diameter of the convex hull. The following line
returns it if desired.
--RETURN MakeLine(PointN(ring,idx1),PointN(ring,idx2));
-- Now for the Minimum Bounding Circle. Since we know the two
points furthest from each
-- other, the MBC must go through those two points. Start with those
points as a diameter of a circle.
-- The radius is half the distance between them and the center is
midway between them
radius = distance(PointN(ring,idx1),PointN(ring,idx2)) / 2.0;
center =
line_interpolate_point(MakeLine(PointN(ring,idx1),PointN(ring,idx2)),0.5);
-- Loop through each vertex and check if the distance from the
center to the point
-- is greater than the current radius.
FOR k in 1 .. (NumPoints(ring)-1)
LOOP
IF(k <> idx1 and k <> idx2) THEN
dist = distance(center,PointN(ring,k));
IF (dist > radius) THEN
-- We have to expand the circle. The new circle must
pass through
-- three points - the two original diameters and this point.
-- Draw a line from the first diameter to this point
l1 = makeline(PointN(ring,idx1),PointN(ring,k));
-- Compute the midpoint
p1 = line_interpolate_point(l1,0.5);
-- Rotate the line 90 degrees around the midpoint
(perpendicular bisector)
l1 =
translate(rotate(translate(l1,-X(p1),-Y(p1)),pi()/2),X(p1),Y(p1));
-- Compute the azimuth of the bisector
a1 = azimuth(PointN(l1,1),PointN(l1,2));
-- Extend the line in each direction the new computed
distance to insure they will intersect
l1 =
addPoint(l1,makepoint(X(PointN(l1,2))+sin(a1)*dist,Y(PointN(l1,2))+cos(a1)*dist),-1);
l1 =
addPoint(l1,makepoint(X(PointN(l1,1))-sin(a1)*dist,Y(PointN(l1,1))-cos(a1)*dist),0);
-- Repeat for the line from the point to the other
diameter point
l2 = makeline(PointN(ring,idx2),PointN(ring,k));
p2 = line_interpolate_point(l2,0.5);
l2 =
translate(rotate(translate(l2,-X(p2),-Y(p2)),pi()/2),X(p2),Y(p2));
a2 = azimuth(PointN(l2,1),PointN(l2,2));
l2 =
addPoint(l2,makepoint(X(PointN(l2,2))+sin(a2)*dist,Y(PointN(l2,2))+cos(a2)*dist),-1);
l2 =
addPoint(l2,makepoint(X(PointN(l2,1))-sin(a2)*dist,Y(PointN(l2,1))-cos(a2)*dist),0);
-- The new center is the intersection of the two bisectors
center = intersection(l1,l2);
-- The new radius is the distance to any of the three points
radius = distance(center,PointN(ring,idx1));
END IF;
END IF;
END LOOP;
--DONE!! Return the MBC via the buffer command
RETURN buffer(center,radius,48);
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE
More information about the postgis-users
mailing list