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