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 }