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.function.Function;
9
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.draw.Drawable3d;
12 import org.djutils.draw.Space3d;
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, Space3d, 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 @Override
209 public PolyLine3d instantiate(final List<Point3d> pointList) throws NullPointerException, DrawRuntimeException
210 {
211 return new PolyLine3d(pointList);
212 }
213
214
215
216
217
218
219 static List<Point3d> iteratorToList(final Iterator<Point3d> iterator)
220 {
221 List<Point3d> result = new ArrayList<>();
222 iterator.forEachRemaining(result::add);
223 return result;
224 }
225
226
227 @Override
228 public int size()
229 {
230 return this.x.length;
231 }
232
233
234 @Override
235 public final Point3d get(final int i) throws IndexOutOfBoundsException
236 {
237 return new Point3d(this.x[i], this.y[i], this.z[i]);
238 }
239
240
241 @Override
242 public final double getX(final int i) throws IndexOutOfBoundsException
243 {
244 return this.x[i];
245 }
246
247
248 @Override
249 public final double getY(final int i) throws IndexOutOfBoundsException
250 {
251 return this.y[i];
252 }
253
254
255
256
257
258
259
260 public final double getZ(final int index) throws IndexOutOfBoundsException
261 {
262 return this.z[index];
263 }
264
265
266 @Override
267 public LineSegment3d getSegment(final int index)
268 {
269 Throw.when(index < 0 || index >= this.x.length - 1, DrawRuntimeException.class, "index must be in range 0..size() - 1");
270 return new LineSegment3d(this.x[index], this.y[index], this.z[index], this.x[index + 1], this.y[index + 1],
271 this.z[index + 1]);
272 }
273
274
275 @Override
276 public final double lengthAtIndex(final int index)
277 {
278 return this.lengthIndexedLine[index];
279 }
280
281
282 @Override
283 public double getLength()
284 {
285 return this.length;
286 }
287
288
289 @Override
290 public Iterator<Point3d> getPoints()
291 {
292 return new Iterator<Point3d>()
293 {
294 private int nextIndex = 0;
295
296
297 @Override
298 public boolean hasNext()
299 {
300 return this.nextIndex < size();
301 }
302
303
304 @Override
305 public Point3d next()
306 {
307 return get(this.nextIndex++);
308 }
309 };
310 }
311
312
313 @Override
314 public Bounds3d getBounds()
315 {
316 return this.bounds;
317 }
318
319
320 @Override
321 public final PolyLine3d noiseFilteredLine(final double noiseLevel)
322 {
323 if (this.size() <= 2)
324 {
325 return this;
326 }
327 Point3d prevPoint = null;
328 List<Point3d> list = new ArrayList<>();
329 for (int index = 0; index < this.size(); index++)
330 {
331 Point3d currentPoint = get(index);
332 if (null != prevPoint && prevPoint.distance(currentPoint) < noiseLevel)
333 {
334 if (index == this.size() - 1)
335 {
336 if (list.size() > 1)
337 {
338
339 list.set(list.size() - 1, currentPoint);
340 }
341 else
342 {
343
344
345 list.add(currentPoint);
346 }
347 }
348 continue;
349 }
350 list.add(currentPoint);
351 prevPoint = currentPoint;
352 }
353 if (list.size() == this.x.length)
354 {
355 return this;
356 }
357 if (list.size() == 2 && list.get(0).equals(list.get(1)))
358 {
359
360 list.add(1, get(1));
361 }
362 try
363 {
364 return new PolyLine3d(list);
365 }
366 catch (DrawRuntimeException exception)
367 {
368
369 CategoryLogger.always().error(exception);
370 throw new Error(exception);
371 }
372 }
373
374
375
376
377
378
379
380
381 public static PolyLine3d concatenate(final PolyLine3d... lines) throws DrawRuntimeException
382 {
383 return concatenate(0.0, lines);
384 }
385
386
387
388
389
390
391
392
393
394 public static PolyLine3d concatenate(final PolyLine3d.html#PolyLine3d">PolyLine3djxr_keyword">double tolerance, final PolyLine3d.html#PolyLine3d">PolyLine3d line1, final PolyLine3d line2)
395 throws DrawRuntimeException
396 {
397 if (line1.getLast().distance(line2.getFirst()) > tolerance)
398 {
399 throw new DrawRuntimeException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst()
400 + " distance is " + line1.getLast().distance(line2.getFirst()) + " > " + tolerance);
401 }
402 int size = line1.size() + line2.size() - 1;
403 Point3dhtml#Point3d">Point3d[] points = new Point3d[size];
404 int nextIndex = 0;
405 for (int j = 0; j < line1.size(); j++)
406 {
407 points[nextIndex++] = line1.get(j);
408 }
409 for (int j = 1; j < line2.size(); j++)
410 {
411 points[nextIndex++] = line2.get(j);
412 }
413 return new PolyLine3d(points);
414 }
415
416
417
418
419
420
421
422
423
424 public static PolyLine3d concatenate(final double tolerance, final PolyLine3d... lines) throws DrawRuntimeException
425 {
426 if (0 == lines.length)
427 {
428 throw new DrawRuntimeException("Empty argument list");
429 }
430 else if (1 == lines.length)
431 {
432 return lines[0];
433 }
434 int size = lines[0].size();
435 for (int i = 1; i < lines.length; i++)
436 {
437 if (lines[i - 1].getLast().distance(lines[i].getFirst()) > tolerance)
438 {
439 throw new DrawRuntimeException(
440 "Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst() + " distance is "
441 + lines[i - 1].getLast().distance(lines[i].getFirst()) + " > " + tolerance);
442 }
443 size += lines[i].size() - 1;
444 }
445 Point3dhtml#Point3d">Point3d[] points = new Point3d[size];
446 int nextIndex = 0;
447 for (int i = 0; i < lines.length; i++)
448 {
449 PolyLine3d line = lines[i];
450 for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
451 {
452 points[nextIndex++] = line.get(j);
453 }
454 }
455 return new PolyLine3d(points);
456 }
457
458
459 @Override
460 public PolyLine2d project() throws DrawRuntimeException
461 {
462 double[] projectedX = new double[this.x.length];
463 double[] projectedY = new double[this.x.length];
464 int nextIndex = 0;
465 for (int i = 0; i < this.x.length; i++)
466 {
467 if (i > 0 && this.x[i] == this.x[i - 1] && this.y[i] == this.y[i - 1])
468 {
469 continue;
470
471 }
472 projectedX[nextIndex] = this.x[i];
473 projectedY[nextIndex] = this.y[i];
474 nextIndex++;
475 }
476 if (nextIndex < projectedX.length)
477 {
478 return new PolyLine2d(false, Arrays.copyOf(projectedX, nextIndex), Arrays.copyOf(projectedY, nextIndex));
479 }
480 return new PolyLine2d(false, this.x, this.y);
481 }
482
483
484
485
486
487
488
489 public static PolyLine3d createAndCleanPolyLine3d(final Point3d... points) throws DrawRuntimeException
490 {
491 if (points.length < 2)
492 {
493 throw new DrawRuntimeException(
494 "Degenerate PolyLine2d; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
495 }
496 return createAndCleanPolyLine3d(new ArrayList<>(Arrays.asList(points)));
497 }
498
499
500
501
502
503
504
505
506 public static PolyLine3d createAndCleanPolyLine3d(final List<Point3d> pointList) throws DrawRuntimeException
507 {
508
509
510 int i = 1;
511 while (i < pointList.size())
512 {
513 if (pointList.get(i - 1).equals(pointList.get(i)))
514 {
515 pointList.remove(i);
516 }
517 else
518 {
519 i++;
520 }
521 }
522 return new PolyLine3d(pointList);
523 }
524
525
526 @Override
527 public final Ray3d getLocationExtended(final double position)
528 {
529 if (position >= 0.0 && position <= getLength())
530 {
531 try
532 {
533 return getLocation(position);
534 }
535 catch (DrawRuntimeException exception)
536 {
537
538 }
539 }
540
541
542 if (position < 0.0)
543 {
544 double fraction = position / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
545 return new Ray3d(this.x[0] + fraction * (this.x[1] - this.x[0]), this.y[0] + fraction * (this.y[1] - this.y[0]),
546 this.z[0] + fraction * (this.z[1] - this.z[0]), this.x[1], this.y[1], this.z[1]);
547 }
548
549
550
551 int n1 = this.x.length - 1;
552 int n2 = this.x.length - 2;
553 double len = position - getLength();
554 double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
555 while (Double.isInfinite(fraction))
556 {
557
558 if (--n2 < 0)
559 {
560 CategoryLogger.always().error("lengthIndexedLine of {} is invalid", this);
561 return new Ray3d(this.x[n1], this.y[n1], this.z[n1], 0.0, 0.0);
562 }
563 fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
564 }
565 return new Ray3d(this.x[n1] + fraction * (this.x[n1] - this.x[n2]), this.y[n1] + fraction * (this.y[n1] - this.y[n2]),
566 this.z[n1] + fraction * (this.z[n1] - this.z[n2]), Math.atan2(this.y[n1] - this.y[n2], this.x[n1] - this.x[n2]),
567 Math.atan2(Math.hypot(this.x[n1] - this.x[n2], this.y[n1] - this.y[n2]), this.z[n1] - this.z[n2]));
568 }
569
570
571 @Override
572 public final Ray3d getLocation(final double position) throws DrawRuntimeException
573 {
574 Throw.when(Double.isNaN(position), DrawRuntimeException.class, "position may not be NaN");
575 Throw.when(position < 0.0 || position > getLength(), DrawRuntimeException.class,
576 "getLocation for line: position < 0.0 or > line length. Position = " + position + "; length = " + getLength());
577
578 if (position == 0.0)
579 {
580 return new Ray3d(this.x[0], this.y[0], this.z[0], this.x[1], this.y[1], this.z[1]);
581 }
582 if (position == getLength())
583 {
584 return new Ray3d(this.x[this.x.length - 1], this.y[this.x.length - 1], this.z[this.x.length - 1],
585 2 * this.x[this.x.length - 1] - this.x[this.x.length - 2],
586 2 * this.y[this.x.length - 1] - this.y[this.x.length - 2],
587 2 * this.z[this.x.length - 1] - this.z[this.x.length - 2]);
588 }
589
590 int index = find(position);
591 double remainder = position - this.lengthIndexedLine[index];
592 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
593
594
595
596
597
598
599
600 return new Ray3d(this.x[index] + fraction * (this.x[index + 1] - this.x[index]),
601 this.y[index] + fraction * (this.y[index + 1] - this.y[index]),
602 this.z[index] + fraction * (this.z[index + 1] - this.z[index]), 2 * this.x[index + 1] - this.x[index],
603 2 * this.y[index + 1] - this.y[index], 2 * this.z[index + 1] - this.z[index]);
604 }
605
606
607
608
609
610
611
612
613
614 private double projectOrthogonalFractional(final Point3d point, final Boolean limitHandling)
615 {
616 Throw.whenNull(point, "point may not be null");
617 double bestDistance = Double.POSITIVE_INFINITY;
618 double result = Double.NaN;
619 double bestDistanceExtended = Double.POSITIVE_INFINITY;
620 for (int index = 1; index < this.size(); index++)
621 {
622 double fraction = point.fractionalPositionOnLine(this.x[index - 1], this.y[index - 1], this.z[index - 1],
623 this.x[index], this.y[index], this.z[index], false, false);
624 double distance = Math.hypot(
625 Math.hypot(point.x - (this.x[index - 1] + fraction * (this.x[index] - this.x[index - 1])),
626 point.y - (this.y[index - 1] + fraction * (this.y[index] - this.y[index - 1]))),
627 point.z - (this.z[index - 1] + fraction * (this.z[index] - this.z[index - 1])));
628 if (distance < bestDistanceExtended && (fraction >= 0.0 && fraction <= 1.0 || (fraction < 0.0 && index == 1)
629 || fraction > 1.0 && index == this.size() - 1))
630 {
631 bestDistanceExtended = distance;
632 }
633 if (distance < bestDistance && (fraction >= 0.0 || index == 1 && limitHandling != null && !limitHandling)
634 && (fraction <= 1.0 || index == this.size() - 1 && limitHandling != null && !limitHandling))
635 {
636 bestDistance = distance;
637 result = lengthAtIndex(index - 1) + fraction * (lengthAtIndex(index) - lengthAtIndex(index - 1));
638 }
639 else if (fraction < 0.0 && limitHandling != null && limitHandling)
640 {
641 distance = Math.hypot(Math.hypot(point.x - this.x[index - 1], point.y - this.y[index - 1]),
642 point.z - this.z[index - 1]);
643 if (distance < bestDistance)
644 {
645 bestDistance = distance;
646 result = lengthAtIndex(index - 1);
647 }
648 }
649 else if (index == this.size() - 1 && limitHandling != null && limitHandling)
650 {
651 distance = Math.hypot(Math.hypot(point.x - this.x[index], point.y - this.y[index]), point.z - this.z[index]);
652 if (distance < bestDistance)
653 {
654 bestDistance = distance;
655 result = lengthAtIndex(index);
656 }
657 }
658 }
659 if (bestDistance > bestDistanceExtended && (limitHandling == null || !limitHandling))
660 {
661 return Double.NaN;
662 }
663 return result / getLength();
664 }
665
666
667 @Override
668 public Point3dPoint3d closestPointOnPolyLine(final Point3d point)
669 {
670 try
671 {
672 return getLocation(projectOrthogonalFractional(point, true) * getLength());
673 }
674 catch (DrawRuntimeException e)
675 {
676
677 e.printStackTrace();
678 return null;
679 }
680 }
681
682
683
684
685
686
687
688
689
690 private Point3dt3d">Point3d projectOrthogonal(final Point3d point, final Boolean limitHandling)
691 {
692 Throw.whenNull(point, "point may not be null");
693 double fraction = projectOrthogonalFractional(point, limitHandling);
694 if (Double.isNaN(fraction))
695 {
696 return null;
697 }
698 return getLocationExtended(fraction * getLength());
699 }
700
701
702 @Override
703 public Point3dt3d">Point3d projectOrthogonal(final Point3d point) throws NullPointerException
704 {
705 return projectOrthogonal(point, null);
706 }
707
708
709 @Override
710 public Point3dnt3d projectOrthogonalExtended(final Point3d point) throws NullPointerException
711 {
712 return projectOrthogonal(point, false);
713 }
714
715
716 @Override
717 public final double projectOrthogonalFractional(final Point3d point) throws NullPointerException
718 {
719 return projectOrthogonalFractional(point, null);
720 }
721
722
723 @Override
724 public double projectOrthogonalFractionalExtended(final Point3d point) throws NullPointerException
725 {
726 return projectOrthogonalFractional(point, false);
727 }
728
729
730 @Override
731 public PolyLine3d extract(final double start, final double end) throws DrawRuntimeException
732 {
733 if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLength())
734 {
735 throw new DrawRuntimeException(
736 "Bad interval (" + start + ".." + end + "; length of this Line3d is " + this.getLength() + ")");
737 }
738 double cumulativeLength = 0;
739 double nextCumulativeLength = 0;
740 double segmentLength = 0;
741 int index = 0;
742 List<Point3d> pointList = new ArrayList<>();
743 while (start > cumulativeLength)
744 {
745 Point3d fromPoint = get(index);
746 index++;
747 Point3d toPoint = get(index);
748 segmentLength = fromPoint.distance(toPoint);
749 cumulativeLength = nextCumulativeLength;
750 nextCumulativeLength = cumulativeLength + segmentLength;
751 if (nextCumulativeLength >= start)
752 {
753 break;
754 }
755 }
756 if (start == nextCumulativeLength)
757 {
758 pointList.add(get(index));
759 }
760 else
761 {
762 pointList.add(get(index - 1).interpolate(get(index), (start - cumulativeLength) / segmentLength));
763 if (end > nextCumulativeLength)
764 {
765 pointList.add(get(index));
766 }
767 }
768 while (end > nextCumulativeLength)
769 {
770 Point3d fromPoint = get(index);
771 index++;
772 if (index >= size())
773 {
774 break;
775 }
776 Point3d toPoint = get(index);
777 segmentLength = fromPoint.distance(toPoint);
778 cumulativeLength = nextCumulativeLength;
779 nextCumulativeLength = cumulativeLength + segmentLength;
780 if (nextCumulativeLength >= end)
781 {
782 break;
783 }
784 pointList.add(toPoint);
785 }
786 if (end == nextCumulativeLength)
787 {
788 pointList.add(get(index));
789 }
790 else if (index < this.x.length)
791 {
792 Point3d point = get(index - 1).interpolate(get(index), (end - cumulativeLength) / segmentLength);
793
794 if (!point.equals(pointList.get(pointList.size() - 1)))
795 {
796 pointList.add(point);
797 }
798 }
799
800 try
801 {
802 return instantiate(pointList);
803 }
804 catch (DrawRuntimeException exception)
805 {
806 CategoryLogger.always().error(exception, "interval " + start + ".." + end + " too short");
807 throw new DrawRuntimeException("interval " + start + ".." + end + "too short");
808 }
809 }
810
811
812 @Override
813 public PolyLine3d offsetLine(final double offset, final double circlePrecision, final double offsetMinimumFilterValue,
814 final double offsetMaximumFilterValue, final double offsetFilterRatio, final double minimumOffset)
815 throws IllegalArgumentException
816 {
817 PolyLine2d flat = project().offsetLine(offset, circlePrecision, offsetMinimumFilterValue, offsetMaximumFilterValue,
818 offsetFilterRatio, minimumOffset);
819 List<Point3d> points = new ArrayList<>();
820 int start = 0;
821 for (int index = 0; index < flat.size(); index++)
822 {
823 Point2d point2d = flat.get(index);
824
825 int bestIndex = start;
826 double bestDistance = Double.POSITIVE_INFINITY;
827 Point3d bestInReference = null;
828 for (int i = start; i < size(); i++)
829 {
830 Point3d pointInReference = get(i);
831 double distance = point2d.distance(pointInReference.project());
832 if (distance < bestDistance)
833 {
834 bestIndex = i;
835 bestDistance = distance;
836 bestInReference = pointInReference;
837 }
838 }
839 points.add(new Point3d(point2d, bestInReference.z));
840 start = bestIndex;
841 }
842 try
843 {
844 return new PolyLine3d(points);
845 }
846 catch (DrawRuntimeException dre)
847 {
848 dre.printStackTrace();
849 return null;
850 }
851 }
852
853
854 @Override
855 public PolyLine3d offsetLine(final double offsetAtStart, final double offsetAtEnd, final double circlePrecision,
856 final double offsetMinimumFilterValue, final double offsetMaximumFilterValue, final double offsetFilterRatio,
857 final double minimumOffset) throws IllegalArgumentException
858 {
859 if (offsetAtStart == offsetAtEnd)
860 {
861 return offsetLine(offsetAtStart, circlePrecision, offsetMinimumFilterValue, offsetMaximumFilterValue,
862 offsetFilterRatio, minimumOffset);
863 }
864 PolyLine3d atStart = offsetLine(offsetAtStart, circlePrecision, offsetMinimumFilterValue, offsetMaximumFilterValue,
865 offsetFilterRatio, minimumOffset);
866 PolyLine3d atEnd = offsetLine(offsetAtEnd, circlePrecision, offsetMinimumFilterValue, offsetMaximumFilterValue,
867 offsetFilterRatio, minimumOffset);
868 return atStart.transitionLine(atEnd, new TransitionFunction()
869 {
870 @Override
871 public double function(final double fraction)
872 {
873 return fraction;
874 }
875 });
876 }
877
878
879 @Override
880 public PolyLine3dlyLine3d">PolyLine3d transitionLine(final PolyLine3d endLine, final TransitionFunction transition) throws DrawRuntimeException
881 {
882 Throw.whenNull(endLine, "endLine may not be null");
883 Throw.whenNull(transition, "transition may not be null");
884 List<Point3d> pointList = new ArrayList<>();
885 int indexInStart = 0;
886 int indexInEnd = 0;
887 while (indexInStart < this.size() && indexInEnd < endLine.size())
888 {
889 double fractionInStart = lengthAtIndex(indexInStart) / getLength();
890 double fractionInEnd = endLine.lengthAtIndex(indexInEnd) / endLine.getLength();
891 if (fractionInStart < fractionInEnd)
892 {
893 pointList.add(get(indexInStart).interpolate(endLine.getLocation(fractionInStart * endLine.getLength()),
894 transition.function(fractionInStart)));
895 indexInStart++;
896 }
897 else if (fractionInStart > fractionInEnd)
898 {
899 pointList.add(this.getLocation(fractionInEnd * getLength()).interpolate(endLine.get(indexInEnd),
900 transition.function(fractionInEnd)));
901 indexInEnd++;
902 }
903 else
904 {
905 pointList.add(this.get(indexInStart).interpolate(endLine.getLocation(fractionInEnd * endLine.getLength()),
906 transition.function(fractionInStart)));
907 indexInStart++;
908 indexInEnd++;
909 }
910 }
911 return createAndCleanPolyLine3d(pointList);
912 }
913
914
915 @Override
916 public PolyLine3d truncate(final double position) throws DrawRuntimeException
917 {
918 if (position <= 0.0 || position > getLength())
919 {
920 throw new DrawRuntimeException("truncate for line: position <= 0.0 or > line length. Position = " + position
921 + ". Length = " + getLength() + " m.");
922 }
923
924
925 if (position == getLength())
926 {
927 return this;
928 }
929
930
931 int index = find(position);
932 double remainder = position - lengthAtIndex(index);
933 double fraction = remainder / (lengthAtIndex(index + 1) - lengthAtIndex(index));
934 Point3d p1 = get(index);
935 Point3d lastPoint;
936 if (0.0 == fraction)
937 {
938 lastPoint = p1;
939 }
940 else
941 {
942 Point3d p2 = get(index + 1);
943 lastPoint = p1.interpolate(p2, fraction);
944 index++;
945 }
946 double[] truncatedX = new double[index + 1];
947 double[] truncatedY = new double[index + 1];
948 double[] truncatedZ = new double[index + 1];
949 for (int i = 0; i < index; i++)
950 {
951 truncatedX[i] = this.x[i];
952 truncatedY[i] = this.y[i];
953 truncatedZ[i] = this.z[i];
954 }
955 truncatedX[index] = lastPoint.x;
956 truncatedY[index] = lastPoint.y;
957 truncatedZ[index] = lastPoint.z;
958 return new PolyLine3d(truncatedX, truncatedY, truncatedZ);
959 }
960
961
962 @Override
963 public String toExcel()
964 {
965 StringBuffer s = new StringBuffer();
966 for (int i = 0; i < size(); i++)
967 {
968 s.append(getX(i) + "\t" + getY(i) + "\t" + getZ(i) + "\n");
969 }
970 return s.toString();
971 }
972
973
974 @Override
975 public String toString()
976 {
977 return toString("%f", false);
978 }
979
980
981 @Override
982 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
983 {
984 StringBuilder result = new StringBuilder();
985 if (!doNotIncludeClassName)
986 {
987 result.append("PolyLine3d ");
988 }
989 String format = String.format("%%sx=%1$s, y=%1$s, z=%1$s", doubleFormat);
990 for (int index = 0; index < this.x.length; index++)
991 {
992 result.append(
993 String.format(Locale.US, format, index == 0 ? "[" : ", ", this.x[index], this.y[index], this.z[index]));
994 }
995 result.append("]");
996 return result.toString();
997 }
998
999
1000 @SuppressWarnings("checkstyle:designforextension")
1001 @Override
1002 public int hashCode()
1003 {
1004 final int prime = 31;
1005 int result = 1;
1006 result = prime * result + Arrays.hashCode(this.x);
1007 result = prime * result + Arrays.hashCode(this.y);
1008 result = prime * result + Arrays.hashCode(this.z);
1009 return result;
1010 }
1011
1012
1013 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
1014 @Override
1015 public boolean equals(final Object obj)
1016 {
1017 if (this == obj)
1018 return true;
1019 if (obj == null)
1020 return false;
1021 if (getClass() != obj.getClass())
1022 return false;
1023 PolyLine3d../../../org/djutils/draw/line/PolyLine3d.html#PolyLine3d">PolyLine3d other = (PolyLine3d) obj;
1024 if (!Arrays.equals(this.x, other.x))
1025 return false;
1026 if (!Arrays.equals(this.y, other.y))
1027 return false;
1028 if (!Arrays.equals(this.z, other.z))
1029 return false;
1030 return true;
1031 }
1032
1033 }