1 package org.opentrafficsim.core.geometry;
2
3 import java.awt.geom.Line2D;
4 import java.awt.geom.Path2D;
5 import java.awt.geom.PathIterator;
6 import java.awt.geom.Point2D;
7 import java.io.Serializable;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.List;
11
12 import org.djunits.unit.DirectionUnit;
13 import org.djunits.value.vdouble.scalar.Direction;
14 import org.djunits.value.vdouble.scalar.Length;
15 import org.djutils.draw.bounds.Bounds2d;
16 import org.djutils.draw.line.PolyLine2d;
17 import org.djutils.draw.line.Ray2d;
18 import org.djutils.draw.point.OrientedPoint2d;
19 import org.djutils.draw.point.Point2d;
20 import org.djutils.exceptions.Throw;
21 import org.djutils.exceptions.Try;
22
23 import nl.tudelft.simulation.dsol.animation.Locatable;
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public class OtsLine2d implements Locatable, Serializable
38 {
39
40 private static final long serialVersionUID = 20150722L;
41
42
43 private PolyLine2d line2d;
44
45
46 private double[] lengthIndexedLine = null;
47
48
49 private Length length;
50
51
52 private Point2d centroid = null;
53
54
55 private Bounds2d bounds = null;
56
57
58 private Point2d[] fractionalHelperCenters = null;
59
60
61 private Point2D.Double[] fractionalHelperDirections = null;
62
63
64 private Point2d firstOffsetIntersection;
65
66
67 private Point2d lastOffsetIntersection;
68
69
70 private static final double FRAC_PROJ_PRECISION = 2e-5 ;
71
72
73 private Length[] vertexRadii;
74
75
76
77
78
79 public OtsLine2d(final Point2d... points)
80 {
81 this(new PolyLine2d(points));
82 }
83
84
85
86
87
88 public OtsLine2d(final PolyLine2d line2d)
89 {
90 init(line2d);
91 }
92
93
94
95
96
97 private void init(final PolyLine2d line2d)
98 {
99 this.lengthIndexedLine = new double[line2d.size()];
100 this.lengthIndexedLine[0] = 0.0;
101 for (int i = 1; i < line2d.size(); i++)
102 {
103 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + line2d.get(i - 1).distance(line2d.get(i));
104 }
105 this.line2d = line2d;
106 this.length = Length.instantiateSI(this.lengthIndexedLine[this.lengthIndexedLine.length - 1]);
107 }
108
109
110
111
112
113
114 public final OtsLine2d offsetLine(final double offset)
115 {
116 return new OtsLine2d(this.line2d.offsetLine(offset));
117 }
118
119
120
121
122
123
124
125
126 @Deprecated
127 public final OtsLine2d noiseFilterRamerDouglasPeucker(final double epsilon, final boolean useHorizontalDistance)
128 {
129
130
131 double maxDeviation = 0;
132 int splitIndex = -1;
133 int pointCount = size();
134
135 for (int i = 1; i < pointCount - 1; i++)
136 {
137 Point2d point = this.line2d.get(i);
138 Point2d closest = point.closestPointOnLine(this.line2d.get(0), this.line2d.get(pointCount - 1));
139 double deviation = useHorizontalDistance ? closest.distance(point) : closest.distance(point);
140 if (deviation > maxDeviation)
141 {
142 splitIndex = i;
143 maxDeviation = deviation;
144 }
145 }
146 if (maxDeviation <= epsilon)
147 {
148
149 return new OtsLine2d(this.line2d.get(0), this.line2d.get(pointCount - 1));
150 }
151
152
153
154 List<Point2d> points = this.line2d.getPointList();
155 OtsLine2d first = new OtsLine2d(points.subList(0, splitIndex + 1).toArray(new Point2d[splitIndex + 1]))
156 .noiseFilterRamerDouglasPeucker(epsilon, useHorizontalDistance);
157 OtsLine2d second = new OtsLine2d(
158 points.subList(splitIndex, this.line2d.size()).toArray(new Point2d[this.line2d.size() - splitIndex]))
159 .noiseFilterRamerDouglasPeucker(epsilon, useHorizontalDistance);
160 return concatenate(epsilon, first, second);
161 }
162
163
164
165
166
167 public PolyLine2d getLine2d()
168 {
169 return this.line2d;
170 }
171
172
173
174
175
176
177
178
179 public final OtsLine2d offsetLine(final double offsetAtStart, final double offsetAtEnd)
180 {
181 return new OtsLine2d(this.line2d.offsetLine(offsetAtStart, offsetAtEnd));
182 }
183
184
185
186
187
188
189
190
191
192
193 public final OtsLine2d offsetLine(final double[] relativeFractions, final double[] offsets) throws OtsGeometryException
194 {
195 return new OtsLine2d(OtsGeometryUtil.offsetLine(this.line2d, relativeFractions, offsets));
196 }
197
198
199
200
201
202
203
204 public static OtsLine2d concatenate(final OtsLine2d... lines)
205 {
206 return concatenate(0.0, lines);
207 }
208
209
210
211
212
213
214
215
216 public static OtsLine2d concatenate(final double toleranceSI, final OtsLine2d line1, final OtsLine2d line2)
217 {
218 return new OtsLine2d(PolyLine2d.concatenate(toleranceSI, line1.line2d, line2.line2d));
219 }
220
221
222
223
224
225
226
227
228 public static OtsLine2d concatenate(final double toleranceSI, final OtsLine2d... lines)
229 {
230 List<PolyLine2d> lines2d = new ArrayList<>();
231 for (OtsLine2d line : lines)
232 {
233 lines2d.add(line.line2d);
234 }
235 return new OtsLine2d(PolyLine2d.concatenate(toleranceSI, lines2d.toArray(new PolyLine2d[lines.length])));
236 }
237
238
239
240
241
242 public final OtsLine2d reverse()
243 {
244 return new OtsLine2d(this.line2d.reverse());
245 }
246
247
248
249
250
251
252
253 public final OtsLine2d extractFractional(final double start, final double end)
254 {
255 return extract(start * this.length.si, end * this.length.si);
256 }
257
258
259
260
261
262
263
264
265 public final OtsLine2d extract(final Length start, final Length end)
266 {
267 return extract(start.si, end.si);
268 }
269
270
271
272
273
274
275
276
277 public final OtsLine2d extract(final double start, final double end)
278 {
279 return new OtsLine2d(this.line2d.extract(start, end));
280 }
281
282
283
284
285
286
287
288 public static OtsLine2d createAndCleanOtsLine2d(final Point2d... points) throws OtsGeometryException
289 {
290 if (points.length < 2)
291 {
292 throw new OtsGeometryException(
293 "Degenerate OtsLine2d; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
294 }
295 return createAndCleanOtsLine2d(new ArrayList<>(Arrays.asList(points)));
296 }
297
298
299
300
301
302
303
304
305 public static OtsLine2d createAndCleanOtsLine2d(final List<Point2d> pointList) throws OtsGeometryException
306 {
307 return new OtsLine2d(new PolyLine2d(true, pointList));
308 }
309
310
311
312
313
314
315
316 public OtsLine2d(final List<Point2d> pointList) throws OtsGeometryException
317 {
318 this(pointList.toArray(new Point2d[pointList.size()]));
319 }
320
321
322
323
324
325
326
327 public OtsLine2d(final Path2D path) throws OtsGeometryException
328 {
329 List<Point2d> pl = new ArrayList<>();
330 for (PathIterator pi = path.getPathIterator(null); !pi.isDone(); pi.next())
331 {
332 double[] p = new double[6];
333 int segType = pi.currentSegment(p);
334 if (segType == PathIterator.SEG_MOVETO || segType == PathIterator.SEG_LINETO)
335 {
336 pl.add(new Point2d(p[0], p[1]));
337 }
338 else if (segType == PathIterator.SEG_CLOSE)
339 {
340 if (!pl.get(0).equals(pl.get(pl.size() - 1)))
341 {
342 pl.add(new Point2d(pl.get(0).x, pl.get(0).y));
343 }
344 break;
345 }
346 }
347 init(new PolyLine2d(pl.toArray(new Point2d[pl.size() - 1])));
348 }
349
350
351
352
353
354 public final int size()
355 {
356 return this.line2d.size();
357 }
358
359
360
361
362
363 public final Point2d getFirst()
364 {
365 return this.line2d.getFirst();
366 }
367
368
369
370
371
372 public final Point2d getLast()
373 {
374 return this.line2d.getLast();
375 }
376
377
378
379
380
381
382
383 public final Point2d get(final int i) throws OtsGeometryException
384 {
385 if (i < 0 || i > size() - 1)
386 {
387 throw new OtsGeometryException("OtsLine2d.get(i=" + i + "); i<0 or i>=size(), which is " + size());
388 }
389 return this.line2d.get(i);
390 }
391
392
393
394
395
396
397 public final Length getLength()
398 {
399 return this.length;
400 }
401
402
403
404
405
406 public final Point2d[] getPoints()
407 {
408 return this.line2d.getPointList().toArray(new Point2d[this.line2d.size()]);
409 }
410
411
412
413
414
415
416
417 public final OrientedPoint2d getLocationExtended(final Length position)
418 {
419 return getLocationExtendedSI(position.getSI());
420 }
421
422
423
424
425
426
427
428
429 public final synchronized OrientedPoint2d getLocationExtendedSI(final double positionSI)
430 {
431 Ray2d ray = this.line2d.getLocationExtended(positionSI);
432 return new OrientedPoint2d(ray.x, ray.y, ray.phi);
433 }
434
435
436
437
438
439
440
441 public final OrientedPoint2d getLocationFraction(final double fraction) throws OtsGeometryException
442 {
443 if (fraction < 0.0 || fraction > 1.0)
444 {
445 throw new OtsGeometryException("getLocationFraction for line: fraction < 0.0 or > 1.0. fraction = " + fraction);
446 }
447 return getLocationSI(fraction * this.length.si);
448 }
449
450
451
452
453
454
455
456
457 public final OrientedPoint2d getLocationFraction(final double fraction, final double tolerance) throws OtsGeometryException
458 {
459 if (fraction < -tolerance || fraction > 1.0 + tolerance)
460 {
461 throw new OtsGeometryException(
462 "getLocationFraction for line: fraction < 0.0 - tolerance or > 1.0 + tolerance; fraction = " + fraction);
463 }
464 double f = fraction < 0 ? 0.0 : fraction > 1.0 ? 1.0 : fraction;
465 return getLocationSI(f * this.length.si);
466 }
467
468
469
470
471
472
473 public final OrientedPoint2d getLocationFractionExtended(final double fraction)
474 {
475 return getLocationExtendedSI(fraction * this.length.si);
476 }
477
478
479
480
481
482
483
484 public final OrientedPoint2d getLocation(final Length position) throws OtsGeometryException
485 {
486 return getLocationSI(position.getSI());
487 }
488
489
490
491
492
493
494
495 private int find(final double pos) throws OtsGeometryException
496 {
497 if (pos == 0)
498 {
499 return 0;
500 }
501
502 int lo = 0;
503 int hi = this.lengthIndexedLine.length - 1;
504 while (lo <= hi)
505 {
506 if (hi == lo)
507 {
508 return lo;
509 }
510 int mid = lo + (hi - lo) / 2;
511 if (pos < this.lengthIndexedLine[mid])
512 {
513 hi = mid - 1;
514 }
515 else if (pos > this.lengthIndexedLine[mid + 1])
516 {
517 lo = mid + 1;
518 }
519 else
520 {
521 return mid;
522 }
523 }
524 throw new OtsGeometryException(
525 "Could not find position " + pos + " on line with length indexes: " + Arrays.toString(this.lengthIndexedLine));
526 }
527
528
529
530
531
532
533
534 public final OrientedPoint2d getLocationSI(final double positionSI) throws OtsGeometryException
535 {
536 Ray2d ray = Try.assign(() -> this.line2d.getLocation(positionSI), OtsGeometryException.class, "Position not on line.");
537 return new OrientedPoint2d(ray.x, ray.y, ray.phi);
538 }
539
540
541
542
543
544
545
546 public final OtsLine2d truncate(final double lengthSI) throws OtsGeometryException
547 {
548 return new OtsLine2d(this.line2d.truncate(lengthSI));
549 }
550
551
552
553
554
555
556
557
558 public final double projectOrthogonal(final double x, final double y)
559 {
560 Point2d closest = this.line2d.closestPointOnPolyLine(new Point2d(x, y));
561 return this.line2d.projectOrthogonalFractionalExtended(closest);
562 }
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619 public final synchronized double projectFractional(final Direction start, final Direction end, final double x,
620 final double y, final FractionalFallback fallback)
621 {
622
623
624 double minDistance = Double.POSITIVE_INFINITY;
625 double minSegmentFraction = 0;
626 int minSegment = -1;
627 Point2d point = new Point2d(x, y);
628
629
630 determineFractionalHelpers(start, end);
631
632
633 double[] d = new double[size() - 1];
634 double minD = Double.POSITIVE_INFINITY;
635 for (int i = 0; i < size() - 1; i++)
636 {
637 d[i] = Line2D.ptSegDist(this.line2d.get(i).x, this.line2d.get(i).y, this.line2d.get(i + 1).x,
638 this.line2d.get(i + 1).y, x, y);
639 minD = d[i] < minD ? d[i] : minD;
640 }
641
642
643 double distance;
644 for (int i = 0; i < size() - 1; i++)
645 {
646
647 if (d[i] > minD + FRAC_PROJ_PRECISION)
648 {
649 continue;
650 }
651 Point2d center = this.fractionalHelperCenters[i];
652 Point2d p;
653 if (center != null)
654 {
655
656 p = intersectionOfLines(center, point, this.line2d.get(i), this.line2d.get(i + 1));
657 if (p == null || (x < center.x + FRAC_PROJ_PRECISION && center.x + FRAC_PROJ_PRECISION < p.x)
658 || (x > center.x - FRAC_PROJ_PRECISION && center.x - FRAC_PROJ_PRECISION > p.x)
659 || (y < center.y + FRAC_PROJ_PRECISION && center.y + FRAC_PROJ_PRECISION < p.y)
660 || (y > center.y - FRAC_PROJ_PRECISION && center.y - FRAC_PROJ_PRECISION > p.y))
661 {
662
663 continue;
664 }
665 }
666 else
667 {
668
669 Point2d offsetPoint =
670 new Point2d(x + this.fractionalHelperDirections[i].x, y + this.fractionalHelperDirections[i].y);
671 p = intersectionOfLines(point, offsetPoint, this.line2d.get(i), this.line2d.get(i + 1));
672 }
673 double segLength = this.line2d.get(i).distance(this.line2d.get(i + 1)) + FRAC_PROJ_PRECISION;
674 if (p == null || this.line2d.get(i).distance(p) > segLength || this.line2d.get(i + 1).distance(p) > segLength)
675 {
676
677
678 continue;
679 }
680
681 double dx = x - p.x;
682 double dy = y - p.y;
683 distance = Math.hypot(dx, dy);
684
685 if (distance < minDistance)
686 {
687 dx = p.x - this.line2d.get(i).x;
688 dy = p.y - this.line2d.get(i).y;
689 double dFrac = Math.hypot(dx, dy);
690
691 minDistance = distance;
692 minSegmentFraction = dFrac / (this.lengthIndexedLine[i + 1] - this.lengthIndexedLine[i]);
693 minSegment = i;
694 }
695 }
696
697
698 if (minSegment == -1)
699
700 {
701
702
703
704
705
706
707
708 return fallback.getFraction(this, x, y);
709 }
710
711 double segLen = this.lengthIndexedLine[minSegment + 1] - this.lengthIndexedLine[minSegment];
712 return (this.lengthIndexedLine[minSegment] + segLen * minSegmentFraction) / this.length.si;
713
714 }
715
716
717
718
719
720
721
722
723
724
725
726
727 public enum FractionalFallback
728 {
729
730 ORTHOGONAL
731 {
732 @Override
733 double getFraction(final OtsLine2d line, final double x, final double y)
734 {
735 return line.projectOrthogonal(x, y);
736 }
737 },
738
739
740 ENDPOINT
741 {
742 @Override
743 double getFraction(final OtsLine2d line, final double x, final double y)
744 {
745 Point2d point = new Point2d(x, y);
746 double dStart = point.distance(line.getFirst());
747 double dEnd = point.distance(line.getLast());
748 if (dStart < dEnd)
749 {
750 return -dStart / line.length.si;
751 }
752 else
753 {
754 return (dEnd + line.length.si) / line.length.si;
755 }
756 }
757 },
758
759
760 NaN
761 {
762 @Override
763 double getFraction(final OtsLine2d line, final double x, final double y)
764 {
765 return Double.NaN;
766 }
767 };
768
769
770
771
772
773
774
775
776 abstract double getFraction(OtsLine2d line, double x, double y);
777
778 }
779
780
781
782
783
784
785
786 private synchronized void determineFractionalHelpers(final Direction start, final Direction end)
787 {
788
789 final int n = size() - 1;
790
791
792 if (this.fractionalHelperCenters == null)
793 {
794 this.fractionalHelperCenters = new Point2d[n];
795 this.fractionalHelperDirections = new Point2D.Double[n];
796 if (size() > 2)
797 {
798
799 PolyLine2d prevOfsSeg = unitOffsetSegment(0);
800 PolyLine2d nextOfsSeg = unitOffsetSegment(1);
801 Point2d parStartPoint;
802 parStartPoint = intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0), nextOfsSeg.get(1));
803 if (parStartPoint == null || prevOfsSeg.get(1).distance(nextOfsSeg.get(0)) < Math
804 .min(prevOfsSeg.get(1).distance(parStartPoint), nextOfsSeg.get(0).distance(parStartPoint)))
805 {
806 parStartPoint = new Point2d((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
807 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
808 }
809
810 this.firstOffsetIntersection = parStartPoint;
811
812 for (int i = 1; i < size() - 2; i++)
813 {
814 prevOfsSeg = nextOfsSeg;
815 nextOfsSeg = unitOffsetSegment(i + 1);
816 Point2d parEndPoint;
817 parEndPoint =
818 intersectionOfLines(prevOfsSeg.get(0), prevOfsSeg.get(1), nextOfsSeg.get(0), nextOfsSeg.get(1));
819 if (parEndPoint == null || prevOfsSeg.get(1).distance(nextOfsSeg.get(0)) < Math
820 .min(prevOfsSeg.get(1).distance(parEndPoint), nextOfsSeg.get(0).distance(parEndPoint)))
821 {
822 parEndPoint = new Point2d((prevOfsSeg.get(1).x + nextOfsSeg.get(0).x) / 2,
823 (prevOfsSeg.get(1).y + nextOfsSeg.get(0).y) / 2);
824 }
825
826 this.fractionalHelperCenters[i] =
827 intersectionOfLines(this.line2d.get(i), parStartPoint, this.line2d.get(i + 1), parEndPoint);
828 if (this.fractionalHelperCenters[i] == null)
829 {
830
831 this.fractionalHelperDirections[i] = new Point2D.Double(parStartPoint.x - this.line2d.get(i).x,
832 parStartPoint.y - this.line2d.get(i).y);
833 }
834 parStartPoint = parEndPoint;
835 }
836
837 this.lastOffsetIntersection = parStartPoint;
838 }
839 }
840
841
842 double ang = (start == null
843 ? Math.atan2(this.line2d.get(1).y - this.line2d.get(0).y, this.line2d.get(1).x - this.line2d.get(0).x)
844 : start.getInUnit(DirectionUnit.DEFAULT)) + Math.PI / 2;
845 Point2d p1 = new Point2d(this.line2d.get(0).x + Math.cos(ang), this.line2d.get(0).y + Math.sin(ang));
846 ang = (end == null
847 ? Math.atan2(this.line2d.get(n).y - this.line2d.get(n - 1).y, this.line2d.get(n).x - this.line2d.get(n - 1).x)
848 : end.getInUnit(DirectionUnit.DEFAULT)) + Math.PI / 2;
849 Point2d p2 = new Point2d(this.line2d.get(n).x + Math.cos(ang), this.line2d.get(n).y + Math.sin(ang));
850
851
852 if (size() > 2)
853 {
854 this.fractionalHelperCenters[0] =
855 intersectionOfLines(this.line2d.get(0), p1, this.line2d.get(1), this.firstOffsetIntersection);
856 this.fractionalHelperCenters[n - 1] =
857 intersectionOfLines(this.line2d.get(n - 1), this.lastOffsetIntersection, this.line2d.get(n), p2);
858 if (this.fractionalHelperCenters[n - 1] == null)
859 {
860
861 this.fractionalHelperDirections[n - 1] =
862 new Point2D.Double(p2.x - this.line2d.get(n).x, p2.y - this.line2d.get(n).y);
863 }
864 }
865 else
866 {
867
868 this.fractionalHelperCenters[0] = intersectionOfLines(this.line2d.get(0), p1, this.line2d.get(1), p2);
869 }
870 if (this.fractionalHelperCenters[0] == null)
871 {
872
873 this.fractionalHelperDirections[0] = new Point2D.Double(p1.x - this.line2d.get(0).x, p1.y - this.line2d.get(0).y);
874 }
875
876 }
877
878
879
880
881
882
883
884
885
886
887
888 private Point2d intersectionOfLines(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
889 final Point2d line2P2)
890 {
891 double l1p1x = line1P1.x;
892 double l1p1y = line1P1.y;
893 double l1p2x = line1P2.x - l1p1x;
894 double l1p2y = line1P2.y - l1p1y;
895 double l2p1x = line2P1.x - l1p1x;
896 double l2p1y = line2P1.y - l1p1y;
897 double l2p2x = line2P2.x - l1p1x;
898 double l2p2y = line2P2.y - l1p1y;
899 double determinant = (0 - l1p2x) * (l2p1y - l2p2y) - (0 - l1p2y) * (l2p1x - l2p2x);
900 if (Math.abs(determinant) < 0.0000001)
901 {
902 return null;
903 }
904 return new Point2d(l1p1x + (l1p2x * (l2p1x * l2p2y - l2p1y * l2p2x)) / determinant,
905 l1p1y + (l1p2y * (l2p1x * l2p2y - l2p1y * l2p2x)) / determinant);
906 }
907
908
909
910
911
912
913 private synchronized PolyLine2d unitOffsetSegment(final int segment)
914 {
915 return new PolyLine2d(this.line2d.get(segment), this.line2d.get(segment + 1)).offsetLine(1.0);
916 }
917
918
919
920
921
922
923
924
925
926
927
928
929 public synchronized Length getProjectedRadius(final double fraction) throws OtsGeometryException
930 {
931 Throw.when(fraction < 0.0 || fraction > 1.0, OtsGeometryException.class, "Fraction %f is out of bounds [0.0 ... 1.0]",
932 fraction);
933 if (this.vertexRadii == null)
934 {
935 this.vertexRadii = new Length[size() - 1];
936 }
937 int index = find(fraction * getLength().si);
938 if (index > 0 && this.vertexRadii[index] == null)
939 {
940 this.vertexRadii[index] = getProjectedVertexRadius(index);
941 }
942 if (index < size() - 2 && this.vertexRadii[index + 1] == null)
943 {
944 this.vertexRadii[index + 1] = getProjectedVertexRadius(index + 1);
945 }
946 if (index == 0)
947 {
948 if (this.vertexRadii.length < 2)
949 {
950 return Length.instantiateSI(Double.NaN);
951 }
952 return this.vertexRadii[1];
953 }
954 if (index == size() - 2)
955 {
956 return this.vertexRadii[size() - 2];
957 }
958 return Math.abs(this.vertexRadii[index].si) < Math.abs(this.vertexRadii[index + 1].si) ? this.vertexRadii[index]
959 : this.vertexRadii[index + 1];
960 }
961
962
963
964
965
966
967
968
969
970
971
972 public synchronized Length getProjectedVertexRadius(final int index) throws OtsGeometryException
973 {
974 Throw.when(index < 1 || index > size() - 2, OtsGeometryException.class, "Index %d is out of bounds [1 ... size() - 2].",
975 index);
976 determineFractionalHelpers(null, null);
977 double length1 = this.lengthIndexedLine[index] - this.lengthIndexedLine[index - 1];
978 double length2 = this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index];
979 int shortIndex = length1 < length2 ? index : index + 1;
980
981 Point2d p1 = new Point2d(.5 * (this.line2d.get(shortIndex - 1).x + this.line2d.get(shortIndex).x),
982 .5 * (this.line2d.get(shortIndex - 1).y + this.line2d.get(shortIndex).y));
983
984 Point2d p2 = new Point2d(p1.x + (this.line2d.get(shortIndex).y - this.line2d.get(shortIndex - 1).y),
985 p1.y - (this.line2d.get(shortIndex).x - this.line2d.get(shortIndex - 1).x));
986
987 Point2d p3 = this.line2d.get(index);
988
989 Point2d p4 = this.fractionalHelperCenters[index];
990 if (p4 == null)
991 {
992
993 p4 = new Point2d(p3.x + this.fractionalHelperDirections[index].x, p3.y + this.fractionalHelperDirections[index].y);
994 }
995 Point2d intersection = intersectionOfLines(p1, p2, p3, p4);
996 if (null == intersection)
997 {
998 return Length.instantiateSI(Double.NaN);
999 }
1000
1001 double refLength = length1 < length2 ? length1 : length2;
1002 double radius = intersection.distance(p1);
1003 double i2p2 = intersection.distance(p2);
1004 if (radius < i2p2 && i2p2 > refLength)
1005 {
1006
1007 return Length.instantiateSI(radius);
1008 }
1009
1010 return Length.instantiateSI(-radius);
1011 }
1012
1013
1014
1015
1016
1017
1018
1019 public double getVertexFraction(final int index) throws OtsGeometryException
1020 {
1021 Throw.when(index < 0 || index > size() - 1, OtsGeometryException.class, "Index %d is out of bounds [0 %d].", index,
1022 size() - 1);
1023 return this.lengthIndexedLine[index] / this.length.si;
1024 }
1025
1026
1027
1028
1029
1030 public final Point2d getCentroid()
1031 {
1032 if (this.centroid == null)
1033 {
1034 this.centroid = this.line2d.getBounds().midPoint();
1035 }
1036 return this.centroid;
1037 }
1038
1039
1040
1041
1042
1043 public final Bounds2d getEnvelope()
1044 {
1045 return this.line2d.getBounds();
1046 }
1047
1048
1049 @Override
1050 @SuppressWarnings("checkstyle:designforextension")
1051 public Point2d getLocation()
1052 {
1053 return getCentroid();
1054 }
1055
1056
1057 @Override
1058 @SuppressWarnings("checkstyle:designforextension")
1059 public Bounds2d getBounds()
1060 {
1061 if (this.bounds == null)
1062 {
1063 Bounds2d envelope = getEnvelope();
1064 this.bounds = new Bounds2d(envelope.getDeltaX(), envelope.getDeltaY());
1065 }
1066 return this.bounds;
1067 }
1068
1069
1070 @Override
1071 @SuppressWarnings("checkstyle:designforextension")
1072 public String toString()
1073 {
1074 return this.line2d.toString();
1075 }
1076
1077
1078 @Override
1079 @SuppressWarnings("checkstyle:designforextension")
1080 public int hashCode()
1081 {
1082 return this.line2d.hashCode();
1083 }
1084
1085
1086 @Override
1087 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
1088 public boolean equals(final Object obj)
1089 {
1090 if (!(obj instanceof OtsLine2d))
1091 {
1092 return false;
1093 }
1094 return this.line2d.equals(((OtsLine2d) obj).line2d);
1095 }
1096
1097
1098
1099
1100
1101 public final String toExcel()
1102 {
1103 return this.line2d.toExcel();
1104 }
1105
1106
1107
1108
1109
1110 public final String toPlot()
1111 {
1112 return this.line2d.toPlot();
1113 }
1114
1115 }