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