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