edu.umd.cfar.lamp.viper.geometry.ConvexPolygon.java Source code

Java tutorial

Introduction

Here is the source code for edu.umd.cfar.lamp.viper.geometry.ConvexPolygon.java

Source

/***************************************
 *            ViPER                    *
 *  The Video Processing               *
 *         Evaluation Resource         *
 *                                     *
 *  Distributed under the GPL license  *
 *        Terms available at gnu.org.  *
 *                                     *
 *  Copyright University of Maryland,  *
 *                      College Park.  *
 ***************************************/

package edu.umd.cfar.lamp.viper.geometry;

import java.util.*;

import org.apache.commons.lang.builder.*;

import edu.umd.cfar.lamp.viper.util.*;
import edu.umd.cfar.lamp.viper.util.reader.*;

/**
 * A class representing a Convex Polygon. Polygon with vertexes listed in
 * counterclockwise order such that all interior angles are less than 180
 * degrees. Unlike polylist or BoundingBox, this is limited to containing a
 * single polygon.
 * 
 * @see "Computational Geometry in C"
 */
public class ConvexPolygon extends PolyList implements Cloneable, HasCentroid {
    /** Polygon "P" is inside. Used for the intersection algorithm. */
    static final private int P_IN = -1;
    /** The first intersection hasn't been found. */
    static final private int UNKNOWN = 0;
    /** Polygon "Q" is inside. Used for the intersection algorithm. */
    static final private int Q_IN = 1;

    private List edgeList = null;

    /**
     * Creates an empty convex polygon, whatever that means.
     */
    public ConvexPolygon() {
        composed = false;
    }

    /**
     * Creates a new convex polygon from the given set of tokens. Useful for
     * parsing, as it will throw exceptions with the appropriate character
     * offset information.
     * 
     * @param input
     *            the tokenized string to parse
     * @throws BadDataException
     *             if the string isn't formatted properly
     */
    public ConvexPolygon(CountingStringTokenizer input) throws BadDataException {
        composed = false;
        try {
            while (input.hasMoreTokens()) {
                int x = Integer.parseInt(input.nextToken());
                int y = Integer.parseInt(input.nextToken());
                if (!addVertex(new Pnt(x, y))) {
                    //err.printWarning point is collinear
                }
            }
        } catch (NumberFormatException nfx) {
            throw (new BadDataException("Bad Polygon - " + input));
        }
    }

    /**
     * Copy constructor.
     * 
     * @param old
     *            the convex polygon to duplicate.
     */
    public ConvexPolygon(ConvexPolygon old) {
        composed = false;
        if (old.edgeList != null) {
            edgeList = new ArrayList(old.edgeList.size());
            Iterator iter = old.getVerteces();
            while (iter.hasNext()) {
                try {
                    addVertex((Pnt) iter.next());
                } catch (BadDataException bdx) {
                    throw new ArithmeticException(bdx.getMessage());
                }
            }
        }
    }

    /**
     * Initialize a convex polygon from a sequence of points.
     * 
     * @param points
     *            a list of points as (x, y) pairs, such that point k's
     *            cooridinates are (points[k/2], points[k/2 + 1]).
     */
    public ConvexPolygon(int[] points) {
        int minX, minY, maxX, maxY;
        try {
            composed = false;
            int i = 2;
            minX = maxX = points[0];
            minY = maxY = points[1];
            addVertex(new Pnt(points[0], points[1]));
            while (i < points.length) {
                addVertex(new Pnt(points[i], points[i + 1]));
                minX = Math.min(minX, points[i]);
                maxX = Math.max(maxX, points[i]);
                i++;

                minY = Math.min(minY, points[i]);
                maxY = Math.max(maxY, points[i]);
                i++;
            }
        } catch (BadDataException bdx) {
            throw new IllegalStateException("Error while making polygon: " + bdx.getMessage());
        }
    }

    /**
     * Initialize a convex polygon from a sequence of points.
     * 
     * @param points
     *            a list of viper.geometry.Pnt objects
     */
    public ConvexPolygon(List points) {
        try {
            composed = false;
            for (Iterator iter = points.iterator(); iter.hasNext();) {
                Pnt curr = (Pnt) iter.next();
                addVertex(curr);
            }
        } catch (BadDataException bdx) {
            throw new IllegalStateException("Error while making polygon: " + bdx.getMessage());
        }
    }

