001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.math.geometry.shape;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Iterator;
036import java.util.List;
037
038import org.openimaj.citation.annotation.Reference;
039import org.openimaj.citation.annotation.ReferenceType;
040import org.openimaj.math.geometry.line.Line2d;
041import org.openimaj.math.geometry.point.Point2d;
042import org.openimaj.math.geometry.point.Point2dImpl;
043import org.openimaj.math.geometry.point.PointList;
044import org.openimaj.math.geometry.shape.util.PolygonUtils;
045import org.openimaj.math.geometry.shape.util.RotatingCalipers;
046
047import Jama.Matrix;
048
049/**
050 * A polygon, modelled as a list of vertices. Polygon extends {@link PointList},
051 * so the vertices are the underlying {@link PointList#points}, and they are
052 * considered to be joined in order.
053 *
054 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
055 */
056public class Polygon extends PointList implements Shape
057{
058        /**
059         * Polygons can contain other polygons. If the polygon is representing a
060         * shape, then the inner polygons can represent holes in the polygon or
061         * other polygons within the polygon.
062         */
063        private List<Polygon> innerPolygons = new ArrayList<Polygon>();
064
065        /** If this polygon is a hole within another polygon, this is set to true */
066        private boolean isHole = false;
067
068        /**
069         * Constructs an empty polygon to which vertices may be added.
070         */
071        public Polygon()
072        {
073                this(false);
074        }
075
076        /**
077         * Constructs an empty polygon to which vertices may be added. The boolean
078         * parameter determines whether this polygon will represent a hole (rather
079         * than a solid).
080         *
081         * @param representsHole
082         *            Whether the polygon will represent a hole.
083         */
084        public Polygon(boolean representsHole)
085        {
086                this.isHole = representsHole;
087        }
088
089        /**
090         * Construct a Polygon from vertices
091         *
092         * @param vertices
093         *            the vertices
094         */
095        public Polygon(Point2d... vertices) {
096                super(vertices);
097        }
098
099        /**
100         * Construct a Polygon from vertices
101         *
102         * @param vertices
103         *            the vertices
104         */
105        public Polygon(Collection<? extends Point2d> vertices) {
106                this(vertices, false);
107        }
108
109        /**
110         * Construct a Polygon from the vertices, possibly copying the vertices
111         * first
112         *
113         * @param vertices
114         *            the vertices
115         * @param copy
116         *            should the vertices be copied
117         */
118        public Polygon(Collection<? extends Point2d> vertices, boolean copy) {
119                super(vertices, copy);
120        }
121
122        /**
123         * Get the vertices of the polygon
124         *
125         * @return the vertices
126         */
127        public List<Point2d> getVertices() {
128                return points;
129        }
130
131        /**
132         * Get the number of vertices
133         *
134         * @return the number of vertices
135         */
136        public int nVertices() {
137                if (isClosed())
138                        return points.size() - 1;
139                return points.size();
140        }
141
142        /**
143         * Is the polygon closed (i.e. is the last vertex equal to the first)?
144         *
145         * @return true if closed; false if open
146         */
147        public boolean isClosed() {
148                if (points.size() > 0 && points.get(0).getX() == points.get(points.size() - 1).getX()
149                                && points.get(0).getY() == points.get(points.size() - 1).getY())
150                        return true;
151                return false;
152        }
153
154        /**
155         * Close the polygon if it's not already closed
156         */
157        public void close() {
158                if (!isClosed() && points.size() > 0)
159                        points.add(points.get(0));
160        }
161
162        /**
163         * Open the polygon if it's closed
164         */
165        public void open() {
166                if (isClosed() && points.size() > 1)
167                        points.remove(points.size() - 1);
168        }
169
170        /**
171         * Test whether the point p is inside the polygon using the winding rule
172         * algorithm. Also tests whether the point is in any of the inner polygons.
173         * If the inner polygon represents a hole and the point is within that
174         * polygon then the point is not within this polygon.
175         *
176         * @param point
177         *            the point to test
178         * @return true if the point is inside; false otherwise
179         */
180        @Override
181        public boolean isInside(Point2d point) {
182                final boolean isClosed = isClosed();
183                if (!isClosed)
184                        close();
185
186                boolean isOdd = false;
187
188                for (int pp = 0; pp < getNumInnerPoly(); pp++)
189                {
190                        final List<Point2d> v = getInnerPoly(pp).getVertices();
191                        int j = v.size() - 1;
192                        for (int i = 0; i < v.size(); i++) {
193                                if (v.get(i).getY() < point.getY() && v.get(j).getY() >= point.getY() ||
194                                                v.get(j).getY() < point.getY() && v.get(i).getY() >= point.getY())
195                                {
196                                        if (v.get(i).getX() + (point.getY() - v.get(i).getY()) /
197                                                        (v.get(j).getY() - v.get(i).getY()) * (v.get(j).getX() - v.get(i).getX()) < point.getX())
198                                        {
199                                                isOdd = !isOdd;
200                                        }
201                                }
202                                j = i;
203                        }
204                }
205
206                if (!isClosed)
207                        open();
208                return isOdd;
209        }
210
211        /**
212         * {@inheritDoc}
213         */
214        @Override
215        public Polygon clone() {
216                final Polygon clone = new Polygon();
217                clone.setIsHole(isHole);
218
219                for (final Point2d p : points)
220                        clone.points.add(p.copy());
221
222                for (final Polygon innerPoly : innerPolygons)
223                        clone.addInnerPolygon(innerPoly.clone());
224
225                return clone;
226        }
227
228        /**
229         * Calculates the difference between two polygons and returns a new polygon.
230         * It assumes that the given polygon and this polygon have the same number
231         * of vertices.
232         *
233         * @param p
234         *            the polygon to subtract.
235         * @return the difference polygon
236         */
237        public Polygon difference(Polygon p)
238        {
239                final List<Point2d> v = new ArrayList<Point2d>();
240
241                for (int i = 0; i < points.size(); i++)
242                        v.add(new Point2dImpl(
243                                        points.get(i).getX() - p.getVertices().get(i).getX(),
244                                        points.get(i).getY() - p.getVertices().get(i).getY()));
245
246                final Polygon p2 = new Polygon(v);
247                for (int i = 0; i < innerPolygons.size(); i++)
248                        p2.addInnerPolygon(innerPolygons.get(i).difference(
249                                        p2.getInnerPoly(i + 1)));
250
251                return p2;
252        }
253
254        @Override
255        public double calculateArea() {
256                return Math.abs(calculateSignedArea());
257        }
258
259        /**
260         * Calculate the area of the polygon. This does not take into account holes
261         * in the polygon.
262         *
263         * @return the area of the polygon
264         */
265        public double calculateSignedArea() {
266                final boolean closed = isClosed();
267                double area = 0;
268
269                if (!closed)
270                        close();
271
272                // TODO: This does not take into account the winding
273                // rule and therefore holes
274                for (int k = 0; k < points.size() - 1; k++) {
275                        final float ik = points.get(k).getX();
276                        final float jk = points.get(k).getY();
277                        final float ik1 = points.get(k + 1).getX();
278                        final float jk1 = points.get(k + 1).getY();
279
280                        area += ik * jk1 - ik1 * jk;
281                }
282
283                if (!closed)
284                        open();
285
286                return 0.5 * area;
287        }
288
289        /**
290         * Calls {@link Polygon#intersectionArea(Shape, int)} with 1 step per pixel
291         * dimension. Subsequently this function returns the shared whole pixels of
292         * this polygon and that.
293         *
294         * @param that
295         * @return intersection area
296         */
297        @Override
298        public double intersectionArea(Shape that) {
299                return this.intersectionArea(that, 1);
300        }
301
302        /**
303         * Return an estimate for the area of the intersection of this polygon and
304         * another polygon. For each pixel step 1 is added if the point is inside
305         * both polygons. For each pixel, perPixelPerDimension steps are taken.
306         * Subsequently the intersection is:
307         *
308         * sumIntersections / (perPixelPerDimension * perPixelPerDimension)
309         *
310         * @param that
311         * @return normalised intersection area
312         */
313        @Override
314        public double intersectionArea(Shape that, int nStepsPerDimension) {
315                final Rectangle overlapping = this.calculateRegularBoundingBox().overlapping(that.calculateRegularBoundingBox());
316                if (overlapping == null)
317                        return 0;
318                double intersection = 0;
319                final double step = Math.max(overlapping.width, overlapping.height) / (double) nStepsPerDimension;
320                double nReads = 0;
321                for (float x = overlapping.x; x < overlapping.x + overlapping.width; x += step) {
322                        for (float y = overlapping.y; y < overlapping.y + overlapping.height; y += step) {
323                                final boolean insideThis = this.isInside(new Point2dImpl(x, y));
324                                final boolean insideThat = that.isInside(new Point2dImpl(x, y));
325                                nReads++;
326                                if (insideThis && insideThat) {
327                                        intersection++;
328                                }
329                        }
330                }
331
332                return (intersection / nReads) * (overlapping.width * overlapping.height);
333        }
334
335        /**
336         * {@inheritDoc}
337         *
338         * @see org.openimaj.math.geometry.shape.Shape#asPolygon()
339         */
340        @Override
341        public Polygon asPolygon() {
342                return this;
343        }
344
345        /**
346         * Add a vertex to the polygon
347         *
348         * @param x
349         *            x-coordinate of the vertex
350         * @param y
351         *            y-coordinate of the vertex
352         */
353        public void addVertex(float x, float y) {
354                points.add(new Point2dImpl(x, y));
355        }
356
357        /**
358         * Add a vertex to the polygon
359         *
360         * @param pt
361         *            coordinate of the vertex
362         */
363        public void addVertex(Point2d pt) {
364                points.add(pt);
365        }
366
367        /**
368         * Iterates through the vertices and rounds all vertices to round integers.
369         * Side-affects this polygon.
370         *
371         * @return this polygon
372         */
373        public Polygon roundVertices()
374        {
375                final Iterator<Point2d> i = this.iterator();
376                while (i.hasNext())
377                {
378                        final Point2d p = i.next();
379                        final Point2dImpl p2 = new Point2dImpl((int) p.getX(), (int) p.getY());
380
381                        int xx = -1;
382                        if ((xx = this.points.indexOf(p2)) != -1 &&
383                                        this.points.get(xx) != p)
384                                i.remove();
385                        else
386                        {
387                                p.setX(p2.x);
388                                p.setY(p2.y);
389                        }
390                }
391
392                for (final Polygon pp : innerPolygons)
393                        pp.roundVertices();
394
395                return this;
396        }
397
398        /**
399         * Return whether this polygon has no vertices or not.
400         *
401         * @return TRUE if this polygon has no vertices
402         */
403        public boolean isEmpty()
404        {
405                return this.points.isEmpty() && innerPolygons.isEmpty();
406        }
407
408        /**
409         * Returns the number of inner polygons in this polygon including this
410         * polygon.
411         *
412         * @return the number of inner polygons in this polygon.
413         */
414        public int getNumInnerPoly()
415        {
416                return innerPolygons.size() + 1;
417        }
418
419        /**
420         * Get the inner polygon at the given index. Note that index 0 will return
421         * this polygon, while index i+1 will return the inner polygon i.
422         *
423         * @param index
424         *            the index of the polygon to get
425         * @return The inner polygon at the given index.
426         */
427        public Polygon getInnerPoly(int index)
428        {
429                if (index == 0)
430                        return this;
431                return innerPolygons.get(index - 1);
432        }
433
434        /**
435         * Add an inner polygon to this polygon. If there is no main polygon defined
436         * (the number of vertices is zero) then the given inner polygon will become
437         * the main polygon if the <code>inferOuter</code> boolean is true. If this
438         * variable is false, the inner polygon will be added to the list of inner
439         * polygons for this polygon regardless of whether a main polygon exists.
440         * When the main polygon is inferred from the given polygon, the vertices
441         * are copied into this polygon's vertex list.
442         *
443         * @param p
444         *            The inner polygon to add
445         * @param inferOuter
446         *            Whether to infer the outer polygon from this inner one
447         */
448        public void addInnerPolygon(Polygon p, boolean inferOuter)
449        {
450                if (!inferOuter)
451                {
452                        this.innerPolygons.add(p);
453                }
454                else
455                {
456                        if (this.points.size() == 0)
457                        {
458                                this.points.addAll(p.points);
459                                this.isHole = p.isHole;
460                        }
461                        else
462                        {
463                                this.addInnerPolygon(p, false);
464                        }
465                }
466        }
467
468        /**
469         * Add an inner polygon to this polygon. If there is no main polygon defined
470         * (the number of vertices is zero) then the given inner polygon will become
471         * the main polygon.
472         *
473         * @param p
474         *            The inner polygon to add
475         */
476        public void addInnerPolygon(Polygon p)
477        {
478                this.addInnerPolygon(p, true);
479        }
480
481        /**
482         * Returns the list of inner polygons.
483         *
484         * @return the list of inner polygons
485         */
486        public List<Polygon> getInnerPolys()
487        {
488                return this.innerPolygons;
489        }
490
491        /**
492         * Set whether this polygon represents a hole in another polygon.
493         *
494         * @param isHole
495         *            Whether this polygon is a whole.
496         */
497        public void setIsHole(boolean isHole)
498        {
499                this.isHole = isHole;
500        }
501
502        /**
503         * Returns whether this polygon is representing a hole in another polygon.
504         *
505         * @return Whether this polygon is representing a hole in another polygon.
506         */
507        public boolean isHole()
508        {
509                return this.isHole;
510        }
511
512        /**
513         * {@inheritDoc}
514         *
515         * @see java.lang.Object#equals(java.lang.Object)
516         */
517        @Override
518        public boolean equals(Object obj)
519        {
520                return (obj instanceof Polygon) &&
521                                this.equals((Polygon) obj);
522        }
523
524        /**
525         * Specific equals method for polygons where the polgyons are tested for the
526         * vertices being in the same order. If the vertices are not in the vertex
527         * list in the same manner but are in the same order (when wrapped around)
528         * the method will return true. So the triangles below will return true:
529         *
530         * {[1,1],[2,2],[1,2]} and {[1,2],[1,1],[2,2]}
531         *
532         * @param p
533         *            The polygon to test against
534         * @return TRUE if the polygons are the same.
535         */
536        public boolean equals(Polygon p)
537        {
538                if (isHole() != p.isHole())
539                        return false;
540                if (this.points.size() != p.points.size())
541                        return false;
542                if (this.isEmpty() && p.isEmpty())
543                        return true;
544
545                final int i = this.points.indexOf(p.points.get(0));
546                if (i == -1)
547                        return false;
548
549                final int s = this.points.size();
550                for (int n = 0; n < s; n++)
551                {
552                        if (!p.points.get(n).equals(this.points.get((n + i) % s)))
553                                return false;
554                }
555
556                return true;
557        }
558
559        /**
560         * {@inheritDoc}
561         *
562         * @see java.lang.Object#hashCode()
563         */
564        @Override
565        public int hashCode()
566        {
567                return points.hashCode() * (isHole() ? -1 : 1);
568        }
569
570        /**
571         * Displays the complete list of vertices unless the number of vertices is
572         * greater than 10 - then a sublist is shown of 5 from the start and 5 from
573         * the end separated by ellipses.
574         *
575         * {@inheritDoc}
576         *
577         * @see java.lang.Object#toString()
578         */
579        @Override
580        public String toString()
581        {
582                final StringBuffer sb = new StringBuffer();
583                if (isHole())
584                        sb.append("H");
585                final int len = 10;
586                if (points.size() < len)
587                        sb.append(points.toString());
588                else
589                        sb.append(points.subList(0, len / 2).toString() + "..." +
590                                        points.subList(points.size() - len / 2, points.size())
591                                        .toString());
592
593                if (innerPolygons.size() > 0)
594                {
595                        sb.append("\n    - " + innerPolygons.size() + " inner polygons:");
596                        for (final Polygon ip : innerPolygons)
597                                sb.append("\n       + " + ip.toString());
598                }
599
600                return sb.toString();
601        }
602
603        /**
604         * Returns the intersection of this polygon and the given polygon.
605         *
606         * @param p2
607         *            The polygon to intersect with.
608         * @return The resulting polygon intersection
609         */
610        public Polygon intersect(Polygon p2)
611        {
612                // FIXME: PolygonUtils should handle inner polys itself, but it seems
613                // there are bugs... This is a hack to work around those problems
614                // without having to understand the ins and outs of how the GPC works,
615                // but it should really be fixed properly in the future!
616                final Polygon clone = new PolygonUtils().intersection(new Polygon(this.points), p2);
617                clone.setIsHole(isHole);
618
619                for (final Polygon innerPoly : innerPolygons)
620                        clone.addInnerPolygon(innerPoly.intersect(p2));
621
622                return clone;
623        }
624
625        /**
626         * Returns the union of this polygon and the given polygon.
627         *
628         * @param p2
629         *            The polygon to union with.
630         * @return The resulting polygon union
631         */
632        public Polygon union(Polygon p2)
633        {
634                return new PolygonUtils().union(this, p2);
635        }
636
637        /**
638         * Returns the XOR of this polygon and the given polygon.
639         *
640         * @param p2
641         *            The polygon to XOR with.
642         * @return The resulting polygon
643         */
644        public Polygon xor(Polygon p2)
645        {
646                return new PolygonUtils().xor(this, p2);
647        }
648
649        /**
650         * Reduce the number of vertices in a polygon. This implementation is based
651         * on a modified Ramer-Douglas–Peucker algorithm that is designed to work
652         * with polygons rather than polylines. The implementation searches for two
653         * initialisation points that are approximatatly maximally distant, then
654         * performs the polyline algorithm on the two segments, and finally ensures
655         * that the joins in the segments are consistent with the given epsilon
656         * value.
657         *
658         * @param eps
659         *            distance value below which a vertex can be ignored
660         * @return new polygon that approximates this polygon, but with fewer
661         *         vertices
662         */
663        public Polygon reduceVertices(double eps)
664        {
665                if (eps == 0 || nVertices() <= 3)
666                        return this.clone();
667
668                int prevStartIndex = 0;
669                int startIndex = 0;
670                for (int init = 0; init < 3; init++) {
671                        double dmax = 0;
672                        prevStartIndex = startIndex;
673
674                        final Point2d first = points.get(startIndex);
675                        for (int i = 0; i < points.size(); i++) {
676                                final double d;
677                                d = Line2d.distance(first, points.get(i));
678
679                                if (d > dmax) {
680                                        startIndex = i;
681                                        dmax = d;
682                                }
683                        }
684                }
685
686                if (prevStartIndex > startIndex) {
687                        final int tmp = prevStartIndex;
688                        prevStartIndex = startIndex;
689                        startIndex = tmp;
690                }
691
692                final List<Point2d> l1 = new ArrayList<Point2d>();
693                l1.addAll(points.subList(prevStartIndex, startIndex + 1));
694
695                final List<Point2d> l2 = new ArrayList<Point2d>();
696                l2.addAll(points.subList(startIndex, points.size()));
697                l2.addAll(points.subList(0, prevStartIndex + 1));
698
699                final Polygon newPoly = new Polygon();
700                final List<Point2d> line1 = ramerDouglasPeucker(l1, eps);
701                final List<Point2d> line2 = ramerDouglasPeucker(l2, eps);
702                newPoly.points.addAll(line1.subList(0, line1.size() - 1));
703                newPoly.points.addAll(line2.subList(0, line2.size() - 1));
704
705                // deal with the joins
706                // if (newPoly.nVertices() > 3) {
707                // Point2d r1 = null, r2 = null;
708                // Point2d p0 = newPoly.points.get(newPoly.points.size() - 1);
709                // Point2d p1 = newPoly.points.get(0);
710                // Point2d p2 = newPoly.points.get(1);
711                //
712                // Line2d l = new Line2d(p0, p2);
713                // Line2d norm = l.getNormal(p1);
714                // Point2d intersect = l.getIntersection(norm).intersectionPoint;
715                // if (intersect != null && Line2d.distance(p1, intersect) <= eps) {
716                // // remove p1
717                // r1 = p1;
718                // }
719                //
720                // p0 = newPoly.points.get(line1.size() - 1);
721                // p1 = newPoly.points.get(line1.size());
722                // p2 = newPoly.points.get((line1.size() + 1) >= newPoly.size() ? 0 :
723                // (line1.size() + 1));
724                //
725                // l = new Line2d(p0, p2);
726                // norm = l.getNormal(p1);
727                // intersect = l.getIntersection(norm).intersectionPoint;
728                // if (intersect != null && Line2d.distance(p1, intersect) <= eps) {
729                // // remove p2
730                // r2 = p2;
731                // }
732                //
733                // if (r1 != null) {
734                // newPoly.points.remove(r1);
735                // }
736                // if (newPoly.nVertices() > 3 && r2 != null) {
737                // newPoly.points.remove(r2);
738                // }
739                // }
740
741                if (!newPoly.isClockwise()) {
742                        Collections.reverse(newPoly.points);
743                }
744
745                for (final Polygon ppp : innerPolygons)
746                        newPoly.addInnerPolygon(ppp.reduceVertices(eps));
747
748                return newPoly;
749        }
750
751        /**
752         * Ramer Douglas Peucker polyline algorithm
753         *
754         * @param p
755         *            the polyline to simplify
756         * @param eps
757         *            distance value below which a vertex can be ignored
758         * @return the simplified polyline
759         */
760        private static List<Point2d> ramerDouglasPeucker(List<Point2d> p, double eps) {
761                // Find the point with the maximum distance
762                double dmax = 0;
763                int index = 0;
764                final int end = p.size() - 1;
765                final Line2d l = new Line2d(p.get(0), p.get(end));
766                for (int i = 1; i < end - 1; i++) {
767                        final double d;
768
769                        final Point2d p1 = p.get(i);
770                        final Line2d norm = l.getNormal(p1);
771                        final Point2d p2 = l.getIntersection(norm).intersectionPoint;
772                        if (p2 == null)
773                                continue;
774                        d = Line2d.distance(p1, p2);
775
776                        if (d > dmax) {
777                                index = i;
778                                dmax = d;
779                        }
780                }
781
782                final List<Point2d> newPoly = new ArrayList<Point2d>();
783
784                // If max distance is greater than epsilon, recursively simplify
785                if (dmax > eps) {
786                        // Recursively call RDP
787                        final List<Point2d> line1 = ramerDouglasPeucker(p.subList(0, index + 1), eps);
788                        final List<Point2d> line2 = ramerDouglasPeucker(p.subList(index, end + 1), eps);
789                        newPoly.addAll(line1.subList(0, line1.size() - 1));
790                        newPoly.addAll(line2);
791                } else {
792                        newPoly.add(p.get(0));
793                        newPoly.add(p.get(end));
794                }
795
796                // Return the result
797                return newPoly;
798        }
799
800        /**
801         * Apply a 3x3 transform matrix to a copy of the polygon and return it
802         *
803         * @param transform
804         *            3x3 transform matrix
805         * @return the transformed polygon
806         */
807        @Override
808        public Polygon transform(Matrix transform) {
809                final List<Point2d> newVertices = new ArrayList<Point2d>();
810
811                for (final Point2d p : points) {
812                        final Matrix p1 = new Matrix(3, 1);
813                        p1.set(0, 0, p.getX());
814                        p1.set(1, 0, p.getY());
815                        p1.set(2, 0, 1);
816
817                        final Matrix p2_est = transform.times(p1);
818
819                        final Point2d out = new Point2dImpl((float) (p2_est.get(0, 0) / p2_est.get(2, 0)),
820                                        (float) (p2_est.get(1, 0) / p2_est.get(2, 0)));
821
822                        newVertices.add(out);
823                }
824
825                final Polygon p = new Polygon(newVertices);
826                for (final Polygon pp : innerPolygons)
827                        p.addInnerPolygon(pp.transform(transform));
828                return p;
829        }
830
831        /**
832         * Compute the regular (oriented to the axes) bounding box of the polygon.
833         *
834         * @return the regular bounding box as [x,y,width,height]
835         */
836        @Override
837        public Rectangle calculateRegularBoundingBox() {
838                float xmin = Float.MAX_VALUE, xmax = -Float.MAX_VALUE, ymin = Float.MAX_VALUE, ymax = -Float.MAX_VALUE;
839
840                for (int pp = 0; pp < getNumInnerPoly(); pp++)
841                {
842                        final Polygon ppp = getInnerPoly(pp);
843                        for (final Point2d p : ppp.getVertices()) {
844                                final float px = p.getX();
845                                final float py = p.getY();
846                                if (px < xmin)
847                                        xmin = px;
848                                if (px > xmax)
849                                        xmax = px;
850                                if (py < ymin)
851                                        ymin = py;
852                                if (py > ymax)
853                                        ymax = py;
854
855                        }
856                }
857
858                return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
859        }
860
861        /**
862         * Translate the polygons position
863         *
864         * @param x
865         *            x-translation
866         * @param y
867         *            y-translation
868         */
869        @Override
870        public void translate(float x, float y) {
871                for (int pp = 0; pp < getNumInnerPoly(); pp++)
872                {
873                        final Polygon ppp = getInnerPoly(pp);
874                        for (final Point2d p : ppp.getVertices()) {
875                                p.setX(p.getX() + x);
876                                p.setY(p.getY() + y);
877                        }
878                }
879        }
880
881        /**
882         * Scale the polygon by the given amount about (0,0). Scalefactors between 0
883         * and 1 shrink the polygon.
884         *
885         * @param sc
886         *            the scale factor.
887         */
888        @Override
889        public void scale(float sc) {
890                for (int pp = 0; pp < getNumInnerPoly(); pp++)
891                {
892                        final Polygon ppp = getInnerPoly(pp);
893                        for (final Point2d p : ppp.getVertices()) {
894                                p.setX(p.getX() * sc);
895                                p.setY(p.getY() * sc);
896                        }
897                }
898        }
899
900        /**
901         * Scale the polygon only in the x-direction by the given amount about
902         * (0,0). Scale factors between 0 and 1 will shrink the polygon
903         *
904         * @param sc
905         *            The scale factor
906         * @return this polygon
907         */
908        @Override
909        public Polygon scaleX(float sc)
910        {
911                for (int pp = 0; pp < getNumInnerPoly(); pp++)
912                {
913                        final Polygon ppp = getInnerPoly(pp);
914                        for (final Point2d p : ppp.getVertices()) {
915                                p.setX(p.getX() * sc);
916                        }
917                }
918                return this;
919        }
920
921        /**
922         * Scale the polygon only in the y-direction by the given amount about
923         * (0,0). Scale factors between 0 and 1 will shrink the polygon
924         *
925         * @param sc
926         *            The scale factor
927         * @return this polygon
928         */
929        @Override
930        public Polygon scaleY(float sc)
931        {
932                for (int pp = 0; pp < getNumInnerPoly(); pp++)
933                {
934                        final Polygon ppp = getInnerPoly(pp);
935                        for (final Point2d p : ppp.getVertices()) {
936                                p.setY(p.getY() * sc);
937                        }
938                }
939                return this;
940        }
941
942        /**
943         * Scale the polygon by the given amount about (0,0). Scale factors between
944         * 0 and 1 shrink the polygon.
945         *
946         * @param scx
947         *            the scale factor in the x direction
948         * @param scy
949         *            the scale factor in the y direction.
950         * @return this polygon
951         */
952        @Override
953        public Polygon scaleXY(float scx, float scy)
954        {
955                for (int pp = 0; pp < getNumInnerPoly(); pp++)
956                {
957                        final Polygon ppp = getInnerPoly(pp);
958                        for (final Point2d p : ppp.getVertices()) {
959                                p.setX(p.getX() * scx);
960                                p.setY(p.getY() * scy);
961                        }
962                }
963                return this;
964        }
965
966        /**
967         * Scale the polygon by the given amount about the given point. Scalefactors
968         * between 0 and 1 shrink the polygon.
969         *
970         * @param centre
971         *            the centre of the scaling operation
972         * @param sc
973         *            the scale factor
974         */
975        @Override
976        public void scale(Point2d centre, float sc) {
977                this.translate(-centre.getX(), -centre.getY());
978                for (int pp = 0; pp < getNumInnerPoly(); pp++)
979                {
980                        final Polygon ppp = getInnerPoly(pp);
981                        for (final Point2d p : ppp.getVertices()) {
982                                p.setX(p.getX() * sc);
983                                p.setY(p.getY() * sc);
984                        }
985                }
986                this.translate(centre.getX(), centre.getY());
987        }
988
989        /**
990         * Calculate the centroid of the polygon.
991         *
992         * @return calls {@link #calculateFirstMoment()};
993         */
994        @Override
995        public Point2d calculateCentroid() {
996                final double[] pt = calculateFirstMoment();
997                return new Point2dImpl((float) pt[0], (float) pt[1]);
998        }
999
1000        /**
1001         * Treating the polygon as a continuous piecewise function, calculate
1002         * exactly the first moment. This follows working presented by Carsten
1003         * Steger in "On the Calculation of Moments of Polygons" ,
1004         *
1005         * @return the first moment
1006         */
1007        @Reference(
1008                        author = { "Carsten Steger" },
1009                        title = "On the Calculation of Moments of Polygons",
1010                        type = ReferenceType.Techreport,
1011                        month = "August",
1012                        year = "1996",
1013                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf"
1014                        )
1015        public double[] calculateFirstMoment() {
1016                final boolean closed = isClosed();
1017
1018                if (!closed)
1019                        close();
1020
1021                double area = 0;
1022                double ax = 0;
1023                double ay = 0;
1024                // TODO: This does not take into account the winding
1025                // rule and therefore holes
1026                for (int k = 0; k < points.size() - 1; k++) {
1027                        final float xk = points.get(k).getX();
1028                        final float yk = points.get(k).getY();
1029                        final float xk1 = points.get(k + 1).getX();
1030                        final float yk1 = points.get(k + 1).getY();
1031
1032                        final float shared = xk * yk1 - xk1 * yk;
1033                        area += shared;
1034                        ax += (xk + xk1) * shared;
1035                        ay += (yk + yk1) * shared;
1036                }
1037
1038                if (!closed)
1039                        open();
1040
1041                area *= 0.5;
1042
1043                return new double[] { ax / (6 * area), ay / (6 * area) };
1044        }
1045
1046        /**
1047         * Treating the polygon as a continuous piecewise function, calculate
1048         * exactly the second moment. This follows working presented by Carsten
1049         * Steger in "On the Calculation of Moments of Polygons" ,
1050         *
1051         * @return the second moment as an array with values: (axx,axy,ayy)
1052         */
1053        @Reference(
1054                        author = { "Carsten Steger" },
1055                        title = "On the Calculation of Moments of Polygons",
1056                        type = ReferenceType.Techreport,
1057                        month = "August",
1058                        year = "1996",
1059                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf"
1060                        )
1061        public double[] calculateSecondMoment() {
1062                final boolean closed = isClosed();
1063                final double area = calculateSignedArea();
1064
1065                if (!closed)
1066                        close();
1067
1068                double axx = 0;
1069                double ayy = 0;
1070                double axy = 0;
1071                // TODO: This does not take into account the winding
1072                // rule and therefore holes
1073                for (int k = 0; k < points.size() - 1; k++) {
1074                        final float xk = points.get(k).getX();
1075                        final float yk = points.get(k).getY();
1076                        final float xk1 = points.get(k + 1).getX();
1077                        final float yk1 = points.get(k + 1).getY();
1078
1079                        final float shared = xk * yk1 - xk1 * yk;
1080                        axx += (xk * xk + xk * xk1 + xk1 * xk1) * shared;
1081                        ayy += (yk * yk + yk * yk1 + yk1 * yk1) * shared;
1082                        axy += (2 * xk * yk + xk * yk1 + xk1 * yk + 2 * xk1 * yk1) * shared;
1083                }
1084
1085                if (!closed)
1086                        open();
1087
1088                return new double[] {
1089                                axx / (12 * area),
1090                                axy / (24 * area),
1091                                ayy / (12 * area)
1092                };
1093        }
1094
1095        /**
1096         * Treating the polygon as a continuous piecewise function, calculate
1097         * exactly the centralised second moment. This follows working presented by
1098         * Carsten Steger in "On the Calculation of Moments of Polygons" ,
1099         *
1100         * @return the second moment as an array with values: (axx,axy,ayy)
1101         */
1102        @Reference(
1103                        author = { "Carsten Steger" },
1104                        title = "On the Calculation of Moments of Polygons",
1105                        type = ReferenceType.Techreport,
1106                        month = "August",
1107                        year = "1996",
1108                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf"
1109                        )
1110        public double[] calculateSecondMomentCentralised() {
1111                final double[] firstMoment = this.calculateFirstMoment();
1112                final double[] secondMoment = this.calculateSecondMoment();
1113
1114                return new double[] {
1115                                secondMoment[0] - firstMoment[0] * firstMoment[0],
1116                                secondMoment[1] - firstMoment[0] * firstMoment[1],
1117                                secondMoment[2] - firstMoment[1] * firstMoment[1],
1118                };
1119
1120        }
1121
1122        /**
1123         * Calculates the principle direction of the connected component. This is
1124         * given by
1125         * <code>0.5 * atan( (M<sub>20</sub>-M<sub>02</sub>) / 2 * M<sub>11</sub> )</code>
1126         * so results in an angle between -PI and +PI.
1127         *
1128         * @return The principle direction (-PI/2 to +PI/2 radians) of the connected
1129         *         component.
1130         */
1131        public double calculateDirection() {
1132                final double[] secondMoment = calculateSecondMomentCentralised();
1133                final double u20 = secondMoment[0];
1134                final double u02 = secondMoment[1];
1135                final double u11 = secondMoment[2];
1136                final double theta = 0.5 * Math.atan2((2 * u11), (u20 - u02));
1137
1138                return theta;
1139        }
1140
1141        /**
1142         * Using
1143         * {@link EllipseUtilities#ellipseFromCovariance(float, float, Matrix, float)}
1144         * and the {@link #calculateSecondMomentCentralised()} return the Ellipse
1145         * best fitting the shape of this polygon.
1146         *
1147         * @return {@link Ellipse} defining the shape of the polygon
1148         */
1149        public Ellipse toEllipse() {
1150                final double[] secondMoment = calculateSecondMomentCentralised();
1151                final double u20 = secondMoment[0];
1152                final double u11 = secondMoment[1];
1153                final double u02 = secondMoment[2];
1154                final Point2d center = calculateCentroid();
1155                final Matrix sm = new Matrix(new double[][] {
1156                                new double[] { u20, u11 },
1157                                new double[] { u11, u02 },
1158                });
1159                // Used the sqrt(3) as the scale, not sure why. This is not correct.
1160                // Find the correct value!
1161                return EllipseUtilities.ellipseFromCovariance(
1162                                center.getX(),
1163                                center.getY(),
1164                                sm,
1165                                (float) Math.sqrt(3)
1166                                );
1167        }
1168
1169        /**
1170         * Test if the outer polygon is convex.
1171         *
1172         * @return true if the outer polygon is convex; false if non-convex or less
1173         *         than two vertices
1174         */
1175        @Override
1176        public boolean isConvex() {
1177                final boolean isOriginallyClosed = this.isClosed();
1178                if (isOriginallyClosed)
1179                        this.open();
1180
1181                final int size = size();
1182
1183                if (size < 3)
1184                        return false;
1185
1186                float res = 0;
1187                for (int i = 0; i < size; i++) {
1188                        final Point2d p = points.get(i);
1189                        final Point2d tmp = points.get((i + 1) % size);
1190                        final Point2dImpl v = new Point2dImpl();
1191                        v.x = tmp.getX() - p.getX();
1192                        v.y = tmp.getY() - p.getY();
1193                        final Point2d u = points.get((i + 2) % size);
1194
1195                        if (i == 0) // in first loop direction is unknown, so save it in res
1196                                res = u.getX() * v.y - u.getY() * v.x + v.x * p.getY() - v.y * p.getX();
1197                        else
1198                        {
1199                                final float newres = u.getX() * v.y - u.getY() * v.x + v.x * p.getY() - v.y * p.getX();
1200                                if ((newres > 0 && res < 0) || (newres < 0 && res > 0))
1201                                        return false;
1202                        }
1203                }
1204
1205                if (isOriginallyClosed)
1206                        close();
1207
1208                return true;
1209        }
1210
1211        /**
1212         * Test if this has no inner polygons or sub-parts
1213         *
1214         * @see Polygon#getInnerPolys()
1215         *
1216         * @return true if this is polygon has no inner polygons; false otherwise.
1217         */
1218        public boolean hasNoInnerPolygons() {
1219                return innerPolygons == null || innerPolygons.size() == 0;
1220        }
1221
1222        @Override
1223        public double calculatePerimeter() {
1224                final Point2d p1 = points.get(0);
1225                float p1x = p1.getX();
1226                float p1y = p1.getY();
1227
1228                Point2d p2 = points.get(points.size() - 1);
1229                float p2x = p2.getX();
1230                float p2y = p2.getY();
1231
1232                double peri = Line2d.distance(p1x, p1y, p2x, p2y);
1233                for (int i = 1; i < this.points.size(); i++) {
1234                        p2 = points.get(i);
1235                        p2x = p2.getX();
1236                        p2y = p2.getY();
1237                        peri += Line2d.distance(p1x, p1y, p2x, p2y);
1238                        p1x = p2x;
1239                        p1y = p2y;
1240                }
1241
1242                return peri;
1243        }
1244
1245        /**
1246         * Test (outer) polygon path direction
1247         *
1248         * @return true is the outer path direction is clockwise w.r.t OpenIMAJ
1249         *         coordinate system
1250         */
1251        public boolean isClockwise() {
1252                double signedArea = 0;
1253                for (int i = 0; i < this.points.size() - 1; i++) {
1254                        final float x1 = points.get(i).getX();
1255                        final float y1 = points.get(i).getY();
1256                        final float x2 = points.get(i + 1).getX();
1257                        final float y2 = points.get(i + 1).getY();
1258
1259                        signedArea += (x1 * y2 - x2 * y1);
1260                }
1261
1262                final float x1 = points.get(points.size() - 1).getX();
1263                final float y1 = points.get(points.size() - 1).getY();
1264                final float x2 = points.get(0).getX();
1265                final float y2 = points.get(0).getY();
1266                signedArea += (x1 * y2 - x2 * y1);
1267
1268                return signedArea >= 0;
1269        }
1270
1271        /**
1272         * Calculate convex hull using Melkman's algorithm. This is faster than the
1273         * {@link #calculateConvexHull()} method, but will only work for simple
1274         * polygons (those that don't self-intersect)
1275         * <p>
1276         * Based on http://softsurfer.com/Archive/algorithm_0203/algorithm_0203.htm,
1277         * but modified (fixed) to deal with the problem of the initialisation
1278         * points potentially being co-linear.
1279         * <p>
1280         * Copyright 2001, softSurfer (www.softsurfer.com) This code may be freely
1281         * used and modified for any purpose providing that this copyright notice is
1282         * included with it. SoftSurfer makes no warranty for this code, and cannot
1283         * be held liable for any real or imagined damage resulting from its use.
1284         * Users of this code must verify correctness for their application.
1285         *
1286         * @return A polygon defining the shape of the convex hull
1287         */
1288        public Polygon calculateConvexHullMelkman() {
1289                if (this.points.size() <= 3)
1290                        return new Polygon(this.points);
1291
1292                final int n = this.points.size();
1293                int i = 1;
1294                while (i + 1 < n && isLeft(points.get(0), points.get(i), points.get(i + 1)) == 0)
1295                        i++;
1296
1297                if (n - i <= 3)
1298                        return new Polygon(this.points);
1299
1300                // initialize a deque D[] from bottom to top so that the
1301                // 1st three vertices of V[] are a counterclockwise triangle
1302                final Point2d[] D = new Point2d[2 * n + 1];
1303                int bot = n - 2, top = bot + 3; // initial bottom and top deque indices
1304                D[bot] = D[top] = points.get(i + 1); // 3rd vertex is at both bot and
1305                // top
1306                if (isLeft(points.get(0), points.get(i), points.get(i + 1)) > 0) {
1307                        D[bot + 1] = points.get(0);
1308                        D[bot + 2] = points.get(i); // ccw vertices are: 2,0,1,2
1309                } else {
1310                        D[bot + 1] = points.get(i);
1311                        D[bot + 2] = points.get(0); // ccw vertices are: 2,1,0,2
1312                }
1313
1314                i += 2;
1315
1316                // compute the hull on the deque D[]
1317                for (; i < n; i++) { // process the rest of vertices
1318                        // test if next vertex is inside the deque hull
1319                        if ((isLeft(D[bot], D[bot + 1], points.get(i)) > 0) &&
1320                                        (isLeft(D[top - 1], D[top], points.get(i)) > 0))
1321                                continue; // skip an interior vertex
1322
1323                        // incrementally add an exterior vertex to the deque hull
1324                        // get the rightmost tangent at the deque bot
1325                        while (isLeft(D[bot], D[bot + 1], points.get(i)) <= 0)
1326                                ++bot; // remove bot of deque
1327                        D[--bot] = points.get(i); // insert V[i] at bot of deque
1328
1329                        // get the leftmost tangent at the deque top
1330                        while (isLeft(D[top - 1], D[top], points.get(i)) <= 0)
1331                                --top; // pop top of deque
1332                        D[++top] = points.get(i); // push V[i] onto top of deque
1333                }
1334
1335                // transcribe deque D[] to the output hull array H[]
1336                final Polygon H = new Polygon();
1337                final List<Point2d> vertices = H.getVertices();
1338                for (int h = 0; h <= (top - bot); h++)
1339                        vertices.add(D[bot + h]);
1340
1341                return H;
1342        }
1343
1344        private float isLeft(Point2d P0, Point2d P1, Point2d P2) {
1345                return (P1.getX() - P0.getX()) * (P2.getY() - P0.getY()) - (P2.getX() -
1346                                P0.getX()) * (P1.getY() - P0.getY());
1347        }
1348
1349        /**
1350         * Compute the minimum size rotated bounding rectangle that contains this
1351         * shape using the rotating calipers approach.
1352         *
1353         * @see org.openimaj.math.geometry.shape.Shape#minimumBoundingRectangle()
1354         * @see RotatingCalipers#getMinimumBoundingRectangle(Polygon, boolean)
1355         */
1356        @Override
1357        public RotatedRectangle minimumBoundingRectangle() {
1358                return RotatingCalipers.getMinimumBoundingRectangle(this, false);
1359        }
1360
1361        /**
1362         * Compute the minimum size rotated bounding rectangle that contains this
1363         * shape using the rotating calipers approach.
1364         *
1365         * @see RotatingCalipers#getMinimumBoundingRectangle(Polygon, boolean)
1366         *
1367         * @param assumeSimple
1368         *            can the algorithm assume the polygon is simple and use an
1369         *            optimised (Melkman's) convex hull?
1370         *
1371         * @return the minimum bounding box
1372         */
1373        public RotatedRectangle minimumBoundingRectangle(boolean assumeSimple) {
1374                return RotatingCalipers.getMinimumBoundingRectangle(this, assumeSimple);
1375        }
1376
1377        /**
1378         * Find the closest point on the polygon to the given point
1379         *
1380         * @param pt
1381         *            the point
1382         * @return the closest point
1383         */
1384        public Point2d closestPoint(Point2d pt) {
1385                final boolean closed = isClosed();
1386
1387                if (!closed)
1388                        close();
1389
1390                final float x = pt.getX();
1391                final float y = pt.getY();
1392                float minDist = Float.MAX_VALUE;
1393                final Point2dImpl min = new Point2dImpl();
1394                final Point2dImpl tpt = new Point2dImpl();
1395
1396                for (int k = 0; k < points.size() - 1; k++) {
1397                        final float vx = points.get(k).getX();
1398                        final float vy = points.get(k).getY();
1399                        final float wx = points.get(k + 1).getX();
1400                        final float wy = points.get(k + 1).getY();
1401
1402                        // Return minimum distance between line segment vw and point p
1403                        final float l2 = (wx - vx) * (wx - vx) + (wy - vy) * (wy - vy);
1404
1405                        if (l2 == 0.0) {
1406                                tpt.x = vx;
1407                                tpt.y = vy;
1408                        } else {
1409                                final float t = ((x - vx) * (wx - vx) + (y - vy) * (wy - vy)) / l2;
1410
1411                                if (t < 0.0) {
1412                                        tpt.x = vx;
1413                                        tpt.y = vy;
1414                                } else if (t > 1.0) {
1415                                        tpt.x = wx;
1416                                        tpt.y = wy;
1417                                } else {
1418                                        tpt.x = vx + t * (wx - vx);
1419                                        tpt.y = vy + t * (wy - vy);
1420                                }
1421                        }
1422
1423                        final float dist = (float) Line2d.distance(x, y, tpt.x, tpt.y);
1424                        if (dist < minDist) {
1425                                minDist = dist;
1426                                min.x = tpt.x;
1427                                min.y = tpt.y;
1428                        }
1429                }
1430
1431                if (!closed)
1432                        open();
1433
1434                return min;
1435        }
1436}