View Javadoc
1   package org.opentrafficsim.road.gtu.lane.plan.operational;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.Map;
7   
8   import org.djunits.unit.AccelerationUnit;
9   import org.djunits.unit.DurationUnit;
10  import org.djunits.unit.LengthUnit;
11  import org.djunits.unit.SpeedUnit;
12  import org.djunits.value.ValueException;
13  import org.djunits.value.vdouble.scalar.Acceleration;
14  import org.djunits.value.vdouble.scalar.Duration;
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djunits.value.vdouble.scalar.Speed;
17  import org.djunits.value.vdouble.scalar.Time;
18  import org.opentrafficsim.base.parameters.ParameterException;
19  import org.opentrafficsim.core.geometry.OTSGeometryException;
20  import org.opentrafficsim.core.geometry.OTSLine3D;
21  import org.opentrafficsim.core.gtu.GTUException;
22  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
24  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.SpeedSegment;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
26  import org.opentrafficsim.core.math.Solver;
27  import org.opentrafficsim.core.network.LateralDirectionality;
28  import org.opentrafficsim.core.network.NetworkException;
29  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
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.Throw;
36  import nl.tudelft.simulation.language.d3.DirectedPoint;
37  
38  /**
39   * Builder for several often used operational plans. E.g., decelerate to come to a full stop at the end of a shape; accelerate
40   * to reach a certain speed at the end of a curve; drive constant on a curve; decelerate or accelerate to reach a given end
41   * speed at the end of a curve, etc.<br>
42   * TODO driving with negative speeds (backward driving) is not yet supported.
43   * <p>
44   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
45   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
46   * </p>
47   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
48   * initial version Nov 15, 2015 <br>
49   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
50   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
51   */
52  public final class LaneOperationalPlanBuilder // class package private for scheduling static method on an instance
53  {
54  
55      /** Use instant lane changes. */
56      public static boolean INSTANT_LANE_CHANGES = true;
57  
58      /** Maximum acceleration for unbounded accelerations: 1E12 m/s2. */
59      private static final Acceleration MAX_ACCELERATION = new Acceleration(1E12, AccelerationUnit.SI);
60  
61      /** Maximum deceleration for unbounded accelerations: -1E12 m/s2. */
62      private static final Acceleration MAX_DECELERATION = new Acceleration(-1E12, AccelerationUnit.SI);
63  
64      /**
65       * Minimum distance of an operational plan path; anything shorter will be truncated to 0. <br>
66       * If objects related to e.g. molecular movements are simulated using this code, a setter for this parameter will be needed.
67       */
68      private static final Length MINIMUM_CREDIBLE_PATH_LENGTH = new Length(0.001, LengthUnit.METER);
69  
70      /** Constructor. */
71      LaneOperationalPlanBuilder()
72      {
73          // class should not be instantiated
74      }
75  
76      /**
77       * Build a plan with a path and a given start speed to try to reach a provided end speed, exactly at the end of the curve.
78       * The acceleration (and deceleration) are capped by maxAcceleration and maxDeceleration. Therefore, there is no guarantee
79       * that the end speed is actually reached by this plan.
80       * @param gtu the GTU for debugging purposes
81       * @param distance distance to drive for reaching the end speed
82       * @param startTime the current time or a time in the future when the plan should start
83       * @param startSpeed the speed at the start of the path
84       * @param endSpeed the required end speed
85       * @param maxAcceleration the maximum acceleration that can be applied, provided as a POSITIVE number
86       * @param maxDeceleration the maximum deceleration that can be applied, provided as a NEGATIVE number
87       * @return the operational plan to accomplish the given end speed
88       * @throws OperationalPlanException when the plan cannot be generated, e.g. because of a path that is too short
89       * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
90       *             constructed segment list differ more than a given threshold
91       * @throws OTSGeometryException in case the lanes are not connected or firstLanePosition is larger than the length of the
92       *             first lane
93       */
94      public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final Length distance,
95              final Time startTime, final Speed startSpeed, final Speed endSpeed, final Acceleration maxAcceleration,
96              final Acceleration maxDeceleration) throws OperationalPlanException, OTSGeometryException
97      {
98          OTSLine3D path = createPathAlongCenterLine(gtu, distance);
99          Segment segment;
100         if (startSpeed.eq(endSpeed))
101         {
102             segment = new SpeedSegment(distance.divideBy(startSpeed));
103         }
104         else
105         {
106             try
107             {
108                 // t = 2x / (vt + v0); a = (vt - v0) / t
109                 Duration duration = distance.multiplyBy(2.0).divideBy(endSpeed.plus(startSpeed));
110                 Acceleration acceleration = endSpeed.minus(startSpeed).divideBy(duration);
111                 if (acceleration.si < 0.0 && acceleration.lt(maxDeceleration))
112                 {
113                     acceleration = maxDeceleration;
114                     duration = new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
115                             DurationUnit.SI);
116                 }
117                 if (acceleration.si > 0.0 && acceleration.gt(maxAcceleration))
118                 {
119                     acceleration = maxAcceleration;
120                     duration = new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
121                             DurationUnit.SI);
122                 }
123                 segment = new OperationalPlan.AccelerationSegment(duration, acceleration);
124             }
125             catch (ValueException ve)
126             {
127                 throw new OperationalPlanException(ve);
128             }
129         }
130         ArrayList<Segment> segmentList = new ArrayList<>();
131         segmentList.add(segment);
132         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, false);
133     }
134 
135     /**
136      * Build a plan with a path and a given start speed to reach a provided end speed, exactly at the end of the curve.
137      * Acceleration and deceleration are virtually unbounded (1E12 m/s2) to reach the end speed (e.g., to come to a complete
138      * stop).
139      * @param gtu the GTU for debugging purposes
140      * @param distance distance to drive for reaching the end speed
141      * @param startTime the current time or a time in the future when the plan should start
142      * @param startSpeed the speed at the start of the path
143      * @param endSpeed the required end speed
144      * @return the operational plan to accomplish the given end speed
145      * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
146      *             constructed segment list differ more than a given threshold
147      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
148      *             first lane
149      */
150     public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final Length distance,
151             final Time startTime, final Speed startSpeed, final Speed endSpeed)
152             throws OperationalPlanException, OTSGeometryException
153     {
154         return buildGradualAccelerationPlan(gtu, distance, startTime, startSpeed, endSpeed, MAX_ACCELERATION, MAX_DECELERATION);
155     }
156 
157     /**
158      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
159      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
160      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
161      * before completing the path, a truncated path that ends where the GTU stops is used instead.
162      * @param gtu the GTU for debugging purposes
163      * @param distance distance to drive for reaching the end speed
164      * @param startTime the current time or a time in the future when the plan should start
165      * @param startSpeed the speed at the start of the path
166      * @param endSpeed the required end speed
167      * @param acceleration the acceleration to use if endSpeed &gt; startSpeed, provided as a POSITIVE number
168      * @param deceleration the deceleration to use if endSpeed &lt; startSpeed, provided as a NEGATIVE number
169      * @return the operational plan to accomplish the given end speed
170      * @throws OperationalPlanException when the construction of the operational path fails
171      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
172      *             first lane
173      */
174     public static LaneBasedOperationalPlan buildMaximumAccelerationPlan(final LaneBasedGTU gtu, final Length distance,
175             final Time startTime, final Speed startSpeed, final Speed endSpeed, final Acceleration acceleration,
176             final Acceleration deceleration) throws OperationalPlanException, OTSGeometryException
177     {
178         OTSLine3D path = createPathAlongCenterLine(gtu, distance);
179         ArrayList<Segment> segmentList = new ArrayList<>();
180         if (startSpeed.eq(endSpeed))
181         {
182             segmentList.add(new OperationalPlan.SpeedSegment(distance.divideBy(startSpeed)));
183         }
184         else
185         {
186             try
187             {
188                 if (endSpeed.gt(startSpeed))
189                 {
190                     Duration t = endSpeed.minus(startSpeed).divideBy(acceleration);
191                     Length x = startSpeed.multiplyBy(t).plus(acceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
192                     if (x.ge(distance))
193                     {
194                         // we cannot reach the end speed in the given distance with the given acceleration
195                         Duration duration =
196                                 new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
197                                         DurationUnit.SI);
198                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, acceleration));
199                     }
200                     else
201                     {
202                         // we reach the (higher) end speed before the end of the segment. Make two segments.
203                         segmentList.add(new OperationalPlan.AccelerationSegment(t, acceleration));
204                         Duration duration = distance.minus(x).divideBy(endSpeed);
205                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
206                     }
207                 }
208                 else
209                 {
210                     Duration t = endSpeed.minus(startSpeed).divideBy(deceleration);
211                     Length x = startSpeed.multiplyBy(t).plus(deceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
212                     if (x.ge(distance))
213                     {
214                         // we cannot reach the end speed in the given distance with the given deceleration
215                         Duration duration =
216                                 new Duration(Solver.firstSolutionAfter(0, deceleration.si / 2, startSpeed.si, -distance.si),
217                                         DurationUnit.SI);
218                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, deceleration));
219                     }
220                     else
221                     {
222                         if (endSpeed.si == 0.0)
223                         {
224                             // if endSpeed == 0, we cannot reach the end of the path. Therefore, build a partial path.
225                             OTSLine3D partialPath = path.truncate(x.si);
226                             segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
227                             return new LaneBasedOperationalPlan(gtu, partialPath, startTime, startSpeed, segmentList, false);
228                         }
229                         // we reach the (lower) end speed, larger than zero, before the end of the segment. Make two segments.
230                         segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
231                         Duration duration = distance.minus(x).divideBy(endSpeed);
232                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
233                     }
234                 }
235             }
236             catch (ValueException ve)
237             {
238                 throw new OperationalPlanException(ve);
239             }
240 
241         }
242         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, false);
243     }
244 
245     /**
246      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
247      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
248      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
249      * before completing the path, a truncated path that ends where the GTU stops is used instead.
250      * @param gtu the GTU for debugging purposes
251      * @param startTime the current time or a time in the future when the plan should start
252      * @param startSpeed the speed at the start of the path
253      * @param acceleration the acceleration to use
254      * @param timeStep time step for the plan
255      * @return the operational plan to accomplish the given end speed
256      * @throws OperationalPlanException when the construction of the operational path fails
257      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
258      *             first lane
259      */
260     public static LaneBasedOperationalPlan buildAccelerationPlan(final LaneBasedGTU gtu, final Time startTime,
261             final Speed startSpeed, final Acceleration acceleration, final Duration timeStep)
262             throws OperationalPlanException, OTSGeometryException
263     {
264         if (startSpeed.si <= OperationalPlan.DRIFTING_SPEED_SI && acceleration.le(Acceleration.ZERO))
265         {
266             return new LaneBasedOperationalPlan(gtu, gtu.getLocation(), startTime, timeStep, false);
267         }
268 
269         Acceleration acc = gtuCapabilities(acceleration, gtu);
270         Duration brakingTime = brakingTime(acc, startSpeed, timeStep);
271         Length distance = Length.createSI(startSpeed.si * brakingTime.si + .5 * acc.si * brakingTime.si * brakingTime.si);
272         List<Segment> segmentList = createAccelerationSegments(startSpeed, acc, brakingTime, timeStep);
273         if (distance.le(MINIMUM_CREDIBLE_PATH_LENGTH))
274         {
275             return new LaneBasedOperationalPlan(gtu, gtu.getLocation(), startTime, timeStep, false);
276         }
277         OTSLine3D path = createPathAlongCenterLine(gtu, distance);
278         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, false);
279     }
280 
281     /**
282      * Creates a path along lane center lines.
283      * @param gtu LaneBasedGTU; gtu
284      * @param distance Length; minimum distance
285      * @return OTSLine3D; path along lane center lines
286      * @throws OTSGeometryException when any of the OTSLine3D operations fails
287      */
288     public static OTSLine3D createPathAlongCenterLine(final LaneBasedGTU gtu, final Length distance) throws OTSGeometryException
289     {
290         OTSLine3D path = null;
291         try
292         {
293             DirectedLanePosition ref = gtu.getReferencePosition();
294             double f = ref.getLane().fraction(ref.getPosition());
295             path = ref.getGtuDirection().isPlus() ? ref.getLane().getCenterLine().extractFractional(f, 1.0)
296                     : ref.getLane().getCenterLine().extractFractional(0.0, f).reverse();
297             LaneDirection prevFrom = null;
298             LaneDirection from = ref.getLaneDirection();
299             int n = 1;
300             while (path.getLength().si < distance.si + n * Lane.MARGIN.si)
301             {
302                 n++;
303                 prevFrom = from;
304                 from = from.getNextLaneDirection(gtu);
305                 try
306                 {
307                     path = OTSLine3D.concatenate(Lane.MARGIN.si, path, from.getDirection().isPlus()
308                             ? from.getLane().getCenterLine() : from.getLane().getCenterLine().reverse());
309                 }
310                 catch (NullPointerException nas)
311                 {
312                     prevFrom.getNextLaneDirection(gtu);
313                     ref.getLaneDirection().getNextLaneDirection(gtu);
314                 }
315             }
316         }
317         catch (GTUException exception)
318         {
319             throw new RuntimeException("Error during creation of path.", exception);
320         }
321         return path;
322     }
323 
324     /**
325      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
326      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
327      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
328      * before completing the path, a truncated path that ends where the GTU stops is used instead.
329      * @param gtu the GTU for debugging purposes
330      * @param laneChangeDirectionality direction of lane change (on initiation only, after that not important)
331      * @param startPosition current position
332      * @param startTime the current time or a time in the future when the plan should start
333      * @param startSpeed the speed at the start of the path
334      * @param acceleration the acceleration to use
335      * @param timeStep time step for the plan
336      * @param laneChange lane change status
337      * @return the operational plan to accomplish the given end speed
338      * @throws OperationalPlanException when the construction of the operational path fails
339      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
340      *             first lane
341      */
342     @SuppressWarnings("checkstyle:parameternumber")
343     public static LaneBasedOperationalPlan buildAccelerationLaneChangePlan(final LaneBasedGTU gtu,
344             final LateralDirectionality laneChangeDirectionality, final DirectedPoint startPosition, final Time startTime,
345             final Speed startSpeed, final Acceleration acceleration, final Duration timeStep, final LaneChange laneChange)
346             throws OperationalPlanException, OTSGeometryException
347     {
348 
349         // on first call during lane change, use laneChangeDirectionality as laneChange.getDirection() is NONE
350         // on successive calls, use laneChange.getDirection() as laneChangeDirectionality is NONE (i.e. no LC initiated)
351         LateralDirectionality direction = laneChange.isChangingLane() ? laneChange.getDirection() : laneChangeDirectionality;
352 
353         Acceleration acc = gtu.getVehicleModel().boundAcceleration(acceleration, gtu);
354         Duration brakingTime = brakingTime(acc, startSpeed, timeStep);
355         Length planDistance = Length.createSI(startSpeed.si * brakingTime.si + .5 * acc.si * brakingTime.si * brakingTime.si);
356         List<Segment> segmentList = createAccelerationSegments(startSpeed, acc, brakingTime, timeStep);
357 
358         try
359         {
360             // get position on from lane
361             Map<Lane, Length> positions = gtu.positions(gtu.getReference());
362             DirectedLanePosition ref = gtu.getReferencePosition();
363             Iterator<Lane> iterator = ref.getLane()
364                     .accessibleAdjacentLanesPhysical(direction, gtu.getGTUType(), ref.getGtuDirection()).iterator();
365             Lane adjLane = iterator.hasNext() ? iterator.next() : null;
366             DirectedLanePosition from = null;
367             if (laneChange.getDirection() == null || (adjLane != null && positions.containsKey(adjLane)))
368             {
369                 // reference lane is from lane, this is ok
370                 from = ref;
371             }
372             else
373             {
374                 // reference lane is to lane, this should be accounted for
375                 for (Lane lane : positions.keySet())
376                 {
377                     if (lane.accessibleAdjacentLanesPhysical(direction, gtu.getGTUType(), ref.getGtuDirection())
378                             .contains(ref.getLane()))
379                     {
380                         from = new DirectedLanePosition(lane, positions.get(lane), ref.getGtuDirection());
381                         break;
382                     }
383                 }
384             }
385             Throw.when(from == null, RuntimeException.class, "From lane could not be determined during lane change.");
386 
387             // get path and make plan
388             OTSLine3D path = laneChange.getPath(timeStep, gtu, from, startPosition, planDistance, direction);
389             LaneBasedOperationalPlan plan = new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, true);
390             return plan;
391         }
392         catch (GTUException exception)
393         {
394             throw new RuntimeException("Error during creation of lane change plan.", exception);
395         }
396     }
397 
398     /**
399      * Makes the acceleration adhere to GTU capabilities.
400      * @param acceleration Acceleration; desired acceleration
401      * @param gtu GTU; gtu
402      * @return Acceleration; possible acceleration
403      */
404     private static Acceleration gtuCapabilities(final Acceleration acceleration, final LaneBasedGTU gtu)
405     {
406         return acceleration;// .gt(gtu.getMaximumDeceleration())
407         // ? (acceleration.lt(gtu.getMaximumAcceleration()) ? acceleration : gtu.getMaximumAcceleration())
408         // : gtu.getMaximumDeceleration();
409     }
410 
411     /**
412      * Returns the effective braking time, which stops if stand-still is reached.
413      * @param acceleration Acceleration; acceleration
414      * @param startSpeed Speed; start speed
415      * @param time Duration; intended time step
416      * @return Duration; effective braking time
417      */
418     public static Duration brakingTime(final Acceleration acceleration, final Speed startSpeed, final Duration time)
419     {
420         if (acceleration.ge0())
421         {
422             return time;
423         }
424         double t = startSpeed.si / -acceleration.si;
425         if (t >= time.si)
426         {
427             return time;
428         }
429         return Duration.createSI(t);
430     }
431 
432     /**
433      * Creates 1 or 2 segments in an operational plan. Two segments are returned of stand-still is reached within the time step.
434      * @param startSpeed Speed; start speed
435      * @param acceleration Acceleration; acceleration
436      * @param brakingTime Duration; braking time until stand-still
437      * @param timeStep Duration; time step
438      * @return 1 or 2 segments in an operational plan
439      */
440     private static List<Segment> createAccelerationSegments(final Speed startSpeed, final Acceleration acceleration,
441             final Duration brakingTime, final Duration timeStep)
442     {
443         List<Segment> segmentList = new ArrayList<>();
444         if (brakingTime.si < timeStep.si)
445         {
446             if (brakingTime.si > 0.0)
447             {
448                 segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
449             }
450             segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
451         }
452         else
453         {
454             segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
455         }
456         return segmentList;
457     }
458 
459     /**
460      * Build an operational plan based on a simple operational plan and status info.
461      * @param gtu gtu
462      * @param startTime start time for plan
463      * @param simplePlan simple operational plan
464      * @param laneChange lane change status
465      * @return operational plan
466      * @throws ParameterException if parameter is not defined
467      * @throws GTUException gtu exception
468      * @throws NetworkException network exception
469      * @throws OperationalPlanException operational plan exeption
470      */
471     public static LaneBasedOperationalPlan buildPlanFromSimplePlan(final LaneBasedGTU gtu, final Time startTime,
472             final SimpleOperationalPlan simplePlan, final LaneChange laneChange)
473             throws ParameterException, GTUException, NetworkException, OperationalPlanException
474     {
475         if (INSTANT_LANE_CHANGES)
476         {
477             if (simplePlan.isLaneChange())
478             {
479                 gtu.changeLaneInstantaneously(simplePlan.getLaneChangeDirection());
480             }
481             try
482             {
483                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, startTime, gtu.getSpeed(),
484                         simplePlan.getAcceleration(), simplePlan.getDuration());
485             }
486             catch (OTSGeometryException exception)
487             {
488                 throw new OperationalPlanException(exception);
489             }
490         }
491 
492         // gradual lane change
493         try
494         {
495             if ((!simplePlan.isLaneChange() && !laneChange.isChangingLane())
496                     || (gtu.getSpeed().si == 0.0 && simplePlan.getAcceleration().si <= 0.0))
497             {
498                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, startTime, gtu.getSpeed(),
499                         simplePlan.getAcceleration(), simplePlan.getDuration());
500             }
501             return LaneOperationalPlanBuilder.buildAccelerationLaneChangePlan(gtu, simplePlan.getLaneChangeDirection(),
502                     gtu.getLocation(), startTime, gtu.getSpeed(), simplePlan.getAcceleration(), simplePlan.getDuration(),
503                     laneChange);
504         }
505         catch (OTSGeometryException exception)
506         {
507             throw new OperationalPlanException(exception);
508         }
509     }
510 
511     /**
512      * Schedules a lane change finalization after the given distance is covered. This distance is known as the plan is created,
513      * but at that point no time can be derived as the plan is required for that. Hence, this method can be scheduled at the
514      * same time (sequentially after creation of the plan) to then schedule the actual finalization by deriving time from
515      * distance with the plan.
516      * @param gtu LaneBasedGTU; gtu
517      * @param distance Length; distance
518      * @param laneChangeDirection LateralDirectionality; lane change direction
519      * @throws SimRuntimeException on bad time
520      */
521     public static void scheduleLaneChangeFinalization(final LaneBasedGTU gtu, final Length distance,
522             final LateralDirectionality laneChangeDirection) throws SimRuntimeException
523     {
524         Time time = gtu.getOperationalPlan().timeAtDistance(distance);
525         if (Double.isNaN(time.si))
526         {
527             // rounding...
528             time = gtu.getOperationalPlan().getEndTime();
529         }
530         gtu.getSimulator().scheduleEventAbs(time, (short) 6, gtu, gtu, "finalizeLaneChange",
531                 new Object[] { laneChangeDirection });
532     }
533 
534     /**
535      * Build a plan with a path and a given start speed to try to come to a stop with a given deceleration. If the GTU can stop
536      * before completing the given path, a truncated path that ends where the GTU stops is used instead. There is no guarantee
537      * that the OperationalPlan will lead to a complete stop.
538      * @param gtu the GTU for debugging purposes
539      * @param distance distance to drive for reaching the end speed
540      * @param startTime the current time or a time in the future when the plan should start
541      * @param startSpeed the speed at the start of the path
542      * @param deceleration the deceleration to use if endSpeed &lt; startSpeed, provided as a NEGATIVE number
543      * @return the operational plan to accomplish the given end speed
544      * @throws OperationalPlanException when construction of the operational path fails
545      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
546      *             first lane
547      */
548     public static LaneBasedOperationalPlan buildStopPlan(final LaneBasedGTU gtu, final Length distance, final Time startTime,
549             final Speed startSpeed, final Acceleration deceleration) throws OperationalPlanException, OTSGeometryException
550     {
551         return buildMaximumAccelerationPlan(gtu, distance, startTime, startSpeed, new Speed(0.0, SpeedUnit.SI),
552                 new Acceleration(1.0, AccelerationUnit.SI), deceleration);
553     }
554 
555 }