    protected void initBbox() {
        if (bbox != null) {
            return;
        }
        super.clearPolyList();
        super.addPoly(this);
        Iterator corners = getVerteces();
        if (corners.hasNext()) {
            Pnt leastPoint = (Pnt) corners.next();
            Pnt greatestPoint = new Pnt(leastPoint);
            Pnt currentPoint;

            while (corners.hasNext()) {
                currentPoint = (Pnt) corners.next();
                if (currentPoint.x.lessThan(leastPoint.x))
                    leastPoint.x.setTo(currentPoint.x);
                else if (greatestPoint.x.lessThan(currentPoint.x))
                    greatestPoint.x.setTo(currentPoint.x);

                if (currentPoint.y.lessThan(leastPoint.y))
                    leastPoint.y.setTo(currentPoint.y);
                else if (greatestPoint.y.lessThan(currentPoint.y))
                    greatestPoint.y.setTo(currentPoint.y);
            }
            Rational width = new Rational();
            Rational height = new Rational();
            Rational.minus(greatestPoint.x, leastPoint.x, width);
            Rational.minus(greatestPoint.y, leastPoint.y, height);
            bbox = new BoundingBox(leastPoint.x.doubleValue(), leastPoint.y.doubleValue(), width.doubleValue(),
                    height.doubleValue());
        } else {
            bbox = new BoundingBox();
        }
    }

    /**
     * Gets a copy of this polygon.
     * 
     * @return a new ConvexPolygon that equals <code>this</code>
     */
    public Object clone() {
        return new ConvexPolygon(this);
    }

    /**
     * Gets the region shared between this polygon and all those in the
     * specified PolyList.
     * 
     * @param other
     *            the region to intersect with
     * @return a set of polygons that together covers only and all of the
     *         region shared by this polygon and the specified region.
     */
    public PolyList getIntersection(PolyList other) {
        if (other.composed || !(other instanceof ConvexPolygon)) {
            return PolyList.intersection(this, other);
        } else if (this.composed) {
            return PolyList.intersection(other, this);
        } else if (getBoundingBox().intersects(other.getBoundingBox())) {
            return ConvexPolygon.intersection(this, (ConvexPolygon) other);
        } else {
            return new ConvexPolygon();
        }
    }

    /**
     * Get the point of intersection between the ray from the centroid of this
     * box through q1 that is closest to q1.
     * 
     * @param q1
     *            A point that is not the centroid.
     * @return A point on the perimeter of the box on the ray from the centroid
     *         through q1.
     */
    public Pnt getNearIntersection(Pnt q1) {
        Pnt p1 = this.getCentroid();
        Pnt r1 = new Pnt();
        Pnt prev = getVertex(getNumberOfVerteces());
        boolean isNegative = false;
        Pnt curr = null;
        for (Iterator iter = getVerteces(); iter.hasNext(); prev = curr) {
            curr = (Pnt) iter.next();

            // Since points are CCW, the line we want is the first positivie
            // value.
            // They cannot all be negative, although one or two may be zero.
            Rational signArea = Util.areaSign(curr, p1, q1);
            if (isNegative && signArea.isPositive()) {
                Util.lineIntersection(p1, q1, prev, curr, r1);
                return r1;
            } else if (signArea.isNegative()) {
                isNegative = true;
            } else if (isNegative && signArea.isZero()) {
                isNegative = false;
                return new Pnt(curr);
            }
        }
        if (!isNegative) {
            throw new ArithmeticException("No near intersection with " + q1);
        } else {
            curr = getVertex(0);
            Util.lineIntersection(p1, q1, prev, curr, r1);
            return r1;
        }
    }

    /**
     * Gets the center of the bounding box of the polygon, for now.
     * 
     * @return supposed to be the center of the polygon
     */
    public Pnt getCentroid() {
        if (this instanceof BoxInformation) {
            return ((BoxInformation) this).getCentroid();
        } else {
            return this.getBoundingBox().getCentroid();
        }
    }

