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