1 package org.opentrafficsim.road.gtu.lane.plan.operational;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Iterator;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9
10 import org.djunits.value.vdouble.scalar.Acceleration;
11 import org.djunits.value.vdouble.scalar.Duration;
12 import org.djunits.value.vdouble.scalar.Length;
13 import org.djunits.value.vdouble.scalar.Speed;
14 import org.djutils.exceptions.Throw;
15 import org.djutils.exceptions.Try;
16 import org.opentrafficsim.base.parameters.ParameterTypes;
17 import org.opentrafficsim.core.geometry.Bezier;
18 import org.opentrafficsim.core.geometry.DirectedPoint;
19 import org.opentrafficsim.core.geometry.OtsGeometryException;
20 import org.opentrafficsim.core.geometry.OtsLine3d;
21 import org.opentrafficsim.core.geometry.OtsLine3d.FractionalFallback;
22 import org.opentrafficsim.core.geometry.OtsPoint3d;
23 import org.opentrafficsim.core.gtu.GtuException;
24 import org.opentrafficsim.core.gtu.perception.EgoPerception;
25 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
26 import org.opentrafficsim.core.network.LateralDirectionality;
27 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
28 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
29 import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
30 import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
31 import org.opentrafficsim.road.network.lane.Lane;
32 import org.opentrafficsim.road.network.lane.LanePosition;
33
34 import nl.tudelft.simulation.dsol.SimRuntimeException;
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class LaneChange implements Serializable
49 {
50
51
52 private static final long serialVersionUID = 20160811L;
53
54
55 private Duration desiredLaneChangeDuration;
56
57
58 private double fraction;
59
60
61 private Length boundary;
62
63
64 private LaneChangePath laneChangePath = LaneChangePath.SINE_INTERP;
65
66
67 private LateralDirectionality laneChangeDirectionality = null;
68
69
70 private final Length minimumLaneChangeDistance;
71
72
73 private static final LaneOperationalPlanBuilder BUILDER = new LaneOperationalPlanBuilder();
74
75
76 public static double MIN_LC_LENGTH_FACTOR = 1.5;
77
78
79
80
81
82 public LaneChange(final LaneBasedGtu gtu)
83 {
84 this.minimumLaneChangeDistance = gtu.getLength().times(MIN_LC_LENGTH_FACTOR);
85 }
86
87
88
89
90
91
92 public LaneChange(final Length minimumLaneChangeDistance, final Duration desiredLaneChangeDuration)
93 {
94 this.minimumLaneChangeDistance = minimumLaneChangeDistance;
95 this.desiredLaneChangeDuration = desiredLaneChangeDuration;
96 }
97
98
99
100
101
102 public Length getMinimumLaneChangeDistance()
103 {
104 return this.minimumLaneChangeDistance;
105 }
106
107
108
109
110
111 public void setDesiredLaneChangeDuration(final Duration duration)
112 {
113 this.desiredLaneChangeDuration = duration;
114 }
115
116
117
118
119
120
121
122 public void setBoundary(final Length boundary)
123 {
124 this.boundary = boundary;
125 }
126
127
128
129
130
131 public double getFraction()
132 {
133 return this.fraction;
134 }
135
136
137
138
139
140 public void setLaneChangePath(final LaneChangePath laneChangePath)
141 {
142 this.laneChangePath = laneChangePath;
143 }
144
145
146
147
148
149 public final boolean isChangingLane()
150 {
151 return this.laneChangeDirectionality != null;
152 }
153
154
155
156
157
158 public final boolean isChangingLeft()
159 {
160 return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
161 }
162
163
164
165
166
167 public final boolean isChangingRight()
168 {
169 return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
170 }
171
172
173
174
175
176 public final LateralDirectionality getDirection()
177 {
178 return this.laneChangeDirectionality;
179 }
180
181
182
183
184
185
186
187
188
189 public final RelativeLane getSecondLane(final LaneBasedGtu gtu) throws OperationalPlanException
190 {
191 Throw.when(!isChangingLane(), OperationalPlanException.class,
192 "Target lane is requested, but no lane change is being performed.");
193 Map<Lane, Length> map;
194 LanePosition dlp;
195 try
196 {
197 map = gtu.positions(gtu.getReference());
198 dlp = gtu.getReferencePosition();
199 }
200 catch (GtuException exception)
201 {
202 throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
203 }
204 Set<Lane> accessibleLanes =
205 dlp.getLane().accessibleAdjacentLanesPhysical(this.laneChangeDirectionality, gtu.getType());
206 if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
207 {
208 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
209 }
210 return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 public final OtsLine3d getPath(final Duration timeStep, final LaneBasedGtu gtu, final LanePosition from,
225 final DirectedPoint startPosition, final Length planDistance, final LateralDirectionality laneChangeDirection)
226 throws OtsGeometryException
227 {
228
229 boolean favoured = false;
230 if (!isChangingLane())
231 {
232 favoured = true;
233 this.laneChangeDirectionality = laneChangeDirection;
234 Try.execute(() -> gtu.initLaneChange(laneChangeDirection), "Error during lane change initialization.");
235 }
236
237 favoured = true;
238
239
240
241
242
243
244
245
246
247
248 Speed meanSpeed = planDistance.divide(timeStep);
249 double minDuration = this.minimumLaneChangeDistance.si / meanSpeed.si;
250 double laneChangeDuration = Math.max(this.desiredLaneChangeDuration.si, minDuration);
251 if (this.boundary != null)
252 {
253 double maxDuration = this.boundary.si / meanSpeed.si;
254 laneChangeDuration = Math.min(laneChangeDuration, maxDuration);
255 }
256
257 double totalLength = laneChangeDuration * meanSpeed.si;
258 double fromDist = (1.0 - this.fraction) * totalLength;
259 Throw.when(fromDist < 0.0, RuntimeException.class, "Lane change results in negative distance along from lanes.");
260
261
262 Lane fromLane = from.getLane();
263 List<Lane> fromLanes = new ArrayList<>();
264 List<Lane> toLanes = new ArrayList<>();
265 fromLanes.add(fromLane);
266 toLanes.add(fromLane.getAdjacentLane(this.laneChangeDirectionality, gtu));
267 double endPosFrom = from.getPosition().si + fromDist;
268 while (endPosFrom + gtu.getFront().getDx().si > fromLane.getLength().si)
269 {
270 Lane nextFromLane;
271 if (!favoured)
272 {
273 nextFromLane = gtu.getNextLaneForRoute(fromLane);
274 }
275 else
276 {
277 Set<Lane> nexts = gtu.getNextLanesForRoute(fromLane);
278 if (nexts != null && !nexts.isEmpty())
279 {
280 Iterator<Lane> it = nexts.iterator();
281 nextFromLane = it.next();
282 while (it.hasNext())
283 {
284 nextFromLane =
285 LaneBasedTacticalPlanner.mostOnSide(nextFromLane, it.next(), this.laneChangeDirectionality);
286 }
287 }
288 else
289 {
290 nextFromLane = null;
291 }
292 }
293 if (nextFromLane == null)
294 {
295
296 double endFromPosLimit = fromLane.getLength().si - gtu.getFront().getDx().si;
297 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
298 laneChangeDuration *= f;
299 endPosFrom = endFromPosLimit;
300 break;
301 }
302 endPosFrom -= fromLane.getLength().si;
303 Lane nextToLane = nextFromLane.getAdjacentLane(this.laneChangeDirectionality, gtu);
304 if (nextToLane == null)
305 {
306
307 double endFromPosLimit = fromLane.getLength().si - gtu.getFront().getDx().si;
308 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
309 laneChangeDuration *= f;
310 endPosFrom = endFromPosLimit;
311 break;
312 }
313 fromLane = nextFromLane;
314 fromLanes.add(fromLane);
315 toLanes.add(nextToLane);
316 }
317
318 while (endPosFrom < 0.0)
319 {
320 fromLanes.remove(fromLanes.size() - 1);
321 toLanes.remove(toLanes.size() - 1);
322 fromLane = fromLanes.get(fromLanes.size() - 1);
323 endPosFrom += fromLane.getLength().si;
324 }
325
326 double endFractionalPositionFrom = fromLane.fractionAtCoveredDistance(Length.instantiateSI(endPosFrom));
327
328 LanePosition fromAdjusted = from;
329 while (fromAdjusted.getPosition().gt(fromAdjusted.getLane().getLength()))
330 {
331
332 fromLanes.remove(0);
333 toLanes.remove(0);
334 Length beyond = fromAdjusted.getPosition().minus(fromAdjusted.getLane().getLength());
335 Length pos = beyond;
336 fromAdjusted = Try.assign(() -> new LanePosition(fromLanes.get(0), pos), OtsGeometryException.class,
337 "Info for lane is null.");
338 }
339
340
341
342
343 double startFractionalPositionFrom = from.getPosition().si / from.getLane().getLength().si;
344 OtsLine3d fromLine = getLine(fromLanes, startFractionalPositionFrom, endFractionalPositionFrom);
345
346 double startFractionalPositionTo = toLanes.get(0).getCenterLine().projectFractional(null, null, startPosition.x,
347 startPosition.y, FractionalFallback.ENDPOINT);
348 int last = fromLanes.size() - 1;
349 double frac = endFractionalPositionFrom;
350 DirectedPoint p = fromLanes.get(last).getCenterLine().getLocationFraction(frac);
351 double endFractionalPositionTo =
352 toLanes.get(last).getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.ENDPOINT);
353 startFractionalPositionTo = startFractionalPositionTo >= 0.0 ? startFractionalPositionTo : 0.0;
354 endFractionalPositionTo = endFractionalPositionTo <= 1.0 ? endFractionalPositionTo : 1.0;
355 endFractionalPositionTo = endFractionalPositionTo <= 0.0 ? endFractionalPositionFrom : endFractionalPositionTo;
356
357 if (fromLanes.size() == 1 && endFractionalPositionTo <= startFractionalPositionTo)
358 {
359 endFractionalPositionTo = Math.min(Math.max(startFractionalPositionTo + 0.001, endFractionalPositionFrom), 1.0);
360 }
361 if (startFractionalPositionTo >= 1.0)
362 {
363 toLanes.remove(0);
364 startFractionalPositionTo = 0.0;
365 }
366 OtsLine3d toLine = getLine(toLanes, startFractionalPositionTo, endFractionalPositionTo);
367
368 OtsLine3d path = this.laneChangePath.getPath(timeStep, planDistance, meanSpeed, fromAdjusted, startPosition,
369 laneChangeDirection, fromLine, toLine, Duration.instantiateSI(laneChangeDuration), this.fraction);
370
371
372 this.fraction += timeStep.si / laneChangeDuration;
373
374
375 double requiredLength = planDistance.si - path.getLength().si;
376 if (requiredLength > 0.0 || this.fraction > 0.999)
377 {
378 try
379 {
380 gtu.getSimulator().scheduleEventNow(BUILDER, "scheduleLaneChangeFinalization",
381 new Object[] {gtu, Length.min(planDistance, path.getLength()), laneChangeDirection});
382 }
383 catch (SimRuntimeException exception)
384 {
385 throw new RuntimeException("Error during lane change finalization.", exception);
386 }
387
388 if (requiredLength > 0.0)
389 {
390 Lane toLane = toLanes.get(toLanes.size() - 1);
391 int n = path.size();
392
393 if (0.0 < endFractionalPositionFrom && endFractionalPositionFrom < 1.0)
394 {
395 OtsLine3d remainder = toLane.getCenterLine().extractFractional(endFractionalPositionTo, 1.0);
396 path = OtsLine3d.concatenate(0.001, path, remainder);
397 requiredLength = planDistance.si - path.getLength().si;
398 }
399
400 while (toLane != null && requiredLength > 0.0)
401 {
402 toLane = gtu.getNextLaneForRoute(toLane);
403 if (toLane != null)
404 {
405 OtsLine3d remainder = toLane.getCenterLine();
406 path = OtsLine3d.concatenate(Lane.MARGIN.si, path, remainder);
407 requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
408 }
409 }
410
411 if (this.fraction > 0.999)
412 {
413 OtsPoint3d[] points = new OtsPoint3d[path.size() - 1];
414 System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
415 System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
416 path = new OtsLine3d(points);
417 }
418 }
419
420 this.laneChangeDirectionality = null;
421 this.boundary = null;
422 this.fraction = 0.0;
423 }
424
425 return path;
426 }
427
428
429
430
431
432
433
434
435
436 private OtsLine3d getLine(final List<Lane> lanes, final double startFractionalPosition, final double endFractionalPosition)
437 throws OtsGeometryException
438 {
439 OtsLine3d line = null;
440 for (Lane lane : lanes)
441 {
442 if (line == null && lane.equals(lanes.get(lanes.size() - 1)))
443 {
444 line = lane.getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition);
445 }
446 else if (line == null)
447 {
448 line = lane.getCenterLine().extractFractional(startFractionalPosition, 1.0);
449 }
450 else if (lane.equals(lanes.get(lanes.size() - 1)))
451 {
452 line = OtsLine3d.concatenate(Lane.MARGIN.si, line,
453 lane.getCenterLine().extractFractional(0.0, endFractionalPosition));
454 }
455 else
456 {
457 line = OtsLine3d.concatenate(Lane.MARGIN.si, line, lane.getCenterLine());
458 }
459 }
460 return line;
461 }
462
463
464
465
466
467
468
469 public boolean checkRoom(final LaneBasedGtu gtu, final Headway headway)
470 {
471 if (this.desiredLaneChangeDuration == null)
472 {
473 this.desiredLaneChangeDuration = Try.assign(() -> gtu.getParameters().getParameter(ParameterTypes.LCDUR),
474 "LaneChange; the desired lane change duration should be set or paramater LCDUR should be defined.");
475 }
476
477 EgoPerception<?, ?> ego = gtu.getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(EgoPerception.class);
478 Speed egoSpeed = ego == null ? gtu.getSpeed() : ego.getSpeed();
479 Acceleration egoAcceleration = ego == null ? gtu.getAcceleration() : ego.getAcceleration();
480 Speed speed = headway.getSpeed() == null ? Speed.ZERO : headway.getSpeed();
481 Acceleration acceleration = headway.getAcceleration() == null ? Acceleration.ZERO : headway.getAcceleration();
482 Length s0 = gtu.getParameters().getParameterOrNull(ParameterTypes.S0);
483 s0 = s0 == null ? Length.ZERO : s0;
484
485 Length distanceToStop;
486 if (speed.eq0())
487 {
488 distanceToStop = Length.ZERO;
489 }
490 else
491 {
492 Acceleration b = gtu.getParameters().getParameterOrNull(ParameterTypes.B);
493 b = b == null ? Acceleration.ZERO : b.neg();
494 Acceleration a = Acceleration.min(Acceleration.max(b, acceleration.plus(b)), acceleration);
495 if (a.ge0())
496 {
497 return true;
498 }
499 double t = speed.si / -a.si;
500 distanceToStop = Length.instantiateSI(speed.si * t + .5 * a.si * t * t);
501 }
502
503 Length availableDistance = headway.getDistance().plus(distanceToStop);
504 double t = this.desiredLaneChangeDuration.si;
505 if (egoAcceleration.lt0())
506 {
507 t = Math.min(egoSpeed.si / -egoAcceleration.si, t);
508 }
509 Length requiredDistance = Length
510 .max(Length.instantiateSI(egoSpeed.si * t + .5 * egoAcceleration.si * t * t), this.minimumLaneChangeDistance)
511 .plus(s0);
512 return availableDistance.gt(requiredDistance);
513 }
514
515
516 @Override
517 public String toString()
518 {
519 return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
520 }
521
522
523
524
525
526
527
528
529
530
531
532
533 public interface LaneChangePath
534 {
535
536
537 LaneChangePath SINE_INTERP = new InterpolatedLaneChangePath()
538 {
539
540 @Override
541 double longitudinalFraction(final double lateralFraction)
542 {
543 return 1.0 - Math.acos(2.0 * (lateralFraction - 0.5)) / Math.PI;
544 }
545
546
547 @Override
548 double lateralFraction(final double longitudinalFraction)
549 {
550 return 0.5 - 0.5 * Math.cos(longitudinalFraction * Math.PI);
551 }
552 };
553
554
555 LaneChangePath LINEAR = new InterpolatedLaneChangePath()
556 {
557
558 @Override
559 double longitudinalFraction(final double lateralFraction)
560 {
561 return lateralFraction;
562 }
563
564 @Override
565 double lateralFraction(final double longitudinalFraction)
566 {
567 return longitudinalFraction;
568 }
569 };
570
571
572 LaneChangePath BEZIER = new LaneChangePath()
573 {
574
575 @Override
576 public OtsLine3d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
577 final LanePosition from, final DirectedPoint startPosition, final LateralDirectionality laneChangeDirection,
578 final OtsLine3d fromLine, final OtsLine3d toLine, final Duration laneChangeDuration,
579 final double lcFraction) throws OtsGeometryException
580 {
581 DirectedPoint target = toLine.getLocationFraction(1.0);
582 return Bezier.cubic(64, startPosition, target, 0.5);
583 }
584 };
585
586
587 LaneChangePath SINE = new SequentialLaneChangePath()
588 {
589
590 @Override
591 protected double lateralFraction(final double lcFraction)
592 {
593 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
594 }
595
596
597 @Override
598 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
599 {
600 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
601 + width / totalLcLength);
602 }
603 };
604
605
606 LaneChangePath POLY3 = new SequentialLaneChangePath()
607 {
608
609 @Override
610 protected double lateralFraction(final double lcFraction)
611 {
612 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
613 }
614
615
616 @Override
617 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
618 {
619 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
620 - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
621 }
622 };
623
624
625
626
627
628
629
630
631
632
633
634
635
636 abstract class SequentialLaneChangePath implements LaneChangePath
637 {
638
639 @Override
640 public OtsLine3d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
641 final LanePosition from, final DirectedPoint startPosition, final LateralDirectionality laneChangeDirection,
642 final OtsLine3d fromLine, final OtsLine3d toLine, final Duration laneChangeDuration,
643 final double lcFraction) throws OtsGeometryException
644 {
645 DirectedPoint toTarget = toLine.getLocationFraction(1.0);
646 DirectedPoint fromTarget = fromLine.getLocationFraction(1.0);
647 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
648 double dFraction = timeStep.si / laneChangeDuration.si;
649 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLine, toLine,
650 laneChangeDuration, lcFraction, dFraction);
651 }
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670 private OtsLine3d getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
671 final double width, final LanePosition from, final DirectedPoint startPosition, final OtsLine3d fromLine,
672 final OtsLine3d toLine, final Duration laneChangeDuration, final double lcFraction, final double dFraction)
673 throws OtsGeometryException
674 {
675
676
677 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
678 cutoff = cutoff > 1.0 ? 1.0 : cutoff;
679
680
681 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;
682
683
684 double f = lateralFraction(lcFractionEnd);
685
686
687 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
688 double cumulLcLength = totalLcLength * lcFractionEnd;
689
690
691
692 return null;
693 }
694
695
696
697
698
699
700 protected abstract double lateralFraction(double lcFraction);
701
702
703
704
705
706
707
708
709
710 protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
711 }
712
713
714
715
716
717
718
719
720
721
722
723
724 abstract class InterpolatedLaneChangePath implements LaneChangePath
725 {
726
727
728 @Override
729 public OtsLine3d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
730 final LanePosition from, final DirectedPoint startPosition, final LateralDirectionality laneChangeDirection,
731 final OtsLine3d fromLine, final OtsLine3d toLine, final Duration laneChangeDuration,
732 final double lcFraction) throws OtsGeometryException
733 {
734
735 double dx = fromLine.get(0).getLocation().x - startPosition.x;
736 double dy = fromLine.get(0).getLocation().y - startPosition.y;
737 double distFromLoc = Math.sqrt(dx * dx + dy * dy);
738 dx = fromLine.get(0).getLocation().x - toLine.get(0).getLocation().x;
739 dy = fromLine.get(0).getLocation().y - toLine.get(0).getLocation().y;
740 double distFromTo = Math.sqrt(dx * dx + dy * dy);
741 double startLateralFraction = distFromLoc / distFromTo;
742
743
744 if (Double.isNaN(startLateralFraction) || startLateralFraction > 1.0)
745 {
746 startLateralFraction = 1.0;
747 }
748 double startLongitudinalFractionTotal = longitudinalFraction(startLateralFraction);
749
750 double nSegments = Math.ceil((64 * (1.0 - lcFraction)));
751 List<OtsPoint3d> pointList = new ArrayList<>();
752 double zStart = (1.0 - startLateralFraction) * fromLine.get(0).z + startLateralFraction * toLine.get(0).z;
753 pointList.add(new OtsPoint3d(startPosition.x, startPosition.y, zStart));
754 for (int i = 1; i <= nSegments; i++)
755 {
756 double f = i / nSegments;
757 double longitudinalFraction = startLongitudinalFractionTotal + f * (1.0 - startLongitudinalFractionTotal);
758 double lateralFraction = lateralFraction(longitudinalFraction);
759 double lateralFractionInv = 1.0 - lateralFraction;
760 DirectedPoint fromPoint = fromLine.getLocationFraction(f);
761 DirectedPoint toPoint = toLine.getLocationFraction(f);
762 pointList.add(new OtsPoint3d(lateralFractionInv * fromPoint.x + lateralFraction * toPoint.x,
763 lateralFractionInv * fromPoint.y + lateralFraction * toPoint.y,
764 lateralFractionInv * fromPoint.z + lateralFraction * toPoint.z));
765 }
766
767 OtsLine3d line = new OtsLine3d(pointList);
768
769 double angleChange = Math.abs(line.getLocation(Length.ZERO).getRotZ() - startPosition.getRotZ());
770 int i = 1;
771 while (angleChange > Math.PI / 4)
772 {
773 i++;
774 if (i >= pointList.size() - 2)
775 {
776
777 return new OtsLine3d(pointList);
778 }
779 List<OtsPoint3d> newPointList = new ArrayList<>(pointList.subList(i, pointList.size()));
780 newPointList.add(0, pointList.get(0));
781 line = new OtsLine3d(newPointList);
782 angleChange = Math.abs(line.getLocation(Length.ZERO).getRotZ() - startPosition.getRotZ());
783 }
784 return line;
785 }
786
787
788
789
790
791
792 abstract double longitudinalFraction(double lateralFraction);
793
794
795
796
797
798
799 abstract double lateralFraction(double longitudinalFraction);
800
801 }
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821 OtsLine3d getPath(Duration timeStep, Length planDistance, Speed meanSpeed, LanePosition from,
822 DirectedPoint startPosition, LateralDirectionality laneChangeDirection, OtsLine3d fromLine, OtsLine3d toLine,
823 Duration laneChangeDuration, double lcFraction) throws OtsGeometryException;
824 }
825 }