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 }