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