    /**
     * Cuts the polygon into two slices. If it cuts it, returns a two-element
     * array, with the first being the area to the left of the segment, and the
     * second with the region to the right. If it does not segment the polygon,
     * it returns a one element array containing a copy of the original
     * polygon.
     * 
     * @param P
     *            the polygon to slice
     * @param a
     *            a point along the slicing line
     * @param b
     *            another point, != to a, along the slicing line
     * @return one or two polygons such that the returned polygons union to the
     *         specified polygon but contain no edges that cross the slicing
     *         line
     */
    static public ConvexPolygon[] clip(ConvexPolygon P, Pnt a, Pnt b) {
        // Collect points in two halves - those to the left and right.
        // the ones at the end mark the line segments that are cut.
        // As a point of reference, corners on the line are marked as
        // on both. Then generate the new corners and make the polygons
        List left = new LinkedList();
        List right = new LinkedList();
        List left2 = new LinkedList();
        List right2 = new LinkedList();
        Pnt collinear1 = null;
        Pnt collinear2 = null;
        boolean alreadyright = false;
        boolean alreadyleft = false;
        boolean alreadycollinear = false;
        Rational ZERO = new Rational(0);
        for (Iterator corners = P.getVerteces(); corners.hasNext();) {
            Pnt curr = (Pnt) corners.next();
            Rational side = Util.areaSign(curr, a, b);

            if (side.lessThan(ZERO)) {
                if (alreadyright) {
                    right.add(curr);
                } else {
                    right2.add(curr);
                }
            } else if (right2.size() > 0) {
                alreadyright = true;
            }

            if (side.greaterThan(ZERO)) {
                if (alreadyleft) {
                    left.add(curr);
                } else {
                    left2.add(curr);
                }
            } else if (left2.size() > 0) {
                alreadyleft = true;
            }

            if (side.equals(ZERO)) {
                if (alreadycollinear) {
                    collinear2 = curr;
                } else {
                    collinear1 = curr;
                }
            } else if (collinear1 != null) {
                alreadycollinear = true;
            }
        }

        left.addAll(left2);
        right.addAll(right2);

        if (left.size() == 0 || right.size() == 0) {
            return new ConvexPolygon[] { new ConvexPolygon(P) };
        } else {
            // so the collinear points must be on the polygon
            // between the left and right segments
            // To find out which one goes where, place them where
            // they are convex
            if (collinear1 != null) {
                if (Util.areaSign(collinear1, (Pnt) left.get(0), (Pnt) right.get(right.size() - 1))
                        .lessThan(ZERO)) {
                    left.add(collinear1);
                    right.add(0, collinear1);
                    if (collinear2 != null) {
                        left.add(0, collinear2);
                        right.add(collinear2);
                    }
                } else {
                    left.add(0, collinear1);
                    right.add(collinear1);
                    if (collinear2 != null) {
                        left.add(collinear2);
                        right.add(0, collinear2);
                    }
                }
            }

            Pnt leftStart = (Pnt) left.get(0);
            Pnt leftEnd = (Pnt) left.get(left.size() - 1);
            Pnt rightStart = (Pnt) right.get(0);
            Pnt rightEnd = (Pnt) right.get(right.size() - 1);

            if (!leftStart.equals(rightEnd)) {
                // The corner does not lie on the line, so segment the line
                // and add the point to both sides
                Pnt neo = new Pnt();
                Util.lineIntersection(a, b, leftStart, rightEnd, neo);
                left.add(0, neo);
                right.add(neo);
            }
            if (!rightStart.equals(leftEnd)) {
                Pnt neo = new Pnt();
                Util.lineIntersection(a, b, rightStart, leftEnd, neo);
                left.add(neo);
                right.add(0, neo);
            }
        }
        return new ConvexPolygon[] { new ConvexPolygon(left), new ConvexPolygon(right) };
    }

