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.opentrafficsim.core.logger.SimLogger;
21
22 import com.vividsolutions.jts.geom.Coordinate;
23 import com.vividsolutions.jts.geom.CoordinateSequence;
24 import com.vividsolutions.jts.geom.Envelope;
25 import com.vividsolutions.jts.geom.Geometry;
26 import com.vividsolutions.jts.geom.GeometryFactory;
27 import com.vividsolutions.jts.geom.LineString;
28 import com.vividsolutions.jts.linearref.LengthIndexedLine;
29
30 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
31 import nl.tudelft.simulation.dsol.animation.Locatable;
32 import nl.tudelft.simulation.language.d3.BoundingBox;
33 import nl.tudelft.simulation.language.d3.DirectedPoint;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class OTSLine3D implements Locatable, Serializable
50 {
51
52 private static final long serialVersionUID = 20150722L;
53
54
55 private OTSPoint3D[] points;
56
57
58 private double[] lengthIndexedLine = null;
59
60
61 private double length = Double.NaN;
62
63
64 private OTSPoint3D centroid = null;
65
66
67 private Bounds bounds = null;
68
69
70 private OTSPoint3D[] fractionalHelperCenters = null;
71
72
73 private Point2D.Double[] fractionalHelperDirections = null;
74
75
76 private OTSPoint3D firstOffsetIntersection;
77
78
79 private OTSPoint3D lastOffsetIntersection;
80
81
82 private static final double FRAC_PROJ_PRECISION = 2e-5 ;
83
84
85 private Envelope envelope;
86
87
88
89
90
91
92
93 public OTSLine3D(final OTSPoint3D... points) throws OTSGeometryException
94 {
95 init(points);
96 }
97
98
99
100
101
102
103
104 private void init(final OTSPoint3D... pts) throws OTSGeometryException
105 {
106 if (pts.length < 2)
107 {
108 throw new OTSGeometryException("Degenerate OTSLine3D; has " + pts.length + " point" + (pts.length != 1 ? "s" : ""));
109 }
110 this.lengthIndexedLine = new double[pts.length];
111 this.lengthIndexedLine[0] = 0.0;
112 for (int i = 1; i < pts.length; i++)
113 {
114 if (pts[i - 1].x == pts[i].x && pts[i - 1].y == pts[i].y && pts[i - 1].z == pts[i].z)
115 {
116 throw new OTSGeometryException(
117 "Degenerate OTSLine3D; point " + (i - 1) + " has the same x, y and z as point " + i);
118 }
119 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + pts[i - 1].distanceSI(pts[i]);
120 }
121 this.points = pts;
122 }
123
124
125 public enum OffsetMethod
126 {
127
128 JTS,
129
130
131 PK;
132 };
133
134
135 public static final OffsetMethod OFFSETMETHOD = OffsetMethod.PK;
136
137
138
139
140
141
142
143 public final OTSLine3D offsetLine(final double offset)
144 {
145 try
146 {
147 switch (OFFSETMETHOD)
148 {
149 case PK:
150 return OTSOffsetLinePK.offsetLine(this, offset);
151
152 case JTS:
153 return OTSBufferingJTS.offsetGeometryOLD(this, offset);
154
155 default:
156 return null;
157 }
158 }
159 catch (OTSGeometryException exception)
160 {
161 SimLogger.always().error(exception);
162 return null;
163 }
164 }
165
166
167
168
169
170
171
172 public final OTSLine3D noiseFilteredLine(final double noiseLevel)
173 {
174 if (this.size() <= 2)
175 {
176 return this;
177 }
178 OTSPoint3D prevPoint = null;
179 List<OTSPoint3D> list = null;
180 for (int index = 0; index < this.size(); index++)
181 {
182 OTSPoint3D currentPoint = this.points[index];
183 if (null != prevPoint && prevPoint.distanceSI(currentPoint) < noiseLevel)
184 {
185 if (null == list)
186 {
187
188 list = new ArrayList<>();
189 for (int i = 0; i < index; i++)
190 {
191 list.add(this.points[i]);
192 }
193 }
194 if (index == this.size() - 1)
195 {
196 if (list.size() > 1)
197 {
198
199 list.set(list.size() - 1, currentPoint);
200 }
201 else
202 {
203
204
205 list.add(currentPoint);
206 }
207 }
208 continue;
209 }
210 else if (null != list)
211 {
212 list.add(currentPoint);
213 }
214 prevPoint = currentPoint;
215 }
216 if (null == list)
217 {
218 return this;
219 }
220 try
221 {
222 return new OTSLine3D(list);
223 }
224 catch (OTSGeometryException exception)
225 {
226 SimLogger.always().error(exception);
227 throw new Error(exception);
228 }
229 }
230
231
232
233
234
235
236
237
238
239 public final OTSLine3D noiseFilterRamerDouglasPeuker(final double epsilon, final boolean useHorizontalDistance)
240 {
241
242 try
243 {
244
245
246 double maxDeviation = 0;
247 int splitIndex = -1;
248 int pointCount = size();
249 OTSLine3D straight = new OTSLine3D(get(0), get(pointCount - 1));
250
251 for (int i = 1; i < pointCount - 1; i++)
252 {
253 OTSPoint3D point = get(i);
254 OTSPoint3D closest =
255 useHorizontalDistance ? point.closestPointOnLine2D(straight) : point.closestPointOnLine(straight);
256 double deviation = useHorizontalDistance ? closest.horizontalDistanceSI(point) : closest.distanceSI(point);
257 if (deviation > maxDeviation)
258 {
259 splitIndex = i;
260 maxDeviation = deviation;
261 }
262 }
263 if (maxDeviation <= epsilon)
264 {
265
266 return straight;
267 }
268
269
270
271 OTSLine3D first = new OTSLine3D(Arrays.copyOfRange(this.points, 0, splitIndex + 1))
272 .noiseFilterRamerDouglasPeuker(epsilon, useHorizontalDistance);
273 OTSLine3D second = new OTSLine3D(Arrays.copyOfRange(this.points, splitIndex, this.points.length))
274 .noiseFilterRamerDouglasPeuker(epsilon, useHorizontalDistance);
275 return concatenate(epsilon, first, second);
276 }
277 catch (OTSGeometryException exception)
278 {
279 SimLogger.always().error(exception);
280 return null;
281 }
282 }
283
284
285
286
287
288
289
290
291
292 public final OTSLine3D offsetLine(final double offsetAtStart, final double offsetAtEnd) throws OTSGeometryException
293 {
294
295
296
297 OTSLine3D offsetLineAtStart = offsetLine(offsetAtStart);
298 if (offsetAtStart == offsetAtEnd)
299 {
300 return offsetLineAtStart;
301 }
302
303
304 OTSLine3D offsetLineAtEnd = offsetLine(offsetAtEnd);
305
306
307 Geometry startGeometry = offsetLineAtStart.getLineString();
308 Geometry endGeometry = offsetLineAtEnd.getLineString();
309 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
310 double firstLength = startGeometry.getLength();
311 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
312 double secondLength = endGeometry.getLength();
313 ArrayList<Coordinate> out = new ArrayList<>();
314 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
315 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
316 int firstIndex = 0;
317 int secondIndex = 0;
318 Coordinate prevCoordinate = null;
319 final double tooClose = 0.05;
320 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
321 {
322 double firstRatio = firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
323 : Double.MAX_VALUE;
324 double secondRatio = secondIndex < secondCoordinates.length
325 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
326 double ratio;
327 if (firstRatio < secondRatio)
328 {
329 ratio = firstRatio;
330 firstIndex++;
331 }
332 else
333 {
334 ratio = secondRatio;
335 secondIndex++;
336 }
337 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
338 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
339 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
340 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
341 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
342 {
343 out.add(resultCoordinate);
344 prevCoordinate = resultCoordinate;
345 }
346 }
347 Coordinate[] resultCoordinates = new Coordinate[out.size()];
348 for (int index = 0; index < out.size(); index++)
349 {
350 resultCoordinates[index] = out.get(index);
351 }
352 return new OTSLine3D(resultCoordinates);
353 }
354
355
356
357
358
359
360
361
362
363
364 public final OTSLine3D offsetLine(final double[] relativeFractions, final double[] offsets) throws OTSGeometryException
365 {
366 OTSLine3D[] offsetLine = new OTSLine3D[relativeFractions.length];
367 for (int i = 0; i < offsets.length; i++)
368 {
369 offsetLine[i] = offsetLine(offsets[i]);
370
371 }
372
373 ArrayList<Coordinate> out = new ArrayList<>();
374 Coordinate prevCoordinate = null;
375 final double tooClose = 0.05;
376 for (int i = 0; i < offsets.length - 1; i++)
377 {
378 Geometry startGeometry =
379 offsetLine[i].extractFractional(relativeFractions[i], relativeFractions[i + 1]).getLineString();
380 Geometry endGeometry =
381 offsetLine[i + 1].extractFractional(relativeFractions[i], relativeFractions[i + 1]).getLineString();
382 LengthIndexedLine first = new LengthIndexedLine(startGeometry);
383 double firstLength = startGeometry.getLength();
384 LengthIndexedLine second = new LengthIndexedLine(endGeometry);
385 double secondLength = endGeometry.getLength();
386 Coordinate[] firstCoordinates = startGeometry.getCoordinates();
387 Coordinate[] secondCoordinates = endGeometry.getCoordinates();
388 int firstIndex = 0;
389 int secondIndex = 0;
390 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
391 {
392 double firstRatio = firstIndex < firstCoordinates.length
393 ? first.indexOf(firstCoordinates[firstIndex]) / firstLength : Double.MAX_VALUE;
394 double secondRatio = secondIndex < secondCoordinates.length
395 ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
396 double ratio;
397 if (firstRatio < secondRatio)
398 {
399 ratio = firstRatio;
400 firstIndex++;
401 }
402 else
403 {
404 ratio = secondRatio;
405 secondIndex++;
406 }
407 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
408 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
409 Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
410 (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
411 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
412 {
413 out.add(resultCoordinate);
414 prevCoordinate = resultCoordinate;
415 }
416 }
417 }
418
419 Coordinate[] resultCoordinates = new Coordinate[out.size()];
420 for (int index = 0; index < out.size(); index++)
421 {
422 resultCoordinates[index] = out.get(index);
423 }
424 return new OTSLine3D(resultCoordinates);
425 }
426
427
428
429
430
431
432
433
434 public static OTSLine3D concatenate(final OTSLine3D... lines) throws OTSGeometryException
435 {
436 return concatenate(0.0, lines);
437 }
438
439
440
441
442
443
444
445
446
447 public static OTSLine3D concatenate(final double toleranceSI, final OTSLine3D line1, final OTSLine3D line2)
448 throws OTSGeometryException
449 {
450 if (line1.getLast().distance(line2.getFirst()).si > toleranceSI)
451 {
452 throw new OTSGeometryException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst()
453 + " distance is " + line1.getLast().distance(line2.getFirst()).si + " > " + toleranceSI);
454 }
455 int size = line1.size() + line2.size() - 1;
456 OTSPoint3D[] points = new OTSPoint3D[size];
457 int nextIndex = 0;
458 for (int j = 0; j < line1.size(); j++)
459 {
460 points[nextIndex++] = line1.get(j);
461 }
462 for (int j = 1; j < line2.size(); j++)
463 {
464 points[nextIndex++] = line2.get(j);
465 }
466 return new OTSLine3D(points);
467 }
468
469
470
471
472
473
474
475
476
477 public static OTSLine3D concatenate(final double toleranceSI, final OTSLine3D... lines) throws OTSGeometryException
478 {
479
480 if (0 == lines.length)
481 {
482 throw new OTSGeometryException("Empty argument list");
483 }
484 else if (1 == lines.length)
485 {
486 return lines[0];
487 }
488 int size = lines[0].size();
489 for (int i = 1; i < lines.length; i++)
490 {
491 if (lines[i - 1].getLast().distance(lines[i].getFirst()).si > toleranceSI)
492 {
493 throw new OTSGeometryException(
494 "Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst() + " distance is "
495 + lines[i - 1].getLast().distance(lines[i].getFirst()).si + " > " + toleranceSI);
496 }
497 size += lines[i].size() - 1;
498 }
499 OTSPoint3D[] points = new OTSPoint3D[size];
500 int nextIndex = 0;
501 for (int i = 0; i < lines.length; i++)
502 {
503 OTSLine3D line = lines[i];
504 for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
505 {
506 points[nextIndex++] = line.get(j);
507 }
508 }
509 return new OTSLine3D(points);
510 }
511
512
513
514
515
516 public final OTSLine3D reverse()
517 {
518 OTSPoint3D[] resultPoints = new OTSPoint3D[size()];
519 int nextIndex = size();
520 for (OTSPoint3D p : getPoints())
521 {
522 resultPoints[--nextIndex] = p;
523 }
524 try
525 {
526 return new OTSLine3D(resultPoints);
527 }
528 catch (OTSGeometryException exception)
529 {
530
531 throw new RuntimeException(exception);
532 }
533 }
534
535
536
537
538
539
540
541
542 public final OTSLine3D extractFractional(final double start, final double end) throws OTSGeometryException
543 {
544 if (start < 0 || start >= end || end > 1)
545 {
546 throw new OTSGeometryException("Bad interval");
547 }
548 getLength();
549 return extract(start * this.length, end * this.length);
550 }
551
552
553
554
555
556
557
558
559
560 public final OTSLine3D extract(final Length start, final Length end) throws OTSGeometryException
561 {
562 return extract(start.si, end.si);
563 }
564
565
566
567
568
569
570
571
572
573 @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
574 public final OTSLine3D extract(final double start, final double end) throws OTSGeometryException
575 {
576 if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLengthSI())
577 {
578 throw new OTSGeometryException(
579 "Bad interval (" + start + ".." + end + "; length of this OTSLine3D is " + this.getLengthSI() + ")");
580 }
581 double cumulativeLength = 0;
582 double nextCumulativeLength = 0;
583 double segmentLength = 0;
584 int index = 0;
585 List<OTSPoint3D> pointList = new ArrayList<>();
586
587 while (start > cumulativeLength)
588 {
589 OTSPoint3D fromPoint = this.points[index];
590 index++;
591 OTSPoint3D toPoint = this.points[index];
592 segmentLength = fromPoint.distanceSI(toPoint);
593 cumulativeLength = nextCumulativeLength;
594 nextCumulativeLength = cumulativeLength + segmentLength;
595 if (nextCumulativeLength >= start)
596 {
597 break;
598 }
599 }
600 if (start == nextCumulativeLength)
601 {
602 pointList.add(this.points[index]);
603 }
604 else
605 {
606 pointList.add(OTSPoint3D.interpolate((start - cumulativeLength) / segmentLength, this.points[index - 1],
607 this.points[index]));
608 if (end > nextCumulativeLength)
609 {
610 pointList.add(this.points[index]);
611 }
612 }
613 while (end > nextCumulativeLength)
614 {
615 OTSPoint3D fromPoint = this.points[index];
616 index++;
617 if (index >= this.points.length)
618 {
619 break;
620 }
621 OTSPoint3D toPoint = this.points[index];
622 segmentLength = fromPoint.distanceSI(toPoint);
623 cumulativeLength = nextCumulativeLength;
624 nextCumulativeLength = cumulativeLength + segmentLength;
625 if (nextCumulativeLength >= end)
626 {
627 break;
628 }
629 pointList.add(toPoint);
630 }
631 if (end == nextCumulativeLength)
632 {
633 pointList.add(this.points[index]);
634 }
635 else
636 {
637 OTSPoint3D point = OTSPoint3D.interpolate((end - cumulativeLength) / segmentLength, this.points[index - 1],
638 this.points[index]);
639
640 if (!point.equals(pointList.get(pointList.size() - 1)))
641 {
642 pointList.add(point);
643 }
644 }
645 try
646 {
647 return new OTSLine3D(pointList);
648 }
649 catch (@SuppressWarnings("unused") OTSGeometryException exception)
650 {
651 SimLogger.always().error(exception, "interval " + start + ".." + end + " too short");
652 throw new OTSGeometryException("interval " + start + ".." + end + "too short");
653 }
654 }
655
656
657
658
659
660
661 private static OTSPoint3D[] coordinatesToOTSPoint3D(final Coordinate[] coordinates)
662 {
663 OTSPoint3D[] result = new OTSPoint3D[coordinates.length];
664 for (int i = 0; i < coordinates.length; i++)
665 {
666 result[i] = new OTSPoint3D(coordinates[i]);
667 }
668 return result;
669 }
670
671
672
673
674
675
676
677 public static OTSLine3D createAndCleanOTSLine3D(final OTSPoint3D... points) throws OTSGeometryException
678 {
679 if (points.length < 2)
680 {
681 throw new OTSGeometryException(
682 "Degenerate OTSLine3D; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
683 }
684 return createAndCleanOTSLine3D(new ArrayList<>(Arrays.asList(points)));
685 }
686
687
688
689
690
691
692
693
694 public static OTSLine3D createAndCleanOTSLine3D(final List<OTSPoint3D> pointList) throws OTSGeometryException
695 {
696
697 int i = 1;
698 while (i < pointList.size())
699 {
700 if (pointList.get(i - 1).equals(pointList.get(i)))
701 {
702 pointList.remove(i);
703 }
704 else
705 {
706 i++;
707 }
708 }
709 return new OTSLine3D(pointList);
710 }
711
712
713
714
715
716
717
718 public OTSLine3D(final Coordinate[] coordinates) throws OTSGeometryException
719 {
720 this(coordinatesToOTSPoint3D(coordinates));
721 }
722
723
724
725
726
727
728
729 public OTSLine3D(final LineString lineString) throws OTSGeometryException
730 {
731 this(lineString.getCoordinates());
732 }
733
734
735
736
737
738
739
740 public OTSLine3D(final Geometry geometry) throws OTSGeometryException
741 {
742 this(geometry.getCoordinates());
743 }
744
745
746
747
748
749
750
751 public OTSLine3D(final List<OTSPoint3D> pointList) throws OTSGeometryException
752 {
753 this(pointList.toArray(new OTSPoint3D[pointList.size()]));
754 }
755
756
757
758
759
760
761
762 public OTSLine3D(final Path2D path) throws OTSGeometryException
763 {
764 List<OTSPoint3D> pl = new ArrayList<>();
765 for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next())
766 {
767 double[] p = new double[6];
768 int segType = pi.currentSegment(p);
769 if (segType == PathIterator.SEG_MOVETO || segType == PathIterator.SEG_LINETO)
770 {
771 pl.add(new OTSPoint3D(p[0], p[1]));
772 }
773 else if (segType == PathIterator.SEG_CLOSE)
774 {
775 if (!pl.get(0).equals(pl.get(pl.size() - 1)))
776 {
777 pl.add(new OTSPoint3D(pl.get(0).x, pl.get(0).y));
778 }
779 break;
780 }
781 }
782 init(pl.toArray(new OTSPoint3D[pl.size() - 1]));
783 }
784
785
786
787
788
789 public final Coordinate[] getCoordinates()
790 {
791 Coordinate[] result = new Coordinate[size()];
792 for (int i = 0; i < size(); i++)
793 {
794 result[i] = this.points[i].getCoordinate();
795 }
796 return result;
797 }
798
799
800
801
802
803 public final LineString getLineString()
804 {
805 GeometryFactory factory = new GeometryFactory();
806 Coordinate[] coordinates = getCoordinates();
807 CoordinateSequence cs = factory.getCoordinateSequenceFactory().create(coordinates);
808 return new LineString(cs, factory);
809 }
810
811
812
813
814
815 public final int size()
816 {
817 return this.points.length;
818 }
819
820
821
822
823
824 public final OTSPoint3D getFirst()
825 {
826 return this.points[0];
827 }
828
829
830
831
832
833 public final OTSPoint3D getLast()
834 {
835 return this.points[size() - 1];
836 }
837
838
839
840
841
842
843
844 public final OTSPoint3D get(final int i) throws OTSGeometryException
845 {
846 if (i < 0 || i > size() - 1)
847 {
848 throw new OTSGeometryException("OTSLine3D.get(i=" + i + "); i<0 or i>=size(), which is " + size());
849 }
850 return this.points[i];
851 }
852
853
854
855
856
857
858 public final synchronized double getLengthSI()
859 {
860 if (Double.isNaN(this.length))
861 {
862 this.length = 0.0;
863 for (int i = 0; i < size() - 1; i++)
864 {
865 this.length += this.points[i].distanceSI(this.points[i + 1]);
866 }
867 }
868 return this.length;
869 }
870
871
872
873
874
875
876 public final Length getLength()
877 {
878 return new Length(getLengthSI(), LengthUnit.SI);
879 }
880
881
882
883
884
885 public final OTSPoint3D[] getPoints()
886 {
887 return this.points;
888 }
889
890
891
892
893 private void makeLengthIndexedLine()
894 {
895 if (this.lengthIndexedLine == null)
896 {
897 this.lengthIndexedLine = new double[this.points.length];
898 this.lengthIndexedLine[0] = 0.0;
899 for (int i = 1; i < this.points.length; i++)
900 {
901 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + this.points[i - 1].distanceSI(this.points[i]);
902 }
903 }
904 }
905
906
907
908
909
910
911
912 public final DirectedPoint getLocationExtended(final Length position)
913 {
914 return getLocationExtendedSI(position.getSI());
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 (@SuppressWarnings("unused") 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 this.fractionalHelperCenters[n - 1] = null;
1556 }
1557 if (this.fractionalHelperCenters[0] == null)
1558 {
1559
1560 this.fractionalHelperDirections[0] = new Point2D.Double(p1.x - this.points[0].x, p1.y - this.points[0].y);
1561 }
1562
1563 }
1564
1565
1566
1567
1568
1569
1570 private OTSLine3D unitOffsetSegment(final int segment)
1571 {
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595 OTSPoint3D from = new OTSPoint3D(this.points[segment].x, this.points[segment].y);
1596 OTSPoint3D to = new OTSPoint3D(this.points[segment + 1].x, this.points[segment + 1].y);
1597 try
1598 {
1599 OTSLine3D line = new OTSLine3D(from, to);
1600 return line.offsetLine(1.0);
1601 }
1602 catch (OTSGeometryException oge)
1603 {
1604
1605 throw new RuntimeException(oge);
1606 }
1607 }
1608
1609
1610
1611
1612
1613 private void calcCentroidBounds()
1614 {
1615 double minX = Double.POSITIVE_INFINITY;
1616 double minY = Double.POSITIVE_INFINITY;
1617 double minZ = Double.POSITIVE_INFINITY;
1618 double maxX = Double.NEGATIVE_INFINITY;
1619 double maxY = Double.NEGATIVE_INFINITY;
1620 double maxZ = Double.NEGATIVE_INFINITY;
1621 for (OTSPoint3D p : this.points)
1622 {
1623 minX = Math.min(minX, p.x);
1624 minY = Math.min(minY, p.y);
1625 minZ = Math.min(minZ, p.z);
1626 maxX = Math.max(maxX, p.x);
1627 maxY = Math.max(maxY, p.y);
1628 maxZ = Math.max(maxZ, p.z);
1629 }
1630 this.centroid = new OTSPoint3D((maxX + minX) / 2, (maxY + minY) / 2, (maxZ + minZ) / 2);
1631 double deltaX = maxX - minX;
1632 double deltaY = maxY - minY;
1633 double deltaZ = maxZ - minZ;
1634
1635 this.bounds = new BoundingBox(new Point3d(-deltaX / 2.0, -deltaY / 2.0, -deltaZ / 2.0),
1636 new Point3d(deltaX / 2, deltaY / 2, deltaZ / 2));
1637 this.envelope = new Envelope(minX, maxX, minY, maxY);
1638 }
1639
1640
1641
1642
1643
1644 public final OTSPoint3D getCentroid()
1645 {
1646 if (this.centroid == null)
1647 {
1648 calcCentroidBounds();
1649 }
1650 return this.centroid;
1651 }
1652
1653
1654
1655
1656
1657 public final Envelope getEnvelope()
1658 {
1659 if (this.envelope == null)
1660 {
1661 calcCentroidBounds();
1662 }
1663 return this.envelope;
1664 }
1665
1666
1667 @Override
1668 @SuppressWarnings("checkstyle:designforextension")
1669 public DirectedPoint getLocation()
1670 {
1671 if (this.centroid == null)
1672 {
1673 calcCentroidBounds();
1674 }
1675 return this.centroid.getDirectedPoint();
1676 }
1677
1678
1679 @Override
1680 @SuppressWarnings("checkstyle:designforextension")
1681 public Bounds getBounds()
1682 {
1683 if (this.bounds == null)
1684 {
1685 calcCentroidBounds();
1686 }
1687 return this.bounds;
1688 }
1689
1690
1691 @Override
1692 @SuppressWarnings("checkstyle:designforextension")
1693 public String toString()
1694 {
1695 return Arrays.toString(this.points);
1696 }
1697
1698
1699 @Override
1700 @SuppressWarnings("checkstyle:designforextension")
1701 public int hashCode()
1702 {
1703 final int prime = 31;
1704 int result = 1;
1705 result = prime * result + Arrays.hashCode(this.points);
1706 return result;
1707 }
1708
1709
1710 @Override
1711 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
1712 public boolean equals(final Object obj)
1713 {
1714 if (this == obj)
1715 return true;
1716 if (obj == null)
1717 return false;
1718 if (getClass() != obj.getClass())
1719 return false;
1720 OTSLine3D other = (OTSLine3D) obj;
1721 if (!Arrays.equals(this.points, other.points))
1722 return false;
1723 return true;
1724 }
1725
1726
1727
1728
1729 public final String toExcel()
1730 {
1731 StringBuffer s = new StringBuffer();
1732 for (OTSPoint3D p : this.points)
1733 {
1734 s.append(p.x + "\t" + p.y + "\n");
1735 }
1736 return s.toString();
1737 }
1738
1739
1740
1741
1742 public final String toPlot()
1743 {
1744 StringBuffer result = new StringBuffer();
1745 for (OTSPoint3D p : this.points)
1746 {
1747 result.append(String.format(Locale.US, "%s%.3f,%.3f", 0 == result.length() ? "M" : " L", p.x, p.y));
1748 }
1749 result.append("\n");
1750 return result.toString();
1751 }
1752
1753
1754
1755
1756
1757 public static void main(final String[] args) throws OTSGeometryException
1758 {
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770 List<OTSPoint3D> list = new ArrayList<>();
1771 boolean laneOn933 = true;
1772 if (!laneOn933)
1773 {
1774 double x = 0;
1775 double y = 0;
1776 double dx = 0.000001;
1777 double dy = 0.05;
1778 double ddx = 1.5;
1779 for (int i = 0; i < 32; i++)
1780 {
1781 list.add(new OTSPoint3D(x, y));
1782 x += dx;
1783 dx *= ddx;
1784 y += dy;
1785 }
1786 }
1787 else
1788 {
1789 String lineStr = "@0 426333.939, 4581372.345@" + "1 426333.92109750526, 4581372.491581111@"
1790 + "2 426333.9016207722, 4581372.6364820665@" + "3 426333.8806181711, 4581372.7797264075@"
1791 + "4 426333.8581377007, 4581372.921337651@" + "5 426333.8342269785, 4581373.061339286@"
1792 + "6 426333.80893323367, 4581373.199754763@" + "7 426333.78230329906, 4581373.336607476@"
1793 + "8 426333.75438360614, 4581373.471920755@" + "9 426333.7252201801, 4581373.605717849@"
1794 + "10 426333.69485863775, 4581373.738021923@" + "11 426333.6633441839, 4581373.868856039@"
1795 + "12 426333.6307216125, 4581373.998243135@" + "13 426333.5970353065, 4581374.1262060385@"
1796 + "14 426333.56232923956, 4581374.252767426@" + "15 426333.54571270826, 4581374.331102062@"
1797 + "16 426333.53121128445, 4581374.399777128@" + "17 426333.51761287224, 4581374.46141805@"
1798 + "18 426333.5035609495, 4581374.524905452@" + "19 426333.4885681211, 4581374.590110448@"
1799 + "20 426333.4750534529, 4581374.648530791@" + "21 426333.4586325006, 4581374.71720738@"
1800 + "22 426333.44573716016, 4581374.770680802@" + "23 426333.4278589452, 4581374.84273674@"
1801 + "24 426333.41565935884, 4581374.891382747@" + "25 426333.39629928104, 4581374.966726161@"
1802 + "26 426333.3640042249, 4581375.089202983@" + "27 426333.3310233974, 4581375.210194213@"
1803 + "28 426333.2974053264, 4581375.329726505@" + "29 426333.26319745823, 4581375.44782613@"
1804 + "30 426333.2284461768, 4581375.564518943@" + "31 426333.1931968143, 4581375.679830365@"
1805 + "32 426333.15749366966, 4581375.793785359@" + "33 426333.12138002727, 4581375.9064084105@"
1806 + "34 426333.0848981781, 4581376.017723508@" + "35 426333.0526068902, 4581376.127395174@"
1807 + "36 426333.0222216131, 4581376.235573194@" + "37 426333.00835773064, 4581376.284013769@"
1808 + "38 426332.9916265083, 4581376.342442355@" + "39 426332.9771780217, 4581376.392075247@"
1809 + "40 426332.96085931134, 4581376.448026933@" + "41 426332.9448449097, 4581376.5021694945@"
1810 + "42 426332.9299564511, 4581376.552350422@" + "43 426332.9123899684, 4581376.610862428@"
1811 + "44 426332.87985284685, 4581376.718179138@" + "45 426332.8472718188, 4581376.824143872@"
1812 + "46 426332.81468381727, 4581376.92878003@" + "47 426332.78212446393, 4581377.032110168@"
1813 + "48 426332.7496281178, 4581377.134155947@" + "49 426332.71722788643, 4581377.234938197@"
1814 + "50 426332.68495568086, 4581377.3344768565@" + "51 426332.6528422234, 4581377.432791035@"
1815 + "52 426332.6209170973, 4581377.529898969@" + "53 426332.59026768577, 4581377.622609458@"
1816 + "54 426332.5618311538, 4581377.708242513@" + "55 426332.5292456913, 4581377.813700842@"
1817 + "56 426332.5007497582, 4581377.905735847@" + "57 426332.4725916431, 4581377.996633883@"
1818 + "58 426332.4447947076, 4581378.086409748@" + "59 426332.41739884845, 4581378.175020202@"
1819 + "60 426332.3904224847, 4581378.262486783@" + "61 426332.37513187295, 4581378.312218361@"
1820 + "62 426332.3474726438, 4581378.402429141@" + "63 426332.3203478011, 4581378.491354613@"
1821 + "64 426332.2937555201, 4581378.579078223@" + "65 426332.26771504263, 4581378.665610338@"
1822 + "66 426332.24224462465, 4581378.750960108@" + "67 426332.21736132156, 4581378.835136287@"
1823 + "68 426332.1930813682, 4581378.918146061@" + "69 426332.1694196611, 4581378.999996922@"
1824 + "70 426332.1468078785, 4581379.079234334@" + "71 426332.1253935003, 4581379.155326921@"
1825 + "72 426332.10456227185, 4581379.230438552@" + "73 426332.08413377195, 4581379.301777359@"
1826 + "74 426332.0575671712, 4581379.393246921@" + "75 426332.037751917, 4581379.463051603@"
1827 + "76 426332.01541074895, 4581379.543672992@" + "77 426331.9954696024, 4581379.617241848@"
1828 + "78 426331.9764488572, 4581379.689794578@" + "79 426331.9581173997, 4581379.761214821@"
1829 + "80 426331.9407607595, 4581379.831643043@" + "81 426331.92459788476, 4581379.898797621@"
1830 + "82 426331.89349001576, 4581380.036207511@" + "83 426331.8662295119, 4581380.167554456@"
1831 + "84 426331.84239882755, 4581380.294825263@" + "85 426331.8220095046, 4581380.41813201@"
1832 + "86 426331.80506772455, 4581380.537631294@" + "87 426331.79158302536, 4581380.653536015@"
1833 + "88 426331.78158027114, 4581380.766126917@" + "89 426331.7754554946, 4581380.838605414@"
1834 + "90 426331.76793314604, 4581380.909291444@" + "91 426331.7605002508, 4581381.016285149@"
1835 + "92 426331.75725734304, 4581381.119549306@" + "93 426331.75814653496, 4581381.219559045@"
1836 + "94 426331.76316353114, 4581381.316908372@" + "95 426331.7723867522, 4581381.412305131@"
1837 + "96 426331.7860053539, 4581381.506554079@" + "97 426331.80434182915, 4581381.600527881@"
1838 + "98 426331.82733581704, 4581381.692992337@" + "99 426331.8531803791, 4581381.777938947@"
1839 + "100 426331.884024255, 4581381.864352291@" + "101 426331.92063241004, 4581381.953224321@"
1840 + "102 426331.96390912175, 4581382.045434713@" + "103 426331.9901409878, 4581382.095566823@"
1841 + "104 426332.0148562894, 4581382.141714169@" + "105 426332.05172826024, 4581382.204388889@"
1842 + "106 426332.12722889386, 4581382.323121141@" + "107 426332.1628785428, 4581382.375872464@"
1843 + "108 426332.22007742553, 4581382.462661629@" + "109 426332.26023980865, 4581382.523784153@"
1844 + "110 426332.3033344728, 4581382.586422447@" + "111 426332.34946240357, 4581382.650580184@"
1845 + "112 426332.3987196004, 4581382.716255575@" + "113 426332.4511967281, 4581382.783441929@"
1846 + "114 426332.50697922776, 4581382.852128648@" + "115 426332.56614731904, 4581382.922301916@"
1847 + "116 426332.628776037, 4581382.993945288@" + "117 426332.6949354622, 4581383.067040358@"
1848 + "118 426332.76469110255, 4581383.141567508@" + "119 426332.8381037568, 4581383.217505949@"
1849 + "120 426332.91523022414, 4581383.294834619@" + "121 426332.9961233405, 4581383.373532268@"
1850 + "122 426333.0808322224, 4581383.453577724@" + "123 426333.1693585424, 4581383.534909724@"
1851 + "124 426333.26164044754, 4581383.61741792@" + "125 426333.3650128907, 4581383.707446191@";
1852 int fromIndex = 0;
1853 while (true)
1854 {
1855 int at1 = lineStr.indexOf('@', fromIndex);
1856 fromIndex = at1 + 1;
1857 int at2 = lineStr.indexOf('@', fromIndex);
1858 if (at2 < 0)
1859 {
1860 break;
1861 }
1862 fromIndex = at2;
1863
1864 String subStr = lineStr.substring(at1 + 5, at2);
1865 int comma = subStr.indexOf(',');
1866 double x = Double.valueOf(subStr.substring(0, comma));
1867 double y = Double.valueOf(subStr.substring(comma + 1));
1868
1869 list.add(new OTSPoint3D(x, y, 0.0));
1870
1871 }
1872 }
1873 OTSLine3D line = new OTSLine3D(list);
1874
1875 line.projectFractional(null, null, 1.0, 0.5, FractionalFallback.NaN);
1876
1877
1878 OTSPoint3D[] array = line.fractionalHelperCenters;
1879 for (int i = 0; i < array.length; i++)
1880 {
1881 if (array[i] == null)
1882 {
1883 array[i] = new OTSPoint3D(Double.NaN, Double.NaN);
1884 }
1885 }
1886 OTSLine3D helpers = new OTSLine3D(line.fractionalHelperCenters);
1887
1888
1889 StringBuilder str = new StringBuilder();
1890 str.append("line = [");
1891 String sep = "";
1892 for (OTSPoint3D p : line.getPoints())
1893 {
1894 str.append(String.format(Locale.US, "%s %.8f, %.8f", sep, p.x, p.y));
1895 sep = ",";
1896 }
1897 str.append("];\n");
1898
1899 str.append("helpers = [");
1900 sep = "";
1901 for (OTSPoint3D p : helpers.getPoints())
1902 {
1903 str.append(String.format(Locale.US, "%s %.8f, %.8f", sep, p.x, p.y));
1904 sep = ",";
1905 }
1906 str.append("];\n");
1907
1908 System.out.print(str);
1909 }
1910
1911 }