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