    /**
     * Subtracts the second shape from the first.
     * 
     * @param P
     *            the shape to subtract from
     * @param Q
     *            the shape to subtract
     * @return P - Q, or P ^ ~Q
     */
    static public PolyList subtract(ConvexPolygon P, ConvexPolygon Q) {
        ConvexPolygon I = ConvexPolygon.intersection(P, Q);
        if (I.area().greaterThan(0)) {
            ConvexPolygon copy = new ConvexPolygon(P);
            PolyList diff = new PolyList();
            // Will work like this: try to get one convex polygon
            // for each edge of the intersection polygon.
            // Uses the "crop" function, that crops the polygon into
            // two segments on either side of a line. I know this is
            // not efficient, but I am just looking for something
            // that works.
            Iterator corners = I.getVerteces();
            Pnt curr = I.getVertex(I.getNumberOfVerteces() - 1);
            Pnt prev;

            while (corners.hasNext()) {
                prev = curr;
                curr = (Pnt) corners.next();
                // The edgelist goes counterclockwise. As such,
                // the region to the left of the line connecting
                // prev->curr contains the hole, and to the right stays.
                ConvexPolygon[] segs = ConvexPolygon.clip(copy, prev, curr);
                if (segs.length == 1) {
                    // this case is uninteresting
                } else {
                    copy = segs[0];
                    diff.addPoly(segs[1]);
                }
            }
            return diff;
        } else {
            return new ConvexPolygon(P);
        }
    }

    /**
     * Returns a set of polygons that, together, cover all and only the region
     * covered by the specified polygons.
     * 
     * @param P
     *            a convex polygon to union
     * @param Q
     *            a convex polygon to union
     * @return a set of non-overlapping convex polygons that represents the
     *         union of P and Q
     */
    static public ConvexPolygon[] add(ConvexPolygon P, ConvexPolygon Q) {
        ConvexPolygon[] r = null;
        ConvexPolygon split = intersection(P, Q);
        if (split.area().equals(0)) {
            // Two distinct polygons
            r = new ConvexPolygon[] { P, Q };
        } else if (split.area() == P.area() || split.area() == Q.area()) {
            r = new ConvexPolygon[] { (P.area().lessThan(Q.area)) ? Q : P };
        } else {
            r = PolyList.union(P, Q).getConvexPolygonArray();
        }
        return r;
    }

    /**
     * Tests to see if the specified polygon is completely contained within
     * this polygon.
     * 
     * @param P
     *            the polygon to check for
     * @return <code>true</code> iff all of P is within this polygon
     */
    public boolean isInside(ConvexPolygon P) {
        boolean hasPoints = false;
        for (Iterator iter = getVerteces(); iter.hasNext();) {
            hasPoints = true;
            Pnt curr = (Pnt) iter.next();
            if (!P.contains(curr)) {
                return false;
            }
        }
        return hasPoints;
    }

