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