View Javadoc

1   /* file : Polyline2D.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 8 mai 2006
24   *
25   */
26  
27  package math.geom2d.polygon;
28  
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Iterator;
32  
33  import math.geom2d.AffineTransform2D;
34  import math.geom2d.Box2D;
35  import math.geom2d.Point2D;
36  import math.geom2d.Shape2D;
37  import math.geom2d.Vector2D;
38  import math.geom2d.circulinear.CirculinearCurve2DUtils;
39  import math.geom2d.circulinear.CirculinearDomain2D;
40  import math.geom2d.circulinear.CirculinearContinuousCurve2D;
41  import math.geom2d.circulinear.PolyCirculinearCurve2D;
42  import math.geom2d.curve.AbstractContinuousCurve2D;
43  import math.geom2d.curve.Curve2D;
44  import math.geom2d.curve.Curve2DUtils;
45  import math.geom2d.curve.CurveArray2D;
46  import math.geom2d.curve.CurveSet2D;
47  import math.geom2d.line.LineSegment2D;
48  import math.geom2d.line.LinearShape2D;
49  import math.geom2d.line.StraightLine2D;
50  import math.geom2d.transform.CircleInversion2D;
51  
52  /**
53   * A polyline is a continuous curve where each piece of the curve is a
54   * LineSegment2D.
55   * 
56   * @author dlegland
57   */
58  public class Polyline2D extends AbstractContinuousCurve2D
59  implements CirculinearContinuousCurve2D, Cloneable {
60  
61  
62      protected ArrayList<Point2D> points = new ArrayList<Point2D>();
63  
64      // ===================================================================
65      // Contructors
66  
67      public Polyline2D() {
68      }
69  
70      public Polyline2D(Point2D initialPoint) {
71          points.add(initialPoint);
72      }
73  
74      public Polyline2D(Point2D[] points) {
75          for (Point2D element : points)
76              this.points.add(element);
77      }
78  
79      public Polyline2D(Collection<? extends Point2D> points) {
80          this.points.addAll(points);
81      }
82  
83      public Polyline2D(double[] xcoords, double[] ycoords) {
84          for (int i = 0; i<xcoords.length; i++)
85              points.add(new Point2D(xcoords[i], ycoords[i]));
86      }
87  
88      // ===================================================================
89      // Static methods
90      
91      /**
92       * Static factory for creating a new Polyline2D from a collection of
93       * points.
94       * @since 0.8.1
95       */
96      public static Polyline2D create(Collection<? extends Point2D> points) {
97      	return new Polyline2D(points);
98      }
99      
100     /**
101      * Static factory for creating a new Polyline2D from an array of
102      * points.
103      * @since 0.8.1
104      */
105     public static Polyline2D create(Point2D[] points) {
106     	return new Polyline2D(points);
107     }
108     
109 
110     // ===================================================================
111     // Methods specific to Polyline2D
112 
113     /**
114      * Return an iterator on the collection of points.
115      */
116     public Iterator<Point2D> getPointsIterator() {
117         return points.iterator();
118     }
119 
120     /**
121      * Return the collection of points as an array of Point2D.
122      * 
123      * @return an array of Point2D
124      */
125     public Point2D[] getPointArray() {
126         Point2D[] tab = new Point2D[points.size()];
127         for (int i = 0; i<points.size(); i++)
128             tab[i] = points.get(i);
129         return tab;
130     }
131 
132     public void addPoint(Point2D point) {
133         points.add(point);
134     }
135 
136     public void removePoint(Point2D point) {
137         points.remove(point);
138     }
139 
140     /**
141      * @deprecated replaced by clearVertices()
142      */
143     @Deprecated
144     public void clearPoints() {
145         points.clear();
146     }
147 
148     public void clearVertices() {
149         points.clear();
150     }
151 
152     /**
153      * Returns the vertices of the polyline.
154      */
155     public Collection<Point2D> getVertices() {
156         return points;
157     }
158 
159     /**
160      * Returns the i-th vertex of the polyline.
161      * 
162      * @param i index of the vertex, between 0 and the number of vertices
163      */
164     public Point2D getVertex(int i) {
165         return points.get(i);
166     }
167 
168     /**
169      * Returns the number of vertices.
170      * 
171      * @return the number of vertices
172      */
173     public int getVertexNumber() {
174         return points.size();
175     }
176 
177     /**
178      * Returns an array of LineSegment2D. The number of edges is the number of
179      * vertices minus one.
180      * 
181      * @return the edges of the polyline
182      */
183     public Collection<LineSegment2D> getEdges() {
184         int n = points.size();
185         ArrayList<LineSegment2D> edges = new ArrayList<LineSegment2D>(n);
186 
187         if (n<2)
188             return edges;
189 
190         for (int i = 0; i<n-1; i++)
191             edges.add(new LineSegment2D(points.get(i), points.get(i+1)));
192 
193         return edges;
194     }
195     
196     public LineSegment2D getEdge(int index) {
197     	return new LineSegment2D(points.get(index), points.get(index+1));
198     }
199 
200     public LineSegment2D getFirstEdge() {
201         if (points.size()<2)
202             return null;
203         return new LineSegment2D(points.get(0), points.get(1));
204     }
205 
206     public LineSegment2D getLastEdge() {
207         int n = points.size();
208         if (n<2)
209             return null;
210         return new LineSegment2D(points.get(n-2), points.get(n-1));
211     }
212 
213     // ===================================================================
214     // methods implementing the CirculinearCurve2D interface
215 
216 	/* (non-Javadoc)
217 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getLength()
218 	 */
219 	public double getLength() {
220 		double sum = 0;
221 		for(LineSegment2D edge : this.getEdges())
222 			sum += edge.getLength();
223 		return sum;
224 	}
225 
226 	/* (non-Javadoc)
227 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getLength(double)
228 	 */
229 	public double getLength(double pos) {
230 		//init
231 		double length = 0;
232 		
233 		// add length of each curve before current curve
234 		int index = (int) Math.floor(pos);
235 		for(int i=0; i<index; i++)
236 			length += this.getEdge(i).getLength();
237 		
238 		// add portion of length for last curve
239 		if(index<points.size()-1) {
240 			double pos2 = pos-index;
241 			length += this.getEdge(index).getLength(pos2);
242 		}
243 		
244 		// return computed length
245 		return length;
246 	}
247 
248 	/* (non-Javadoc)
249 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getPosition(double)
250 	 */
251 	public double getPosition(double length) {
252 		
253 		// position to compute
254 		double pos = 0;
255 		
256 		// index of current curve
257 		int index = 0;
258 		
259 		// cumulative length
260 		double cumLength = this.getLength(this.getT0());
261 		
262 		// iterate on all curves
263 		for(LineSegment2D edge : getEdges()) {
264 			// length of current curve
265 			double edgeLength = edge.getLength();
266 			
267 			// add either 2, or fraction of length
268 			if(cumLength+edgeLength<length) {
269 				cumLength += edgeLength;
270 				index ++;
271 			} else {
272 				// add local position on current curve
273 				double pos2 = edge.getPosition(length-cumLength);
274 				pos = index + pos2;
275 				break;
276 			}			
277 		}
278 		
279 		// return the result
280 		return pos;
281 	}
282 
283 	/* (non-Javadoc)
284 	 * @see math.geom2d.circulinear.CirculinearShape2D#getBuffer(double)
285 	 */
286 	public CirculinearDomain2D getBuffer(double dist) {
287 		return CirculinearCurve2DUtils.computeBuffer(this, dist);
288 	}
289 
290 	/* (non-Javadoc)
291 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getParallel(double)
292 	 */
293 	public CirculinearContinuousCurve2D getParallel(double d) {
294 		return CirculinearCurve2DUtils.createContinuousParallel(this, d);
295 	}
296 
297 	/* (non-Javadoc)
298 	 * @see math.geom2d.circulinear.CirculinearCurve2D#transform(math.geom2d.transform.CircleInversion2D)
299 	 */
300 	public CirculinearContinuousCurve2D transform(CircleInversion2D inv) {
301 		
302 		// Create array for storing transformed arcs
303 		Collection<LineSegment2D> edges = this.getEdges();
304 		ArrayList<CirculinearContinuousCurve2D> arcs = 
305 			new ArrayList<CirculinearContinuousCurve2D>(edges.size());
306 		
307 		// Transform each arc
308 		for(LineSegment2D edge : edges) {
309 			arcs.add(edge.transform(inv));
310 		}
311 		
312 		// create the transformed shape
313 		return new PolyCirculinearCurve2D<CirculinearContinuousCurve2D>(arcs);
314 	}
315 
316     // ===================================================================
317     // Methods inherited from ContinuousCurve2D
318 
319 	/* (non-Javadoc)
320 	 * @see math.geom2d.curve.ContinuousCurve2D#getLeftTangent(double)
321 	 */
322 	public Vector2D getLeftTangent(double t) {
323 		int index = (int) Math.floor(t);
324 		if(Math.abs(t-index)<Shape2D.ACCURACY)
325 			index--;
326 		return this.getEdge(index).getTangent(0);
327 	}
328 
329 	/* (non-Javadoc)
330 	 * @see math.geom2d.curve.ContinuousCurve2D#getRightTangent(double)
331 	 */
332 	public Vector2D getRightTangent(double t) {
333 		int index = (int) Math.ceil(t);
334 		return this.getEdge(index).getTangent(0);
335 	}
336 
337     // ===================================================================
338     // Methods implementing OrientedCurve2D interface
339 
340     /*
341      * (non-Javadoc)
342      * 
343      * @see math.geom2d.OrientedCurve2D#getSignedDistance(double, double)
344      */
345     public double getSignedDistance(double x, double y) {
346         double dist = this.getDistance(x, y);
347         if (isInside(new Point2D(x, y)))
348             return -dist;
349         else
350             return dist;
351     }
352 
353     /*
354      * (non-Javadoc)
355      * 
356      * @see math.geom2d.OrientedCurve2D#getSignedDistance(java.awt.geom.Point2D)
357      */
358     public double getSignedDistance(java.awt.geom.Point2D point) {
359         double dist = this.getDistance(point.getX(), point.getY());
360         if (isInside(point))
361             return -dist;
362         else
363             return dist;
364     }
365 
366     /*
367      * (non-Javadoc)
368      * 
369      * @see math.geom2d.OrientedCurve2D#getWindingAngle(java.awt.geom.Point2D)
370      */
371     public double getWindingAngle(java.awt.geom.Point2D point) {
372         double angle = 0;
373         int n = points.size();
374         for (int i = 0; i<n-1; i++)
375             angle += new LineSegment2D(points.get(i), points.get(i+1))
376                     .getWindingAngle(point);
377 
378         return angle;
379     }
380 
381     /*
382      * (non-Javadoc)
383      * 
384      * @see math.geom2d.OrientedCurve2D#isInside(java.awt.geom.Point2D)
385      */
386     public boolean isInside(java.awt.geom.Point2D pt) {
387         if (new LinearRing2D(this.getPointArray()).isInside(pt))
388             return true;
389 
390         if (this.points.size()<3)
391             return false;
392 
393         Point2D p0 = this.getFirstPoint();
394         Point2D q0 = this.points.get(1);
395         if (new StraightLine2D(q0, p0).isInside(pt))
396             return false;
397 
398         Point2D p1 = this.getLastPoint();
399         Point2D q1 = this.points.get(points.size()-2);
400         if (new StraightLine2D(p1, q1).isInside(pt))
401             return false;
402 
403         if (new StraightLine2D(p0, p1).isInside(pt))
404             return true;
405 
406         return false;
407     }
408 
409     // ===================================================================
410     // Methods inherited from ContinuousCurve2D
411 
412     /**
413      * return false, as Polyline2D is not closed by definition.
414      */
415     public boolean isClosed() {
416         return false;
417     }
418 
419     /*
420      * (non-Javadoc)
421      * 
422      * @see math.geom2d.ContinuousCurve2D#getSmoothPieces()
423      */
424     public Collection<? extends LineSegment2D> getSmoothPieces() {
425         return getEdges();
426     }
427 
428     // ===================================================================
429     // Methods inherited from Curve2D interface
430 
431     /*
432      * (non-Javadoc)
433      * 
434      * @see math.geom2d.Curve2D#getIntersections(math.geom2d.LinearShape2D)
435      */
436     public Collection<Point2D> getIntersections(LinearShape2D line) {
437         ArrayList<Point2D> list = new ArrayList<Point2D>();
438 
439         // extract intersections with each edge, and add to a list
440         Point2D point;
441         for (LineSegment2D edge : this.getEdges()) {
442             point = edge.getIntersection(line);
443             if (point!=null)
444                 if (!list.contains(point))
445                     list.add(point);
446         }
447 
448         // return result
449         return list;
450     }
451 
452     /*
453      * (non-Javadoc)
454      * 
455      * @see math.geom2d.Curve2D#getPoint(double, math.geom2d.Point2D)
456      */
457     public math.geom2d.Point2D getPoint(double t) {
458         // format position to stay between limits
459         double t0 = this.getT0();
460         double t1 = this.getT1();
461         t = Math.max(Math.min(t, t1), t0);
462 
463         // index of vertex before point
464         int ind0 = (int) Math.floor(t+Shape2D.ACCURACY);
465         double tl = t-ind0;
466         Point2D p0 = points.get(ind0);
467 
468         // check if equal to a vertex
469         if (Math.abs(t-ind0)<Shape2D.ACCURACY)
470             return new Point2D(p0);
471 
472         // index of vertex after point
473         int ind1 = ind0+1;
474         Point2D p1 = points.get(ind1);
475 
476         // position on line;
477 
478         double x0 = p0.getX();
479         double y0 = p0.getY();
480         double dx = p1.getX()-x0;
481         double dy = p1.getY()-y0;
482 
483         return new Point2D(x0+tl*dx, y0+tl*dy);
484     }
485 
486     /*
487      * (non-Javadoc)
488      * 
489      * @see math.geom2d.Curve2D#getPosition(math.geom2d.Point2D)
490      */
491     public double getPosition(java.awt.geom.Point2D point) {
492         int ind = 0;
493         double dist, minDist = Double.POSITIVE_INFINITY;
494         double x = point.getX();
495         double y = point.getY();
496 
497         int i = 0;
498         LineSegment2D closest = null;
499         for (LineSegment2D edge : this.getEdges()) {
500             dist = edge.getDistance(x, y);
501             if (dist<minDist) {
502                 minDist = dist;
503                 ind = i;
504                 closest = edge;
505             }
506             i++;
507         }
508 
509         return closest.getPosition(point)+ind;
510     }
511 
512     /*
513      * (non-Javadoc)
514      * 
515      * @see math.geom2d.Curve2D#getPosition(math.geom2d.Point2D)
516      */
517     public double project(java.awt.geom.Point2D point) {
518         double dist, minDist = Double.POSITIVE_INFINITY;
519         double x = point.getX();
520         double y = point.getY();
521         double pos = Double.NaN;
522 
523         int i = 0;
524         for (LineSegment2D edge : this.getEdges()) {
525             dist = edge.getDistance(x, y);
526             if (dist<minDist) {
527                 minDist = dist;
528                 pos = edge.project(point)+i;
529             }
530             i++;
531         }
532 
533         return pos;
534     }
535 
536     /**
537      * returns 0.
538      */
539     public double getT0() {
540         return 0;
541     }
542 
543     /**
544      * return the number of points in the polyline, minus one.
545      */
546     public double getT1() {
547         return points.size()-1;
548     }
549 
550 	@Override
551     public Point2D getFirstPoint() {
552         if (points.size()==0)
553             return null;
554         return points.get(0);
555     }
556 
557     /**
558      * if polyline is closed, return the first point.
559      */
560 	@Override
561     public Point2D getLastPoint() {
562         if (points.size()==0)
563             return null;
564         return points.get(points.size()-1);
565     }
566 
567     public Collection<Point2D> getSingularPoints() {
568         return points;
569     }
570 
571     public boolean isSingular(double pos) {
572         if (Math.abs(pos-Math.round(pos))<Shape2D.ACCURACY)
573             return true;
574         return false;
575     }
576 
577     /**
578      * Returns the polyline with same points considered in reverse order.
579      * Reversed polyline keep same references as original polyline.
580      */
581     public Polyline2D getReverseCurve() {
582         Point2D[] points2 = new Point2D[points.size()];
583         int n = points.size();
584         if (n>0) {
585             for (int i = 0; i<n; i++)
586                 points2[i] = points.get(n-1-i);
587         }
588         return new Polyline2D(points2);
589     }
590 
591 	@Override
592     public Collection<? extends Polyline2D> getContinuousCurves() {
593     	return wrapCurve(this);
594     }
595 
596 
597     /**
598      * Return an instance of Polyline2D. If t1 is lower than t0, return an
599      * instance of Polyline2D with zero points.
600      */
601     public Polyline2D getSubCurve(double t0, double t1) {
602         // code adapted from CurveSet2D
603 
604         Polyline2D res = new Polyline2D();
605 
606         if (t1<t0)
607             return res;
608 
609         // number of points in the polyline
610         int indMax = (int) this.getT1();
611 
612         // format to ensure t is between T0 and T1
613         t0 = Math.min(Math.max(t0, 0), indMax);
614         t1 = Math.min(Math.max(t1, 0), indMax);
615 
616         // find curves index
617         int ind0 = (int) Math.floor(t0);
618         int ind1 = (int) Math.floor(t1);
619 
620         // need to subdivide only one line segment
621         if (ind0==ind1) {
622             // extract limit points
623             res.addPoint(this.getPoint(t0));
624             res.addPoint(this.getPoint(t1));
625             // return result
626             return res;
627         }
628 
629         // add the point corresponding to t0
630         res.addPoint(this.getPoint(t0));
631 
632         // add all the whole points between the 2 cuts
633         for (int n = ind0+1; n<=ind1; n++)
634             res.addPoint(points.get(n));
635 
636         // add the last point
637         res.addPoint(this.getPoint(t1));
638 
639         // return the polyline
640         return res;
641     }
642 
643     // ===================================================================
644     // Methods implementing the Shape2D interface
645 
646     /** Always returns true, because a polyline is always bounded. */
647     public boolean isBounded() {
648         return true;
649     }
650 
651     /**
652      * Returns true if the polyline does not contain any point.
653      */
654     public boolean isEmpty() {
655         return points.size()==0;
656     }
657 
658     /**
659      * Clip the polyline by a box. The result is an instance of CurveSet2D<Polyline2D>,
660      * which contains only instances of Polyline2D. If the ellipse arc is not
661      * clipped, the result is an instance of CurveSet2D<Polyline2D> which
662      * contains 0 curves.
663      */
664     public CurveSet2D<? extends Polyline2D> clip(Box2D box) {
665         // Clip the curve
666         CurveSet2D<? extends Curve2D> set = Curve2DUtils.clipCurve(this, box);
667 
668         // Stores the result in appropriate structure
669         CurveArray2D<Polyline2D> result =
670         	new CurveArray2D<Polyline2D>(set.getCurveNumber());
671 
672         // convert the result
673         for (Curve2D curve : set.getCurves()) {
674             if (curve instanceof Polyline2D)
675                 result.addCurve((Polyline2D) curve);
676         }
677         return result;
678     }
679 
680     public Box2D getBoundingBox() {
681         double xmin = Double.MAX_VALUE;
682         double ymin = Double.MAX_VALUE;
683         double xmax = Double.MIN_VALUE;
684         double ymax = Double.MIN_VALUE;
685 
686         Iterator<Point2D> iter = points.iterator();
687         Point2D point;
688         double x, y;
689         while (iter.hasNext()) {
690             point = iter.next();
691             x = point.getX();
692             y = point.getY();
693             xmin = Math.min(xmin, x);
694             xmax = Math.max(xmax, x);
695             ymin = Math.min(ymin, y);
696             ymax = Math.max(ymax, y);
697         }
698 
699         return new Box2D(xmin, xmax, ymin, ymax);
700     }
701 
702     /*
703      * (non-Javadoc)
704      * 
705      * @see math.geom2d.Shape2D#getDistance(double, double)
706      */
707     public double getDistance(double x, double y) {
708         double dist = Double.MAX_VALUE;
709         for (LineSegment2D edge : this.getEdges())
710             dist = Math.min(dist, edge.getDistance(x, y));
711         return dist;
712     }
713 
714     /*
715      * (non-Javadoc)
716      * 
717      * @see math.geom2d.Shape2D#getDistance(java.awt.geom.Point2D)
718      */
719     public double getDistance(java.awt.geom.Point2D point) {
720         return getDistance(point.getX(), point.getY());
721     }
722 
723     /*
724      * (non-Javadoc)
725      * 
726      * @see math.geom2d.Shape2D#transform(math.geom2d.AffineTransform2D)
727      */
728     public Polyline2D transform(AffineTransform2D trans) {
729         Point2D[] pts = new Point2D[points.size()];
730         for (int i = 0; i<points.size(); i++)
731             pts[i] = trans.transform(points.get(i));
732         return new Polyline2D(pts);
733     }
734 
735     // ===================================================================
736     // Methods inherited from Shape interface
737 
738     /*
739      * (non-Javadoc)
740      * 
741      * @see java.awt.Shape#contains(double, double)
742      */
743     public boolean contains(double x, double y) {
744         for (LineSegment2D edge : this.getEdges())
745             if (edge.contains(x, y))
746                 return true;
747         return false;
748     }
749 
750     /*
751      * (non-Javadoc)
752      * 
753      * @see java.awt.Shape#contains(java.awt.geom.Point2D)
754      */
755     public boolean contains(java.awt.geom.Point2D point) {
756         return this.contains(point.getX(), point.getY());
757     }
758 
759     /*
760      * (non-Javadoc)
761      * 
762      * @see math.geom2d.ContinuousCurve2D#appendPath(java.awt.geom.GeneralPath)
763      */
764     public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
765 
766         if (points.size()<2)
767             return path;
768 
769         // line to each point
770         for (Point2D point : points)
771             path.lineTo((float) (point.getX()), (float) (point.getY()));
772 
773         return path;
774     }
775 
776     /**
777      * Return a general path iterator.
778      */
779     public java.awt.geom.GeneralPath getGeneralPath() {
780         java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
781         if (points.size()<2)
782             return path;
783 
784         // get point iterator
785         Iterator<Point2D> iter = points.iterator();
786 
787         // move to first point
788         Point2D point = iter.next();
789         path.moveTo((float) (point.getX()), (float) (point.getY()));
790 
791         // line to each other point
792         while (iter.hasNext()) {
793             point = iter.next();
794             path.lineTo((float) (point.getX()), (float) (point.getY()));
795         }
796 
797         return path;
798     }
799 
800     // ===================================================================
801     // Methods inherited from the Object Class
802 
803     @Override
804     public boolean equals(Object object) {
805         if (!(object instanceof Polyline2D))
806             return false;
807         Polyline2D polyline = (Polyline2D) object;
808 
809         if (points.size()!=polyline.points.size())
810             return false;
811         for (int i = 0; i<points.size(); i++)
812             if (!(points.get(i)).equals(polyline.points.get(i)))
813                 return false;
814         return true;
815     }
816     
817     @Override
818     public Polyline2D clone() {
819         ArrayList<Point2D> array = new ArrayList<Point2D>(points.size());
820         for(Point2D point : points)
821             array.add(point.clone());
822         return new Polyline2D(array);
823     }
824 
825 }