[postgis-users] Minimum Bounding Circle - was Roeck Test

Lee Meilleur lee.meilleur at gis.leg.mn
Wed Oct 29 12:59:44 PDT 2008


Thanks Bruce, it works well.  When I compare it with our commercial software it's within a couple of hundredths of the Roeck test, but not in any predictable fashion.  Some are .02 over the Roeck, some are .01 under,  the majority are right on, but there were several that were  1 to 3 tenths off, fairly significant.  I have no way of knowing how the commercial software computes the Roeck test.  There was one district that created an error, I had to run the query on each of the districts to figure it out.  Here's the message:

Error: getPoint2d_p:  point offset out of range

The district that is making it crash has an unusual shape:

http://www.gis.leg.mn/l2002/pdf/47A.pdf



-----Original Message-----
From: postgis-users-bounces at postgis.refractions.net [mailto:postgis-users-bounces at postgis.refractions.net] On Behalf Of Bruce Rindahl
Sent: Wednesday, October 29, 2008 12:42 PM
To: PostGIS Users Discussion
Subject: [postgis-users] Minimum Bounding Circle - was Roeck Test


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

_______________________________________________
postgis-users mailing list postgis-users at postgis.refractions.net
http://postgis.refractions.net/mailman/listinfo/postgis-users



More information about the postgis-users mailing list