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.draw.point.OrientedPoint2d;
15 import org.djutils.draw.point.Point2d;
16 import org.djutils.exceptions.Throw;
17 import org.djutils.exceptions.Try;
18 import org.opentrafficsim.base.parameters.ParameterTypes;
19 import org.opentrafficsim.core.geometry.Bezier;
20 import org.opentrafficsim.core.geometry.OtsGeometryException;
21 import org.opentrafficsim.core.geometry.OtsLine2d;
22 import org.opentrafficsim.core.geometry.OtsLine2d.FractionalFallback;
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 = dlp.lane().accessibleAdjacentLanesPhysical(this.laneChangeDirectionality, gtu.getType());
205 if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
206 {
207 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
208 }
209 return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223 public final OtsLine2d getPath(final Duration timeStep, final LaneBasedGtu gtu, final LanePosition from,
224 final OrientedPoint2d startPosition, final Length planDistance, final LateralDirectionality laneChangeDirection)
225 throws OtsGeometryException
226 {
227
228 boolean favoured = false;
229 if (!isChangingLane())
230 {
231 favoured = true;
232 this.laneChangeDirectionality = laneChangeDirection;
233 Try.execute(() -> gtu.initLaneChange(laneChangeDirection), "Error during lane change initialization.");
234 }
235
236 favoured = true;
237
238
239
240
241
242
243
244
245
246
247 Speed meanSpeed = planDistance.divide(timeStep);
248 double minDuration = this.minimumLaneChangeDistance.si / meanSpeed.si;
249 double laneChangeDuration = Math.max(this.desiredLaneChangeDuration.si, minDuration);
250 if (this.boundary != null)
251 {
252 double maxDuration = this.boundary.si / meanSpeed.si;
253 laneChangeDuration = Math.min(laneChangeDuration, maxDuration);
254 }
255
256 double totalLength = laneChangeDuration * meanSpeed.si;
257 double fromDist = (1.0 - this.fraction) * totalLength;
258 Throw.when(fromDist < 0.0, RuntimeException.class, "Lane change results in negative distance along from lanes.");
259
260
261 Lane fromLane = from.lane();
262 List<Lane> fromLanes = new ArrayList<>();
263 List<Lane> toLanes = new ArrayList<>();
264 fromLanes.add(fromLane);
265 toLanes.add(fromLane.getAdjacentLane(this.laneChangeDirectionality, gtu.getType()));
266 double endPosFrom = from.position().si + fromDist;
267 while (endPosFrom + gtu.getFront().dx().si > fromLane.getLength().si)
268 {
269 Lane nextFromLane;
270 if (!favoured)
271 {
272 nextFromLane = gtu.getNextLaneForRoute(fromLane);
273 }
274 else
275 {
276 Set<Lane> nexts = gtu.getNextLanesForRoute(fromLane);
277 if (nexts != null && !nexts.isEmpty())
278 {
279 Iterator<Lane> it = nexts.iterator();
280 nextFromLane = it.next();
281 while (it.hasNext())
282 {
283 nextFromLane =
284 LaneBasedTacticalPlanner.mostOnSide(nextFromLane, it.next(), this.laneChangeDirectionality);
285 }
286 }
287 else
288 {
289 nextFromLane = null;
290 }
291 }
292 if (nextFromLane == null)
293 {
294
295 double endFromPosLimit = fromLane.getLength().si - gtu.getFront().dx().si;
296 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
297 laneChangeDuration *= f;
298 endPosFrom = endFromPosLimit;
299 break;
300 }
301 endPosFrom -= fromLane.getLength().si;
302 Lane nextToLane = nextFromLane.getAdjacentLane(this.laneChangeDirectionality, gtu.getType());
303 if (nextToLane == null)
304 {
305
306 double endFromPosLimit = fromLane.getLength().si - gtu.getFront().dx().si;
307 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
308 laneChangeDuration *= f;
309 endPosFrom = endFromPosLimit;
310 break;
311 }
312 fromLane = nextFromLane;
313 fromLanes.add(fromLane);
314 toLanes.add(nextToLane);
315 }
316
317 while (endPosFrom < 0.0)
318 {
319 fromLanes.remove(fromLanes.size() - 1);
320 toLanes.remove(toLanes.size() - 1);
321 fromLane = fromLanes.get(fromLanes.size() - 1);
322 endPosFrom += fromLane.getLength().si;
323 }
324
325 double endFractionalPositionFrom = fromLane.fractionAtCoveredDistance(Length.instantiateSI(endPosFrom));
326
327 LanePosition fromAdjusted = from;
328 while (fromAdjusted.position().gt(fromAdjusted.lane().getLength()))
329 {
330
331 fromLanes.remove(0);
332 toLanes.remove(0);
333 Length beyond = fromAdjusted.position().minus(fromAdjusted.lane().getLength());
334 Length pos = beyond;
335 fromAdjusted = Try.assign(() -> new LanePosition(fromLanes.get(0), pos), OtsGeometryException.class,
336 "Info for lane is null.");
337 }
338
339
340
341
342 double startFractionalPositionFrom = from.position().si / from.lane().getLength().si;
343 OtsLine2d fromLine = getLine(fromLanes, startFractionalPositionFrom, endFractionalPositionFrom);
344
345 double startFractionalPositionTo = toLanes.get(0).getCenterLine().projectFractional(null, null, startPosition.x,
346 startPosition.y, FractionalFallback.ENDPOINT);
347 int last = fromLanes.size() - 1;
348 double frac = endFractionalPositionFrom;
349 OrientedPoint2d p = fromLanes.get(last).getCenterLine().getLocationFraction(frac);
350 double endFractionalPositionTo =
351 toLanes.get(last).getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.ENDPOINT);
352 startFractionalPositionTo = startFractionalPositionTo >= 0.0 ? startFractionalPositionTo : 0.0;
353 endFractionalPositionTo = endFractionalPositionTo <= 1.0 ? endFractionalPositionTo : 1.0;
354 endFractionalPositionTo = endFractionalPositionTo <= 0.0 ? endFractionalPositionFrom : endFractionalPositionTo;
355
356 if (fromLanes.size() == 1 && endFractionalPositionTo <= startFractionalPositionTo)
357 {
358 endFractionalPositionTo = Math.min(Math.max(startFractionalPositionTo + 0.001, endFractionalPositionFrom), 1.0);
359 }
360 if (startFractionalPositionTo >= 1.0)
361 {
362 toLanes.remove(0);
363 startFractionalPositionTo = 0.0;
364 }
365 OtsLine2d toLine = getLine(toLanes, startFractionalPositionTo, endFractionalPositionTo);
366
367 OtsLine2d path = this.laneChangePath.getPath(timeStep, planDistance, meanSpeed, fromAdjusted, startPosition,
368 laneChangeDirection, fromLine, toLine, Duration.instantiateSI(laneChangeDuration), this.fraction);
369
370
371 this.fraction += timeStep.si / laneChangeDuration;
372
373
374 double requiredLength = planDistance.si - path.getLength().si;
375 if (requiredLength > 0.0 || this.fraction > 0.999)
376 {
377 try
378 {
379 gtu.getSimulator().scheduleEventNow(BUILDER, "scheduleLaneChangeFinalization",
380 new Object[] {gtu, Length.min(planDistance, path.getLength()), laneChangeDirection});
381 }
382 catch (SimRuntimeException exception)
383 {
384 throw new RuntimeException("Error during lane change finalization.", exception);
385 }
386
387 if (requiredLength > 0.0)
388 {
389 Lane toLane = toLanes.get(toLanes.size() - 1);
390 int n = path.size();
391
392 if (0.0 < endFractionalPositionFrom && endFractionalPositionFrom < 1.0)
393 {
394 OtsLine2d remainder = toLane.getCenterLine().extractFractional(endFractionalPositionTo, 1.0);
395 path = OtsLine2d.concatenate(0.001, path, remainder);
396 requiredLength = planDistance.si - path.getLength().si;
397 }
398
399 while (toLane != null && requiredLength > 0.0)
400 {
401 toLane = gtu.getNextLaneForRoute(toLane);
402 if (toLane != null)
403 {
404 OtsLine2d remainder = toLane.getCenterLine();
405 path = OtsLine2d.concatenate(Lane.MARGIN.si, path, remainder);
406 requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
407 }
408 }
409
410 if (this.fraction > 0.999)
411 {
412 Point2d[] points = new Point2d[path.size() - 1];
413 System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
414 System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
415 path = new OtsLine2d(points);
416 }
417 }
418
419 this.laneChangeDirectionality = null;
420 this.boundary = null;
421 this.fraction = 0.0;
422 }
423
424 return path;
425 }
426
427
428
429
430
431
432
433
434
435 private OtsLine2d getLine(final List<Lane> lanes, final double startFractionalPosition, final double endFractionalPosition)
436 throws OtsGeometryException
437 {
438 OtsLine2d line = null;
439 for (Lane lane : lanes)
440 {
441 if (line == null && lane.equals(lanes.get(lanes.size() - 1)))
442 {
443 if (endFractionalPosition < startFractionalPosition)
444 {
445 System.out.println("hmmm");
446 }
447 line = lane.getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition);
448 }
449 else if (line == null)
450 {
451 line = lane.getCenterLine().extractFractional(startFractionalPosition, 1.0);
452 }
453 else if (lane.equals(lanes.get(lanes.size() - 1)))
454 {
455 line = OtsLine2d.concatenate(Lane.MARGIN.si, line,
456 lane.getCenterLine().extractFractional(0.0, endFractionalPosition));
457 }
458 else
459 {
460 line = OtsLine2d.concatenate(Lane.MARGIN.si, line, lane.getCenterLine());
461 }
462 }
463 return line;
464 }
465
466
467
468
469
470
471
472 public boolean checkRoom(final LaneBasedGtu gtu, final Headway headway)
473 {
474 if (this.desiredLaneChangeDuration == null)
475 {
476 this.desiredLaneChangeDuration = Try.assign(() -> gtu.getParameters().getParameter(ParameterTypes.LCDUR),
477 "LaneChange; the desired lane change duration should be set or paramater LCDUR should be defined.");
478 }
479
480 EgoPerception<?, ?> ego = gtu.getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(EgoPerception.class);
481 Speed egoSpeed = ego == null ? gtu.getSpeed() : ego.getSpeed();
482 Acceleration egoAcceleration = ego == null ? gtu.getAcceleration() : ego.getAcceleration();
483 Speed speed = headway.getSpeed() == null ? Speed.ZERO : headway.getSpeed();
484 Acceleration acceleration = headway.getAcceleration() == null ? Acceleration.ZERO : headway.getAcceleration();
485 Length s0 = gtu.getParameters().getParameterOrNull(ParameterTypes.S0);
486 s0 = s0 == null ? Length.ZERO : s0;
487
488 Length distanceToStop;
489 if (speed.eq0())
490 {
491 distanceToStop = Length.ZERO;
492 }
493 else
494 {
495 Acceleration b = gtu.getParameters().getParameterOrNull(ParameterTypes.B);
496 b = b == null ? Acceleration.ZERO : b.neg();
497 Acceleration a = Acceleration.min(Acceleration.max(b, acceleration.plus(b)), acceleration);
498 if (a.ge0())
499 {
500 return true;
501 }
502 double t = speed.si / -a.si;
503 distanceToStop = Length.instantiateSI(speed.si * t + .5 * a.si * t * t);
504 }
505
506 Length availableDistance = headway.getDistance().plus(distanceToStop);
507 double t = this.desiredLaneChangeDuration.si;
508 if (egoAcceleration.lt0())
509 {
510 t = Math.min(egoSpeed.si / -egoAcceleration.si, t);
511 }
512 Length requiredDistance = Length
513 .max(Length.instantiateSI(egoSpeed.si * t + .5 * egoAcceleration.si * t * t), this.minimumLaneChangeDistance)
514 .plus(s0);
515 return availableDistance.gt(requiredDistance);
516 }
517
518
519 @Override
520 public String toString()
521 {
522 return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
523 }
524
525
526
527
528
529
530
531
532
533
534
535
536 public interface LaneChangePath
537 {
538
539
540 LaneChangePath SINE_INTERP = new InterpolatedLaneChangePath()
541 {
542
543 @Override
544 double longitudinalFraction(final double lateralFraction)
545 {
546 return 1.0 - Math.acos(2.0 * (lateralFraction - 0.5)) / Math.PI;
547 }
548
549
550 @Override
551 double lateralFraction(final double longitudinalFraction)
552 {
553 return 0.5 - 0.5 * Math.cos(longitudinalFraction * Math.PI);
554 }
555 };
556
557
558 LaneChangePath LINEAR = new InterpolatedLaneChangePath()
559 {
560
561 @Override
562 double longitudinalFraction(final double lateralFraction)
563 {
564 return lateralFraction;
565 }
566
567 @Override
568 double lateralFraction(final double longitudinalFraction)
569 {
570 return longitudinalFraction;
571 }
572 };
573
574
575 LaneChangePath BEZIER = new LaneChangePath()
576 {
577
578 @Override
579 public OtsLine2d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
580 final LanePosition from, final OrientedPoint2d startPosition,
581 final LateralDirectionality laneChangeDirection, final OtsLine2d fromLine, final OtsLine2d toLine,
582 final Duration laneChangeDuration, final double lcFraction) throws OtsGeometryException
583 {
584 OrientedPoint2d target = toLine.getLocationFraction(1.0);
585 return Bezier.cubic(64, startPosition, target, 0.5);
586 }
587 };
588
589
590 LaneChangePath SINE = new SequentialLaneChangePath()
591 {
592
593 @Override
594 protected double lateralFraction(final double lcFraction)
595 {
596 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
597 }
598
599
600 @Override
601 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
602 {
603 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
604 + width / totalLcLength);
605 }
606 };
607
608
609 LaneChangePath POLY3 = new SequentialLaneChangePath()
610 {
611
612 @Override
613 protected double lateralFraction(final double lcFraction)
614 {
615 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
616 }
617
618
619 @Override
620 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
621 {
622 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
623 - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
624 }
625 };
626
627
628
629
630
631
632
633
634
635
636
637
638
639 abstract class SequentialLaneChangePath implements LaneChangePath
640 {
641
642 @Override
643 public OtsLine2d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
644 final LanePosition from, final OrientedPoint2d startPosition,
645 final LateralDirectionality laneChangeDirection, final OtsLine2d fromLine, final OtsLine2d toLine,
646 final Duration laneChangeDuration, final double lcFraction) throws OtsGeometryException
647 {
648 OrientedPoint2d toTarget = toLine.getLocationFraction(1.0);
649 OrientedPoint2d fromTarget = fromLine.getLocationFraction(1.0);
650 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
651 double dFraction = timeStep.si / laneChangeDuration.si;
652 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLine, toLine,
653 laneChangeDuration, lcFraction, dFraction);
654 }
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673 private OtsLine2d getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
674 final double width, final LanePosition from, final OrientedPoint2d startPosition, final OtsLine2d fromLine,
675 final OtsLine2d toLine, final Duration laneChangeDuration, final double lcFraction, final double dFraction)
676 throws OtsGeometryException
677 {
678
679
680 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
681 cutoff = cutoff > 1.0 ? 1.0 : cutoff;
682
683
684 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;
685
686
687 double f = lateralFraction(lcFractionEnd);
688
689
690 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
691 double cumulLcLength = totalLcLength * lcFractionEnd;
692
693
694
695 return null;
696 }
697
698
699
700
701
702
703 protected abstract double lateralFraction(double lcFraction);
704
705
706
707
708
709
710
711
712
713 protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
714 }
715
716
717
718
719
720
721
722
723
724
725
726
727 abstract class InterpolatedLaneChangePath implements LaneChangePath
728 {
729
730
731 @Override
732 public OtsLine2d getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
733 final LanePosition from, final OrientedPoint2d startPosition,
734 final LateralDirectionality laneChangeDirection, final OtsLine2d fromLine, final OtsLine2d toLine,
735 final Duration laneChangeDuration, final double lcFraction) throws OtsGeometryException
736 {
737
738 double dx = fromLine.get(0).x - startPosition.x;
739 double dy = fromLine.get(0).y - startPosition.y;
740 double distFromLoc = Math.hypot(dx, dy);
741 dx = fromLine.get(0).x - toLine.get(0).x;
742 dy = fromLine.get(0).y - toLine.get(0).y;
743 double distFromTo = Math.hypot(dx, dy);
744 double startLateralFraction = distFromLoc / distFromTo;
745
746
747 if (Double.isNaN(startLateralFraction) || startLateralFraction > 1.0)
748 {
749 startLateralFraction = 1.0;
750 }
751 double startLongitudinalFractionTotal = longitudinalFraction(startLateralFraction);
752
753 double nSegments = Math.ceil((64 * (1.0 - lcFraction)));
754 List<Point2d> pointList = new ArrayList<>();
755
756 pointList.add(startPosition);
757 for (int i = 1; i <= nSegments; i++)
758 {
759 double f = i / nSegments;
760 double longitudinalFraction = startLongitudinalFractionTotal + f * (1.0 - startLongitudinalFractionTotal);
761 double lateralFraction = lateralFraction(longitudinalFraction);
762 double lateralFractionInv = 1.0 - lateralFraction;
763 OrientedPoint2d fromPoint = fromLine.getLocationFraction(f);
764 OrientedPoint2d toPoint = toLine.getLocationFraction(f);
765 pointList.add(new Point2d(lateralFractionInv * fromPoint.x + lateralFraction * toPoint.x,
766 lateralFractionInv * fromPoint.y + lateralFraction * toPoint.y));
767 }
768
769 OtsLine2d line = new OtsLine2d(pointList);
770
771 double angleChange = Math.abs(line.getLocation(Length.ZERO).getDirZ() - startPosition.getDirZ());
772 int i = 1;
773 while (angleChange > Math.PI / 4)
774 {
775 i++;
776 if (i >= pointList.size() - 2)
777 {
778
779 return new OtsLine2d(pointList);
780 }
781 List<Point2d> newPointList = new ArrayList<>(pointList.subList(i, pointList.size()));
782 newPointList.add(0, pointList.get(0));
783 line = new OtsLine2d(newPointList);
784 angleChange = Math.abs(line.getLocation(Length.ZERO).getDirZ() - startPosition.getDirZ());
785 }
786 return line;
787 }
788
789
790
791
792
793
794 abstract double longitudinalFraction(double lateralFraction);
795
796
797
798
799
800
801 abstract double lateralFraction(double longitudinalFraction);
802
803 }
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823 OtsLine2d getPath(Duration timeStep, Length planDistance, Speed meanSpeed, LanePosition from,
824 OrientedPoint2d startPosition, LateralDirectionality laneChangeDirection, OtsLine2d fromLine, OtsLine2d toLine,
825 Duration laneChangeDuration, double lcFraction) throws OtsGeometryException;
826 }
827 }