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