    /**
     * This creates a new area that is the intersection of both.
     * 
     * @param P a polygon to intersect
     * @param Q a polygon to intersect
     * @return the polygon representing the region in both polygons
     */
    static public ConvexPolygon intersection(ConvexPolygon P, ConvexPolygon Q) {
        if (!P.getBoundingBox().intersects(Q.getBoundingBox())) {
            return new ConvexPolygon();
        }
        ConvexPolygon solutionPoly = new ConvexPolygon();
        int n = P.getNumberOfVerteces();
        int m = Q.getNumberOfVerteces();
        int a = 0;
        int b = 0;
        int a1, b1;
        Component A, B;
        Rational crossProduct;
        Rational bHa, aHb;
        Pnt Origin = new Pnt(0, 0);
        Pnt p = new Pnt();
        Pnt q = new Pnt();
        int inflag = UNKNOWN; // -1 = outside, 0 = UNKNOWN, +1 - inside
        int aa = 0;
        int ba = 0;
        boolean firstPoint = true;
        char code;

        // Advance around the edge of both polygons, so that the
        // they chase each other. Save the relevant verteces to the edge
        // list.
        do {
            /* Calculate key variables */
            a1 = a - 1;
            b1 = b - 1;
            A = P.getVertex(a1).minus(P.getVertex(a));
            B = Q.getVertex(b1).minus(Q.getVertex(b));
            crossProduct = Util.areaSign(Origin, A, B);
            aHb = Util.areaSign(Q.getVertex(b1), Q.getVertex(b), P.getVertex(a));
            bHa = Util.areaSign(P.getVertex(a1), P.getVertex(a), Q.getVertex(b));

            /* If A & B intersect, update inflag. */
            code = Util.lineIntersection(P.getVertex(a1), P.getVertex(a), Q.getVertex(b1), Q.getVertex(b), p);

            if (code == '1' || code == 'v') {
                if (inflag == UNKNOWN && firstPoint) {
                    aa = ba = 0;
                    firstPoint = false;
                }
                try {
                    solutionPoly.addVertex(p);
                } catch (BadDataException bdx) {
                    StringBuffer errMsg = new StringBuffer();
                    errMsg.append("While intersecting ").append(P.toString()).append(" with ").append(Q.toString())
                            .append(" adding ").append(P.toString());
                    errMsg.append("\n\t").append(bdx.getMessage());
                    errMsg.append("\n\t").append(" to get " + solutionPoly);
                    throw new ArithmeticException(errMsg.toString());
                }
                inflag = inOut(inflag, aHb, bHa);
            }

            /* Advance! */
            /* A & B colinear in opposite directions */
            if ((code == 'e') && (Component.dot(A, B).lessThan(0))) {
                try {
                    solutionPoly.clearPolygon();
                    solutionPoly.addVertex(p);
                    solutionPoly.addVertex(q);
                } catch (BadDataException bdx) {
                    String s = "\nWhile intersecting " + P + " with " + Q + " to get " + solutionPoly;
                    throw new ArithmeticException(bdx.getMessage() + s);
                }
                solutionPoly.initBbox();
                return solutionPoly;
            }

            /* Special case: A & B parallel and separated. */
            else if (crossProduct.equals(0) && aHb.lessThan(0) && bHa.lessThan(0)) {
                return solutionPoly;
            }

            /* Special case: A & B collinear. */
            else if (crossProduct.equals(0) && aHb.equals(0) && bHa.equals(0)) {
                /* Advance but do not output point. */
                if (inflag == P_IN) {
                    ba++;
                    b++;
                } else {
                    aa++;
                    a++;
                }
            }

            /* Generic cases. */
            else if ((!crossProduct.lessThan(0) && bHa.greaterThan(0))
                    || (crossProduct.lessThan(0) && !aHb.greaterThan(0))) {
                if (inflag == P_IN) {
                    try {
                        solutionPoly.addVertex(P.getVertex(a));
                    } catch (BadDataException bdx) {
                        String s = "\nWhile intersecting " + P + " with " + Q + " to get " + solutionPoly;
                        throw new ArithmeticException(bdx.getMessage() + s);
                    }
                }
                aa++;
                a++;
            } else if ((!crossProduct.lessThan(0) && !bHa.greaterThan(0))
                    || (crossProduct.lessThan(0) && aHb.greaterThan(0))) {
                if (inflag == Q_IN) {
                    try {
                        solutionPoly.addVertex(Q.getVertex(b));
                    } catch (BadDataException bdx) {
                        String s = "\nWhile intersecting " + P + " with " + Q + " to get " + solutionPoly;
                        throw new ArithmeticException(bdx.getMessage() + s);
                    }
                }
                ba++;
                b++;
            }

            /*
             * Quit when both adv. indices have cycled, or one has cycled
             * twice.
             */
        } while (((aa < n) || (ba < m)) && (aa < 2 * n) && (ba < 2 * m));

        // If the boundaries don't cross, see if one is inside the other
        if (inflag == 0) {
            if (P.contains(Q.getVertex(0))) {
                return new ConvexPolygon(Q);
            } else if (Q.contains(P.getVertex(0))) {
                return new ConvexPolygon(P);
            } else { // Boundaries don't cross, one is not inside the other
                return new ConvexPolygon();
            }
        }
        solutionPoly.initBbox();
        return solutionPoly;
    }

