1 package org.opentrafficsim.core.geometry;
2
3 import java.awt.geom.Line2D;
4 import java.awt.geom.Path2D;
5 import java.awt.geom.PathIterator;
6 import java.awt.geom.Point2D;
7 import java.io.Serializable;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.stream.Collectors;
13 import java.util.stream.DoubleStream;
14
15 import org.djunits.unit.DirectionUnit;
16 import org.djunits.value.vdouble.scalar.Direction;
17 import org.djunits.value.vdouble.scalar.Length;
18 import org.djutils.draw.point.Point3d;
19 import org.djutils.exceptions.Throw;
20 import org.djutils.logger.CategoryLogger;
21 import org.locationtech.jts.geom.Coordinate;
22 import org.locationtech.jts.geom.CoordinateSequence;
23 import org.locationtech.jts.geom.Geometry;
24 import org.locationtech.jts.geom.GeometryFactory;
25 import org.locationtech.jts.geom.LineString;
26 import org.locationtech.jts.linearref.LengthIndexedLine;
27
28 import nl.tudelft.simulation.dsol.animation.Locatable;
29
30
31
32
33
34
35
36
37
38
39
40
41
42 public class OtsLine3d implements Locatable, Serializable
43 {
44
45 private static final long serialVersionUID = 20150722L;
46
47
48 private OtsPoint3d[] points;
49
50
51 private double[] lengthIndexedLine = null;
52
53
54 private Length length;
55
56
57 private OtsPoint3d centroid = null;
58
59
60 private Bounds bounds = null;
61
62
63 private OtsPoint3d[] fractionalHelperCenters = null;
64
65
66 private Point2D.Double[] fractionalHelperDirections = null;
67
68
69 private OtsPoint3d firstOffsetIntersection;
70
71
72 private OtsPoint3d lastOffsetIntersection;
73
74
75 private static final double FRAC_PROJ_PRECISION = 2e-5 ;
76
77
78 private Length[] vertexRadii;
79
80
81 private Bounds envelope;
82
83
84
85
86
87
88
89 public OtsLine3d(final OtsPoint3d... points) throws OtsGeometryException
90 {
91 init(points);
92 }
93
94
95
96
97
98
99
100 private void init(final OtsPoint3d... pts) throws OtsGeometryException
101 {
102 if (pts.length < 2)
103 {
104 throw new OtsGeometryException("Degenerate OtsLine3d; has " + pts.length + " point" + (pts.length != 1 ? "s" : ""));
105 }
106 this.lengthIndexedLine = new double[pts.length];
107 this.lengthIndexedLine[0] = 0.0;
108 for (int i = 1; i < pts.length; i++)
109 {
110 if (pts[i - 1].x == pts[i].x && pts[i - 1].y == pts[i].y && pts[i - 1].z == pts[i].z)
111 {
112 throw new OtsGeometryException(
113 "Degenerate OtsLine3d; point " + (i - 1) + " has the same x, y and z as point " + i);
114 }
115 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + pts[i - 1].distanceSI(pts[i]);
116 }
117 this.points = pts;
118 this.length = Length.instantiateSI(this.lengthIndexedLine[this.lengthIndexedLine.length - 1]);
119 }
120
121
122 public enum OffsetMethod
123 {
124
125 JTS,
126
127
128 PK;
129 };
130
131
132 public static final OffsetMethod OFFSETMETHOD = OffsetMethod.PK;
133
134
135
136
137
138
139
140 public final OtsLine3d offsetLine(final double offset)
141 {
142 try
143 {
144 switch (OFFSETMETHOD)
145 {
146 case PK:
147 return OtsOffsetLinePk.offsetLine(this, offset);
148
149 case JTS:
150 return OtsBufferingJts.offsetLine(this, offset);
151
152 default:
153 return null;
154 }
155 }
156 catch (OtsGeometryException exception)
157 {
158 CategoryLogger.always().error(exception);
159 return null;
160 }
161 }
162
163
164
165
166
167
168
169 public final OtsLine3d noiseFilteredLine(final double noiseLevel)
170 {
171 if (this.size() <= 2)
172 {
173 return this;
174 }
175 OtsPoint3d prevPoint = null;
176 List<OtsPoint3d> list = null;
177 for (int index = 0; index < this.size(); index++)
178 {
179 OtsPoint3d currentPoint = this.points[index];
180 if (null != prevPoint && prevPoint.distanceSI(currentPoint) < noiseLevel)
181 {
182 if (null == list)
183 {
184
185 list = new ArrayList<>();
186 for (int i = 0; i < index; i++)
187 {
188 list.add(this.points[i]);
189 }
190 }
191 if (index == this.size() - 1)
192 {
193 if (list.size() > 1)
194 {
195
196 list.set(list.size() - 1, currentPoint);
197 }
198 else
199 {
200
201
202 list.add(currentPoint);
203 }
204 }
205 continue;
206 }
207 else if (null != list)
208 {
209 list.add(currentPoint);
210 }
211 prevPoint = currentPoint;
212 }
213 if (null == list)
214 {
215 return this;
216 }
217 if (list.size() == 2 && list.get(0).equals(list.get(1)))
218 {
219
220
221 for (int index = 1; index < this.size() - 1; index++)
222 {
223 if (!this.points[index].equals(list.get(0)))
224 {
225 list.add(1, this.points[index]);
226 break;
227 }
228 }
229 }
230 try
231 {
232 return new OtsLine3d(list);
233 }
234 catch (OtsGeometryException exception)
235 {
236 CategoryLogger.always().error(exception);
237 throw new Error(exception);
238 }
239 }
240
241
242
243
244
245
246
247
248
249 public final OtsLine3d noiseFilterRamerDouglasPeucker(final double epsilon, final boolean useHorizontalDistance)
250 {
251 try
252 {
253
254
255 double maxDeviation = 0;
256 int splitIndex = -1;
257 int pointCount = size();
258 OtsLine3d straight = new OtsLine3d(get(0), get(pointCount - 1));
259
260 for (int i = 1; i < pointCount - 1; i++)
261 {
262 OtsPoint3d point = get(i);
263 OtsPoint3d closest =
264 useHorizontalDistance ? point.closestPointOnLine2D(straight) : point.closestPointOnLine(straight);
265 double deviation = useHorizontalDistance ? closest.horizontalDistanceSI(point) : closest.distanceSI(point);
266 if (deviation > maxDeviation)
267 {
268 splitIndex = i;
269 maxDeviation = deviation;
270 }
271 }
272 if (maxDeviation <= epsilon)
273 {
274
275 return straight;
276 }
277
278
279
280 OtsLine3d first = new OtsLine3d(Arrays.copyOfRange(this.points, 0, splitIndex + 1))
281 .noiseFilterRamerDouglasPeucker(epsilon, useHorizontalDistance);
282 OtsLine3d second = new OtsLine3d(Arrays.copyOfRange(this.points, splitIndex, this.points.length))
283 .noiseFilterRamerDouglasPeucker(epsilon, useHorizontalDistance);
284 return concatenate(epsilon, first, second);
285 }
286 catch (OtsGeometryException exception)
287 {
288 CategoryLogger.always().error(exception);
289 return null;
290 }
291 }
292
293
294
295
296
297
298
299
300
301 public final OtsLine3d offsetLine(final double offsetAtStart, final double offsetAtEnd) throws OtsGeometryException
302
303 {
304
305
306
307 OtsLine3d offsetLineAtStart = offsetLine(offsetAtStart);
308 if (offsetAtStart == offsetAtEnd)
309 {
310 return offsetLineAtStart;
311 }
312
313
314 OtsLine3d offsetLineAtEnd = offsetLine(offsetAtEnd);
315
316
317 Geometry startGeometry = offsetLineAtStart.getLineString();
318 Geometry endGeometry = offsetLineAtEnd.getLineString();
319 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
320 double firstLength = startGeometry.getLength();
321 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
322 double secondLength = endGeometry.getLength();
323 ArrayList<Coordinate> out = new ArrayList<>();
324 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
325 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
326 int firstIndex = 0;
327 int secondIndex = 0;
328 Coordinate prevCoordinate = null;
329 final double tooClose = 0.05;
330 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
331 {
332 double firstRatio = firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
333 : Double.MAX_VALUE;
334 double secondRatio = secondIndex < secondCoordinates.length
335 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
336 double ratio;
337 if (firstRatio < secondRatio)
338 {
339 ratio = firstRatio;
340 firstIndex++;
341 }
342 else
343 {
344 ratio = secondRatio;
345 secondIndex++;
346 }
347 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
348 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
349 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
350 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
351 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
352 {
353 out.add(resultCoordinate);
354 prevCoordinate = resultCoordinate;
355 }
356 }
357 Coordinate[] resultCoordinates = new Coordinate[out.size()];
358 for (int index = 0; index < out.size(); index++)
359 {
360 resultCoordinates[index] = out.get(index);
361 }
362 return new OtsLine3d(resultCoordinates);
363 }
364
365
366
367
368
369
370
371
372
373
374 public final OtsLine3d offsetLine(final double[] relativeFractions, final double[] offsets) throws OtsGeometryException
375
376 {
377 Throw.whenNull(relativeFractions, "relativeFraction may not be null");
378 Throw.whenNull(offsets, "offsets may not be null");
379 Throw.when(relativeFractions.length < 2, OtsGeometryException.class, "size of relativeFractions must be >= 2");
380 Throw.when(relativeFractions.length != offsets.length, OtsGeometryException.class,
381 "size of relativeFractions must be equal to size of offsets");
382 Throw.when(relativeFractions[0] < 0, OtsGeometryException.class, "relativeFractions may not start before 0");
383 Throw.when(relativeFractions[relativeFractions.length - 1] > 1, OtsGeometryException.class,
384 "relativeFractions may not end beyond 1");
385 List<Double> fractionsList = DoubleStream.of(relativeFractions).boxed().collect(Collectors.toList());
386 List<Double> offsetsList = DoubleStream.of(offsets).boxed().collect(Collectors.toList());
387 if (relativeFractions[0] != 0)
388 {
389 fractionsList.add(0, 0.0);
390 offsetsList.add(0, 0.0);
391 }
392 if (relativeFractions[relativeFractions.length - 1] < 1.0)
393 {
394 fractionsList.add(1.0);
395 offsetsList.add(0.0);
396 }
397 OtsLine3d[] offsetLine = new OtsLine3d[fractionsList.size()];
398 for (int i = 0; i < fractionsList.size(); i++)
399 {
400 offsetLine[i] = offsetLine(offsetsList.get(i));
401 System.out.println("# offset is " + offsetsList.get(i));
402 System.out.println(offsetLine[i].toPlot());
403 }
404 List<Coordinate> out = new ArrayList<>();
405 Coordinate prevCoordinate = null;
406 final double tooClose = 0.05;
407
408 for (int i = 0; i < offsetsList.size() - 1; i++)
409 {
410 Throw.when(fractionsList.get(i + 1) <= fractionsList.get(i), OtsGeometryException.class,
411 "fractions must be in ascending order");
412 Geometry startGeometry =
413 offsetLine[i].extractFractional(fractionsList.get(i), fractionsList.get(i + 1)).getLineString();
414 Geometry endGeometry =
415 offsetLine[i + 1].extractFractional(fractionsList.get(i), fractionsList.get(i + 1)).getLineString();
416 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
417 double firstLength = startGeometry.getLength();
418 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
419 double secondLength = endGeometry.getLength();
420 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
421 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
422 int firstIndex = 0;
423 int secondIndex = 0;
424 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
425 {
426 double firstRatio = firstIndex < firstCoordinates.length
427 ? first.indexOf(firstCoordinates[firstIndex]) / firstLength : Double.MAX_VALUE;
428 double secondRatio = secondIndex < secondCoordinates.length
429 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
430 double ratio;
431 if (firstRatio < secondRatio)
432 {
433 ratio = firstRatio;
434 firstIndex++;
435 }
436 else
437 {
438 ratio = secondRatio;
439 secondIndex++;
440 }
441 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
442 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
443 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
444 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
445 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
446 {
447 out.add(resultCoordinate);
448 prevCoordinate = resultCoordinate;
449 }
450 }
451 }
452 Coordinate[] resultCoordinates = new Coordinate[out.size()];
453 for (int index = 0; index < out.size(); index++)
454 {
455 resultCoordinates[index] = out.get(index);
456 }
457 return new OtsLine3d(resultCoordinates);
458 }
459
460
461
462
463
464
465
466
467 public static OtsLine3d concatenate(final OtsLine3d... lines) throws OtsGeometryException
468 {
469 return concatenate(0.0, lines);
470 }
471
472
473
474
475
476
477
478
479
480 public static OtsLine3d concatenate(final double toleranceSI, final OtsLine3d line1, final OtsLine3d line2)
481 throws OtsGeometryException
482 {
483 if (line1.getLast().distance(line2.getFirst()).si > toleranceSI)
484 {
485 throw new OtsGeometryException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst()
486 + " distance is " + line1.getLast().distance(line2.getFirst()).si + " > " + toleranceSI);
487 }
488 int size = line1.size() + line2.size() - 1;
489 OtsPoint3d[] points = new OtsPoint3d[size];
490 int nextIndex = 0;
491 for (int j = 0; j < line1.size(); j++)
492 {
493 points[nextIndex++] = line1.get(j);
494 }
495 for (int j = 1; j < line2.size(); j++)
496 {
497 points[nextIndex++] = line2.get(j);
498 }
499 return new OtsLine3d(points);
500 }
501
502
503
504
505
506
507
508
509
510 public static OtsLine3d concatenate(final double toleranceSI, final OtsLine3d... lines) throws OtsGeometryException
511 {
512
513 if (0 == lines.length)
514 {
515 throw new OtsGeometryException("Empty argument list");
516 }
517 else if (1 == lines.length)
518 {
519 return lines[0];
520 }
521 int size = lines[0].size();
522 for (int i = 1; i < lines.length; i++)
523 {
524 if (lines[i - 1].getLast().distance(lines[i].getFirst()).si > toleranceSI)
525 {
526 throw new OtsGeometryException(
527 "Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst() + " distance is "
528 + lines[i - 1].getLast().distance(lines[i].getFirst()).si + " > " + toleranceSI);
529 }
530 size += lines[i].size() - 1;
531 }
532 OtsPoint3d[] points = new OtsPoint3d[size];
533 int nextIndex = 0;
534 for (int i = 0; i < lines.length; i++)
535 {
536 OtsLine3d line = lines[i];
537 for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
538 {
539 points[nextIndex++] = line.get(j);
540 }
541 }
542 return new OtsLine3d(points);
543 }
544
545
546
547
548
549 public final OtsLine3d reverse()
550 {
551 OtsPoint3d[] resultPoints = new OtsPoint3d[size()];
552 int nextIndex = size();
553 for (OtsPoint3d p : getPoints())
554 {
555 resultPoints[--nextIndex] = p;
556 }
557 try
558 {
559 return new OtsLine3d(resultPoints);
560 }
561 catch (OtsGeometryException exception)
562 {
563
564 throw new RuntimeException(exception);
565 }
566 }
567
568
569
570
571
572
573
574
575 public final OtsLine3d extractFractional(final double start, final double end) throws OtsGeometryException
576 {
577 if (start < 0 || start >= end || end > 1)
578 {
579 throw new OtsGeometryException(
580 "Bad interval (start=" + start + ", end=" + end + ", this is " + this.toString() + ")");
581 }
582 return extract(start * this.length.si, end * this.length.si);
583 }
584
585
586
587
588
589
590
591
592
593 public final OtsLine3d extract(final Length start, final Length end) throws OtsGeometryException
594 {
595 return extract(start.si, end.si);
596 }
597
598
599
600
601
602
603
604
605
606 public final OtsLine3d extract(final double start, final double end) throws OtsGeometryException
607 {
608 if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLengthSI())
609 {
610 throw new OtsGeometryException(
611 "Bad interval (" + start + ".." + end + "; length of this OtsLine3d is " + this.getLengthSI() + ")");
612 }
613 double cumulativeLength = 0;
614 double nextCumulativeLength = 0;
615 double segmentLength = 0;
616 int index = 0;
617 List<OtsPoint3d> pointList = new ArrayList<>();
618
619 while (start > cumulativeLength)
620 {
621 OtsPoint3d fromPoint = this.points[index];
622 index++;
623 OtsPoint3d toPoint = this.points[index];
624 segmentLength = fromPoint.distanceSI(toPoint);
625 cumulativeLength = nextCumulativeLength;
626 nextCumulativeLength = cumulativeLength + segmentLength;
627 if (nextCumulativeLength >= start)
628 {
629 break;
630 }
631 }
632 if (start == nextCumulativeLength)
633 {
634 pointList.add(this.points[index]);
635 }
636 else
637 {
638 pointList.add(OtsPoint3d.interpolate((start - cumulativeLength) / segmentLength, this.points[index - 1],
639 this.points[index]));
640 if (end > nextCumulativeLength)
641 {
642 pointList.add(this.points[index]);
643 }
644 }
645 while (end > nextCumulativeLength)
646 {
647 OtsPoint3d fromPoint = this.points[index];
648 index++;
649 if (index >= this.points.length)
650 {
651 break;
652 }
653 OtsPoint3d toPoint = this.points[index];
654 segmentLength = fromPoint.distanceSI(toPoint);
655 cumulativeLength = nextCumulativeLength;
656 nextCumulativeLength = cumulativeLength + segmentLength;
657 if (nextCumulativeLength >= end)
658 {
659 break;
660 }
661 pointList.add(toPoint);
662 }
663 if (end == nextCumulativeLength)
664 {
665 pointList.add(this.points[index]);
666 }
667 else
668 {
669 OtsPoint3d point = OtsPoint3d.interpolate((end - cumulativeLength) / segmentLength, this.points[index - 1],
670 this.points[index]);
671
672 if (!point.equals(pointList.get(pointList.size() - 1)))
673 {
674 pointList.add(point);
675 }
676 }
677 try
678 {
679 return new OtsLine3d(pointList);
680 }
681 catch (OtsGeometryException exception)
682 {
683 CategoryLogger.always().error(exception, "interval " + start + ".." + end + " too short");
684 throw new OtsGeometryException("interval " + start + ".." + end + "too short");
685 }
686 }
687
688
689
690
691
692
693 private static OtsPoint3d[] coordinatesToOtsPoint3d(final Coordinate[] coordinates)
694 {
695 OtsPoint3d[] result = new OtsPoint3d[coordinates.length];
696 for (int i = 0; i < coordinates.length; i++)
697 {
698 result[i] = new OtsPoint3d(coordinates[i]);
699 }
700 return result;
701 }
702
703
704
705
706
707
708
709 public static OtsLine3d createAndCleanOtsLine3d(final OtsPoint3d... points) throws OtsGeometryException
710 {
711 if (points.length < 2)
712 {
713 throw new OtsGeometryException(
714 "Degenerate OtsLine3d; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
715 }
716 return createAndCleanOtsLine3d(new ArrayList<>(Arrays.asList(points)));
717 }
718
719
720
721
722
723
724
725
726 public static OtsLine3d createAndCleanOtsLine3d(final List<OtsPoint3d> pointList) throws OtsGeometryException
727 {
728
729 int i = 1;
730 while (i < pointList.size())
731 {
732 if (pointList.get(i - 1).equals(pointList.get(i)))
733 {
734 pointList.remove(i);
735 }
736 else
737 {
738 i++;
739 }
740 }
741 return new OtsLine3d(pointList);
742 }
743
744
745
746
747
748
749
750 public OtsLine3d(final Coordinate[] coordinates) throws OtsGeometryException
751 {
752 this(coordinatesToOtsPoint3d(coordinates));
753 }
754
755
756
757
758
759
760
761 public OtsLine3d(final LineString lineString) throws OtsGeometryException
762 {
763 this(lineString.getCoordinates());
764 }
765
766
767
768
769
770
771
772 public OtsLine3d(final Geometry geometry) throws OtsGeometryException
773 {
774 this(geometry.getCoordinates());
775 }
776
777
778
779
780
781
782
783 public OtsLine3d(final List<OtsPoint3d> pointList) throws OtsGeometryException
784 {
785 this(pointList.toArray(new OtsPoint3d[pointList.size()]));
786 }
787
788
789
790
791
792
793
794 public OtsLine3d(final Path2D path) throws OtsGeometryException
795 {
796 List<OtsPoint3d> pl = new ArrayList<>();
797 for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next())
798 {
799 double[] p = new double[6];
800 int segType = pi.currentSegment(p);
801 if (segType == PathIterator.SEG_MOVETO || segType == PathIterator.SEG_LINETO)
802 {
803 pl.add(new OtsPoint3d(p[0], p[1]));
804 }
805 else if (segType == PathIterator.SEG_CLOSE)
806 {
807 if (!pl.get(0).equals(pl.get(pl.size() - 1)))
808 {
809 pl.add(new OtsPoint3d(pl.get(0).x, pl.get(0).y));
810 }
811 break;
812 }
813 }
814 init(pl.toArray(new OtsPoint3d[pl.size() - 1]));
815 }
816
817
818
819
820
821 public final Coordinate[] getCoordinates()
822 {
823 Coordinate[] result = new Coordinate[size()];
824 for (int i = 0; i < size(); i++)
825 {
826 result[i] = this.points[i].getCoordinate();
827 }
828 return result;
829 }
830
831
832
833
834
835 public final LineString getLineString()
836 {
837 GeometryFactory factory = new GeometryFactory();
838 Coordinate[] coordinates = getCoordinates();
839 CoordinateSequence cs = factory.getCoordinateSequenceFactory().create(coordinates);
840 return new LineString(cs, factory);
841 }
842
843
844
845
846
847 public final int size()
848 {
849 return this.points.length;
850 }
851
852
853
854
855
856 public final OtsPoint3d getFirst()
857 {
858 return this.points[0];
859 }
860
861
862
863
864
865 public final OtsPoint3d getLast()
866 {
867 return this.points[size() - 1];
868 }
869
870
871
872
873
874
875
876 public final OtsPoint3d get(final int i) throws OtsGeometryException
877 {
878 if (i < 0 || i > size() - 1)
879 {
880 throw new OtsGeometryException("OtsLine3d.get(i=" + i + "); i<0 or i>=size(), which is " + size());
881 }
882 return this.points[i];
883 }
884
885
886
887
888
889
890 public final double getLengthSI()
891 {
892 return this.length.si;
893 }
894
895
896
897
898
899
900 public final Length getLength()
901 {
902 return this.length;
903 }
904
905
906
907
908
909 public final OtsPoint3d[] getPoints()
910 {
911 return this.points;
912 }
913
914
915
916
917 private synchronized void makeLengthIndexedLine()
918 {
919 if (this.lengthIndexedLine == null)
920 {
921 this.lengthIndexedLine = new double[this.points.length];
922 this.lengthIndexedLine[0] = 0.0;
923 for (int i = 1; i < this.points.length; i++)
924 {
925 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + this.points[i - 1].distanceSI(this.points[i]);
926 }
927 }
928 }
929
930
931
932
933
934
935
936 public final DirectedPoint getLocationExtended(final Length position)
937 {
938 return getLocationExtendedSI(position.getSI());
939 }
940
941
942
943
944
945
946
947
948 public final synchronized DirectedPoint getLocationExtendedSI(final double positionSI)
949 {
950 makeLengthIndexedLine();
951 if (positionSI >= 0.0 && positionSI <= getLengthSI())
952 {
953 try
954 {
955 return getLocationSI(positionSI);
956 }
957 catch (OtsGeometryException exception)
958 {
959
960 }
961 }
962
963
964 if (positionSI < 0.0)
965 {
966 double len = positionSI;
967 double fraction = len / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
968 OtsPoint3d p1 = this.points[0];
969 OtsPoint3d p2 = this.points[1];
970 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
971 p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
972 }
973
974
975 int n1 = this.lengthIndexedLine.length - 1;
976 int n2 = this.lengthIndexedLine.length - 2;
977 double len = positionSI - getLengthSI();
978 double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
979 while (Double.isInfinite(fraction))
980 {
981 if (--n2 < 0)
982 {
983 CategoryLogger.always().error("lengthIndexedLine of {} is invalid", this);
984 OtsPoint3d p = this.points[n1];
985 return new DirectedPoint(p.x, p.y, p.z, 0.0, 0.0, 0.0);
986 }
987 fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
988 }
989 OtsPoint3d p1 = this.points[n2];
990 OtsPoint3d p2 = this.points[n1];
991 return new DirectedPoint(p2.x + fraction * (p2.x - p1.x), p2.y + fraction * (p2.y - p1.y),
992 p2.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
993 }
994
995
996
997
998
999
1000
1001 public final DirectedPoint getLocationFraction(final double fraction) throws OtsGeometryException
1002 {
1003 if (fraction < 0.0 || fraction > 1.0)
1004 {
1005 throw new OtsGeometryException("getLocationFraction for line: fraction < 0.0 or > 1.0. fraction = " + fraction);
1006 }
1007 return getLocationSI(fraction * getLengthSI());
1008 }
1009
1010
1011
1012
1013
1014
1015
1016
1017 public final DirectedPoint getLocationFraction(final double fraction, final double tolerance) throws OtsGeometryException
1018 {
1019 if (fraction < -tolerance || fraction > 1.0 + tolerance)
1020 {
1021 throw new OtsGeometryException(
1022 "getLocationFraction for line: fraction < 0.0 - tolerance or > 1.0 + tolerance; fraction = " + fraction);
1023 }
1024 double f = fraction < 0 ? 0.0 : fraction > 1.0 ? 1.0 : fraction;
1025 return getLocationSI(f * getLengthSI());
1026 }
1027
1028
1029
1030
1031
1032
1033 public final DirectedPoint getLocationFractionExtended(final double fraction)
1034 {
1035 return getLocationExtendedSI(fraction * getLengthSI());
1036 }
1037
1038
1039
1040
1041
1042
1043
1044 public final DirectedPoint getLocation(final Length position) throws OtsGeometryException
1045 {
1046 return getLocationSI(position.getSI());
1047 }
1048
1049
1050
1051
1052
1053
1054
1055 private int find(final double pos) throws OtsGeometryException
1056 {
1057 if (pos == 0)
1058 {
1059 return 0;
1060 }
1061
1062 int lo = 0;
1063 int hi = this.lengthIndexedLine.length - 1;
1064 while (lo <= hi)
1065 {
1066 if (hi == lo)
1067 {
1068 return lo;
1069 }
1070 int mid = lo + (hi - lo) / 2;
1071 if (pos < this.lengthIndexedLine[mid])
1072 {
1073 hi = mid - 1;
1074 }
1075 else if (pos > this.lengthIndexedLine[mid + 1])
1076 {
1077 lo = mid + 1;
1078 }
1079 else
1080 {
1081 return mid;
1082 }
1083 }
1084 throw new OtsGeometryException(
1085 "Could not find position " + pos + " on line with length indexes: " + Arrays.toString(this.lengthIndexedLine));
1086 }
1087
1088
1089
1090
1091
1092
1093
1094 public final synchronized DirectedPoint getLocationSI(final double positionSI) throws OtsGeometryException
1095
1096 {
1097 makeLengthIndexedLine();
1098 if (positionSI < 0.0 || positionSI > getLengthSI())
1099 {
1100 throw new OtsGeometryException("getLocationSI for line: position < 0.0 or > line length. Position = " + positionSI
1101 + " m. Length = " + getLengthSI() + " m.");
1102 }
1103
1104
1105 if (positionSI == 0.0)
1106 {
1107 OtsPoint3d p1 = this.points[0];
1108 OtsPoint3d p2 = this.points[1];
1109 return new DirectedPoint(p1.x, p1.y, p1.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1110 }
1111 if (positionSI == getLengthSI())
1112 {
1113 OtsPoint3d p1 = this.points[this.points.length - 2];
1114 OtsPoint3d p2 = this.points[this.points.length - 1];
1115 return new DirectedPoint(p2.x, p2.y, p2.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1116 }
1117
1118
1119 int index = find(positionSI);
1120 double remainder = positionSI - this.lengthIndexedLine[index];
1121 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
1122 OtsPoint3d p1 = this.points[index];
1123 OtsPoint3d p2 = this.points[index + 1];
1124 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
1125 p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1126 }
1127
1128
1129
1130
1131
1132
1133
1134 public final synchronized OtsLine3d truncate(final double lengthSI) throws OtsGeometryException
1135 {
1136 makeLengthIndexedLine();
1137 if (lengthSI <= 0.0 || lengthSI > getLengthSI())
1138 {
1139 throw new OtsGeometryException("truncate for line: position <= 0.0 or > line length. Position = " + lengthSI
1140 + " m. Length = " + getLengthSI() + " m.");
1141 }
1142
1143
1144 if (lengthSI == getLengthSI())
1145 {
1146 return new OtsLine3d(getPoints());
1147 }
1148
1149
1150 int index = find(lengthSI);
1151 double remainder = lengthSI - this.lengthIndexedLine[index];
1152 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
1153 OtsPoint3d p1 = this.points[index];
1154 OtsPoint3d lastPoint;
1155 if (0.0 == fraction)
1156 {
1157 index--;
1158 lastPoint = p1;
1159 }
1160 else
1161 {
1162 OtsPoint3d p2 = this.points[index + 1];
1163 lastPoint = new OtsPoint3d(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
1164 p1.z + fraction * (p2.z - p1.z));
1165
1166 }
1167 OtsPoint3d[] coords = new OtsPoint3d[index + 2];
1168 for (int i = 0; i <= index; i++)
1169 {
1170 coords[i] = this.points[i];
1171 }
1172 coords[index + 1] = lastPoint;
1173 return new OtsLine3d(coords);
1174 }
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217 public final synchronized double projectOrthogonal(final double x, final double y)
1218 {
1219
1220
1221 makeLengthIndexedLine();
1222 double minDistance = Double.POSITIVE_INFINITY;
1223 double minSegmentFraction = 0;
1224 int minSegment = -1;
1225
1226
1227 for (int i = 0; i < size() - 1; i++)
1228 {
1229 double dx = this.points[i + 1].x - this.points[i].x;
1230 double dy = this.points[i + 1].y - this.points[i].y;
1231
1232 double px = x - this.points[i].x;
1233 double py = y - this.points[i].y;
1234
1235 double dot1 = px * dx + py * dy;
1236 double f;
1237 double distance;
1238 if (dot1 > 0)
1239 {
1240
1241 px = dx - px;
1242 py = dy - py;
1243
1244 double dot2 = px * dx + py * dy;
1245 if (dot2 > 0)
1246 {
1247
1248 double len2 = dx * dx + dy * dy;
1249 double proj = dot2 * dot2 / len2;
1250 f = dot1 / len2;
1251 distance = px * px + py * py - proj;
1252 }
1253 else
1254 {
1255
1256 f = 1;
1257 distance = px * px + py * py;
1258 }
1259 }
1260 else
1261 {
1262
1263 f = 0;
1264 distance = px * px + py * py;
1265 }
1266
1267 if (distance < minDistance)
1268 {
1269 minDistance = distance;
1270 minSegmentFraction = f;
1271 minSegment = i;
1272 }
1273 }
1274
1275
1276 double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
1277 return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / getLengthSI();
1278
1279 }
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336 public final synchronized double projectFractional(final Direction start, final Direction end, final double x,
1337 final double y, final FractionalFallback fallback)
1338 {
1339
1340
1341 makeLengthIndexedLine();
1342 double minDistance = Double.POSITIVE_INFINITY;
1343 double minSegmentFraction = 0;
1344 int minSegment = -1;
1345 OtsPoint3d point = new OtsPoint3d(x, y);
1346
1347
1348 determineFractionalHelpers(start, end);
1349
1350
1351 double[] d = new double[this.points.length - 1];
1352 double minD = Double.POSITIVE_INFINITY;
1353 for (int i = 0; i < this.points.length - 1; i++)
1354 {
1355 d[i] = Line2D.ptSegDist(this.points[i].x, this.points[i].y, this.points[i + 1].x, this.points[i + 1].y, x, y);
1356 minD = d[i] < minD ? d[i] : minD;
1357 }
1358
1359
1360 double distance;
1361 for (int i = 0; i < this.points.length - 1; i++)
1362 {
1363
1364 if (d[i] > minD + FRAC_PROJ_PRECISION)
1365 {
1366 continue;
1367 }
1368 OtsPoint3d center = this.fractionalHelperCenters[i];
1369 OtsPoint3d p;
1370 if (center != null)
1371 {
1372
1373 p = OtsPoint3d.intersectionOfLines(center, point, this.points[i], this.points[i + 1]);
1374 if (p == null || (x < center.x + FRAC_PROJ_PRECISION && center.x + FRAC_PROJ_PRECISION < p.x)
1375 || (x > center.x - FRAC_PROJ_PRECISION && center.x - FRAC_PROJ_PRECISION > p.x)
1376 || (y < center.y + FRAC_PROJ_PRECISION && center.y + FRAC_PROJ_PRECISION < p.y)
1377 || (y > center.y - FRAC_PROJ_PRECISION && center.y - FRAC_PROJ_PRECISION > p.y))
1378 {
1379
1380 continue;
1381 }
1382 }
1383 else
1384 {
1385
1386 OtsPoint3d offsetPoint =
1387 new OtsPoint3d(x + this.fractionalHelperDirections[i].x, y + this.fractionalHelperDirections[i].y);
1388 p = OtsPoint3d.intersectionOfLines(point, offsetPoint, this.points[i], this.points[i + 1]);
1389 }
1390 double segLength = this.points[i].distance(this.points[i + 1]).si + FRAC_PROJ_PRECISION;
1391 if (p == null || this.points[i].distance(p).si > segLength || this.points[i + 1].distance(p).si > segLength)
1392 {
1393
1394
1395 continue;
1396 }
1397
1398 double dx = x - p.x;
1399 double dy = y - p.y;
1400 distance = Math.sqrt(dx * dx + dy * dy);
1401
1402 if (distance < minDistance)
1403 {
1404 dx = p.x - this.points[i].x;
1405 dy = p.y - this.points[i].y;
1406 double dFrac = Math.sqrt(dx * dx + dy * dy);
1407
1408 minDistance = distance;
1409 minSegmentFraction = dFrac / (this.lengthIndexedLine[i + 1] - this.lengthIndexedLine[i]);
1410 minSegment = i;
1411 }
1412 }
1413
1414
1415 if (minSegment == -1)
1416
1417 {
1418
1419
1420
1421
1422
1423
1424
1425 return fallback.getFraction(this, x, y);
1426 }
1427
1428 double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
1429 return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / getLengthSI();
1430
1431 }
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444 public enum FractionalFallback
1445 {
1446
1447 ORTHOGONAL
1448 {
1449 @Override
1450 double getFraction(final OtsLine3d line, final double x, final double y)
1451 {
1452 return line.projectOrthogonal(x, y);
1453 }
1454 },
1455
1456
1457 ENDPOINT
1458 {
1459 @Override
1460 double getFraction(final OtsLine3d line, final double x, final double y)
1461 {
1462 OtsPoint3d point = new OtsPoint3d(x, y);
1463 double dStart = point.distanceSI(line.getFirst());
1464 double dEnd = point.distanceSI(line.getLast());
1465 if (dStart < dEnd)
1466 {
1467 return -dStart / line.getLengthSI();
1468 }
1469 else
1470 {
1471 return (dEnd + line.getLengthSI()) / line.getLengthSI();
1472 }
1473 }
1474 },
1475
1476
1477 NaN
1478 {
1479 @Override
1480 double getFraction(final OtsLine3d line, final double x, final double y)
1481 {
1482 return Double.NaN;
1483 }
1484 };
1485
1486
1487
1488
1489
1490
1491
1492
1493 abstract double getFraction(OtsLine3d line, double x, double y);
1494
1495 }
1496
1497
1498
1499
1500
1501
1502
1503 private synchronized void determineFractionalHelpers(final Direction start, final Direction end)
1504 {
1505
1506 final int n = this.points.length - 1;
1507
1508
1509 if (this.fractionalHelperCenters == null)
1510 {
1511 this.fractionalHelperCenters = new OtsPoint3d[n];
1512 this.fractionalHelperDirections = new Point2D.Double[n];
1513 if (this.points.length > 2)
1514 {
1515
1516 OtsLine3d prevOfsSeg = unitOffsetSegment(0);
1517 OtsLine3d nextOfsSeg = unitOffsetSegment(1);
1518 OtsPoint3d parStartPoint;
1519 try
1520 {
1521 parStartPoint = OtsPoint3d.intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0),
1522 nextOfsSeg.get(1));
1523 if (parStartPoint == null || prevOfsSeg.get(1).distanceSI(nextOfsSeg.get(0)) < Math
1524 .min(prevOfsSeg.get(1).distanceSI(parStartPoint), nextOfsSeg.get(0).distanceSI(parStartPoint)))
1525 {
1526 parStartPoint = new OtsPoint3d((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
1527 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
1528 }
1529 }
1530 catch (OtsGeometryException oge)
1531 {
1532
1533 throw new RuntimeException(oge);
1534 }
1535
1536 this.firstOffsetIntersection = parStartPoint;
1537
1538 for (int i = 1; i < this.points.length - 2; i++)
1539 {
1540 prevOfsSeg = nextOfsSeg;
1541 nextOfsSeg = unitOffsetSegment(i + 1);
1542 OtsPoint3d parEndPoint;
1543 try
1544 {
1545 parEndPoint = OtsPoint3d.intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0),
1546 nextOfsSeg.get(1));
1547 if (parEndPoint == null || prevOfsSeg.get(1).distanceSI(nextOfsSeg.get(0)) < Math
1548 .min(prevOfsSeg.get(1).distanceSI(parEndPoint), nextOfsSeg.get(0).distanceSI(parEndPoint)))
1549 {
1550 parEndPoint = new OtsPoint3d((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
1551 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
1552 }
1553 }
1554 catch (OtsGeometryException oge)
1555 {
1556
1557 throw new RuntimeException(oge);
1558 }
1559
1560 this.fractionalHelperCenters[i] =
1561 OtsPoint3d.intersectionOfLines(this.points[i], parStartPoint, this.points[i + 1], parEndPoint);
1562 if (this.fractionalHelperCenters[i] == null)
1563 {
1564
1565 this.fractionalHelperDirections[i] =
1566 new Point2D.Double(parStartPoint.x - this.points[i].x, parStartPoint.y - this.points[i].y);
1567 }
1568 parStartPoint = parEndPoint;
1569 }
1570
1571 this.lastOffsetIntersection = parStartPoint;
1572 }
1573 }
1574
1575
1576 double ang = (start == null ? Math.atan2(this.points[1].y - this.points[0].y, this.points[1].x - this.points[0].x)
1577 : start.getInUnit(DirectionUnit.DEFAULT)) + Math.PI / 2;
1578 OtsPoint3d p1 = new OtsPoint3d(this.points[0].x + Math.cos(ang), this.points[0].y + Math.sin(ang));
1579 ang = (end == null ? Math.atan2(this.points[n].y - this.points[n - 1].y, this.points[n].x - this.points[n - 1].x)
1580 : end.getInUnit(DirectionUnit.DEFAULT)) + Math.PI / 2;
1581 OtsPoint3d p2 = new OtsPoint3d(this.points[n].x + Math.cos(ang), this.points[n].y + Math.sin(ang));
1582
1583
1584 if (this.points.length > 2)
1585 {
1586 this.fractionalHelperCenters[0] =
1587 OtsPoint3d.intersectionOfLines(this.points[0], p1, this.points[1], this.firstOffsetIntersection);
1588 this.fractionalHelperCenters[n - 1] =
1589 OtsPoint3d.intersectionOfLines(this.points[n - 1], this.lastOffsetIntersection, this.points[n], p2);
1590 if (this.fractionalHelperCenters[n - 1] == null)
1591 {
1592
1593 this.fractionalHelperDirections[n - 1] = new Point2D.Double(p2.x - this.points[n].x, p2.y - this.points[n].y);
1594 }
1595 }
1596 else
1597 {
1598
1599 this.fractionalHelperCenters[0] = OtsPoint3d.intersectionOfLines(this.points[0], p1, this.points[1], p2);
1600 }
1601 if (this.fractionalHelperCenters[0] == null)
1602 {
1603
1604 this.fractionalHelperDirections[0] = new Point2D.Double(p1.x - this.points[0].x, p1.y - this.points[0].y);
1605 }
1606
1607 }
1608
1609
1610
1611
1612
1613
1614 private synchronized OtsLine3d unitOffsetSegment(final int segment)
1615 {
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639 OtsPoint3d from = new OtsPoint3d(this.points[segment].x, this.points[segment].y);
1640 OtsPoint3d to = new OtsPoint3d(this.points[segment + 1].x, this.points[segment + 1].y);
1641 try
1642 {
1643 OtsLine3d line = new OtsLine3d(from, to);
1644 return line.offsetLine(1.0);
1645 }
1646 catch (OtsGeometryException oge)
1647 {
1648
1649 throw new RuntimeException(oge);
1650 }
1651 }
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663 public synchronized Length getProjectedRadius(final double fraction) throws OtsGeometryException
1664 {
1665 Throw.when(fraction < 0.0 || fraction > 1.0, OtsGeometryException.class, "Fraction %f is out of bounds [0.0 ... 1.0]",
1666 fraction);
1667 if (this.vertexRadii == null)
1668 {
1669 this.vertexRadii = new Length[size() - 1];
1670 }
1671 int index = find(fraction * getLength().si);
1672 if (index > 0 && this.vertexRadii[index] == null)
1673 {
1674 this.vertexRadii[index] = getProjectedVertexRadius(index);
1675 }
1676 if (index < size() - 2 && this.vertexRadii[index + 1] == null)
1677 {
1678 this.vertexRadii[index + 1] = getProjectedVertexRadius(index + 1);
1679 }
1680 if (index == 0)
1681 {
1682 if (this.vertexRadii.length < 2)
1683 {
1684 return Length.instantiateSI(Double.NaN);
1685 }
1686 return this.vertexRadii[1];
1687 }
1688 if (index == size() - 2)
1689 {
1690 return this.vertexRadii[size() - 2];
1691 }
1692 return Math.abs(this.vertexRadii[index].si) < Math.abs(this.vertexRadii[index + 1].si) ? this.vertexRadii[index]
1693 : this.vertexRadii[index + 1];
1694 }
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705 public synchronized Length getProjectedVertexRadius(final int index) throws OtsGeometryException
1706 {
1707 Throw.when(index < 1 || index > size() - 2, OtsGeometryException.class, "Index %d is out of bounds [1 ... size() - 2].",
1708 index);
1709 makeLengthIndexedLine();
1710 determineFractionalHelpers(null, null);
1711 double length1 = this.lengthIndexedLine[index] - this.lengthIndexedLine[index - 1];
1712 double length2 = this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index];
1713 int shortIndex = length1 < length2 ? index : index + 1;
1714
1715 OtsPoint3d p1 = new OtsPoint3d(.5 * (this.points[shortIndex - 1].x + this.points[shortIndex].x),
1716 .5 * (this.points[shortIndex - 1].y + this.points[shortIndex].y),
1717 .5 * (this.points[shortIndex - 1].z + this.points[shortIndex].z));
1718
1719 OtsPoint3d p2 = new OtsPoint3d(p1.x + (this.points[shortIndex].y - this.points[shortIndex - 1].y),
1720 p1.y - (this.points[shortIndex].x - this.points[shortIndex - 1].x), p1.z);
1721
1722 OtsPoint3d p3 = this.points[index];
1723
1724 OtsPoint3d p4 = this.fractionalHelperCenters[index];
1725 if (p4 == null)
1726 {
1727
1728 p4 = new OtsPoint3d(p3.x + this.fractionalHelperDirections[index].x,
1729 p3.y + this.fractionalHelperDirections[index].y);
1730 }
1731 OtsPoint3d intersection = OtsPoint3d.intersectionOfLines(p1, p2, p3, p4);
1732 if (null == intersection)
1733 {
1734 return Length.instantiateSI(Double.NaN);
1735 }
1736
1737 double refLength = length1 < length2 ? length1 : length2;
1738 Length radius = intersection.horizontalDistance(p1);
1739 Length i2p2 = intersection.horizontalDistance(p2);
1740 if (radius.si < i2p2.si && i2p2.si > refLength)
1741 {
1742
1743 return radius;
1744 }
1745
1746 return radius.neg();
1747 }
1748
1749
1750
1751
1752
1753
1754
1755 public synchronized double getVertexFraction(final int index) throws OtsGeometryException
1756 {
1757 Throw.when(index < 0 || index > size() - 1, OtsGeometryException.class, "Index %d is out of bounds [0 %d].", index,
1758 size() - 1);
1759 makeLengthIndexedLine();
1760 return this.lengthIndexedLine[index] / getLengthSI();
1761 }
1762
1763
1764
1765
1766 private synchronized void calcCentroidBounds()
1767 {
1768 double minX = Double.POSITIVE_INFINITY;
1769 double minY = Double.POSITIVE_INFINITY;
1770 double minZ = Double.POSITIVE_INFINITY;
1771 double maxX = Double.NEGATIVE_INFINITY;
1772 double maxY = Double.NEGATIVE_INFINITY;
1773 double maxZ = Double.NEGATIVE_INFINITY;
1774 for (OtsPoint3d p : this.points)
1775 {
1776 minX = Math.min(minX, p.x);
1777 minY = Math.min(minY, p.y);
1778 minZ = Math.min(minZ, p.z);
1779 maxX = Math.max(maxX, p.x);
1780 maxY = Math.max(maxY, p.y);
1781 maxZ = Math.max(maxZ, p.z);
1782 }
1783 this.centroid = new OtsPoint3d((maxX + minX) / 2, (maxY + minY) / 2, (maxZ + minZ) / 2);
1784 double deltaX = maxX - minX;
1785 double deltaY = maxY - minY;
1786 double deltaZ = maxZ - minZ;
1787 this.bounds = new Bounds(new Point3d(-deltaX / 2.0, -deltaY / 2.0, -deltaZ / 2.0),
1788 new Point3d(deltaX / 2, deltaY / 2, deltaZ / 2));
1789 this.envelope = new Bounds(new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0));
1790 }
1791
1792
1793
1794
1795
1796 public final synchronized OtsPoint3d getCentroid()
1797 {
1798 if (this.centroid == null)
1799 {
1800 calcCentroidBounds();
1801 }
1802 return this.centroid;
1803 }
1804
1805
1806
1807
1808
1809 public final synchronized Bounds getEnvelope()
1810 {
1811 if (this.envelope == null)
1812 {
1813 calcCentroidBounds();
1814 }
1815 return this.envelope;
1816 }
1817
1818
1819 @Override
1820 @SuppressWarnings("checkstyle:designforextension")
1821 public synchronized DirectedPoint getLocation()
1822 {
1823 if (this.centroid == null)
1824 {
1825 calcCentroidBounds();
1826 }
1827 return this.centroid.getDirectedPoint();
1828 }
1829
1830
1831 @Override
1832 @SuppressWarnings("checkstyle:designforextension")
1833 public synchronized Bounds getBounds()
1834 {
1835 if (this.bounds == null)
1836 {
1837 calcCentroidBounds();
1838 }
1839 return this.bounds;
1840 }
1841
1842
1843 @Override
1844 @SuppressWarnings("checkstyle:designforextension")
1845 public String toString()
1846 {
1847 return Arrays.toString(this.points);
1848 }
1849
1850
1851 @Override
1852 @SuppressWarnings("checkstyle:designforextension")
1853 public int hashCode()
1854 {
1855 final int prime = 31;
1856 int result = 1;
1857 result = prime * result + Arrays.hashCode(this.points);
1858 return result;
1859 }
1860
1861
1862 @Override
1863 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
1864 public boolean equals(final Object obj)
1865 {
1866 if (this == obj)
1867 return true;
1868 if (obj == null)
1869 return false;
1870 if (getClass() != obj.getClass())
1871 return false;
1872 OtsLine3d other = (OtsLine3d) obj;
1873 if (!Arrays.equals(this.points, other.points))
1874 return false;
1875 return true;
1876 }
1877
1878
1879
1880
1881
1882 public final String toExcel()
1883 {
1884 StringBuffer s = new StringBuffer();
1885 for (OtsPoint3d p : this.points)
1886 {
1887 s.append(p.x + "\t" + p.y + "\n");
1888 }
1889 return s.toString();
1890 }
1891
1892
1893
1894
1895
1896 public final String toPlot()
1897 {
1898 StringBuffer result = new StringBuffer();
1899 for (OtsPoint3d p : this.points)
1900 {
1901 result.append(String.format(Locale.US, "%s%.3f,%.3f", 0 == result.length() ? "M" : " L", p.x, p.y));
1902 }
1903 result.append("\n");
1904 return result.toString();
1905 }
1906
1907 }