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