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