    /**
     * Adds a single point to the polygon. Note that this
     * must maintain the convexity property.
     * 
     * Before calling this, allocate edge. After calling this, call initBbox.
     * 
     * @param point the point to add to the polygon
     * @return true if added, false if collinear
     * @throws BadDataException
     *             if attempting to add reflex vertex
     */
    public boolean addVertex(Pnt point) throws BadDataException {
        if (point == null) {
            throw new NullPointerException("Trying to add a null point");
        }
        if (edgeList == null) {
            edgeList = new LinkedList();
        } else {
            int length = edgeList.size();
            if (length > 2) {
                Pnt a = getVertex(getNumberOfVerteces() - 1);
                Pnt b = getVertex(getNumberOfVerteces() - 2);
                Rational isOnEdge = Util.areaSign(point, getVertex(0), a);
                if (isOnEdge.equals(0)) {
                    // three cases - either it is between the points and
                    // redundant,
                    // or it is to one side or the other and bad.
                    if (Util.areaSign(point, getVertex(0), getVertex(1)).lessThan(0)
                            || Util.areaSign(point, b, a).lessThan(0)) {
                        throw new BadDataException("Trying to add a non-convex point " + point
                                + " to a convex polygon " + toStringListOfPoints() + "\n isOnEdge = " + isOnEdge);
                    } else {
                        return false;
                    }
                } else if (isOnEdge.lessThan(0)) {
                    throw new BadDataException("Trying to add a non-convex point " + point + " to a convex polygon "
                            + toStringListOfPoints() + "\n isOnEdge = " + isOnEdge);
                }
                isOnEdge = Util.areaSign(point, b, a);
                if (isOnEdge.lessThan(0)) {
                    throw new BadDataException("Trying to add a reflex point " + point + " to a convex polygon "
                            + toStringListOfPoints() + "\n isOnEdge = " + isOnEdge);
                } else if (isOnEdge.equals(0)) {
                    // This means that the most recent segment is being
                    // extended
                    edgeList.remove(length - 1);
                } // else it is in the right region and can simply be added
            } else if ((length == 1) && (point.equals(getVertex(0)))) {
                return false;
            } else if (length == 2) {
                Rational isOnEdge = Util.areaSign(point, getVertex(0), getVertex(1));
                if (!isOnEdge.greaterThan(0)) {
                    if (isOnEdge.equals(0)) {
                        return false;
                    } else {
                        throw new BadDataException("Trying to add a non-convex point " + point
                                + " to a convex polygon " + toStringListOfPoints() + "\n isOnEdge = " + isOnEdge);
                    }
                }
            }
        }
        edgeList.add(new Pnt(point));
        bbox = null;
        return true;
    }

    private Pnt getVertex(int index) {
        int length = getNumberOfVerteces();
        if (length == 0) {
            return null;
        }
        if (index < 0) {
            index += length;
        }
        return new Pnt((Pnt) edgeList.get(index % length));
    }

    int getNumberOfVerteces() {
        return (edgeList == null) ? 0 : edgeList.size();
    }

