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