1 package org.opentrafficsim.road.gtu.lane.plan.operational;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8
9 import org.djunits.value.vdouble.scalar.Duration;
10 import org.djunits.value.vdouble.scalar.Length;
11 import org.djunits.value.vdouble.scalar.Speed;
12 import org.opentrafficsim.core.geometry.Bezier;
13 import org.opentrafficsim.core.geometry.OTSGeometryException;
14 import org.opentrafficsim.core.geometry.OTSLine3D;
15 import org.opentrafficsim.core.geometry.OTSPoint3D;
16 import org.opentrafficsim.core.gtu.GTUException;
17 import org.opentrafficsim.core.gtu.Try;
18 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
19 import org.opentrafficsim.core.network.LateralDirectionality;
20 import org.opentrafficsim.road.gtu.lane.AbstractLaneBasedGTU;
21 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
22 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
23 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
24 import org.opentrafficsim.road.network.lane.Lane;
25 import org.opentrafficsim.road.network.lane.LaneDirection;
26
27 import nl.tudelft.simulation.dsol.SimRuntimeException;
28 import nl.tudelft.simulation.language.Throw;
29 import nl.tudelft.simulation.language.d3.DirectedPoint;
30
31 /**
32 * Lane change status across operational plans. This class allows lane based tactical planners to perform lane changes without
33 * having to deal with many complexities concerning paths and lane registration. The main purpose of the tactical planner is to
34 * request a path using {@code getPath()} for each step of the tactical planner.
35 * <p>
36 * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
37 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
38 * <p>
39 * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
40 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
41 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
42 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
43 */
44 public class LaneChange implements Serializable
45 {
46
47 /** */
48 private static final long serialVersionUID = 20160811L;
49
50 /** Total lane change duration. */
51 private Duration desiredLaneChangeDuration;
52
53 /** Fraction of lane change had. */
54 private double fraction;
55
56 /** Boundary length within which the lane change should be performed. */
57 private Length boundary;
58
59 /** Lane change path. */
60 private LaneChangePath laneChangePath = LaneChangePath.BEZIER;
61
62 /** Whether the GTU is changing lane. */
63 private LateralDirectionality laneChangeDirectionality = null;
64
65 /** Instance to invoke static method through scheduled event on. */
66 private static final LaneOperationalPlanBuilder BUILDER = new LaneOperationalPlanBuilder();
67
68 /** Minimum distance required to perform a lane change as factor on vehicle length. */
69 public static double MIN_LC_LENGTH_FACTOR = 2.0;
70
71 /**
72 * Sets the desired lane change duration. Should be set by a tactical planner.
73 * @param duration Duration; desired lane change duration
74 */
75 public void setDesiredLaneChangeDuration(final Duration duration)
76 {
77 this.desiredLaneChangeDuration = duration;
78 }
79
80 /**
81 * Sets the distance within which a lane change should be finished. Should be set by a tactical planner. In case of a single
82 * lane change required before some point, this is not required as the found center line length is intrinsically limited.
83 * For multiple lane changes being required, space after a lane change is required.
84 * @param boundary DirectedPoint; boundary
85 */
86 public void setBoundary(final Length boundary)
87 {
88 this.boundary = boundary;
89 }
90
91 /**
92 * Returns the fraction of the lane change performed.
93 * @return double; fraction of lane change performed
94 */
95 public double getFraction()
96 {
97 return this.fraction;
98 }
99
100 /**
101 * Sets a lane change path.
102 * @param laneChangePath LaneChangePath; lane change path
103 */
104 public void setLaneChangePath(final LaneChangePath laneChangePath)
105 {
106 this.laneChangePath = laneChangePath;
107 }
108
109 /**
110 * Return whether the GTU is changing lane.
111 * @return whether the GTU is changing lane
112 */
113 public final boolean isChangingLane()
114 {
115 return this.laneChangeDirectionality != null;
116 }
117
118 /**
119 * Return whether the GTU is changing left.
120 * @return whether the GTU is changing left
121 */
122 public final boolean isChangingLeft()
123 {
124 return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
125 }
126
127 /**
128 * Return whether the GTU is changing right.
129 * @return whether the GTU is changing right
130 */
131 public final boolean isChangingRight()
132 {
133 return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
134 }
135
136 /**
137 * Return lateral lane change direction.
138 * @return LateralDirectionality; lateral lane change direction
139 */
140 public final LateralDirectionality getDirection()
141 {
142 return this.laneChangeDirectionality;
143 }
144
145 /**
146 * Second lane of lane change relative to the reference lane. Note that the reference lane may either be the source or the
147 * target lane. Thus, the second lane during a lane change may either be the left or right lane, regardless of the lane
148 * change direction.
149 * @param gtu LaneBasedGTU; the GTU
150 * @return target lane of lane change
151 * @throws OperationalPlanException If no lane change is being performed.
152 */
153 public final RelativeLane getSecondLane(final LaneBasedGTU gtu) throws OperationalPlanException
154 {
155 Throw.when(!isChangingLane(), OperationalPlanException.class,
156 "Target lane is requested, but no lane change is being performed.");
157 Map<Lane, Length> map;
158 DirectedLanePosition dlp;
159 try
160 {
161 map = gtu.positions(gtu.getReference());
162 dlp = gtu.getReferencePosition();
163 }
164 catch (GTUException exception)
165 {
166 throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
167 }
168 Set<Lane> accessibleLanes = dlp.getLane().accessibleAdjacentLanesPhysical(this.laneChangeDirectionality,
169 gtu.getGTUType(), dlp.getGtuDirection());
170 if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
171 {
172 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
173 }
174 return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
175 }
176
177 /**
178 * Returns the path for a lane change. Lane change initialization and finalization events are automatically performed.
179 * @param timeStep Duration; plan time step
180 * @param gtu LaneBasedGTU; gtu
181 * @param from DirectedLanePosition; current position on the from lane (i.e. not necessarily the reference position)
182 * @param startPosition DirectedPoint; current position in 2D
183 * @param planDistance Length; absolute distance that will be covered during the time step
184 * @param laneChangeDirection LateralDirectionality; lane change direction
185 * @return OTSLine3D; path
186 * @throws OTSGeometryException on path or shape error
187 */
188 public final OTSLine3D getPath(final Duration timeStep, final LaneBasedGTU gtu, final DirectedLanePosition from,
189 final DirectedPoint startPosition, final Length planDistance, final LateralDirectionality laneChangeDirection)
190 throws OTSGeometryException
191 {
192
193 // initiate lane change
194 if (!isChangingLane())
195 {
196 this.laneChangeDirectionality = laneChangeDirection;
197 Try.execute(() -> ((AbstractLaneBasedGTU) gtu).initLaneChange(laneChangeDirection),
198 "Error during lane change initialization.");
199 }
200
201 // determine longitudinal distance along the from lanes
202 /*
203 * We take 3 factors in to account. The first two are 1) minimum physical lane change length, and 2) desired lane change
204 * duration. With the current mean speed of the plan, we take the maximum. So at very low speeds, the minimum physical
205 * length may increase the lane change duration. We also have 3) the maximum length before a lane change needs to have
206 * been performed. To overcome simulation troubles, we allow this to result in an even shorted length than the minimum
207 * physical distance. So: length = min( max("1", "2"), "3" ). These distances are all considered along the from-lanes.
208 * Actual path distance is different.
209 */
210 Speed meanSpeed = planDistance.divideBy(timeStep);
211 double minDistance = gtu.getLength().si * MIN_LC_LENGTH_FACTOR; // simple bare minimum
212 double minDuration = minDistance / meanSpeed.si;
213 double laneChangeDuration = Math.max(this.desiredLaneChangeDuration.si, minDuration);
214 if (this.boundary != null)
215 {
216 double maxDuration = this.boundary.si / meanSpeed.si;
217 laneChangeDuration = Math.min(laneChangeDuration, maxDuration);
218 }
219
220 double totalLength = laneChangeDuration * meanSpeed.si;
221 double fromDist = (1.0 - this.fraction) * totalLength; // remaining distance along from lanes to lane change end
222 Throw.when(fromDist < 0.0, RuntimeException.class, "Lane change results in negative distance along from lanes.");
223
224 // get fractional location there, build lane lists as we search over the distance
225 LaneDirection fromLane = from.getLaneDirection();
226 List<LaneDirection> fromLanes = new ArrayList<>();
227 List<LaneDirection> toLanes = new ArrayList<>();
228 fromLanes.add(fromLane);
229 toLanes.add(fromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu));
230 double endPosFrom = from.getPosition().si + fromDist;
231 while (endPosFrom + gtu.getFront().getDx().si > fromLane.getLane().getLength().si)
232 {
233 LaneDirection nextFromLane = fromLane.getNextLaneDirection(gtu);
234 if (nextFromLane == null)
235 {
236 // there are no lanes to move on, restrict lane change length/duration (given fixed mean speed)
237 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
238 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
239 laneChangeDuration *= f;
240 endPosFrom = endFromPosLimit;
241 break;
242 }
243 endPosFrom -= fromLane.getLane().getLength().si;
244 LaneDirection nextToLane = nextFromLane.getAdjacentLaneDirection(this.laneChangeDirectionality, gtu);
245 if (nextToLane == null)
246 {
247 // there are no lanes to move change to, restrict lane change length/duration (given fixed mean speed)
248 double endFromPosLimit = fromLane.getLane().getLength().si - gtu.getFront().getDx().si;
249 double f = 1.0 - (endPosFrom - endFromPosLimit) / fromDist;
250 laneChangeDuration *= f;
251 endPosFrom = endFromPosLimit;
252 break;
253 }
254 fromLane = nextFromLane;
255 fromLanes.add(fromLane);
256 toLanes.add(nextToLane);
257 }
258 // for long vehicles and short lanes, revert
259 while (endPosFrom < 0.0)
260 {
261 fromLanes.remove(fromLanes.size() - 1);
262 toLanes.remove(toLanes.size() - 1);
263 fromLane = fromLanes.get(fromLanes.size() - 1);
264 endPosFrom += fromLane.getLane().getLength().si;
265 }
266 // finally, get location at the final lane available
267 double endFraction = fromLane.fractionAtCoveredDistance(Length.createSI(endPosFrom));
268
269 // get path from shape
270 OTSLine3D path = this.laneChangePath.getPath(timeStep, planDistance, meanSpeed, from, startPosition,
271 laneChangeDirection, fromLanes, toLanes, endFraction, Duration.createSI(laneChangeDuration), this.fraction);
272
273 // update
274 this.fraction += timeStep.si / laneChangeDuration; // the total fraction this step increases
275
276 // deal with lane change end
277 double requiredLength = planDistance.si - path.getLength().si;
278 if (requiredLength > 0.0 || this.fraction > 0.999)
279 {
280 try
281 {
282 // TODO get rid of cast to AbstractLaneBasedGTU
283 gtu.getSimulator().scheduleEventNow(gtu, BUILDER, "scheduleLaneChangeFinalization", new Object[] {
284 (AbstractLaneBasedGTU) gtu, Length.min(planDistance, path.getLength()), laneChangeDirection });
285 }
286 catch (SimRuntimeException exception)
287 {
288 throw new RuntimeException("Error during lane change finalization.", exception);
289 }
290 // add length to path on to lanes
291 if (requiredLength > 0.0)
292 {
293 LaneDirection toLane = toLanes.get(toLanes.size() - 1);
294 int n = path.size();
295 // ignore remainder of first lane if fraction is at the end of the lane
296 if (0.0 < endFraction && endFraction < 1.0)
297 {
298 OTSLine3D remainder = toLane.getDirection().isPlus()
299 ? toLane.getLane().getCenterLine().extractFractional(endFraction, 1.0)
300 : toLane.getLane().getCenterLine().extractFractional(0.0, endFraction).reverse();
301 path = OTSLine3D.concatenate(0.001, path, remainder);
302 requiredLength = planDistance.si - path.getLength().si;
303 }
304 // add further lanes
305 while (requiredLength > 0.0)
306 {
307 toLane = toLane.getNextLaneDirection(gtu);
308 OTSLine3D remainder = toLane.getDirection().isPlus() ? toLane.getLane().getCenterLine()
309 : toLane.getLane().getCenterLine().reverse();
310 path = OTSLine3D.concatenate(Lane.MARGIN.si, path, remainder);
311 requiredLength = planDistance.si - path.getLength().si + Lane.MARGIN.si;
312 }
313 // filter near-duplicate point which results in projection exceptions
314 if (this.fraction > 0.999) // this means point 'target' is essentially at the design line
315 {
316 OTSPoint3D[] points = new OTSPoint3D[path.size() - 1];
317 System.arraycopy(path.getPoints(), 0, points, 0, n - 1);
318 System.arraycopy(path.getPoints(), n, points, n - 1, path.size() - n);
319 path = new OTSLine3D(points);
320 }
321 }
322 // reset lane change
323 this.laneChangeDirectionality = null;
324 this.boundary = null;
325 this.fraction = 0.0;
326 }
327 return path;
328 }
329
330 /** {@inheritDoc} */
331 @Override
332 public String toString()
333 {
334 return "LaneChange [fraction=" + this.fraction + ", laneChangeDirectionality=" + this.laneChangeDirectionality + "]";
335 }
336
337 /**
338 * Provides a (partial) path during lane changes.
339 * <p>
340 * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
341 * <br>
342 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
343 * <p>
344 * @version $Revision$, $LastChangedDate$, by $Author$, initial version 30 apr. 2018 <br>
345 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
346 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
347 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
348 */
349 public interface LaneChangePath
350 {
351 /** A simple Bezier curve directly to the lane change target position. */
352 public final static LaneChangePath BEZIER = new LaneChangePath()
353 {
354 @Override
355 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
356 final DirectedLanePosition from, final DirectedPoint startPosition,
357 final LateralDirectionality laneChangeDirection, final List<LaneDirection> fromLanes,
358 final List<LaneDirection> toLanes, final double endFractionalPosition, final Duration laneChangeDuration,
359 final double lcFraction) throws OTSGeometryException
360 {
361 DirectedPoint target = toLanes.get(toLanes.size() - 1).getLocationFraction(endFractionalPosition);
362 return Bezier.cubic(64, startPosition, target, 0.5);
363 }
364 };
365
366 /** The target point (including rotation) for the coming time step is based on a sine wave. */
367 public final static LaneChangePath SINE = new SequentialLaneChangePath()
368 {
369 /** {@inheritDoc} */
370 @Override
371 protected double lateralFraction(final double lcFraction)
372 {
373 return -1.0 / (2 * Math.PI) * Math.sin(2 * Math.PI * lcFraction) + lcFraction;
374 }
375
376 /** {@inheritDoc} */
377 @Override
378 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
379 {
380 return Math.atan((-width * Math.cos(2 * Math.PI * cumulLcLength / totalLcLength) / totalLcLength)
381 + width / totalLcLength);
382 }
383 };
384
385 /** The target point (including rotation) for the coming time step is based on a 3rd-degree polynomial. */
386 public final static LaneChangePath POLY3 = new SequentialLaneChangePath()
387 {
388 /** {@inheritDoc} */
389 @Override
390 protected double lateralFraction(final double lcFraction)
391 {
392 return 3 * (lcFraction * lcFraction) - 2 * (lcFraction * lcFraction * lcFraction);
393 }
394
395 /** {@inheritDoc} */
396 @Override
397 protected double angle(final double width, final double cumulLcLength, final double totalLcLength)
398 {
399 return Math.atan(cumulLcLength * 6 * width / (totalLcLength * totalLcLength)
400 - cumulLcLength * cumulLcLength * 6 * width / (totalLcLength * totalLcLength * totalLcLength));
401 }
402 };
403
404 /**
405 * A helper class to allow a lane change to follow a sequential determination of the target position (including
406 * rotation) for each time step.
407 * <p>
408 * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
409 * reserved. <br>
410 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
411 * <p>
412 * @version $Revision$, $LastChangedDate$, by $Author$, initial version 30 apr. 2018 <br>
413 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
414 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
415 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
416 */
417 public abstract static class SequentialLaneChangePath implements LaneChangePath
418 {
419 /** {@inheritDoc} */
420 @Override
421 public OTSLine3D getPath(final Duration timeStep, final Length planDistance, final Speed meanSpeed,
422 final DirectedLanePosition from, final DirectedPoint startPosition,
423 final LateralDirectionality laneChangeDirection, final List<LaneDirection> fromLanes,
424 final List<LaneDirection> toLanes, final double endFractionalPosition, final Duration laneChangeDuration,
425 final double lcFraction) throws OTSGeometryException
426 {
427 DirectedPoint toTarget = toLanes.get(toLanes.size() - 1).getLocationFraction(endFractionalPosition);
428 DirectedPoint fromTarget = fromLanes.get(fromLanes.size() - 1).getLocationFraction(endFractionalPosition);
429 double width = laneChangeDirection.isRight() ? fromTarget.distance(toTarget) : -fromTarget.distance(toTarget);
430 double dFraction = timeStep.si / laneChangeDuration.si;
431 return getPathRecursive(planDistance, meanSpeed, 1.0, width, from, startPosition, fromLanes, toLanes,
432 laneChangeDuration, lcFraction, dFraction);
433 }
434
435 /**
436 * Attempts to derive a path. If the resulting path is shorter than {@code planDistance} (e.g. lane change towards
437 * the inside of a curve), this method calls itself using a larger look-ahead distance.
438 * @param planDistance Length; plan distance
439 * @param meanSpeed Speed; mean speed during plan
440 * @param buffer double; buffer factor to assure sufficient path length is found, increased recursively
441 * @param width double; lateral deviation from from lanes at lane change end
442 * @param from DirectedLanePosition; current position on the from-lanes
443 * @param startPosition DirectedPoint; current 2D position
444 * @param fromLanes List<LaneDirection>; lane list of the from-lane concerned
445 * @param toLanes List<LaneDirection>; lane list of the to-lane concerned
446 * @param laneChangeDuration Duration; current considered duration of the entire lane change
447 * @param lcFraction double; lane change fraction at beginning of the plan
448 * @param dFraction double; additional lane change fraction to be made during the plan
449 * @return OTSLine3D a (partial) path for a lane change
450 * @throws OTSGeometryException on wrong fractional position
451 */
452 private OTSLine3D getPathRecursive(final Length planDistance, final Speed meanSpeed, final double buffer,
453 final double width, final DirectedLanePosition from, final DirectedPoint startPosition,
454 final List<LaneDirection> fromLanes, final List<LaneDirection> toLanes, final Duration laneChangeDuration,
455 final double lcFraction, final double dFraction) throws OTSGeometryException
456 {
457 // factor on path length to not overshoot a fraction of 1.0 in lane change progress, i.e. <1 if lane change will
458 // be finished in the coming time step
459 double cutoff = (1.0 - lcFraction) / (dFraction * buffer);
460 cutoff = cutoff > 1.0 ? 1.0 : cutoff;
461
462 // lane change fraction at end of plan
463 double lcFractionEnd = lcFraction + dFraction * buffer * cutoff;
464
465 // lateral fraction at that point according to shape
466 double f = lateralFraction(lcFractionEnd);
467
468 // from-lane length
469 double totalLcLength = meanSpeed.si * laneChangeDuration.si;
470 double cumulLcLength = totalLcLength * lcFractionEnd;
471
472 // find lane we will end up on at the end of the plan
473 double positionAtEnd = (from.getGtuDirection().isPlus() ? from.getPosition().si
474 : from.getLane().getLength().si - from.getPosition().si) + planDistance.si * buffer * cutoff;
475 for (int i = 0; i < fromLanes.size(); i++)
476 {
477 LaneDirection fromLane = fromLanes.get(i);
478 if (fromLane.getLength().si >= positionAtEnd)
479 {
480 // get target point by interpolation between from and to lane
481 double endFraction = fromLane.fractionAtCoveredDistance(Length.createSI(positionAtEnd));
482 DirectedPoint pFrom = fromLane.getLocationFraction(endFraction);
483 DirectedPoint pTo = toLanes.get(i).getLocationFraction(endFraction);
484 DirectedPoint target = new DirectedPoint((1 - f) * pFrom.x + f * pTo.x, (1 - f) * pFrom.y + f * pTo.y,
485 (1 - f) * pFrom.z + f * pTo.z);
486 // set rotation according to shape, relative to lane center line
487 target.setRotZ(
488 (1 - f) * pFrom.getRotZ() + f * pTo.getRotZ() - angle(width, cumulLcLength, totalLcLength));
489 // Bezier path towards that point
490 OTSLine3D path = Bezier.cubic(64, startPosition, target, 0.5);
491 // check if long enough, otherwise look further (e.g. changing to inside of curve gives a shorter path)
492 if (path.getLength().si < planDistance.si && cutoff == 1.0)
493 {
494 return getPathRecursive(planDistance, meanSpeed, buffer * 1.25, width, from, startPosition,
495 fromLanes, toLanes, laneChangeDuration, lcFraction, dFraction);
496 }
497 return path;
498 }
499 positionAtEnd -= fromLane.getLength().si;
500 }
501 Throw.when(lcFraction + dFraction < 0.999, RuntimeException.class,
502 "No partial path for lane change could be determined; fromLanes are too short.");
503 return null;
504 }
505
506 /**
507 * Returns the fractional lateral deviation given a fraction of lane change being completed.
508 * @param lcFraction double; fraction of lane change
509 * @return double; lateral deviation
510 */
511 protected abstract double lateralFraction(double lcFraction);
512
513 /**
514 * Returns the angle, relative to the lane center line, at the given cumulative length for a lane change of given
515 * total length and lateral deviation.
516 * @param width double; lateral deviation from from lanes at lane change end
517 * @param cumulLcLength double; cumulative length (along from lanes) covered so far
518 * @param totalLcLength double; total (along from lanes) length to cover in lane change
519 * @return double; angle, relative to the lane center line
520 */
521 protected abstract double angle(double width, double cumulLcLength, double totalLcLength);
522 }
523
524 /**
525 * Returns a (partial) path for a lane change. The method is called both at the start and during a lane change, and
526 * should return a valid path. This path should at least have a length of {@code planDistance}, unless the lane change
527 * will be finished during the coming time step. In that case, the caller of this method is to lengthen the path along
528 * the center line of the target lane.
529 * @param timeStep Duration; time step
530 * @param planDistance Length; distance covered during the plan
531 * @param meanSpeed Speed; mean speed during time step
532 * @param from DirectedLanePosition; current position on the from-lanes
533 * @param startPosition DirectedPoint; current 2D position
534 * @param laneChangeDirection LateralDirectionality; lane change direction
535 * @param fromLanes List<LaneDirection>; lane list of the from-lane concerned
536 * @param toLanes List<LaneDirection>; lane list of the to-lane concerned
537 * @param endFractionalPosition double; fractional position at the end of the plan at the last lanes in the lane lists
538 * @param laneChangeDuration Duration; current considered duration of the entire lane change
539 * @param lcFraction double; fraction of lane change done so far
540 * @return OTSLine3D a (partial) path for a lane change
541 * @throws OTSGeometryException on wrong fractional position
542 */
543 OTSLine3D getPath(Duration timeStep, Length planDistance, Speed meanSpeed, DirectedLanePosition from,
544 DirectedPoint startPosition, LateralDirectionality laneChangeDirection, List<LaneDirection> fromLanes,
545 List<LaneDirection> toLanes, double endFractionalPosition, Duration laneChangeDuration, double lcFraction)
546 throws OTSGeometryException;
547 }
548 }