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
12 import javax.media.j3d.Bounds;
13
14 import org.djunits.unit.LengthUnit;
15 import org.djunits.value.vdouble.scalar.Direction;
16 import org.djunits.value.vdouble.scalar.Length;
17
18 import com.vividsolutions.jts.geom.Coordinate;
19 import com.vividsolutions.jts.geom.CoordinateSequence;
20 import com.vividsolutions.jts.geom.Envelope;
21 import com.vividsolutions.jts.geom.Geometry;
22 import com.vividsolutions.jts.geom.GeometryFactory;
23 import com.vividsolutions.jts.geom.LineString;
24 import com.vividsolutions.jts.linearref.LengthIndexedLine;
25
26 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
27 import nl.tudelft.simulation.dsol.animation.Locatable;
28 import nl.tudelft.simulation.language.d3.BoundingBox;
29 import nl.tudelft.simulation.language.d3.DirectedPoint;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 public class OTSLine3D implements Locatable, Serializable
46 {
47
48 private static final long serialVersionUID = 20150722L;
49
50
51 private OTSPoint3D[] points;
52
53
54 private double[] lengthIndexedLine = null;
55
56
57 private double length = Double.NaN;
58
59
60 private OTSPoint3D centroid = null;
61
62
63 private Bounds bounds = null;
64
65
66 private OTSPoint3D[] fractionalHelperCenters = null;
67
68
69 private Point2D.Double[] fractionalHelperDirections = null;
70
71
72 private OTSPoint3D firstOffsetIntersection;
73
74
75 private OTSPoint3D lastOffsetIntersection;
76
77
78 private static final double FRAC_PROJ_PRECISION = 1e-6;
79
80
81 private Envelope envelope;
82
83
84
85
86
87
88
89 public OTSLine3D(final OTSPoint3D... points) throws OTSGeometryException
90 {
91 init(points);
92 }
93
94
95
96
97
98
99
100 private void init(final OTSPoint3D... pts) throws OTSGeometryException
101 {
102 if (pts.length < 2)
103 {
104 throw new OTSGeometryException("Degenerate OTSLine3D; has " + pts.length + " point" + (pts.length != 1 ? "s" : ""));
105 }
106 this.lengthIndexedLine = new double[pts.length];
107 this.lengthIndexedLine[0] = 0.0;
108 for (int i = 1; i < pts.length; i++)
109 {
110 if (pts[i - 1].x == pts[i].x && pts[i - 1].y == pts[i].y && pts[i - 1].z == pts[i].z)
111 {
112 throw new OTSGeometryException(
113 "Degenerate OTSLine3D; point " + (i - 1) + " has the same x, y and z as point " + i);
114 }
115 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + pts[i - 1].distanceSI(pts[i]);
116 }
117 this.points = pts;
118 }
119
120
121 public enum OffsetMethod
122 {
123
124 JTS,
125
126
127 PK;
128 };
129
130
131 public static final OffsetMethod OFFSETMETHOD = OffsetMethod.PK;
132
133
134
135
136
137
138
139 public final OTSLine3D offsetLine(final double offset)
140 {
141 try
142 {
143 switch (OFFSETMETHOD)
144 {
145 case PK:
146 return OTSOffsetLinePK.offsetLine(this, offset);
147
148 case JTS:
149 return OTSBufferingJTS.offsetGeometryOLD(this, offset);
150
151 default:
152 return null;
153 }
154 }
155 catch (OTSGeometryException exception)
156 {
157 exception.printStackTrace();
158 return null;
159 }
160 }
161
162
163
164
165
166
167
168 public final OTSLine3D noiseFilteredLine(final double noiseLevel)
169 {
170 if (this.size() <= 2)
171 {
172 return this;
173 }
174 OTSPoint3D prevPoint = null;
175 List<OTSPoint3D> list = null;
176 for (int index = 0; index < this.size(); index++)
177 {
178 OTSPoint3D currentPoint = this.points[index];
179 if (null != prevPoint && prevPoint.distanceSI(currentPoint) < noiseLevel)
180 {
181 if (null == list)
182 {
183
184 list = new ArrayList<OTSPoint3D>();
185 for (int i = 0; i < index; i++)
186 {
187 list.add(this.points[i]);
188 }
189 }
190 if (index == this.size() - 1)
191 {
192 if (list.size() > 1)
193 {
194
195 list.set(list.size() - 1, currentPoint);
196 }
197 else
198 {
199
200
201 list.add(currentPoint);
202 }
203 }
204 continue;
205 }
206 else if (null != list)
207 {
208 list.add(currentPoint);
209 }
210 prevPoint = currentPoint;
211 }
212 if (null == list)
213 {
214 return this;
215 }
216 try
217 {
218 return new OTSLine3D(list);
219 }
220 catch (OTSGeometryException exception)
221 {
222 System.err.println("CANNOT HAPPEN");
223 exception.printStackTrace();
224 throw new Error(exception);
225 }
226 }
227
228
229
230
231
232
233
234
235
236 public final OTSLine3D noiseFilterRamerDouglasPeuker(final double epsilon, final boolean useHorizontalDistance)
237 {
238 try
239 {
240
241
242 double maxDeviation = 0;
243 int splitIndex = -1;
244 int pointCount = size();
245 OTSLine3D straight = new OTSLine3D(get(0), get(pointCount - 1));
246
247 for (int i = 1; i < pointCount - 1; i++)
248 {
249 OTSPoint3D point = get(i);
250 OTSPoint3D closest =
251 useHorizontalDistance ? point.closestPointOnLine2D(straight) : point.closestPointOnLine(straight);
252 double deviation = useHorizontalDistance ? closest.horizontalDistanceSI(point) : closest.distanceSI(point);
253 if (deviation > maxDeviation)
254 {
255 splitIndex = i;
256 maxDeviation = deviation;
257 }
258 }
259 if (maxDeviation <= epsilon)
260 {
261
262 return straight;
263 }
264
265
266
267 OTSLine3D first = new OTSLine3D(Arrays.copyOfRange(this.points, 0, splitIndex + 1))
268 .noiseFilterRamerDouglasPeuker(epsilon, useHorizontalDistance);
269 OTSLine3D second = new OTSLine3D(Arrays.copyOfRange(this.points, splitIndex, this.points.length))
270 .noiseFilterRamerDouglasPeuker(epsilon, useHorizontalDistance);
271 return concatenate(epsilon, first, second);
272 }
273 catch (OTSGeometryException exception)
274 {
275 exception.printStackTrace();
276 return null;
277 }
278 }
279
280
281
282
283
284
285
286
287
288 public final OTSLine3D offsetLine(final double offsetAtStart, final double offsetAtEnd) throws OTSGeometryException
289 {
290
291
292
293 OTSLine3D offsetLineAtStart = offsetLine(offsetAtStart);
294 if (offsetAtStart == offsetAtEnd)
295 {
296 return offsetLineAtStart;
297 }
298
299 OTSLine3D offsetLineAtEnd = offsetLine(offsetAtEnd);
300
301 Geometry startGeometry = offsetLineAtStart.getLineString();
302 Geometry endGeometry = offsetLineAtEnd.getLineString();
303 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
304 double firstLength = startGeometry.getLength();
305 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
306 double secondLength = endGeometry.getLength();
307 ArrayList<Coordinate> out = new ArrayList<Coordinate>();
308 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
309 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
310 int firstIndex = 0;
311 int secondIndex = 0;
312 Coordinate prevCoordinate = null;
313 final double tooClose = 0.05;
314 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
315 {
316 double firstRatio = firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
317 : Double.MAX_VALUE;
318 double secondRatio = secondIndex < secondCoordinates.length
319 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
320 double ratio;
321 if (firstRatio < secondRatio)
322 {
323 ratio = firstRatio;
324 firstIndex++;
325 }
326 else
327 {
328 ratio = secondRatio;
329 secondIndex++;
330 }
331 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
332 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
333 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
334 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
335 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
336 {
337 out.add(resultCoordinate);
338 prevCoordinate = resultCoordinate;
339 }
340 }
341 Coordinate[] resultCoordinates = new Coordinate[out.size()];
342 for (int index = 0; index < out.size(); index++)
343 {
344 resultCoordinates[index] = out.get(index);
345 }
346 return new OTSLine3D(resultCoordinates);
347 }
348
349
350
351
352
353
354
355
356
357
358 public final OTSLine3D offsetLine(final double[] relativeFractions, final double[] offsets) throws OTSGeometryException
359 {
360 OTSLine3D[] offsetLine = new OTSLine3D[relativeFractions.length];
361 for (int i = 0; i < offsets.length; i++)
362 {
363 offsetLine[i] = offsetLine(offsets[i]);
364
365
366 }
367
368 ArrayList<Coordinate> out = new ArrayList<Coordinate>();
369 Coordinate prevCoordinate = null;
370 final double tooClose = 0.05;
371 for (int i = 0; i < offsets.length - 1; i++)
372 {
373 Geometry startGeometry =
374 offsetLine[i].extractFractional(relativeFractions[i], relativeFractions[i + 1]).getLineString();
375 Geometry endGeometry =
376 offsetLine[i + 1].extractFractional(relativeFractions[i], relativeFractions[i + 1]).getLineString();
377 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
378 double firstLength = startGeometry.getLength();
379 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
380 double secondLength = endGeometry.getLength();
381 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
382 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
383 int firstIndex = 0;
384 int secondIndex = 0;
385 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
386 {
387 double firstRatio = firstIndex < firstCoordinates.length
388 ? first.indexOf(firstCoordinates[firstIndex]) / firstLength : Double.MAX_VALUE;
389 double secondRatio = secondIndex < secondCoordinates.length
390 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
391 double ratio;
392 if (firstRatio < secondRatio)
393 {
394 ratio = firstRatio;
395 firstIndex++;
396 }
397 else
398 {
399 ratio = secondRatio;
400 secondIndex++;
401 }
402 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
403 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
404 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
405 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
406 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
407 {
408 out.add(resultCoordinate);
409 prevCoordinate = resultCoordinate;
410 }
411 }
412 }
413
414 Coordinate[] resultCoordinates = new Coordinate[out.size()];
415 for (int index = 0; index < out.size(); index++)
416 {
417 resultCoordinates[index] = out.get(index);
418 }
419 return new OTSLine3D(resultCoordinates);
420 }
421
422
423
424
425
426
427
428
429 public static OTSLine3D concatenate(final OTSLine3D... lines) throws OTSGeometryException
430 {
431 return concatenate(0.0, lines);
432 }
433
434
435
436
437
438
439
440
441
442 public static OTSLine3D concatenate(final double toleranceSI, final OTSLine3D line1, final OTSLine3D line2)
443 throws OTSGeometryException
444 {
445 if (line1.getLast().distance(line2.getFirst()).si > toleranceSI)
446 {
447 throw new OTSGeometryException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst()
448 + " distance is " + line1.getLast().distance(line2.getFirst()).si + " > " + toleranceSI);
449 }
450 int size = line1.size() + line2.size() - 1;
451 OTSPoint3D[] points = new OTSPoint3D[size];
452 int nextIndex = 0;
453 for (int j = 0; j < line1.size(); j++)
454 {
455 points[nextIndex++] = line1.get(j);
456 }
457 for (int j = 1; j < line2.size(); j++)
458 {
459 points[nextIndex++] = line2.get(j);
460 }
461 return new OTSLine3D(points);
462 }
463
464
465
466
467
468
469
470
471
472 public static OTSLine3D concatenate(final double toleranceSI, final OTSLine3D... lines) throws OTSGeometryException
473 {
474 System.out.println("Concatenating " + lines.length + " lines.");
475 if (0 == lines.length)
476 {
477 throw new OTSGeometryException("Empty argument list");
478 }
479 else if (1 == lines.length)
480 {
481 return lines[0];
482 }
483 int size = lines[0].size();
484 for (int i = 1; i < lines.length; i++)
485 {
486 if (lines[i - 1].getLast().distance(lines[i].getFirst()).si > toleranceSI)
487 {
488 throw new OTSGeometryException(
489 "Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst() + " distance is "
490 + lines[i - 1].getLast().distance(lines[i].getFirst()).si + " > " + toleranceSI);
491 }
492 size += lines[i].size() - 1;
493 }
494 OTSPoint3D[] points = new OTSPoint3D[size];
495 int nextIndex = 0;
496 for (int i = 0; i < lines.length; i++)
497 {
498 OTSLine3D line = lines[i];
499 for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
500 {
501 points[nextIndex++] = line.get(j);
502 }
503 }
504 return new OTSLine3D(points);
505 }
506
507
508
509
510
511 public final OTSLine3D reverse()
512 {
513 OTSPoint3D[] resultPoints = new OTSPoint3D[size()];
514 int nextIndex = size();
515 for (OTSPoint3D p : getPoints())
516 {
517 resultPoints[--nextIndex] = p;
518 }
519 try
520 {
521 return new OTSLine3D(resultPoints);
522 }
523 catch (OTSGeometryException exception)
524 {
525
526 throw new RuntimeException(exception);
527 }
528 }
529
530
531
532
533
534
535
536
537 public final OTSLine3D extractFractional(final double start, final double end) throws OTSGeometryException
538 {
539 if (start < 0 || start >= end || end > 1)
540 {
541 throw new OTSGeometryException("Bad interval");
542 }
543 getLength();
544 return extract(start * this.length, end * this.length);
545 }
546
547
548
549
550
551
552
553
554
555 public final OTSLine3D extract(final Length start, final Length end) throws OTSGeometryException
556 {
557 return extract(start.si, end.si);
558 }
559
560
561
562
563
564
565
566
567
568 @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
569 public final OTSLine3D extract(final double start, final double end) throws OTSGeometryException
570 {
571 if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLengthSI())
572 {
573 throw new OTSGeometryException(
574 "Bad interval (" + start + ".." + end + "; length of this OTSLine3D is " + this.getLengthSI() + ")");
575 }
576 double cumulativeLength = 0;
577 double nextCumulativeLength = 0;
578 double segmentLength = 0;
579 int index = 0;
580 List<OTSPoint3D> pointList = new ArrayList<>();
581
582 while (start > cumulativeLength)
583 {
584 OTSPoint3D fromPoint = this.points[index];
585 index++;
586 OTSPoint3D toPoint = this.points[index];
587 segmentLength = fromPoint.distanceSI(toPoint);
588 cumulativeLength = nextCumulativeLength;
589 nextCumulativeLength = cumulativeLength + segmentLength;
590 if (nextCumulativeLength >= start)
591 {
592 break;
593 }
594 }
595 if (start == nextCumulativeLength)
596 {
597 pointList.add(this.points[index]);
598 }
599 else
600 {
601 pointList.add(OTSPoint3D.interpolate((start - cumulativeLength) / segmentLength, this.points[index - 1],
602 this.points[index]));
603 if (end > nextCumulativeLength)
604 {
605 pointList.add(this.points[index]);
606 }
607 }
608 while (end > nextCumulativeLength)
609 {
610 OTSPoint3D fromPoint = this.points[index];
611 index++;
612 if (index >= this.points.length)
613 {
614 break;
615 }
616 OTSPoint3D toPoint = this.points[index];
617 segmentLength = fromPoint.distanceSI(toPoint);
618 cumulativeLength = nextCumulativeLength;
619 nextCumulativeLength = cumulativeLength + segmentLength;
620 if (nextCumulativeLength >= end)
621 {
622 break;
623 }
624 pointList.add(toPoint);
625 }
626 if (end == nextCumulativeLength)
627 {
628 pointList.add(this.points[index]);
629 }
630 else
631 {
632 pointList.add(OTSPoint3D.interpolate((end - cumulativeLength) / segmentLength, this.points[index - 1],
633 this.points[index]));
634 }
635 try
636 {
637 return new OTSLine3D(pointList);
638 }
639 catch (OTSGeometryException exception)
640 {
641 System.err.println("interval " + start + ".." + end + "too short");
642 throw new OTSGeometryException("interval " + start + ".." + end + "too short");
643 }
644 }
645
646
647
648
649
650
651 private static OTSPoint3D[] coordinatesToOTSPoint3D(final Coordinate[] coordinates)
652 {
653 OTSPoint3D[] result = new OTSPoint3D[coordinates.length];
654 for (int i = 0; i < coordinates.length; i++)
655 {
656 result[i] = new OTSPoint3D(coordinates[i]);
657 }
658 return result;
659 }
660
661
662
663
664
665
666
667 public static OTSLine3D createAndCleanOTSLine3D(final OTSPoint3D... points) throws OTSGeometryException
668 {
669 if (points.length < 2)
670 {
671 throw new OTSGeometryException(
672 "Degenerate OTSLine3D; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
673 }
674 return createAndCleanOTSLine3D(new ArrayList<>(Arrays.asList(points)));
675 }
676
677
678
679
680
681
682
683
684 public static OTSLine3D createAndCleanOTSLine3D(final List<OTSPoint3D> pointList) throws OTSGeometryException
685 {
686
687 int i = 1;
688 while (i < pointList.size())
689 {
690 if (pointList.get(i - 1).equals(pointList.get(i)))
691 {
692 pointList.remove(i);
693 }
694 else
695 {
696 i++;
697 }
698 }
699 return new OTSLine3D(pointList);
700 }
701
702
703
704
705
706
707
708 public OTSLine3D(final Coordinate[] coordinates) throws OTSGeometryException
709 {
710 this(coordinatesToOTSPoint3D(coordinates));
711 }
712
713
714
715
716
717
718
719 public OTSLine3D(final LineString lineString) throws OTSGeometryException
720 {
721 this(lineString.getCoordinates());
722 }
723
724
725
726
727
728
729
730 public OTSLine3D(final Geometry geometry) throws OTSGeometryException
731 {
732 this(geometry.getCoordinates());
733 }
734
735
736
737
738
739
740
741 public OTSLine3D(final List<OTSPoint3D> pointList) throws OTSGeometryException
742 {
743 this(pointList.toArray(new OTSPoint3D[pointList.size()]));
744 }
745
746
747
748
749
750
751
752 public OTSLine3D(final Path2D path) throws OTSGeometryException
753 {
754 List<OTSPoint3D> pl = new ArrayList<>();
755 for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next())
756 {
757 double[] p = new double[6];
758 int segType = pi.currentSegment(p);
759 if (segType == PathIterator.SEG_MOVETO || segType == PathIterator.SEG_LINETO)
760 {
761 pl.add(new OTSPoint3D(p[0], p[1]));
762 }
763 else if (segType == PathIterator.SEG_CLOSE)
764 {
765 if (!pl.get(0).equals(pl.get(pl.size() - 1)))
766 {
767 pl.add(new OTSPoint3D(pl.get(0).x, pl.get(0).y));
768 }
769 break;
770 }
771 }
772 init(pl.toArray(new OTSPoint3D[pl.size() - 1]));
773 }
774
775
776
777
778
779 public final Coordinate[] getCoordinates()
780 {
781 Coordinate[] result = new Coordinate[size()];
782 for (int i = 0; i < size(); i++)
783 {
784 result[i] = this.points[i].getCoordinate();
785 }
786 return result;
787 }
788
789
790
791
792
793 public final LineString getLineString()
794 {
795 GeometryFactory factory = new GeometryFactory();
796 Coordinate[] coordinates = getCoordinates();
797 CoordinateSequence cs = factory.getCoordinateSequenceFactory().create(coordinates);
798 return new LineString(cs, factory);
799 }
800
801
802
803
804
805 public final int size()
806 {
807 return this.points.length;
808 }
809
810
811
812
813
814 public final OTSPoint3D getFirst()
815 {
816 return this.points[0];
817 }
818
819
820
821
822
823 public final OTSPoint3D getLast()
824 {
825 return this.points[size() - 1];
826 }
827
828
829
830
831
832
833
834 public final OTSPoint3D get(final int i) throws OTSGeometryException
835 {
836 if (i < 0 || i > size() - 1)
837 {
838 throw new OTSGeometryException("OTSLine3D.get(i=" + i + "); i<0 or i>=size(), which is " + size());
839 }
840 return this.points[i];
841 }
842
843
844
845
846
847
848 public final synchronized double getLengthSI()
849 {
850 if (Double.isNaN(this.length))
851 {
852 this.length = 0.0;
853 for (int i = 0; i < size() - 1; i++)
854 {
855 this.length += this.points[i].distanceSI(this.points[i + 1]);
856 }
857 }
858 return this.length;
859 }
860
861
862
863
864
865
866 public final Length getLength()
867 {
868 return new Length(getLengthSI(), LengthUnit.SI);
869 }
870
871
872
873
874
875 public final OTSPoint3D[] getPoints()
876 {
877 return this.points;
878 }
879
880
881
882
883 private void makeLengthIndexedLine()
884 {
885 if (this.lengthIndexedLine == null)
886 {
887 this.lengthIndexedLine = new double[this.points.length];
888 this.lengthIndexedLine[0] = 0.0;
889 for (int i = 1; i < this.points.length; i++)
890 {
891 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + this.points[i - 1].distanceSI(this.points[i]);
892 }
893 }
894 }
895
896
897
898
899
900
901
902 public final DirectedPoint getLocationExtended(final Length position)
903 {
904 return getLocationExtendedSI(position.getSI());
905 }
906
907
908
909
910
911
912
913 public final DirectedPoint getLocationExtendedSI(final double positionSI)
914 {
915 makeLengthIndexedLine();
916 if (positionSI >= 0.0 && positionSI <= getLengthSI())
917 {
918 try
919 {
920 return getLocationSI(positionSI);
921 }
922 catch (OTSGeometryException exception)
923 {
924
925 }
926 }
927
928
929 if (positionSI < 0.0)
930 {
931 double len = positionSI;
932 double fraction = len / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
933 OTSPoint3D p1 = this.points[0];
934 OTSPoint3D p2 = this.points[1];
935 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
936 p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
937 }
938
939
940 int n1 = this.lengthIndexedLine.length - 1;
941 int n2 = this.lengthIndexedLine.length - 2;
942 double len = positionSI - getLengthSI();
943 double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
944 OTSPoint3D p1 = this.points[n2];
945 OTSPoint3D p2 = this.points[n1];
946 return new DirectedPoint(p2.x + fraction * (p2.x - p1.x), p2.y + fraction * (p2.y - p1.y),
947 p2.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
948 }
949
950
951
952
953
954
955
956 public final DirectedPoint getLocationFraction(final double fraction) throws OTSGeometryException
957 {
958 if (fraction < 0.0 || fraction > 1.0)
959 {
960 throw new OTSGeometryException("getLocationFraction for line: fraction < 0.0 or > 1.0. fraction = " + fraction);
961 }
962 return getLocationSI(fraction * getLengthSI());
963 }
964
965
966
967
968
969
970
971
972 public final DirectedPoint getLocationFraction(final double fraction, final double tolerance) throws OTSGeometryException
973 {
974 if (fraction < -tolerance || fraction > 1.0 + tolerance)
975 {
976 throw new OTSGeometryException(
977 "getLocationFraction for line: fraction < 0.0 - tolerance or > 1.0 + tolerance; fraction = " + fraction);
978 }
979 double f = fraction < 0 ? 0.0 : fraction > 1.0 ? 1.0 : fraction;
980 return getLocationSI(f * getLengthSI());
981 }
982
983
984
985
986
987
988 public final DirectedPoint getLocationFractionExtended(final double fraction)
989 {
990 return getLocationExtendedSI(fraction * getLengthSI());
991 }
992
993
994
995
996
997
998
999 public final DirectedPoint getLocation(final Length position) throws OTSGeometryException
1000 {
1001 return getLocationSI(position.getSI());
1002 }
1003
1004
1005
1006
1007
1008
1009
1010 private int find(final double pos) throws OTSGeometryException
1011 {
1012 if (pos == 0)
1013 {
1014 return 0;
1015 }
1016
1017 for (int i = 0; i < this.lengthIndexedLine.length - 2; i++)
1018 {
1019 if (pos > this.lengthIndexedLine[i] && pos <= this.lengthIndexedLine[i + 1])
1020 {
1021 return i;
1022 }
1023 }
1024
1025 return this.lengthIndexedLine.length - 2;
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 }
1050
1051
1052
1053
1054
1055
1056
1057 public final DirectedPoint getLocationSI(final double positionSI) throws OTSGeometryException
1058 {
1059 makeLengthIndexedLine();
1060 if (positionSI < 0.0 || positionSI > getLengthSI())
1061 {
1062 throw new OTSGeometryException("getLocationSI for line: position < 0.0 or > line length. Position = " + positionSI
1063 + " m. Length = " + getLengthSI() + " m.");
1064 }
1065
1066
1067 if (positionSI == 0.0)
1068 {
1069 OTSPoint3D p1 = this.points[0];
1070 OTSPoint3D p2 = this.points[1];
1071 return new DirectedPoint(p1.x, p1.y, p1.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1072 }
1073 if (positionSI == getLengthSI())
1074 {
1075 OTSPoint3D p1 = this.points[this.points.length - 2];
1076 OTSPoint3D p2 = this.points[this.points.length - 1];
1077 return new DirectedPoint(p2.x, p2.y, p2.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1078 }
1079
1080
1081 int index = find(positionSI);
1082 double remainder = positionSI - this.lengthIndexedLine[index];
1083 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
1084 OTSPoint3D p1 = this.points[index];
1085 OTSPoint3D p2 = this.points[index + 1];
1086 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
1087 p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
1088 }
1089
1090
1091
1092
1093
1094
1095
1096 public final OTSLine3D truncate(final double lengthSI) throws OTSGeometryException
1097 {
1098 makeLengthIndexedLine();
1099 if (lengthSI <= 0.0 || lengthSI > getLengthSI())
1100 {
1101 throw new OTSGeometryException("truncate for line: position <= 0.0 or > line length. Position = " + lengthSI
1102 + " m. Length = " + getLengthSI() + " m.");
1103 }
1104
1105
1106 if (lengthSI == getLengthSI())
1107 {
1108 return new OTSLine3D(getPoints());
1109 }
1110
1111
1112 int index = find(lengthSI);
1113 double remainder = lengthSI - this.lengthIndexedLine[index];
1114 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
1115 OTSPoint3D p1 = this.points[index];
1116 OTSPoint3D p2 = this.points[index + 1];
1117 OTSPoint3D newLastPoint = new OTSPoint3D(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
1118 p1.z + fraction * (p2.z - p1.z));
1119 OTSPoint3D[] coords = new OTSPoint3D[index + 2];
1120 for (int i = 0; i <= index; i++)
1121 {
1122 coords[i] = this.points[i];
1123 }
1124 coords[index + 1] = newLastPoint;
1125 return new OTSLine3D(coords);
1126 }
1127
1128
1129
1130
1131
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 public final double projectOrthogonal(final double x, final double y)
1170 {
1171
1172
1173 makeLengthIndexedLine();
1174 double minDistance = Double.POSITIVE_INFINITY;
1175 double minSegmentFraction = 0;
1176 int minSegment = -1;
1177
1178
1179 for (int i = 0; i < size() - 1; i++)
1180 {
1181 double dx = this.points[i + 1].x - this.points[i].x;
1182 double dy = this.points[i + 1].y - this.points[i].y;
1183
1184 double px = x - this.points[i].x;
1185 double py = y - this.points[i].y;
1186
1187 double dot1 = px * dx + py * dy;
1188 double f;
1189 double distance;
1190 if (dot1 > 0)
1191 {
1192
1193 px = dx - px;
1194 py = dy - py;
1195
1196 double dot2 = px * dx + py * dy;
1197 if (dot2 > 0)
1198 {
1199
1200 double len2 = dx * dx + dy * dy;
1201 double proj = dot2 * dot2 / len2;
1202 f = dot1 / len2;
1203 distance = px * px + py * py - proj;
1204 }
1205 else
1206 {
1207
1208 f = 1;
1209 distance = px * px + py * py;
1210 }
1211 }
1212 else
1213 {
1214
1215 f = 0;
1216 distance = px * px + py * py;
1217 }
1218
1219 if (distance < minDistance)
1220 {
1221 minDistance = distance;
1222 minSegmentFraction = f;
1223 minSegment = i;
1224 }
1225 }
1226
1227
1228 double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
1229 return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / getLengthSI();
1230
1231 }
1232
1233
1234
1235
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 public final double projectFractional(final Direction start, final Direction end, final double x, final double y)
1287 {
1288
1289
1290 makeLengthIndexedLine();
1291 double minDistance = Double.POSITIVE_INFINITY;
1292 double minSegmentFraction = 0;
1293 int minSegment = -1;
1294 OTSPoint3D point = new OTSPoint3D(x, y);
1295
1296
1297 determineFractionalHelpers(start, end);
1298
1299
1300 double[] d = new double[this.points.length - 1];
1301 double minD = Double.POSITIVE_INFINITY;
1302 for (int i = 0; i < this.points.length - 1; i++)
1303 {
1304 d[i] = Line2D.ptSegDist(this.points[i].x, this.points[i].y, this.points[i + 1].x, this.points[i + 1].y, x, y);
1305 minD = d[i] < minD ? d[i] : minD;
1306 }
1307
1308
1309 double distance;
1310 for (int i = 0; i < this.points.length - 1; i++)
1311 {
1312
1313 if (d[i] > minD + FRAC_PROJ_PRECISION)
1314 {
1315 continue;
1316 }
1317 OTSPoint3D center = this.fractionalHelperCenters[i];
1318 OTSPoint3D p;
1319 if (center != null)
1320 {
1321
1322 p = OTSPoint3D.intersectionOfLines(center, point, this.points[i], this.points[i + 1]);
1323 if (p == null || (x < center.x + FRAC_PROJ_PRECISION && center.x + FRAC_PROJ_PRECISION < p.x)
1324 || (x > center.x - FRAC_PROJ_PRECISION && center.x - FRAC_PROJ_PRECISION > p.x)
1325 || (y < center.y + FRAC_PROJ_PRECISION && center.y + FRAC_PROJ_PRECISION < p.y)
1326 || (y > center.y - FRAC_PROJ_PRECISION && center.y - FRAC_PROJ_PRECISION > p.y))
1327 {
1328
1329 continue;
1330 }
1331 }
1332 else
1333 {
1334
1335 OTSPoint3D offsetPoint =
1336 new OTSPoint3D(x + this.fractionalHelperDirections[i].x, y + this.fractionalHelperDirections[i].y);
1337 p = OTSPoint3D.intersectionOfLines(point, offsetPoint, this.points[i], this.points[i + 1]);
1338 }
1339 if (p == null || p.x < Math.min(this.points[i].x, this.points[i + 1].x) - FRAC_PROJ_PRECISION
1340 || p.x > Math.max(this.points[i].x, this.points[i + 1].x) + FRAC_PROJ_PRECISION
1341 || p.y < Math.min(this.points[i].y, this.points[i + 1].y) - FRAC_PROJ_PRECISION
1342 || p.y > Math.max(this.points[i].y, this.points[i + 1].y) + FRAC_PROJ_PRECISION)
1343 {
1344
1345
1346 continue;
1347 }
1348
1349 double dx = x - p.x;
1350 double dy = y - p.y;
1351 distance = Math.sqrt(dx * dx + dy * dy);
1352
1353 if (distance < minDistance)
1354 {
1355 dx = p.x - this.points[i].x;
1356 dy = p.y - this.points[i].y;
1357 double dFrac = Math.sqrt(dx * dx + dy * dy);
1358
1359 minDistance = distance;
1360 minSegmentFraction = dFrac / (this.lengthIndexedLine[i + 1] - this.lengthIndexedLine[i]);
1361 minSegment = i;
1362 }
1363 }
1364
1365
1366 if (minSegment == -1)
1367 {
1368
1369
1370
1371
1372
1373 return projectOrthogonal(x, y);
1374 }
1375 double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
1376 return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / getLengthSI();
1377
1378 }
1379
1380
1381
1382
1383
1384
1385
1386 private void determineFractionalHelpers(final Direction start, final Direction end)
1387 {
1388
1389 final int n = this.points.length - 1;
1390
1391
1392 if (this.fractionalHelperCenters == null)
1393 {
1394 this.fractionalHelperCenters = new OTSPoint3D[n];
1395 this.fractionalHelperDirections = new Point2D.Double[n];
1396 if (this.points.length > 2)
1397 {
1398
1399 OTSLine3D prevOfsSeg = unitOffsetSegment(0);
1400 OTSLine3D nextOfsSeg = unitOffsetSegment(1);
1401 OTSPoint3D parStartPoint;
1402 try
1403 {
1404 parStartPoint = OTSPoint3D.intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0),
1405 nextOfsSeg.get(1));
1406 if (parStartPoint == null)
1407 {
1408 parStartPoint = new OTSPoint3D((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
1409 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
1410 }
1411 }
1412 catch (OTSGeometryException oge)
1413 {
1414
1415 throw new RuntimeException(oge);
1416 }
1417
1418 this.firstOffsetIntersection = parStartPoint;
1419
1420 for (int i = 1; i < this.points.length - 2; i++)
1421 {
1422 prevOfsSeg = nextOfsSeg;
1423 nextOfsSeg = unitOffsetSegment(i + 1);
1424 OTSPoint3D parEndPoint;
1425 try
1426 {
1427 parEndPoint = OTSPoint3D.intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0),
1428 nextOfsSeg.get(1));
1429 if (parEndPoint == null)
1430 {
1431 parEndPoint = new OTSPoint3D((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
1432 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
1433 }
1434 }
1435 catch (OTSGeometryException oge)
1436 {
1437
1438 throw new RuntimeException(oge);
1439 }
1440
1441 this.fractionalHelperCenters[i] =
1442 OTSPoint3D.intersectionOfLines(this.points[i], parStartPoint, this.points[i + 1], parEndPoint);
1443 if (this.fractionalHelperCenters[i] == null)
1444 {
1445
1446 this.fractionalHelperDirections[i] =
1447 new Point2D.Double(parStartPoint.x - this.points[i].x, parStartPoint.y - this.points[i].y);
1448 }
1449 parStartPoint = parEndPoint;
1450 }
1451
1452 this.lastOffsetIntersection = parStartPoint;
1453 }
1454 }
1455
1456
1457 double ang = start.si + Math.PI / 2;
1458 OTSPoint3D p1 = new OTSPoint3D(this.points[0].x + Math.cos(ang), this.points[0].y + Math.sin(ang));
1459 ang = end.si + Math.PI / 2;
1460 OTSPoint3D p2 = new OTSPoint3D(this.points[n].x + Math.cos(ang), this.points[n].y + Math.sin(ang));
1461
1462
1463 if (this.points.length > 2)
1464 {
1465 this.fractionalHelperCenters[0] =
1466 OTSPoint3D.intersectionOfLines(this.points[0], p1, this.points[1], this.firstOffsetIntersection);
1467 this.fractionalHelperCenters[n - 1] =
1468 OTSPoint3D.intersectionOfLines(this.points[n - 1], this.lastOffsetIntersection, this.points[n], p2);
1469 if (this.fractionalHelperCenters[n - 1] == null)
1470 {
1471
1472 this.fractionalHelperDirections[n - 1] = new Point2D.Double(p2.x - this.points[n].x, p2.y - this.points[n].y);
1473 }
1474 }
1475 else
1476 {
1477
1478 this.fractionalHelperCenters[0] = OTSPoint3D.intersectionOfLines(this.points[0], p1, this.points[1], p2);
1479 this.fractionalHelperCenters[n - 1] = null;
1480 }
1481 if (this.fractionalHelperCenters[0] == null)
1482 {
1483
1484 this.fractionalHelperDirections[0] = new Point2D.Double(p1.x - this.points[0].x, p1.y - this.points[0].y);
1485 }
1486
1487 }
1488
1489
1490
1491
1492
1493
1494 private OTSLine3D unitOffsetSegment(final int segment)
1495 {
1496 OTSPoint3D from = new OTSPoint3D(this.points[segment].x, this.points[segment].y);
1497 OTSPoint3D to = new OTSPoint3D(this.points[segment + 1].x, this.points[segment + 1].y);
1498 try
1499 {
1500 OTSLine3D line = new OTSLine3D(from, to);
1501 return line.offsetLine(1.0);
1502 }
1503 catch (OTSGeometryException oge)
1504 {
1505
1506 throw new RuntimeException(oge);
1507 }
1508 }
1509
1510
1511
1512
1513
1514 private void calcCentroidBounds()
1515 {
1516 double minX = Double.POSITIVE_INFINITY;
1517 double minY = Double.POSITIVE_INFINITY;
1518 double minZ = Double.POSITIVE_INFINITY;
1519 double maxX = Double.NEGATIVE_INFINITY;
1520 double maxY = Double.NEGATIVE_INFINITY;
1521 double maxZ = Double.NEGATIVE_INFINITY;
1522 for (OTSPoint3D p : this.points)
1523 {
1524 minX = Math.min(minX, p.x);
1525 minY = Math.min(minY, p.y);
1526 minZ = Math.min(minZ, p.z);
1527 maxX = Math.max(maxX, p.x);
1528 maxY = Math.max(maxY, p.y);
1529 maxZ = Math.max(maxZ, p.z);
1530 }
1531 this.centroid = new OTSPoint3D((maxX + minX) / 2, (maxY + minY) / 2, (maxZ + minZ) / 2);
1532 double deltaX = Math.max(maxX - minX, 0.5);
1533 double deltaY = Math.max(maxY - minY, 0.5);
1534 double deltaZ = Math.max(maxZ - minZ, 0.5);
1535 this.bounds = new BoundingBox(deltaX, deltaY, deltaZ);
1536 this.envelope = new Envelope(minX, maxX, minY, maxY);
1537 }
1538
1539
1540
1541
1542
1543 public final OTSPoint3D getCentroid()
1544 {
1545 if (this.centroid == null)
1546 {
1547 calcCentroidBounds();
1548 }
1549 return this.centroid;
1550 }
1551
1552
1553
1554
1555
1556 public final Envelope getEnvelope()
1557 {
1558 if (this.envelope == null)
1559 {
1560 calcCentroidBounds();
1561 }
1562 return this.envelope;
1563 }
1564
1565
1566 @Override
1567 @SuppressWarnings("checkstyle:designforextension")
1568 public DirectedPoint getLocation()
1569 {
1570 if (this.centroid == null)
1571 {
1572 calcCentroidBounds();
1573 }
1574 return this.centroid.getDirectedPoint();
1575 }
1576
1577
1578 @Override
1579 @SuppressWarnings("checkstyle:designforextension")
1580 public Bounds getBounds()
1581 {
1582 if (this.bounds == null)
1583 {
1584 calcCentroidBounds();
1585 }
1586 return this.bounds;
1587 }
1588
1589
1590 @Override
1591 @SuppressWarnings("checkstyle:designforextension")
1592 public String toString()
1593 {
1594 return Arrays.toString(this.points);
1595 }
1596
1597
1598 @Override
1599 @SuppressWarnings("checkstyle:designforextension")
1600 public int hashCode()
1601 {
1602 final int prime = 31;
1603 int result = 1;
1604 result = prime * result + Arrays.hashCode(this.points);
1605 return result;
1606 }
1607
1608
1609 @Override
1610 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
1611 public boolean equals(final Object obj)
1612 {
1613 if (this == obj)
1614 return true;
1615 if (obj == null)
1616 return false;
1617 if (getClass() != obj.getClass())
1618 return false;
1619 OTSLine3D other = (OTSLine3D) obj;
1620 if (!Arrays.equals(this.points, other.points))
1621 return false;
1622 return true;
1623 }
1624
1625
1626
1627
1628 public final String toExcel()
1629 {
1630 StringBuffer s = new StringBuffer();
1631 for (OTSPoint3D p : this.points)
1632 {
1633 s.append(p.x + "\t" + p.y + "\n");
1634 }
1635 return s.toString();
1636 }
1637
1638
1639
1640
1641
1642 public static void main(final String[] args) throws OTSGeometryException
1643 {
1644 OTSLine3D line = new OTSLine3D(new OTSPoint3D(-263.811, -86.551, 1.180), new OTSPoint3D(-262.945, -84.450, 1.180),
1645 new OTSPoint3D(-261.966, -82.074, 1.180), new OTSPoint3D(-260.890, -79.464, 1.198),
1646 new OTSPoint3D(-259.909, -76.955, 1.198), new OTSPoint3D(-258.911, -74.400, 1.198),
1647 new OTSPoint3D(-257.830, -71.633, 1.234));
1648 System.out.println(line.toExcel());
1649 double[] relativeFractions = new double[] { 0.0, 0.19827228089475762, 0.30549496392494213, 0.5824753163948581,
1650 0.6815307752261827, 0.7903990449840241, 0.8942375145295614, 1.0 };
1651 double[] offsets = new double[] { 2.9779999256134, 4.6029999256134, 3.886839156071996, 2.3664845198627207,
1652 1.7858981925396709, 1.472348149010167, 2.0416709053157285, 2.798692100483229 };
1653 System.out.println(line.offsetLine(relativeFractions, offsets).toExcel());
1654 }
1655 }