1 /* file : LinearRing2D.java
2 *
3 * Project : geometry
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 * Created on 16 avr. 2007
24 *
25 */
26
27 package math.geom2d.polygon;
28
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.Point2D;
35 import math.geom2d.Shape2D;
36 import math.geom2d.circulinear.*;
37 import math.geom2d.domain.ContinuousBoundary2D;
38 import math.geom2d.domain.Domain2D;
39 import math.geom2d.domain.GenericDomain2D;
40 import math.geom2d.line.LineSegment2D;
41 import math.geom2d.transform.CircleInversion2D;
42
43 /**
44 * <p>
45 * A LinearRing2D is a Polyline2D whose last point is connected to the first one.
46 * This is typically the boundary of a SimplePolygon2D.
47 * </p>
48 * <p>
49 * The name 'LinearRing2D' was used for 2 reasons:
50 * <ul><li>it is short</li> <li>it is consistent with the JTS name</li></ul>
51 * </p>
52 * @author dlegland
53 */
54 public class LinearRing2D extends Polyline2D implements CirculinearContour2D {
55
56 public LinearRing2D() {
57 super();
58 }
59
60 public LinearRing2D(Point2D initialPoint) {
61 super(initialPoint);
62 }
63
64 public LinearRing2D(Point2D[] points) {
65 super(points);
66 }
67
68 public LinearRing2D(double[] xcoords, double[] ycoords) {
69 super(xcoords, ycoords);
70 }
71
72 public LinearRing2D(Collection<? extends Point2D> points) {
73 super(points);
74 }
75
76
77 // ===================================================================
78 // Static methods
79
80 /**
81 * Static factory for creating a new LinearRing2D from a collection of
82 * points.
83 * @since 0.8.1
84 */
85 public static LinearRing2D create(Collection<? extends Point2D> points) {
86 return new LinearRing2D(points);
87 }
88
89 /**
90 * Static factory for creating a new LinearRing2D from an array of
91 * points.
92 * @since 0.8.1
93 */
94 public static LinearRing2D create(Point2D[] points) {
95 return new LinearRing2D(points);
96 }
97
98
99 // ===================================================================
100 // Methods specific to ClosedPolyline2D
101
102 /**
103 * Computes area of the polyline, by returning the absolute value of the
104 * signed area.
105 */
106 public double getArea() {
107 return Math.abs(this.getSignedArea());
108 }
109
110 /**
111 * Computes the signed area of the polyline. Algorithm is taken from page:
112 * <a href="http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/">
113 * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/</a>. Signed are
114 * is positive if polyline is oriented counter-clockwise, and negative
115 * otherwise. Result is wrong if polyline is self-intersecting.
116 *
117 * @return the signed area of the polyline.
118 */
119 public double getSignedArea() {
120 double area = 0;
121 Point2D prev = this.points.get(this.points.size()-1);
122 Point2D point;
123 for (int i = 0; i<points.size(); i++) {
124 point = this.points.get(i);
125 area += prev.getX()*point.getY()-prev.getY()*point.getX();
126 prev = point;
127 }
128 return area /= 2;
129 }
130
131 // ===================================================================
132 // Methods specific to Polyline2D
133
134 /**
135 * Returns an array of LineSegment2D. The number of edges is the same as
136 * the number of vertices.
137 *
138 * @return the edges of the polyline
139 */
140 @Override
141 public Collection<LineSegment2D> getEdges() {
142 int n = points.size();
143 ArrayList<LineSegment2D> edges = new ArrayList<LineSegment2D>();
144
145 if (n<2)
146 return edges;
147
148 for (int i = 0; i<n-1; i++)
149 edges.add(new LineSegment2D(points.get(i), points.get(i+1)));
150 edges.add(new LineSegment2D(points.get(n-1), points.get(0)));
151 return edges;
152 }
153
154 @Override
155 public LineSegment2D getLastEdge() {
156 int n = points.size();
157 if (n<2)
158 return null;
159 return new LineSegment2D(points.get(n-1), points.get(0));
160 }
161
162 // ===================================================================
163 // Methods inherited from CirculinearCurve2D
164
165 @Override
166 public CirculinearRing2D getParallel(double dist) {
167 return GenericCirculinearRing2D.create(
168 CirculinearCurve2DUtils.createContinuousParallel(this, dist)
169 .getSmoothPieces());
170 }
171
172 /* (non-Javadoc)
173 * @see math.geom2d.circulinear.CirculinearCurve2D#transform(math.geom2d.transform.CircleInversion2D)
174 */
175 @Override
176 public CirculinearContour2D transform(CircleInversion2D inv) {
177
178 // Create array for storing transformed arcs
179 Collection<LineSegment2D> edges = this.getEdges();
180 ArrayList<CirculinearElement2D> arcs =
181 new ArrayList<CirculinearElement2D>(edges.size());
182
183 // Transform each arc
184 for(LineSegment2D edge : edges) {
185 arcs.add(edge.transform(inv));
186 }
187
188 // create the transformed shape
189 return new BoundaryPolyCirculinearCurve2D<CirculinearElement2D>(arcs);
190 }
191
192 // ===================================================================
193 // Methods inherited from Boundary2D
194
195 public Collection<ContinuousBoundary2D> getBoundaryCurves() {
196 ArrayList<ContinuousBoundary2D> list =
197 new ArrayList<ContinuousBoundary2D>(1);
198 list.add(this);
199 return list;
200 }
201
202 public Domain2D getDomain() {
203 return new GenericDomain2D(this);
204 }
205
206 public void fill(Graphics2D g2) {
207 g2.fill(this.getGeneralPath());
208 }
209
210 // ===================================================================
211 // Methods inherited from interface OrientedCurve2D
212
213 /*
214 * (non-Javadoc)
215 *
216 * @see math.geom2d.OrientedCurve2D#getSignedDistance(double, double)
217 */
218 @Override
219 public double getSignedDistance(double x, double y) {
220 return (this.isInside(x, y) ? -1 : 1)*this.getDistance(x, y);
221 }
222
223 /*
224 * (non-Javadoc)
225 *
226 * @see math.geom2d.OrientedCurve2D#getSignedDistance(java.awt.geom.Point2D)
227 */
228 @Override
229 public double getSignedDistance(java.awt.geom.Point2D point) {
230 return getSignedDistance(point.getX(), point.getY());
231 }
232
233 /*
234 * (non-Javadoc)
235 *
236 * @see math.geom2d.OrientedCurve2D#getWindingAngle(java.awt.geom.Point2D)
237 */
238 @Override
239 public double getWindingAngle(java.awt.geom.Point2D point) {
240 int wn = Polygon2DUtils.windingNumber(this.points, point);
241 return wn*2*Math.PI;
242 }
243
244 public boolean isInside(double x, double y) {
245 return this.isInside(new Point2D(x, y));
246 }
247
248 /*
249 * (non-Javadoc)
250 *
251 * @see math.geom2d.OrientedCurve2D#isInside(java.awt.geom.Point2D)
252 */
253 @Override
254 public boolean isInside(java.awt.geom.Point2D point) {
255 // TODO: choose convention for points on the boundary
256 int wn = Polygon2DUtils.windingNumber(this.points, point);
257 if (wn==1)
258 return true;
259 if (this.contains(point))
260 return true;
261 return false;
262 }
263
264 // ===================================================================
265 // Methods inherited from interface ContinuousCurve2D
266
267 /**
268 * return true, by definition.
269 */
270 @Override
271 public boolean isClosed() {
272 return true;
273 }
274
275 // ===================================================================
276 // Methods inherited from interface Curve2D
277
278 /**
279 * Returns point from position as double. Position t can be from 0 to n,
280 * with n equal to the number of vertices of the polyline.
281 */
282 @Override
283 public math.geom2d.Point2D getPoint(double t) {
284 // format position to stay between limits
285 double t0 = this.getT0();
286 double t1 = this.getT1();
287 t = Math.max(Math.min(t, t1), t0);
288
289 int n = points.size();
290
291 // index of vertex before point
292 int ind0 = (int) Math.floor(t+Shape2D.ACCURACY);
293 double tl = t-ind0;
294
295 if (ind0==n)
296 ind0 = 0;
297 Point2D p0 = points.get(ind0);
298
299 // check if equal to a vertex
300 if (Math.abs(t-ind0)<Shape2D.ACCURACY)
301 return new Point2D(p0);
302
303 // index of vertex after point
304 int ind1 = ind0+1;
305 if (ind1==n)
306 ind1 = 0;
307 Point2D p1 = points.get(ind1);
308
309 // position on line;
310 double x0 = p0.getX();
311 double y0 = p0.getY();
312 double dx = p1.getX()-x0;
313 double dy = p1.getY()-y0;
314
315 return new Point2D(x0+tl*dx, y0+tl*dy);
316 }
317
318 /**
319 * returns 0.
320 */
321 @Override
322 public double getT0() {
323 return 0;
324 }
325
326 /**
327 * return the number of points in the polyline.
328 */
329 @Override
330 public double getT1() {
331 return points.size();
332 }
333
334 /**
335 * return the first point of the polyline.
336 */
337 @Override
338 public Point2D getFirstPoint() {
339 if (points.size()==0)
340 return null;
341 return points.get(0);
342 }
343
344 /**
345 * return the first point, as this is the same as the last point.
346 */
347 @Override
348 public Point2D getLastPoint() {
349 if (points.size()==0)
350 return null;
351 return points.get(0);
352 }
353
354 @Override
355 public Collection<? extends LinearRing2D> getContinuousCurves() {
356 return wrapCurve(this);
357 }
358
359 /**
360 * Returns the closed polyline with same points taken in reverse order. The
361 * first points is still the same. Points of reverse curve are the same as
362 * the original curve (same pointers).
363 */
364 @Override
365 public LinearRing2D getReverseCurve() {
366 Point2D[] points2 = new Point2D[points.size()];
367 int n = points.size();
368 if (n>0) {
369 points2[0] = points.get(0);
370 for (int i = 1; i<n; i++)
371 points2[i] = points.get(n-i);
372 }
373 return new LinearRing2D(points2);
374 }
375
376 /**
377 * Return an instance of Polyline2D. If t1 is lower than t0, the returned
378 * Polyline contains the origin of the curve.
379 */
380 @Override
381 public Polyline2D getSubCurve(double t0, double t1) {
382 // code adapted from CurveSet2D
383
384 Polyline2D res = new Polyline2D();
385
386 // number of points in the polyline
387 int indMax = this.getVertexNumber();
388
389 // format to ensure t is between T0 and T1
390 t0 = Math.min(Math.max(t0, 0), indMax);
391 t1 = Math.min(Math.max(t1, 0), indMax);
392
393 // find curves index
394 int ind0 = (int) Math.floor(t0+Shape2D.ACCURACY);
395 int ind1 = (int) Math.floor(t1+Shape2D.ACCURACY);
396
397 // need to subdivide only one line segment
398 if (ind0==ind1&&t0<t1) {
399 // extract limit points
400 res.addPoint(this.getPoint(t0));
401 res.addPoint(this.getPoint(t1));
402 // return result
403 return res;
404 }
405
406 // add the point corresponding to t0
407 res.addPoint(this.getPoint(t0));
408
409 if (ind1>ind0) {
410 // add all the whole points between the 2 cuts
411 for (int n = ind0+1; n<=ind1; n++)
412 res.addPoint(points.get(n));
413 } else {
414 // add all points until the end of the set
415 for (int n = ind0+1; n<indMax; n++)
416 res.addPoint(points.get(n));
417
418 // add all points from the beginning of the set
419 for (int n = 0; n<=ind1; n++)
420 res.addPoint(points.get(n));
421 }
422
423 // add the last point
424 res.addPoint(this.getPoint(t1));
425
426 // return the curve set
427 return res;
428 }
429
430 // ===================================================================
431 // Methods inherited from interface Shape2D
432
433 /**
434 * Return the transformed shape, as a ClosePolyline2D.
435 */
436 @Override
437 public LinearRing2D transform(AffineTransform2D trans) {
438 Point2D[] pts = new Point2D[points.size()];
439 for (int i = 0; i<points.size(); i++)
440 pts[i] = trans.transform(points.get(i));
441 return new LinearRing2D(pts);
442 }
443
444 /*
445 * (non-Javadoc)
446 *
447 * @see math.geom2d.ContinuousCurve2D#appendPath(java.awt.geom.GeneralPath)
448 */
449 @Override
450 public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
451
452 if (points.size()<2)
453 return path;
454
455 // move to last first point of the curve (then a line will be drawn to
456 // the first point)
457 Point2D p0 = this.getLastPoint();
458 path.moveTo((float) p0.getX(), (float) p0.getY());
459
460 // process each point
461 for(Point2D point : points)
462 path.lineTo((float) point.getX(), (float) point.getY());
463
464 // close the path, even if the path is already at the right position
465 path.closePath();
466
467 return path;
468 }
469
470 /**
471 * Return a general path iterator.
472 */
473 @Override
474 public java.awt.geom.GeneralPath getGeneralPath() {
475 java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
476 if (points.size()<2)
477 return path;
478 return this.appendPath(path);
479 // // get point iterator
480 // Iterator<Point2D> iter = points.iterator();
481 //
482 // // move to first point
483 // Point2D point = iter.next();
484 // path.moveTo((float) (point.getX()), (float) (point.getY()));
485 //
486 // // line to each other point
487 // while (iter.hasNext()) {
488 // point = iter.next();
489 // path.lineTo((float) (point.getX()), (float) (point.getY()));
490 // }
491 //
492 // // closes the path
493 // path.closePath();
494 //
495 // return path;
496 }
497
498 @Override
499 public boolean equals(Object object) {
500 if (!(object instanceof LinearRing2D))
501 return false;
502 LinearRing2D ring = (LinearRing2D) object;
503
504 if (points.size()!=ring.points.size())
505 return false;
506 for (int i = 0; i<points.size(); i++)
507 if (!(points.get(i)).equals(ring.points.get(i)))
508 return false;
509 return true;
510 }
511
512 @Override
513 public LinearRing2D clone() {
514 ArrayList<Point2D> array = new ArrayList<Point2D>(points.size());
515 for(Point2D point : points)
516 array.add(point.clone());
517 return new LinearRing2D(array);
518 }
519 }