View Javadoc

1   /* File SimplePolygon2D.java 
2    *
3    * Project : Java Geometry Library
4    *
5    * ===========================================
6    * 
7    * This library is free software; you can redistribute it and/or modify it 
8    * under the terms of the GNU Lesser General Public License as published by
9    * the Free Software Foundation, either version 2.1 of the License, or (at
10   * your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful, but 
13   * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
14   * or FITNESS FOR A PARTICULAR PURPOSE.
15   *
16   * See the GNU Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with this library. if not, write to :
20   * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21   * Boston, MA 02111-1307, USA.
22   */
23  
24  // package
25  
26  package math.geom2d.polygon;
27  
28  // Imports
29  import java.awt.Graphics2D;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  
33  import math.geom2d.AffineTransform2D;
34  import math.geom2d.Box2D;
35  import math.geom2d.Point2D;
36  import math.geom2d.circulinear.CirculinearBoundarySet2D;
37  import math.geom2d.circulinear.CirculinearDomain2D;
38  import math.geom2d.circulinear.CirculinearDomain2DUtils;
39  import math.geom2d.circulinear.GenericCirculinearDomain2D;
40  import math.geom2d.domain.Boundary2DUtils;
41  import math.geom2d.domain.BoundarySet2D;
42  import math.geom2d.domain.ContinuousBoundary2D;
43  import math.geom2d.domain.Domain2D;
44  import math.geom2d.domain.GenericDomain2D;
45  import math.geom2d.line.LineSegment2D;
46  import math.geom2d.transform.CircleInversion2D;
47  
48  /**
49   * Represent a polygonal domain whose boundary is a single closed polyline.
50   */
51  public class SimplePolygon2D implements Polygon2D {
52  
53      // ===================================================================
54      // constants
55  
56      // ===================================================================
57      // class variables
58  
59      /**
60       * The inner ordered list of vertices. The last point is connected to the
61       * first one.
62       */
63      protected ArrayList<Point2D> points = new ArrayList<Point2D>();
64  
65      // ===================================================================
66      // constructors
67  
68      /**
69       * Empty constructor: no vertex.
70       */
71      public SimplePolygon2D() {
72      }
73  
74      /**
75       * Constructor from an array of points
76       * 
77       * @param tab the vertices stored in an array of Point2D
78       */
79      public SimplePolygon2D(Point2D[] tab) {
80          points = new ArrayList<Point2D>(tab.length);
81          for (Point2D element : tab)
82              points.add(element);
83      }
84  
85      /**
86       * Constructor from two arrays, one for each coordinate.
87       * 
88       * @param xcoords the x coordinate of each vertex
89       * @param ycoords the y coordinate of each vertex
90       */
91      public SimplePolygon2D(double[] xcoords, double[] ycoords) {
92          points = new ArrayList<Point2D>(xcoords.length);
93          for (int i = 0; i<xcoords.length; i++)
94              points.add(new Point2D(xcoords[i], ycoords[i]));
95      }
96  
97      public SimplePolygon2D(Collection<? extends Point2D> points) {
98          this.points = new ArrayList<Point2D>(points.size());
99          this.points.addAll(points);
100     }
101 
102     
103     // ===================================================================
104     // Static methods
105     
106     /**
107      * Static factory for creating a new SimplePolygon2D from a collection of
108      * points.
109      * @since 0.8.1
110      */
111     public static SimplePolygon2D create(Collection<? extends Point2D> points) {
112     	return new SimplePolygon2D(points);
113     }
114     
115     /**
116      * Static factory for creating a new SimplePolygon2D from an array of
117      * points.
118      * @since 0.8.1
119      */
120     public static SimplePolygon2D create(Point2D[] points) {
121     	return new SimplePolygon2D(points);
122     }
123     
124 
125     // ===================================================================
126     // methods specific to SimplePolygon2D
127 
128     /**
129      * Adds a point as the last vertex.
130      */
131     public void addVertex(Point2D point) {
132         this.points.add(point);
133     }
134 
135     /**
136      * Removes a vertex of the polygon.
137      * 
138      * @param point the vertex to be removed.
139      */
140     public void removeVertex(Point2D point) {
141         this.points.remove(point);
142     }
143 
144     /**
145      * Adds a point as the last vertex.
146      * @deprecated replaced by addVertex() (0.7.1)
147      */
148     @Deprecated
149     public void addPoint(Point2D point) {
150         this.points.add(point);
151     }
152 
153     /**
154      * Removes a vertex of the polygon.
155      * 
156      * @deprecated replaced by removeVertex() (0.7.1)
157      * @param point the vertex to be removed.
158      */
159     @Deprecated
160     public void removePoint(Point2D point) {
161         this.points.remove(point);
162     }
163     
164     /**
165      * Computes area of the polygon, by returning the absolute value of the
166      * signed area.
167      */
168     public double getArea() {
169         return Math.abs(this.getSignedArea());
170     }
171 
172     /**
173      * Computes the signed area of the polygon. Algorithm is taken from page: <a
174      * href="http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/">
175      * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/</a>. Signed are
176      * is positive if polygon is oriented counter-clockwise, and negative
177      * otherwise. Result is wrong if polygon is self-intersecting.
178      * 
179      * @return the signed area of the polygon.
180      */
181     public double getSignedArea() {
182         double area = 0;
183         Point2D prev = this.points.get(points.size()-1);
184         for (Point2D point : this.points) {
185             area += prev.getX()*point.getY()-prev.getY()*point.getX();
186             prev = point;
187         }
188         return area /= 2;
189     }
190 
191     /**
192      * Computes the centroid (center of mass) of the polygon. Algorithm is taken
193      * from page: <a
194      * href="http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/">
195      * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/</a>.
196      * 
197      * @return the centroid of the polygon
198      */
199     public Point2D getCentroid() {
200         double xc = 0;
201         double yc = 0;
202         double tmp = 0;
203         Point2D prev = this.points.get(points.size()-1);
204         for (Point2D point : this.points) {
205             tmp = prev.getX()*point.getY()-prev.getY()*point.getX();
206             xc += tmp*(point.getX()+prev.getX());
207             yc += tmp*(point.getY()+prev.getY());
208             prev = point;
209         }
210         double area = this.getSignedArea()*6;
211         return new Point2D(xc/area, yc/area);
212     }
213 
214     /**
215      * Computes the winding number of the polygon. Algorithm adapted from
216      * http://www.geometryalgorithms.com/Archive/algorithm_0103/algorithm_0103.htm
217      * 
218      * @param x the x-coordinate of the point
219      * @param y the y-coordinate of the point
220      * @return the number of windings of the curve around the point
221      */
222     public int getWindingNumber(double x, double y) {
223         return Polygon2DUtils.windingNumber(points, new Point2D(x, y));
224     }
225     
226     /**
227      * Removes all the vertices of the polygon.
228      * @deprecated use clearVertices instead
229      */
230     @Deprecated
231     public void clearPoints() {
232         this.points.clear();
233     }
234 
235     /**
236      * Removes all the vertices of the polygon.
237      */
238     public void clearVertices() {
239         this.points.clear();
240     }
241     
242     /**
243      * Changes the position of the i-th vertex.
244      */
245     public void setVertex(int index, Point2D position) {
246         this.points.set(index, position);
247     }
248 
249     
250     // ===================================================================
251     // methods inherited from Polygon2D interface
252 
253     /**
254      * Returns the points of the polygon. The result is a pointer to the inner
255      * collection of vertices.
256      */
257     public Collection<Point2D> getVertices() {
258         return points;
259     }
260 
261     /**
262      * Returns the i-th vertex of the polygon.
263      * 
264      * @param i index of the vertex, between 0 and the number of vertices
265      */
266     public Point2D getVertex(int i) {
267         return points.get(i);
268     }
269 
270     /**
271      * Returns the number of vertices of the polygon.
272      * 
273      * @since 0.6.3
274      */
275     public int getVertexNumber() {
276         return points.size();
277     }
278 
279     /**
280      * Returns the set of edges, as a collection of LineSegment2D.
281      */
282     public Collection<LineSegment2D> getEdges() {
283 
284         int nPoints = this.points.size();
285         ArrayList<LineSegment2D> edges = new ArrayList<LineSegment2D>(nPoints);
286 
287         if (nPoints==0)
288             return edges;
289 
290         for (int i = 0; i<nPoints-1; i++)
291             edges.add(new LineSegment2D(points.get(i), points.get(i+1)));
292 
293         edges.add(new LineSegment2D(points.get(nPoints-1), points.get(0)));
294 
295         return edges;
296     }
297 
298     /**
299      * Returns the number of edges. For a simple polygon, this equals the
300      * number of vertices.
301      */
302     public int getEdgeNumber() {
303         return points.size();
304     }
305 
306     /* (non-Javadoc)
307      * @see math.geom2d.polygon.Polygon2D#getRings()
308      */
309     public Collection<LinearRing2D> getRings() {
310         ArrayList<LinearRing2D> rings = new ArrayList<LinearRing2D>(1);
311         rings.add(new LinearRing2D(points));
312         return rings;
313     }
314 
315     
316 	// ===================================================================
317     // methods inherited from Domain2D interface
318 
319 	/* (non-Javadoc)
320 	 * @see math.geom2d.circulinear.CirculinearDomain2D#transform(math.geom2d.transform.CircleInversion2D)
321 	 */
322 	public CirculinearDomain2D transform(CircleInversion2D inv) {
323 		return new GenericCirculinearDomain2D(
324 				this.getBoundary().transform(inv));
325 	}
326 
327 	/* (non-Javadoc)
328 	 * @see math.geom2d.circulinear.CirculinearShape2D#getBuffer(double)
329 	 */
330 	public CirculinearDomain2D getBuffer(double dist) {
331 		return CirculinearDomain2DUtils.computeBuffer(this, dist);
332 	}
333 
334 	// ===================================================================
335     // methods inherited from Domain2D interface
336 
337     /**
338      * Returns a set of one LinearRing2D, which encloses the polygon.
339      */
340     public CirculinearBoundarySet2D<LinearRing2D> getBoundary() {
341         Point2D[] array = new Point2D[this.points.size()];
342         for (int i = 0; i<this.points.size(); i++)
343             array[i] = this.points.get(i);
344 
345         return new CirculinearBoundarySet2D<LinearRing2D>(
346         		new LinearRing2D(array));
347     }
348 
349     /**
350      * Returns the polygon created by reversing the order of the vertices.
351      */
352     public SimplePolygon2D complement() {
353         int nPoints = this.points.size();
354 
355         Point2D[] res = new Point2D[nPoints];
356 
357         if (nPoints>0)
358             res[0] = this.points.get(0);
359 
360         for (int i = 1; i<nPoints; i++) {
361             res[i] = this.points.get(nPoints-i);
362         }
363         return new SimplePolygon2D(res);
364     }
365 
366     
367     // ===================================================================
368     // methods inherited from Shape2D interface
369 
370     /**
371      * Returns the distance of the point to the polygon. This is actually the
372      * minimal distance computed for each edge if the polygon, or ZERO if the
373      * point belong to the polygon.
374      */
375     public double getDistance(java.awt.geom.Point2D p) {
376         return getDistance(p.getX(), p.getY());
377     }
378 
379     /**
380      * Returns the distance of the point to the polygon. This is actually the
381      * minimal distance computed for each edge if the polygon, or ZERO if the
382      * point belong to the polygon.
383      */
384     public double getDistance(double x, double y) {
385         if (contains(x, y))
386             return 0;
387         return getBoundary().getDistance(x, y);
388     }
389 
390     /**
391      * Returns the signed distance of the shape to the given point: this distance
392      * is positive if the point lies outside the shape, and is negative if the
393      * point lies inside the shape. In this case, absolute value of distance is
394      * equals to the distance to the border of the shape.
395      */
396     public double getSignedDistance(java.awt.geom.Point2D p) {
397         return getSignedDistance(p.getX(), p.getY());
398     }
399 
400     /**
401      * Returns the signed distance of the shape to the given point: this distance
402      * is positive if the point lies outside the shape, and is negative if the
403      * point lies inside the shape. In this case, absolute value of distance is
404      * equals to the distance to the border of the shape.
405      */
406     public double getSignedDistance(double x, double y) {
407         double dist = getBoundary().getDistance(x, y);
408         if (contains(x, y))
409             return -dist;
410         else
411             return dist;
412     }
413 
414     /**
415      * Returns the shape formed by the polygon clipped by the given box.
416      */
417     public Domain2D clip(Box2D box) {
418         BoundarySet2D<ContinuousBoundary2D> boundarySet = 
419             Boundary2DUtils.clipBoundary(this.getBoundary(), box);
420 
421         // TODO: should return an instance of MultiPolygon2D.
422         return new GenericDomain2D(boundarySet);
423     }
424 
425     /**
426      * Returns the bounding box of the polygon.
427      */
428     public Box2D getBoundingBox() {
429         return getBoundary().getBoundingBox();
430     }
431 
432     /**
433      * Returns true if polygon is oriented counter-clockwise, false otherwise.
434      */
435     public boolean isBounded() {
436         return this.getSignedArea()>0;
437     }
438 
439     public boolean isEmpty() {
440         return points.size()==0;
441     }
442 
443     /**
444      * Returns the new Polygon created by an affine transform of this polygon.
445      * If the transform is not direct, the order of vertices is reversed.
446      */
447     public SimplePolygon2D transform(AffineTransform2D trans) {
448         int nPoints = this.points.size();
449 
450         Point2D[] array = new Point2D[nPoints];
451         Point2D[] res = new Point2D[nPoints];
452 
453         for (int i = 0; i<nPoints; i++) {
454             array[i] = this.points.get(i);
455             res[i] = new Point2D();
456         }
457         trans.transform(array, res);
458 
459         SimplePolygon2D poly = new SimplePolygon2D(res);
460         if (!trans.isDirect())
461             poly = poly.complement();
462 
463         return poly;
464     }
465 
466     // ===================================================================
467     // methods inherited from Shape interface
468 
469     /**
470      * Return true if the point p lies inside the polygon, with precision given
471      * by Shape2D.ACCURACY.
472      */
473     public boolean contains(java.awt.geom.Point2D p) {
474         return contains(p.getX(), p.getY());
475     }
476 
477     /**
478      * Returns true if the point (x, y) lies inside the polygon, with precision
479      * given by Shape2D.ACCURACY.
480      */
481     public boolean contains(double x, double y) {
482         return this.getWindingNumber(x, y)==1
483                 ||this.getBoundary().contains(x, y);
484     }
485 
486     /**
487      * Returns a general path iterator.
488      */
489     public java.awt.geom.GeneralPath getGeneralPath() {
490         java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
491         if (points.size()<2)
492             return path;
493 
494         // move to first point
495         Point2D point = points.get(0);
496         path.moveTo((float) (point.getX()), (float) (point.getY()));
497 
498         // line to each point
499         for (int i = 0; i<points.size(); i++) {
500             point = points.get(i);
501             path.lineTo((float) (point.getX()), (float) (point.getY()));
502         }
503 
504         // close polygon
505         point = points.get(0);
506         path.lineTo((float) (point.getX()), (float) (point.getY()));
507         path.closePath();
508 
509         return path;
510     }
511 
512     public void draw(Graphics2D g2) {
513         g2.draw(this.getGeneralPath());
514     }
515 
516     public void fill(Graphics2D g) {
517         g.fill(this.getGeneralPath());
518     }
519 
520     // ===================================================================
521     // methods inherited from Object interface
522 
523     /**
524      * Tests if the two polygons are equal. Test first the number of vertices,
525      * then the bounding boxes, then if each vertex of the polygon is contained
526      * in the vertices array of this polygon.
527      */
528     @Override
529     public boolean equals(Object obj) {
530         if (!(obj instanceof SimplePolygon2D))
531             return false;
532 
533         SimplePolygon2D polygon = (SimplePolygon2D) obj;
534 
535         if (polygon.getVertexNumber()!=this.getVertexNumber())
536             return false;
537 
538         if (!polygon.getBoundingBox().equals(this.getBoundingBox()))
539             return false;
540 
541         for (Point2D point : polygon.getVertices()) {
542             if (!this.points.contains(point))
543                 return false;
544         }
545 
546         return true;
547     }
548     
549     @Override
550     public SimplePolygon2D clone() {
551         ArrayList<Point2D> array = new ArrayList<Point2D>(points.size());
552         for(Point2D point : points)
553             array.add(point.clone());
554         return new SimplePolygon2D(array);
555     }
556 
557 }