    public Iterator getVerteces() {
        if (edgeList == null) {
            return new Iterator() {
                public boolean hasNext() {
                    return false;
                }

                public Object next() {
                    throw new NoSuchElementException();
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        } else {
            return new Iterator() {
                private Iterator iter = edgeList.iterator();

                public boolean hasNext() {
                    return iter.hasNext();
                }

                public Object next() {
                    return new Pnt((Pnt) iter.next());
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }

    protected void clearPolygon() {
        edgeList = null;
    }

    /**
     * Toggles in/out flag. See Computational Geometry in C
     * 
     * @param inflag The previous value for the inflag.
     * @param aHb 
     * @param bHa 
     * @return The new value for the inflag.
     */
    static private int inOut(int inflag, Rational aHb, Rational bHa) {
        /* Update inflag. */
        if (aHb.greaterThan(0)) {
            return P_IN;
        } else if (bHa.greaterThan(0)) {
            return Q_IN;
        } else /* Keep status quo. */
            return inflag;
    }

    /**
     * Gets a String representation of the polygon as a
     * list of points, where each point is given as (x,y).
     * @return the points around the edge of the polygon
     */
    public final String toStringListOfPoints() {
        if (composed) {
            return super.toStringListOfPoints();
        }
        StringBuffer buf = new StringBuffer();
        buf.append('(');
        Iterator pointIterator = getVerteces();
        while (pointIterator.hasNext()) {
            buf.append(pointIterator.next().toString());
        }
        buf.append(')');
        return buf.toString();
    }

    /**
     * Gets a string representation of the polygon.
     * If it is composed, uses the PolyList version.
     * @return a String representation of the object
     */
    public String toString() {
        if (composed) {
            return super.toString();
        }
        return toStringListOfPoints();
    }

    /**
     * Finds the area inside the polygon.
     * @return an approximation of the polygon's size
     */
    public Rational area() {
        if (composed) {
            return super.area();
        }
        if (getNumberOfVerteces() < 3) {
            return new Rational(0);
        }

        // This works by finding the area of the trapezoid between
        // the x axis and each line segment (not parallel to y).
        // If the line points from left to right, it must be on
        // the bottom of the trapezoid.
        Rational total = new Rational(0);
        Iterator pointIterator = getVerteces();
        Pnt currPoint = (Pnt) pointIterator.next();
        Rational nextArea = new Rational(0);
        Rational width = new Rational(0);
        Rational height = new Rational(0);
        Pnt nextPoint;
        while (pointIterator.hasNext()) {
            nextPoint = (Pnt) pointIterator.next();
            // If it is a bottom edge, subtract the area beneath it.
            // Thankfully, if it is a bottom edge, curr.x - next.x
            // returns the negation of the length in the x direction!
            Rational.minus(currPoint.x, nextPoint.x, width);
            Rational.plus(nextPoint.y, currPoint.y, height);
            Rational.multiply(width, height, nextArea);
            Rational.plus(total, nextArea, total);
            currPoint = nextPoint;
        }
        nextPoint = getVertex(0);
        Rational.minus(currPoint.x, nextPoint.x, width);
        Rational.plus(nextPoint.y, currPoint.y, height);
        Rational.multiply(width, height, nextArea);
        Rational.plus(total, nextArea, total);

        nextArea.setTo(1, 2);
        Rational.multiply(total, nextArea, total);
        return total;
    }

    /**
     * Tests to see if the point is within this region.
     * @param X the x-coordinate of the point to check
     * @param Y the y-coordinate of the point to check
     * @return <code>true</code> iff the point is within the polygon
     */
    public boolean contains(int X, int Y) {
        if (bbox.contains(X, Y)) {
            return contains(new Pnt(X, Y));
        } else {
            return false;
        }
    }

    /**
     * Tests to see if the point is within this region.
     * @param point the point to look for within the polygon
     * @return <code>true</code> iff the point is within the polygon
     */
    public boolean contains(Pnt point) {
        int length = getNumberOfVerteces();
        if (length == 1) {
            return getVertex(0).equals(point);
        }
        for (int i = 0; i < length; i++) {
            Pnt a = getVertex(i - 1);
            Pnt b = getVertex(i);
            if (Util.areaSign(a, b, point).lessThan(0))
                return false;
        }
        return true;
    }

    /**
     * Tests to see if this region is the same as 
     * that covered by the specified shape.
     * @param o the shape to check against. Works for most children of PolyList
     * @return <code>false</code> if the two regions are unequal
     */
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o instanceof ConvexPolygon) {
            if (composed || ((ConvexPolygon) o).composed) {
                return super.equals(o);
            }

            ConvexPolygon other = (ConvexPolygon) o;
            if (!this.getBoundingBox().equals(other.getBoundingBox())) {
                return false; // The bounding boxes are of different size
            }

            int length = getNumberOfVerteces();
            if (length != other.getNumberOfVerteces())
                return false; // They have a different number of edges

            int offset = 0;
            Iterator iterThis = this.getVerteces();
            Iterator iterOther = other.getVerteces();

            // Find a common point, and compare. All the points should
            // be the same.
            Pnt curr = (Pnt) iterThis.next();
            Pnt opposite = (Pnt) iterOther.next();
            while (iterThis.hasNext() && !curr.equals(opposite)) {
                curr = (Pnt) iterThis.next();
                offset++;
            }
            if (offset == length)
                return false; // There is no common point

            offset = 1;
            while (offset++ < length) {
                if (!iterThis.hasNext())
                    iterThis = this.getVerteces();
                curr = (Pnt) iterThis.next();

                opposite = (Pnt) iterOther.next();
                if (!opposite.equals(curr))
                    return false; //These points should line up
            }
            return true; // All of the points line up
        } else {
            return false;
        }
    }

    public int hashCode() {
        return new HashCodeBuilder().append(area()).append(getBoundingBox()).toHashCode();
    }

    /**
     * Gets all the component polygons of this set.
     * 
     * @return an iterator that just returns one element, <code>this</code>
     */
    public Iterator getPolys() {
        ArrayList temp = new ArrayList(1);
        temp.add(this);
        return temp.iterator();
    }
}