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
379 this.fraction += timeStep.si / laneChangeDuration;
380
381
382 double requiredLength = planDistance.si - path.getLength().si;
383 if (requiredLength > 0.0 || this.fraction > 0.999)
384 {
385 try
386 {
387 gtu.getSimulator().scheduleEventNow(gtu, BUILDER, "scheduleLaneChangeFinalization",
388 new Object[] { gtu, Length.min(planDistance, path.getLength()), laneChangeDirection });
389 }
390 catch (SimRuntimeException exception)
391 {
392 throw new RuntimeException("Error during lane change finalization.", exception);
393 }
394
395 if (requiredLength > 0.0)
396 {
397 LaneDirection toLane = toLanes.get(toLanes.size() - 1);
398 int n = path.size();
399
400 if (0.0 < endFractionalPositionFrom && endFractionalPositionFrom < 1.0)
401 {
402 OTSLine3D remainder = toLane.getDirection().isPlus()
403 ? toLane.getLane().getCenterLine().extractFractional(endFractionalPositionTo, 1.0)
404 : toLane.getLane().getCenterLine().extractFractional(0.0, endFractionalPositionTo).reverse();
405 path = OTSLine3D.concatenate(0.001, path, remainder);
406 requiredLength = planDistance.si - path.getLength().si;
407 }
408
409 while (toLane != null && requiredLength > 0.0)
410 {
411 toLane = toLane.getNextLaneDirection(gtu);
412 if (toLane != null)
413 {
414 OTSLine3D remainder = toLane.getDirection().isPlus() ? toLane.getLane().getCenterLine()
415 : toLane.getLane().getCenterLine().reverse();
416 path = OTSLine3D.concatenate(Lane.MARGIN.si, path, remainder);
417 requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
418 }
419 }
420
421 if (this.fraction > 0.999)
422 {
423 OTSPoint3D[] points = new OTSPoint3D[path.size() - 1];
424 System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
425 System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
426 path = new OTSLine3D(points);
427 }
428 }
429
430 this.laneChangeDirectionality = null;
431 this.boundary = null;
432 this.fraction = 0.0;
433 }
434
435 return path;
436 }
437
438
439
440
441
442
443
444
445
446 private OTSLine3D getLine(final List<LaneDirection> lanes, final double startFractionalPosition,
447 final double endFractionalPosition) throws OTSGeometryException
448 {
449 OTSLine3D line = null;
450 for (LaneDirection lane : lanes)
451 {
452 if (line == null && lane.equals(lanes.get(lanes.size() - 1)))
453 {
454 if (lane.getDirection().isPlus())
455 {
456 line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition);
457 }
458 else
459 {
460 line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, endFractionalPosition)
461 .reverse();
462 }
463 }
464 else if (line == null)
465 {
466 if (lane.getDirection().isPlus())
467 {
468 line = lane.getLane().getCenterLine().extractFractional(startFractionalPosition, 1.0);
469 }
470 else
471 {
472 line = lane.getLane().getCenterLine().extractFractional(0.0, startFractionalPosition).reverse();
473 }
474 }
475 else if (lane.equals(lanes.get(lanes.size() - 1)))
476 {
477 if (lane.getDirection().isPlus())
478 {
479 line = OTSLine3D.concatenate(Lane.MARGIN.si, line,
480 lane.getLane().getCenterLine().extractFractional(0.0, endFractionalPosition));
481 }
482 else
483 {
484 line = OTSLine3D.concatenate(Lane.MARGIN.si, line,
485 lane.getLane().getCenterLine().extractFractional(endFractionalPosition, 1.0).reverse());
486 }
487 }
488 else
489 {
490 if (lane.getDirection().isPlus())
491 {
492 line = OTSLine3D.concatenate(Lane.MARGIN.si, line, lane.getLane().getCenterLine());
493 }
494 else
495 {
496 line = OTSLine3D.concatenate(Lane.MARGIN.si, line, lane.getLane().getCenterLine().reverse());
497 }
498 }
499 }
500 return line;
501 }
502
503
504
505
506
507
508
509 public boolean checkRoom(final LaneBasedGTU gtu, final Headway headway)
510 {
511 if (this.desiredLaneChangeDuration == null)
512 {
513 this.desiredLaneChangeDuration = Try.assign(() -> gtu.getParameters().getParameter(ParameterTypes.LCDUR),
514 "LaneChange; the desired lane change duration should be set or paramater LCDUR should be defined.");
515 }
516
517 EgoPerception<?, ?> ego = gtu.getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(EgoPerception.class);
518 Speed egoSpeed = ego == null ? gtu.getSpeed() : ego.getSpeed();
519 Acceleration egoAcceleration = ego == null ? gtu.getAcceleration() : ego.getAcceleration();
520 Speed speed = headway.getSpeed() == null ? Speed.ZERO : headway.getSpeed();
521 Acceleration acceleration = headway.getAcceleration() == null ? Acceleration.ZERO : headway.getAcceleration();
522 Length s0 = gtu.getParameters().getParameterOrNull(ParameterTypes.S0);
523 s0 = s0 == null ? Length.ZERO : s0;
524
525 Length distanceToStop;
526 if (speed.eq0())
527 {
528 distanceToStop = Length.ZERO;
529 }
530 else
531 {
532 Acceleration b = gtu.getParameters().getParameterOrNull(ParameterTypes.B);
533 b = b == null ? Acceleration.ZERO : b.neg();
534 Acceleration a = Acceleration.min(Acceleration.max(b, acceleration.plus(b)), acceleration);
535 if (a.ge0())
536 {
537 return true;
538 }
539 double t = speed.si / -a.si;
540 distanceToStop = Length.instantiateSI(speed.si * t + .5 * a.si * t * t);
541 }
542
543 Length availableDistance = headway.getDistance().plus(distanceToStop);
544 double t = this.desiredLaneChangeDuration.si;
545 if (egoAcceleration.lt0())
546 {
547 t = Math.min(egoSpeed.si / -egoAcceleration.si, t);
548 }
549 Length requiredDistance =
550 Length.max(Length.instantiateSI(egoSpeed.si * t + .5 * egoAcceleration.si * t * t), this.minimumLaneChangeDistance)
551 .plus(s0);
552 return availableDistance.gt(requiredDistance);
553 }
554
555
556 @Override
557 public String toString()
558 {
559 return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574 public interface LaneChangePath
575 {
576
577
578 LaneChangePath SINE_INTERP = new InterpolatedLaneChangePath()
579 {
580
581 @Override
582 double longitudinalFraction(final double lateralFraction)
583 {
584 return 1.0 - Math.acos(2.0 * (lateralFraction - 0.5)) / Math.PI;
585 }
586
587
588 @Override
589 double lateralFraction(final double longitudinalFraction)
590 {
591 return 0.5 - 0.5 * Math.cos(longitudinalFraction * Math.PI);
592 }
593 };
594
595
596 LaneChangePath LINEAR = new InterpolatedLaneChangePath()
597 {
598
599 @Override
600 double longitudinalFraction(final double lateralFraction)
601 {
602 return lateralFraction;
603 }
604
605 @Override
606 double lateralFraction(final double longitudinalFraction)
607 {
608 return longitudinalFraction;
609 }
610 };
611
612
613 LaneChangePath BEZIER = new LaneChangePath()
614 {
615
616 @Override
617 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
618 final DirectedLanePosition from, final DirectedPoint startPosition,
619 final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
620 final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
621 {
622 DirectedPoint target = toLine.getLocationFraction(1.0);
623 return Bezier.cubic(64, startPosition, target, 0.5);
624 }
625 };
626
627
628 LaneChangePath SINE = new SequentialLaneChangePath()
629 {
630
631 @Override
632 protected double lateralFraction(final double lcFraction)
633 {
634 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
635 }
636
637
638 @Override
639 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
640 {
641 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
642 + width / totalLcLength);
643 }
644 };
645
646
647 LaneChangePath POLY3 = new SequentialLaneChangePath()
648 {
649
650 @Override
651 protected double lateralFraction(final double lcFraction)
652 {
653 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
654 }
655
656
657 @Override
658 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
659 {
660 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
661 - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
662 }
663 };
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678 abstract class SequentialLaneChangePath implements LaneChangePath
679 {
680
681 @Override
682 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
683 final DirectedLanePosition from, final DirectedPoint startPosition,
684 final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
685 final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
686 {
687 DirectedPoint toTarget = toLine.getLocationFraction(1.0);
688 DirectedPoint fromTarget = fromLine.getLocationFraction(1.0);
689 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
690 double dFraction = timeStep.si / laneChangeDuration.si;
691 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLine, toLine,
692 laneChangeDuration, lcFraction, dFraction);
693 }
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712 private OTSLine3D getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
713 final double width, final DirectedLanePosition from, final DirectedPoint startPosition,
714 final OTSLine3D fromLine, final OTSLine3D toLine, final Duration laneChangeDuration,
715 final double lcFraction, final double dFraction) throws OTSGeometryException
716 {
717
718
719 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
720 cutoff = cutoff > 1.0 ? 1.0 : cutoff;
721
722
723 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;
724
725
726 double f = lateralFraction(lcFractionEnd);
727
728
729 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
730 double cumulLcLength = totalLcLength * lcFractionEnd;
731
732
733
734 return null;
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
776 protected abstract double lateralFraction(double lcFraction);
777
778
779
780
781
782
783
784
785
786 protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
787 }
788
789
790
791
792
793
794
795
796
797
798
799
800
801 abstract class InterpolatedLaneChangePath implements LaneChangePath
802 {
803
804
805 @Override
806 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
807 final DirectedLanePosition from, final DirectedPoint startPosition,
808 final LateralDirectionality laneChangeDirection, final OTSLine3D fromLine, final OTSLine3D toLine,
809 final Duration laneChangeDuration, final double lcFraction) throws OTSGeometryException
810 {
811
812 double dx = fromLine.get(0).getLocation().x - startPosition.x;
813 double dy = fromLine.get(0).getLocation().y - startPosition.y;
814 double distFromLoc = Math.sqrt(dx * dx + dy * dy);
815 dx = fromLine.get(0).getLocation().x - toLine.get(0).getLocation().x;
816 dy = fromLine.get(0).getLocation().y - toLine.get(0).getLocation().y;
817 double distFromTo = Math.sqrt(dx * dx + dy * dy);
818 double startLateralFraction = distFromLoc / distFromTo;
819
820 if (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 }