1   package org.djutils.draw.line;
2   
3   import java.awt.geom.Path2D;
4   import java.awt.geom.PathIterator;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Iterator;
8   import java.util.List;
9   import java.util.Locale;
10  
11  import org.djutils.draw.DrawException;
12  import org.djutils.draw.DrawRuntimeException;
13  import org.djutils.draw.Drawable2d;
14  import org.djutils.draw.Space2d;
15  import org.djutils.draw.bounds.Bounds2d;
16  import org.djutils.draw.point.Point2d;
17  import org.djutils.exceptions.Throw;
18  import org.djutils.logger.CategoryLogger;
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  public class PolyLine2d implements Drawable2d, PolyLine<PolyLine2d, Point2d, Space2d, Ray2d>
30  {
31      
32      private static final long serialVersionUID = 20200911L;
33  
34      
35      private final Point2d[] points;
36  
37      
38      private final double[] lengthIndexedLine;
39  
40      
41      private final double length;
42  
43      
44      private final Bounds2d bounds;
45  
46      
47  
48  
49  
50  
51  
52  
53  
54      private PolyLine2d(final boolean copyNeeded, final Point2d[] points) throws NullPointerException, DrawRuntimeException
55      {
56          Throw.whenNull(points, "points may not be null");
57          Throw.when(points.length < 2, DrawRuntimeException.class, "Need at least two points");
58          this.points = copyNeeded ? Arrays.copyOf(points, points.length) : points;
59          Point2d prevPoint = points[0];
60          double minX = prevPoint.x;
61          double minY = prevPoint.y;
62          double maxX = prevPoint.x;
63          double maxY = prevPoint.y;
64          this.lengthIndexedLine = new double[points.length];
65          this.lengthIndexedLine[0] = 0.0;
66          for (int i = 1; i < points.length; i++)
67          {
68              Point2d point = points[i];
69              minX = Math.min(minX, point.x);
70              minY = Math.min(minY, point.y);
71              maxX = Math.max(maxX, point.x);
72              maxY = Math.max(maxY, point.y);
73              if (prevPoint.x == point.x && prevPoint.y == point.y)
74              {
75                  throw new DrawRuntimeException("Degenerate Line2d; point " + (i - 1) + " has the same x and y as point " + i);
76              }
77              this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + prevPoint.distance(point);
78              prevPoint = point;
79          }
80          this.length = this.lengthIndexedLine[this.lengthIndexedLine.length - 1];
81          this.bounds = new Bounds2d(minX, maxX, minY, maxY);
82      }
83  
84      
85  
86  
87  
88  
89  
90  
91  
92  
93      public PolyLine2d(final Point2d.html#Point2d">Point2d point1, final Point2d point2, final Point2d... otherPoints)
94              throws NullPointerException, DrawRuntimeException
95      {
96          this(false, spliceArray(point1, point2, otherPoints));
97      }
98  
99      
100 
101 
102 
103 
104 
105 
106 
107     private static Point2d.html#Point2d">Point2dPoint2d">Point2d[] spliceArray(final Point2d.html#Point2d">Point2d point1, final Point2d point2, final Point2d... otherPoints)
108     {
109         Point2dhtml#Point2d">Point2d[] result = new Point2d[2 + (otherPoints == null ? 0 : otherPoints.length)];
110         result[0] = point1;
111         result[1] = point2;
112         if (otherPoints != null)
113         {
114             for (int i = 0; i < otherPoints.length; i++)
115             {
116                 result[i + 2] = otherPoints[i];
117             }
118         }
119         return result;
120     }
121 
122     
123 
124 
125 
126 
127 
128 
129     public PolyLine2d(final Point2d[] points) throws NullPointerException, DrawRuntimeException
130     {
131         this(true, checkLengthIsTwoOrMore(Throw.whenNull(points, "points may not be null")));
132     }
133 
134     
135 
136 
137 
138 
139 
140     private static Point2dint2d[] checkLengthIsTwoOrMore(final Point2d[] points) throws DrawRuntimeException
141     {
142         Throw.when(points.length < 2, DrawRuntimeException.class, "Need at least two points");
143         return points;
144     }
145 
146     
147 
148 
149 
150 
151 
152     public PolyLine2d(final Iterator<Point2d> iterator) throws NullPointerException, DrawException
153     {
154         this(iteratorToList(Throw.whenNull(iterator, "iterator cannot be null")));
155     }
156 
157     
158 
159 
160 
161 
162 
163     public PolyLine2d(final List<Point2d> pointList) throws DrawRuntimeException
164     {
165         this(false, pointList.toArray(new Point2d[pointList.size()]));
166     }
167 
168     
169     @Override
170     public PolyLine2d instantiate(final List<Point2d> pointList) throws NullPointerException, DrawRuntimeException
171     {
172         return new PolyLine2d(pointList);
173     }
174 
175     
176 
177 
178 
179 
180 
181     public PolyLine2d(final Path2D path) throws DrawException
182     {
183         this(false, path2DtoArray(path));
184     }
185 
186     
187 
188 
189 
190 
191 
192     private static Point2d[] path2DtoArray(final Path2D path) throws DrawException
193     {
194         List<Point2d> result = new ArrayList<>();
195         for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next())
196         {
197             double[] p = new double[6];
198             int segType = pi.currentSegment(p);
199             if (segType == PathIterator.SEG_MOVETO || segType == PathIterator.SEG_LINETO)
200             {
201                 result.add(new Point2d(p[0], p[1]));
202             }
203             else if (segType == PathIterator.SEG_CLOSE)
204             {
205                 if (!result.get(0).equals(result.get(result.size() - 1)))
206                 {
207                     result.add(result.get(0));
208                 }
209                 break;
210             }
211             else
212             {
213                 throw new DrawException("path2DtoArray only handles SEG_MOVETO, SEG_LINETO and SEG_CLOSE");
214             }
215         }
216         return result.toArray(new Point2d[result.size() - 1]);
217     }
218 
219     
220 
221 
222 
223 
224     private static List<Point2d> iteratorToList(final Iterator<Point2d> iterator)
225     {
226         List<Point2d> result = new ArrayList<>();
227         iterator.forEachRemaining(result::add);
228         return result;
229     }
230 
231     
232     @Override
233     public int size()
234     {
235         return this.points.length;
236     }
237 
238     
239     @Override
240     public final Point2d get(final int i) throws IndexOutOfBoundsException
241     {
242         return this.points[i];
243     }
244 
245     
246     @Override
247     public final double lengthAtIndex(final int index)
248     {
249         return this.lengthIndexedLine[index];
250     }
251 
252     
253     @Override
254     public final double getLength()
255     {
256         return this.length;
257     }
258 
259     
260     @Override
261     public Iterator<Point2d> getPoints()
262     {
263         return Arrays.stream(this.points).iterator();
264     }
265 
266     
267     @Override
268     public Bounds2d getBounds()
269     {
270         return this.bounds;
271     }
272 
273     
274 
275 
276 
277 
278 
279     public final PolyLine2d noiseFilteredLine(final double noiseLevel)
280     {
281         if (this.size() <= 2)
282         {
283             return this; 
284         }
285         Point2d prevPoint = null;
286         List<Point2d> list = null;
287         for (int index = 0; index < this.size(); index++)
288         {
289             Point2d currentPoint = this.points[index];
290             if (null != prevPoint && prevPoint.distance(currentPoint) < noiseLevel)
291             {
292                 if (null == list)
293                 {
294                     
295                     list = new ArrayList<>();
296                     for (int i = 0; i < index; i++)
297                     {
298                         list.add(this.points[i]);
299                     }
300                 }
301                 if (index == this.size() - 1)
302                 {
303                     if (list.size() > 1)
304                     {
305                         
306                         list.set(list.size() - 1, currentPoint);
307                     }
308                     else
309                     {
310                         
311                         
312                         list.add(currentPoint);
313                     }
314                 }
315                 continue; 
316             }
317             else if (null != list)
318             {
319                 list.add(currentPoint);
320             }
321             prevPoint = currentPoint;
322         }
323         if (null == list)
324         {
325             return this;
326         }
327         if (list.size() == 2 && list.get(0).equals(list.get(1)))
328         {
329             
330             list.add(1, this.points[1]);
331         }
332         try
333         {
334             return new PolyLine2d(list);
335         }
336         catch (DrawRuntimeException exception)
337         {
338             
339             CategoryLogger.always().error(exception);
340             throw new Error(exception);
341         }
342     }
343 
344     
345 
346 
347 
348 
349 
350 
351     public static PolyLine2d concatenate(final PolyLine2d... lines) throws DrawException
352     {
353         return concatenate(0.0, lines);
354     }
355 
356     
357 
358 
359 
360 
361 
362 
363 
364     public static PolyLine2d concatenate(final PolyLine2d.html#PolyLine2d">PolyLine2djxr_keyword">double tolerance, final PolyLine2d.html#PolyLine2d">PolyLine2d line1, final PolyLine2d line2)
365             throws DrawException
366     {
367         if (line1.getLast().distance(line2.getFirst()) > tolerance)
368         {
369             throw new DrawException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst() + " distance is "
370                     + line1.getLast().distance(line2.getFirst()) + " > " + tolerance);
371         }
372         int size = line1.size() + line2.size() - 1;
373         Point2dhtml#Point2d">Point2d[] points = new Point2d[size];
374         int nextIndex = 0;
375         for (int j = 0; j < line1.size(); j++)
376         {
377             points[nextIndex++] = line1.get(j);
378         }
379         for (int j = 1; j < line2.size(); j++)
380         {
381             points[nextIndex++] = line2.get(j);
382         }
383         return new PolyLine2d(false, points);
384     }
385 
386     
387 
388 
389 
390 
391 
392 
393 
394     public static PolyLine2d concatenate(final double tolerance, final PolyLine2d... lines) throws DrawException
395     {
396         if (0 == lines.length)
397         {
398             throw new DrawException("Empty argument list");
399         }
400         else if (1 == lines.length)
401         {
402             return lines[0];
403         }
404         int size = lines[0].size();
405         for (int i = 1; i < lines.length; i++)
406         {
407             if (lines[i - 1].getLast().distance(lines[i].getFirst()) > tolerance)
408             {
409                 throw new DrawException("Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst()
410                         + " distance is " + lines[i - 1].getLast().distance(lines[i].getFirst()) + " > " + tolerance);
411             }
412             size += lines[i].size() - 1;
413         }
414         Point2dhtml#Point2d">Point2d[] points = new Point2d[size];
415         int nextIndex = 0;
416         for (int i = 0; i < lines.length; i++)
417         {
418             PolyLine2d line = lines[i];
419             for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
420             {
421                 points[nextIndex++] = line.get(j);
422             }
423         }
424         return new PolyLine2d(false, points);
425     }
426 
427     
428 
429 
430 
431 
432 
433     public static PolyLine2d createAndCleanPolyLine2d(final Point2d... points) throws DrawException
434     {
435         if (points.length < 2)
436         {
437             throw new DrawException("Degenerate Line2d; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
438         }
439         return createAndCleanPolyLine2d(new ArrayList<>(Arrays.asList(points)));
440     }
441 
442     
443 
444 
445 
446 
447 
448 
449     public static PolyLine2d createAndCleanPolyLine2d(final List<Point2d> pointList) throws DrawException
450     {
451         
452         
453         int i = 1;
454         while (i < pointList.size())
455         {
456             if (pointList.get(i - 1).equals(pointList.get(i)))
457             {
458                 pointList.remove(i);
459             }
460             else
461             {
462                 i++;
463             }
464         }
465         return new PolyLine2d(pointList);
466     }
467 
468     
469     @Override
470     public final Ray2d getLocationExtended(final double position)
471     {
472         if (position >= 0.0 && position <= getLength())
473         {
474             try
475             {
476                 return getLocation(position);
477             }
478             catch (DrawException exception)
479             {
480                 
481             }
482         }
483 
484         
485         if (position < 0.0)
486         {
487             double len = position;
488             double fraction = len / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
489             Point2d p1 = this.points[0];
490             Point2d p2 = this.points[1];
491             return new Ray2d(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y), p2);
492         }
493 
494         
495         
496         int n1 = this.lengthIndexedLine.length - 1;
497         int n2 = this.lengthIndexedLine.length - 2;
498         double len = position - getLength();
499         double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
500         while (Double.isInfinite(fraction))
501         {
502             if (--n2 < 0)
503             {
504                 CategoryLogger.always().error("lengthIndexedLine of {} is invalid", this);
505                 Point2d p = this.points[n1];
506                 return new Ray2d(p.x, p.y, 0.0); 
507             }
508             fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
509         }
510         Point2d p1 = this.points[n2];
511         Point2d p2 = this.points[n1];
512         return new Ray2d(p2.x + fraction * (p2.x - p1.x), p2.y + fraction * (p2.y - p1.y), p1.directionTo(p2));
513     }
514 
515     
516     @Override
517     public final Ray2d getLocation(final double position) throws DrawException
518     {
519         if (position < 0.0 || position > getLength())
520         {
521             throw new DrawException("getLocation for line: position < 0.0 or > line length. Position = " + position
522                     + "; length = " + getLength());
523         }
524 
525         
526         if (position == 0.0)
527         {
528             Point2d p1 = this.points[0];
529             Point2d p2 = this.points[1];
530             return new Ray2d(p1.x, p1.y, p2);
531         }
532         if (position == getLength())
533         {
534             Point2d p1 = this.points[this.points.length - 2];
535             Point2d p2 = this.points[this.points.length - 1];
536             return new Ray2d(p2.x, p2.y, p1.directionTo(p2));
537         }
538 
539         
540         int index = find(position);
541         double remainder = position - this.lengthIndexedLine[index];
542         double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
543         Point2d p1 = this.points[index];
544         Point2d p2 = this.points[index + 1];
545         return new Ray2d(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y), p1.directionTo(p2));
546     }
547 
548     
549 
550 
551 
552 
553 
554     public final double projectOrthogonal(final Point2d point)
555     {
556         return projectOrthogonal(point.x, point.y);
557     }
558 
559     
560 
561 
562 
563 
564 
565 
566     public final double projectOrthogonal(final double x, final double y)
567     {
568         
569         double minDistance = Double.POSITIVE_INFINITY;
570         double minSegmentFraction = 0;
571         int minSegment = -1;
572 
573         
574         for (int i = 0; i < size() - 1; i++)
575         {
576             double dx = this.points[i + 1].x - this.points[i].x;
577             double dy = this.points[i + 1].y - this.points[i].y;
578             
579             double px = x - this.points[i].x;
580             double py = y - this.points[i].y;
581             
582             double dot1 = px * dx + py * dy;
583             double f;
584             double distance;
585             if (dot1 > 0)
586             {
587                 
588                 px = dx - px;
589                 py = dy - py;
590                 
591                 double dot2 = px * dx + py * dy;
592                 if (dot2 > 0)
593                 {
594                     
595                     double len2 = dx * dx + dy * dy;
596                     double proj = dot2 * dot2 / len2;
597                     f = dot1 / len2;
598                     distance = px * px + py * py - proj;
599                 }
600                 else
601                 {
602                     
603                     f = 1;
604                     distance = px * px + py * py;
605                 }
606             }
607             else
608             {
609                 
610                 f = 0;
611                 distance = px * px + py * py;
612             }
613             
614             if (distance < minDistance)
615             {
616                 minDistance = distance;
617                 minSegmentFraction = f;
618                 minSegment = i;
619             }
620         }
621 
622         
623         double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
624         return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / getLength();
625     }
626 
627     
628     @Override
629     public PolyLine2d extract(final double start, final double end) throws DrawException
630     {
631         if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLength())
632         {
633             throw new DrawException(
634                     "Bad interval (" + start + ".." + end + "; length of this Line2d is " + this.getLength() + ")");
635         }
636         double cumulativeLength = 0;
637         double nextCumulativeLength = 0;
638         double segmentLength = 0;
639         int index = 0;
640         List<Point2d> pointList = new ArrayList<>();
641         while (start > cumulativeLength)
642         {
643             Point2d fromPoint = get(index);
644             index++;
645             Point2d toPoint = get(index);
646             segmentLength = fromPoint.distance(toPoint);
647             cumulativeLength = nextCumulativeLength;
648             nextCumulativeLength = cumulativeLength + segmentLength;
649             if (nextCumulativeLength >= start)
650             {
651                 break;
652             }
653         }
654         if (start == nextCumulativeLength)
655         {
656             pointList.add(get(index));
657         }
658         else
659         {
660             pointList.add(get(index - 1).interpolate(get(index), (start - cumulativeLength) / segmentLength));
661             if (end > nextCumulativeLength)
662             {
663                 pointList.add(get(index));
664             }
665         }
666         while (end > nextCumulativeLength)
667         {
668             Point2d fromPoint = get(index);
669             index++;
670             if (index >= size())
671             {
672                 break; 
673             }
674             Point2d toPoint = get(index);
675             segmentLength = fromPoint.distance(toPoint);
676             cumulativeLength = nextCumulativeLength;
677             nextCumulativeLength = cumulativeLength + segmentLength;
678             if (nextCumulativeLength >= end)
679             {
680                 break;
681             }
682             pointList.add(toPoint);
683         }
684         if (end == nextCumulativeLength)
685         {
686             pointList.add(get(index));
687         }
688         else
689         {
690             Point2d point = get(index - 1).interpolate(get(index), (end - cumulativeLength) / segmentLength);
691             
692             if (!point.equals(pointList.get(pointList.size() - 1)))
693             {
694                 pointList.add(point);
695             }
696         }
697         try
698         {
699             return instantiate(pointList);
700         }
701         catch (DrawRuntimeException exception)
702         {
703             CategoryLogger.always().error(exception, "interval " + start + ".." + end + " too short");
704             throw new DrawException("interval " + start + ".." + end + "too short");
705         }
706     }
707 
708     
709     @Override
710     public PolyLine2d truncate(final double position) throws DrawException
711     {
712         if (position <= 0.0 || position > getLength())
713         {
714             throw new DrawException("truncate for line: position <= 0.0 or > line length. Position = " + position
715                     + ". Length = " + getLength() + " m.");
716         }
717 
718         
719         if (position == getLength())
720         {
721             return this;
722         }
723 
724         
725         int index = find(position);
726         double remainder = position - lengthAtIndex(index);
727         double fraction = remainder / (lengthAtIndex(index + 1) - lengthAtIndex(index));
728         Point2d p1 = get(index);
729         Point2d lastPoint;
730         if (0.0 == fraction)
731         {
732             index--;
733             lastPoint = p1;
734         }
735         else
736         {
737             Point2d p2 = get(index + 1);
738             lastPoint = p1.interpolate(p2, fraction);
739 
740         }
741         
742         List<Point2d> coords = new ArrayList<>(index + 2);
743         for (int i = 0; i <= index; i++)
744         {
745             coords.add(get(i));
746         }
747         coords.add(lastPoint);
748         return instantiate(coords);
749     }
750 
751     
752     public static final double DEFAULT_CIRCLE_PRECISION = 0.001;
753 
754     
755     public static final double DEFAULT_OFFSET_MINIMUM_FILTER_VALUE = 0.001;
756 
757     
758     public static final double DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE = 0.1;
759 
760     
761 
762 
763 
764     public static final double DEFAULT_OFFSET_FILTER_RATIO = 10;
765 
766     
767     public static final double DEFAULT_OFFSET_PRECISION = 0.00001;
768 
769     
770 
771 
772 
773 
774 
775 
776 
777 
778 
779 
780 
781 
782     public PolyLine2d offsetLine(final double offset)
783     {
784         return offsetLine(offset, DEFAULT_CIRCLE_PRECISION, DEFAULT_OFFSET_MINIMUM_FILTER_VALUE,
785                 DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, DEFAULT_OFFSET_FILTER_RATIO, DEFAULT_OFFSET_PRECISION);
786     }
787 
788     
789 
790 
791 
792 
793 
794 
795 
796 
797 
798 
799 
800 
801 
802 
803 
804 
805 
806 
807     @SuppressWarnings("checkstyle:methodlength")
808     public PolyLine2d offsetLine(final double offset, final double circlePrecision, final double offsetMinimumFilterValue,
809             final double offsetMaximumFilterValue, final double offsetFilterRatio, final double minimumOffset)
810             throws IllegalArgumentException
811     {
812         Throw.when(Double.isNaN(offset), IllegalArgumentException.class, "Offset may not be NaN");
813         Throw.when(Double.isNaN(circlePrecision) || circlePrecision <= 0, IllegalArgumentException.class,
814                 "bad circlePrecision");
815         Throw.when(Double.isNaN(offsetMinimumFilterValue) || offsetMinimumFilterValue <= 0, IllegalArgumentException.class,
816                 "bad offsetMinimumFilterValue");
817         Throw.when(Double.isNaN(offsetMaximumFilterValue) || offsetMaximumFilterValue <= 0, IllegalArgumentException.class,
818                 "bad offsetMaximumFilterValue");
819         Throw.when(Double.isNaN(offsetFilterRatio) || offsetFilterRatio <= 0, IllegalArgumentException.class,
820                 "bad offsetFilterRatio");
821         Throw.when(Double.isNaN(minimumOffset) || minimumOffset <= 0, IllegalArgumentException.class, "bad minimumOffset");
822         Throw.when(offsetMinimumFilterValue >= offsetMaximumFilterValue, IllegalArgumentException.class,
823                 "bad offset filter values; minimum must be less than maximum");
824         double bufferOffset = Math.abs(offset);
825         if (bufferOffset < minimumOffset)
826         {
827             return this;
828         }
829 
830         PolyLine2d filteredReferenceLine = noiseFilteredLine(
831                 Math.max(offsetMinimumFilterValue, Math.min(bufferOffset / offsetFilterRatio, offsetMaximumFilterValue)));
832         List<Point2d> tempPoints = new ArrayList<>();
833         
834         Point2d prevPoint = filteredReferenceLine.get(0);
835         Double prevAngle = null;
836         for (int index = 0; index < filteredReferenceLine.size() - 1; index++)
837         {
838             Point2d nextPoint = filteredReferenceLine.get(index + 1);
839             double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
840             Point2dl#Point2d">Point2d segmentFrom = new Point2d(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
841             Point2dtml#Point2d">Point2d segmentTo = new Point2d(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
842             boolean addSegment = true;
843             if (index > 0)
844             {
845                 double deltaAngle = angle - prevAngle;
846                 if (Math.abs(deltaAngle) > Math.PI)
847                 {
848                     deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
849                 }
850                 if (deltaAngle * offset <= 0)
851                 {
852                     
853                     
854                     
855                     int numSegments = 1;
856                     if (Math.abs(deltaAngle) > Math.PI / 2)
857                     {
858                         numSegments = 2;
859                     }
860                     while (true)
861                     {
862                         double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
863                         if (maxError < circlePrecision)
864                         {
865                             break; 
866                         }
867                         numSegments *= 2;
868                     }
869                     Point2d prevArcPoint = tempPoints.get(tempPoints.size() - 1);
870                     
871                     for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
872                     {
873                         double intermediateAngle =
874                                 (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
875                         if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
876                         {
877                             intermediateAngle += Math.PI;
878                         }
879                         Point2dt2d">Point2d intermediatePoint = new Point2d(prevPoint.x - Math.sin(intermediateAngle) * offset,
880                                 prevPoint.y + Math.cos(intermediateAngle) * offset);
881                         
882                         Point2d prevSegFrom = null;
883                         int stopAt = tempPoints.size();
884                         for (int i = 0; i < stopAt; i++)
885                         {
886                             Point2d prevSegTo = tempPoints.get(i);
887                             if (null != prevSegFrom)
888                             {
889                                 Point2d prevSegIntersection = Point2d.intersectionOfLineSegments(prevArcPoint,
890                                         intermediatePoint, prevSegFrom, prevSegTo);
891                                 if (null != prevSegIntersection && prevSegIntersection.distance(prevArcPoint) > circlePrecision
892                                         && prevSegIntersection.distance(prevSegFrom) > circlePrecision
893                                         && prevSegIntersection.distance(prevSegTo) > circlePrecision)
894                                 {
895                                     tempPoints.add(prevSegIntersection);
896                                     
897                                 }
898                             }
899                             prevSegFrom = prevSegTo;
900                         }
901                         Point2d nextSegmentIntersection =
902                                 Point2d.intersectionOfLineSegments(prevSegFrom, intermediatePoint, segmentFrom, segmentTo);
903                         if (null != nextSegmentIntersection)
904                         {
905                             tempPoints.add(nextSegmentIntersection);
906                             
907                         }
908                         tempPoints.add(intermediatePoint);
909                         
910                         prevArcPoint = intermediatePoint;
911                     }
912                 }
913                 
914                 
915                 Point2d pPoint = null;
916                 int currentSize = tempPoints.size(); 
917                 for (int i = 0; i < currentSize ; i++)
918                 {
919                     Point2d p = tempPoints.get(i);
920                     if (null != pPoint)
921                     {
922                         double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
923                         double angleDifference = angle - pAngle;
924                         if (Math.abs(angleDifference) > Math.PI)
925                         {
926                             angleDifference -= Math.signum(angleDifference) * 2 * Math.PI;
927                         }
928                         if (Math.abs(angleDifference) > 0)
929                         {
930                             Point2d intersection = Point2d.intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
931                             if (null != intersection)
932                             {
933                                 if (tempPoints.size() - 1 == i)
934                                 {
935                                     tempPoints.remove(tempPoints.size() - 1);
936                                     segmentFrom = intersection;
937                                 }
938                                 else
939                                 {
940                                     tempPoints.add(intersection);
941                                 }
942                             }
943                         }
944                         else
945                         {
946                             
947                             if (i == tempPoints.size() - 1)
948                             {
949                                 tempPoints.remove(tempPoints.size() - 1);
950                                 segmentFrom = tempPoints.get(tempPoints.size() - 1);
951                                 tempPoints.remove(tempPoints.size() - 1);
952                             }
953                         }
954                     }
955                     pPoint = p;
956                 }
957             }
958             if (addSegment)
959             {
960                 tempPoints.add(segmentFrom);
961                 tempPoints.add(segmentTo);
962                 prevPoint = nextPoint;
963                 prevAngle = angle;
964             }
965         }
966         
967         for (int index = 1; index < tempPoints.size() - 1; index++)
968         {
969             Point2d checkPoint = tempPoints.get(index);
970             prevPoint = null;
971             boolean tooClose = false;
972             boolean somewhereAtCorrectDistance = false;
973             for (int i = 0; i < filteredReferenceLine.size(); i++)
974             {
975                 Point2d p = filteredReferenceLine.get(i);
976                 if (null != prevPoint)
977                 {
978                     Point2d closestPoint = checkPoint.closestPointOnSegment(prevPoint, p);
979                     double distance = closestPoint.distance(checkPoint);
980                     if (distance < bufferOffset - circlePrecision)
981                     {
982                         tooClose = true;
983                         break;
984                     }
985                     else if (distance < bufferOffset + minimumOffset)
986                     {
987                         somewhereAtCorrectDistance = true;
988                     }
989                 }
990                 prevPoint = p;
991             }
992             if (tooClose || !somewhereAtCorrectDistance)
993             {
994                 tempPoints.remove(index);
995                 index--;
996             }
997         }
998         try
999         {
1000             return PolyLine2d.createAndCleanPolyLine2d(tempPoints);
1001         }
1002         catch (DrawException exception)
1003         {
1004             exception.printStackTrace();
1005         }
1006         return null;
1007     }
1008 
1009     
1010 
1011 
1012 
1013 
1014 
1015 
1016 
1017 
1018 
1019 
1020     public double projectRay(final Ray2d orientedPoint) throws NullPointerException
1021     {
1022         Throw.whenNull(orientedPoint, "directedPoint may not be null");
1023         double bestDistance = Double.POSITIVE_INFINITY;
1024         double positionAtBestDistance = Double.NaN;
1025         Point2d prevPoint = null;
1026         
1027         Point2d perpendicular = orientedPoint.translate(-Math.sin(orientedPoint.phi), Math.cos(orientedPoint.phi));
1028         double cumulativeDistance = 0;
1029         for (Point2d nextPoint : this.points)
1030         {
1031             if (null != prevPoint)
1032             {
1033                 double segmentLength = prevPoint.distance(nextPoint);
1034                 Point2d intersection = Point2d.intersectionOfLines(orientedPoint, perpendicular, prevPoint, nextPoint);
1035                 double distanceToPrevPoint = intersection.distance(prevPoint);
1036                 if (distanceToPrevPoint <= segmentLength && intersection.distance(nextPoint) <= segmentLength)
1037                 {
1038                     
1039                     double thisDistance = intersection.distance(orientedPoint);
1040                     if (thisDistance < bestDistance)
1041                     {
1042                         positionAtBestDistance = cumulativeDistance + distanceToPrevPoint;
1043                         bestDistance = thisDistance;
1044                     }
1045                 }
1046                 cumulativeDistance += segmentLength;
1047             }
1048             prevPoint = nextPoint;
1049         }
1050         return positionAtBestDistance;
1051     }
1052 
1053     
1054     @Override
1055     public String toString()
1056     {
1057         return "PolyLine2d [points=" + Arrays.toString(this.points) + "]";
1058     }
1059 
1060     
1061 
1062 
1063 
1064     public final String toExcel()
1065     {
1066         StringBuffer s = new StringBuffer();
1067         for (Point2d p : this.points)
1068         {
1069             s.append(p.x + "\t" + p.y + "\n");
1070         }
1071         return s.toString();
1072     }
1073 
1074     
1075 
1076 
1077 
1078     public final String toPlot()
1079     {
1080         StringBuffer result = new StringBuffer();
1081         for (Point2d p : this.points)
1082         {
1083             result.append(String.format(Locale.US, "%s%.3f,%.3f", 0 == result.length() ? "M" : " L", p.x, p.y));
1084         }
1085         result.append("\n");
1086         return result.toString();
1087     }
1088 
1089     
1090     @Override
1091     public int hashCode()
1092     {
1093         final int prime = 31;
1094         int result = 1;
1095         result = prime * result + Arrays.hashCode(this.points);
1096         return result;
1097     }
1098 
1099     
1100     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
1101     @Override
1102     public boolean equals(final Object obj)
1103     {
1104         if (this == obj)
1105             return true;
1106         if (obj == null)
1107             return false;
1108         if (getClass() != obj.getClass())
1109             return false;
1110         PolyLine2d../../../org/djutils/draw/line/PolyLine2d.html#PolyLine2d">PolyLine2d other = (PolyLine2d) obj;
1111         if (!Arrays.equals(this.points, other.points))
1112             return false;
1113         return true;
1114     }
1115 
1116 }