View Javadoc
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   
7   import nl.tudelft.simulation.language.d3.DirectedPoint;
8   
9   import org.djunits.unit.AccelerationUnit;
10  import org.djunits.unit.LengthUnit;
11  import org.djunits.unit.SpeedUnit;
12  import org.djunits.unit.TimeUnit;
13  import org.djunits.value.ValueException;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.opentrafficsim.core.Throw;
20  import org.opentrafficsim.core.geometry.OTSGeometryException;
21  import org.opentrafficsim.core.geometry.OTSLine3D;
22  import org.opentrafficsim.core.geometry.OTSPoint3D;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
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.road.gtu.lane.LaneBasedGTU;
29  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
30  import org.opentrafficsim.road.network.lane.Lane;
31  
32  /**
33   * Builder for several often used operational plans, based on a list of lanes. E.g., decelerate to come to a full stop at the
34   * end of a shape; accelerate to reach a certain speed at the end of a curve; drive constant on a curve; decelerate or
35   * accelerate to reach a given end speed at the end of a curve, etc.<br>
36   * TODO driving with negative speeds (backward driving) is not yet supported.
37   * <p>
38   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
39   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
40   * </p>
41   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
42   * initial version Nov 15, 2015 <br>
43   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
44   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
45   */
46  public final class LaneOperationalPlanBuilder
47  {
48      /** Maximum acceleration for unbounded accelerations: 1E12 m/s2. */
49      private static final Acceleration MAX_ACCELERATION = new Acceleration(1E12, AccelerationUnit.SI);
50  
51      /** Maximum deceleration for unbounded accelerations: -1E12 m/s2. */
52      private static final Acceleration MAX_DECELERATION = new Acceleration(-1E12, AccelerationUnit.SI);
53  
54      /** Private constructor. */
55      private LaneOperationalPlanBuilder()
56      {
57          // class should not be instantiated
58      }
59  
60      /**
61       * 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.
62       * The acceleration (and deceleration) are capped by maxAcceleration and maxDeceleration. Therefore, there is no guarantee
63       * that the end speed is actually reached by this plan.
64       * @param gtu the GTU for debugging purposes
65       * @param lanes a list of connected Lanes to do the driving on
66       * @param firstLanePosition position on the first lane with the reference point of the GTU
67       * @param distance distance to drive for reaching the end speed
68       * @param startTime the current time or a time in the future when the plan should start
69       * @param startSpeed the speed at the start of the path
70       * @param endSpeed the required end speed
71       * @param maxAcceleration the maximum acceleration that can be applied, provided as a POSITIVE number
72       * @param maxDeceleration the maximum deceleration that can be applied, provided as a NEGATIVE number
73       * @return the operational plan to accomplish the given end speed
74       * @throws OperationalPlanException when the plan cannot be generated, e.g. because of a path that is too short
75       * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
76       *             constructed segment list differ more than a given threshold
77       * @throws OTSGeometryException in case the lanes are not connected or firstLanePosition is larger than the length of the
78       *             first lane
79       */
80      public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
81          final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
82          final Speed endSpeed, final Acceleration maxAcceleration, final Acceleration maxDeceleration)
83          throws OperationalPlanException, OTSGeometryException
84      {
85          OTSLine3D path = makePath(lanes, firstLanePosition, distance);
86          OperationalPlan.Segment segment;
87          if (startSpeed.eq(endSpeed))
88          {
89              segment = new SpeedSegment(distance.divideBy(startSpeed));
90          }
91          else
92          {
93              try
94              {
95                  // t = 2x / (vt + v0); a = (vt - v0) / t
96                  Duration duration = distance.multiplyBy(2.0).divideBy(endSpeed.plus(startSpeed));
97                  Acceleration acceleration = endSpeed.minus(startSpeed).divideBy(duration);
98                  if (acceleration.si < 0.0 && acceleration.lt(maxDeceleration))
99                  {
100                     acceleration = maxDeceleration;
101                     duration =
102                         new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
103                             TimeUnit.SI);
104                 }
105                 if (acceleration.si > 0.0 && acceleration.gt(maxAcceleration))
106                 {
107                     acceleration = maxAcceleration;
108                     duration =
109                         new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
110                             TimeUnit.SI);
111                 }
112                 segment = new OperationalPlan.AccelerationSegment(duration, acceleration);
113             }
114             catch (ValueException ve)
115             {
116                 throw new OperationalPlanException(ve);
117             }
118         }
119         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
120         segmentList.add(segment);
121         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
122     }
123 
124     /**
125      * 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.
126      * The acceleration (and deceleration) are capped by maxAcceleration and maxDeceleration. Therefore, there is no guarantee
127      * that the end speed is actually reached by this plan.
128      * @param lanes a list of connected Lanes to do the driving on
129      * @param firstLanePosition position on the first lane with the reference point of the GTU
130      * @param distance distance to drive for reaching the end speed
131      * @return the driving path as a line
132      * @throws OperationalPlanException when the length of the lanes is less than the distance when we start at the
133      *             firstLanePosition on the first lane, or when the lanes list contains no elements
134      * @throws OTSGeometryException in case the lanes are not connected or firstLanePosition is larger than the length of the
135      *             first lane
136      */
137     public static OTSLine3D makePath(final List<Lane> lanes, final Length firstLanePosition, final Length distance)
138         throws OperationalPlanException, OTSGeometryException
139     {
140         if (lanes.size() == 0)
141         {
142             throw new OperationalPlanException("LaneOperationalPlanBuilder.makePath got a lanes list with size = 0");
143         }
144         OTSLine3D path = lanes.get(0).getCenterLine().extract(firstLanePosition, lanes.get(0).getLength());
145         for (int i = 1; i < lanes.size(); i++)
146         {
147             path = OTSLine3D.concatenate(0.15, path, lanes.get(i).getCenterLine());
148         }
149         return path.extract(0.0, distance.si);
150     }
151 
152     /**
153      * Build a plan with a path and a given start speed to reach a provided end speed, exactly at the end of the curve.
154      * Acceleration and deceleration are virtually unbounded (1E12 m/s2) to reach the end speed (e.g., to come to a complete
155      * stop).
156      * @param gtu the GTU for debugging purposes
157      * @param lanes a list of connected Lanes to do the driving on
158      * @param firstLanePosition position on the first lane with the reference point of the GTU
159      * @param distance distance to drive for reaching the end speed
160      * @param startTime the current time or a time in the future when the plan should start
161      * @param startSpeed the speed at the start of the path
162      * @param endSpeed the required end speed
163      * @return the operational plan to accomplish the given end speed
164      * @throws OperationalPlanException when the length of the path and the calculated driven distance implied by the
165      *             constructed segment list differ more than a given threshold
166      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
167      *             first lane
168      */
169     public static LaneBasedOperationalPlan buildGradualAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
170         final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
171         final Speed endSpeed) throws OperationalPlanException, OTSGeometryException
172     {
173         return buildGradualAccelerationPlan(gtu, lanes, firstLanePosition, distance, startTime, startSpeed, endSpeed,
174             MAX_ACCELERATION, MAX_DECELERATION);
175     }
176 
177     /**
178      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
179      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
180      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
181      * before completing the path, a truncated path that ends where the GTU stops is used instead.
182      * @param gtu the GTU for debugging purposes
183      * @param lanes a list of connected Lanes to do the driving on
184      * @param firstLanePosition position on the first lane with the reference point of the GTU
185      * @param distance distance to drive for reaching the end speed
186      * @param startTime the current time or a time in the future when the plan should start
187      * @param startSpeed the speed at the start of the path
188      * @param endSpeed the required end speed
189      * @param acceleration the acceleration to use if endSpeed &gt; startSpeed, provided as a POSITIVE number
190      * @param deceleration the deceleration to use if endSpeed &lt; startSpeed, provided as a NEGATIVE number
191      * @return the operational plan to accomplish the given end speed
192      * @throws OperationalPlanException when the construction of the operational path fails
193      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
194      *             first lane
195      */
196     public static LaneBasedOperationalPlan buildMaximumAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
197         final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
198         final Speed endSpeed, final Acceleration acceleration, final Acceleration deceleration)
199         throws OperationalPlanException, OTSGeometryException
200     {
201         OTSLine3D path = makePath(lanes, firstLanePosition, distance);
202         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
203         if (startSpeed.eq(endSpeed))
204         {
205             segmentList.add(new OperationalPlan.SpeedSegment(distance.divideBy(startSpeed)));
206         }
207         else
208         {
209             try
210             {
211                 if (endSpeed.gt(startSpeed))
212                 {
213                     Duration t = endSpeed.minus(startSpeed).divideBy(acceleration);
214                     Length x = startSpeed.multiplyBy(t).plus(acceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
215                     if (x.ge(distance))
216                     {
217                         // we cannot reach the end speed in the given distance with the given acceleration
218                         Duration duration =
219                             new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
220                                 TimeUnit.SI);
221                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, acceleration));
222                     }
223                     else
224                     {
225                         // we reach the (higher) end speed before the end of the segment. Make two segments.
226                         segmentList.add(new OperationalPlan.AccelerationSegment(t, acceleration));
227                         Duration duration = distance.minus(x).divideBy(endSpeed);
228                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
229                     }
230                 }
231                 else
232                 {
233                     Duration t = endSpeed.minus(startSpeed).divideBy(deceleration);
234                     Length x = startSpeed.multiplyBy(t).plus(deceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
235                     if (x.ge(distance))
236                     {
237                         // we cannot reach the end speed in the given distance with the given deceleration
238                         Duration duration =
239                             new Duration(Solver.firstSolutionAfter(0, deceleration.si / 2, startSpeed.si, -distance.si),
240                                 TimeUnit.SI);
241                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, deceleration));
242                     }
243                     else
244                     {
245                         if (endSpeed.si == 0.0)
246                         {
247                             // if endSpeed == 0, we cannot reach the end of the path. Therefore, build a partial path.
248                             OTSLine3D partialPath = path.truncate(x.si);
249                             segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
250                             return new LaneBasedOperationalPlan(gtu, partialPath, startTime, startSpeed, segmentList, lanes);
251                         }
252                         // we reach the (lower) end speed, larger than zero, before the end of the segment. Make two segments.
253                         segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
254                         Duration duration = distance.minus(x).divideBy(endSpeed);
255                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
256                     }
257                 }
258             }
259             catch (ValueException ve)
260             {
261                 throw new OperationalPlanException(ve);
262             }
263 
264         }
265         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
266     }
267 
268     /**
269      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
270      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
271      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
272      * before completing the path, a truncated path that ends where the GTU stops is used instead.
273      * @param gtu the GTU for debugging purposes
274      * @param lanes a list of connected Lanes to do the driving on
275      * @param firstLanePosition position on the first lane with the reference point of the GTU
276      * @param startTime the current time or a time in the future when the plan should start
277      * @param startSpeed the speed at the start of the path
278      * @param acceleration the acceleration to use
279      * @param timeStep time step for the plan
280      * @return the operational plan to accomplish the given end speed
281      * @throws OperationalPlanException when the construction of the operational path fails
282      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
283      *             first lane
284      */
285     public static LaneBasedOperationalPlan buildAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
286         final Length firstLanePosition, final Time startTime, final Speed startSpeed, final Acceleration acceleration,
287         final Duration timeStep) throws OperationalPlanException, OTSGeometryException
288     {
289 
290         if (startSpeed.eq(Speed.ZERO) && acceleration.le(Acceleration.ZERO))
291         {
292             // stand-still
293             return new LaneBasedOperationalPlan(gtu, gtu.getLocation(), startTime, timeStep, lanes.get(0));
294         }
295         Length distance;
296         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
297         if (startSpeed.plus(acceleration.multiplyBy(timeStep)).lt(Speed.ZERO))
298         {
299             // will reach stand-still within time step
300             Duration brakingTime = startSpeed.divideBy(acceleration.multiplyBy(-1.0));
301             segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
302             segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
303             distance =
304                 new Length(startSpeed.si * brakingTime.si + .5 * acceleration.si * brakingTime.si * brakingTime.si,
305                     LengthUnit.SI);
306         }
307         else
308         {
309             segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
310             distance =
311                 new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
312         }
313         OTSLine3D path;
314         try
315         {
316             path = makePath(lanes, firstLanePosition, distance);
317         }
318         catch (Exception e)
319         {
320             path = makePath(lanes, firstLanePosition, distance);
321             throw new Error("Bad!");
322         }
323         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
324     }
325 
326     /**
327      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
328      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
329      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
330      * before completing the path, a truncated path that ends where the GTU stops is used instead.
331      * @param gtu the GTU for debugging purposes
332      * @param fromLanes lanes where the GTU changes from
333      * @param laneChangeDirectionality direction of lane change
334      * @param startPosition current position
335      * @param startTime the current time or a time in the future when the plan should start
336      * @param startSpeed the speed at the start of the path
337      * @param acceleration the acceleration to use
338      * @param timeStep time step for the plan
339      * @param laneChange lane change status
340      * @return the operational plan to accomplish the given end speed
341      * @throws OperationalPlanException when the construction of the operational path fails
342      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
343      *             first lane
344      */
345     @SuppressWarnings("checkstyle:parameternumber")
346     public static LaneBasedOperationalPlan buildAccelerationLaneChangePlan(final LaneBasedGTU gtu,
347         final List<Lane> fromLanes, final LateralDirectionality laneChangeDirectionality, final DirectedPoint startPosition,
348         final Time startTime, final Speed startSpeed, final Acceleration acceleration, final Duration timeStep,
349         final LaneChange laneChange) throws OperationalPlanException, OTSGeometryException
350     {
351         Length fromLaneDistance =
352             new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
353         // TODO also for other driving directions, additional arguments in projectFractional?
354         double fractionalLinkPositionFirst =
355             fromLanes.get(0).getCenterLine().projectFractional(
356                 fromLanes.get(0).getParentLink().getStartNode().getDirection(),
357                 fromLanes.get(0).getParentLink().getEndNode().getDirection(), startPosition.x, startPosition.y);
358         Length fromLaneFirstPosition = fromLanes.get(0).position(fractionalLinkPositionFirst);
359         Length cumulDistance = fromLanes.get(0).getLength().minus(fromLaneFirstPosition);
360         int lastLaneIndex = 0;
361         while (cumulDistance.lt(fromLaneDistance))
362         {
363             lastLaneIndex++;
364             cumulDistance = cumulDistance.plus(fromLanes.get(lastLaneIndex).getLength());
365         }
366         double fractionalLinkPositionLast =
367             fromLanes.get(lastLaneIndex).getLength().minus(cumulDistance.minus(fromLaneDistance)).si
368                 / fromLanes.get(lastLaneIndex).getLength().si;
369 
370         List<Lane> toLanes = new ArrayList<>();
371         for (Lane lane : fromLanes)
372         {
373             if (!lane.accessibleAdjacentLanes(laneChangeDirectionality, gtu.getGTUType()).isEmpty())
374             {
375                 toLanes.add(lane.accessibleAdjacentLanes(laneChangeDirectionality, gtu.getGTUType()).iterator().next());
376             }
377             else
378             {
379                 new Exception().printStackTrace();
380                 System.exit(-1);
381             }
382         }
383 
384         Length toLaneFirstPosition = toLanes.get(0).position(fractionalLinkPositionFirst);
385         Length fromLaneLastPosition = fromLanes.get(lastLaneIndex).position(fractionalLinkPositionLast);
386         Length toLaneLastPosition = toLanes.get(lastLaneIndex).position(fractionalLinkPositionLast);
387 
388         DirectedPoint fromFirst = fromLanes.get(0).getCenterLine().getLocation(fromLaneFirstPosition);
389         DirectedPoint toFirst = toLanes.get(0).getCenterLine().getLocation(toLaneFirstPosition);
390         DirectedPoint fromLast = fromLanes.get(lastLaneIndex).getCenterLine().getLocation(fromLaneLastPosition);
391         DirectedPoint toLast = toLanes.get(lastLaneIndex).getCenterLine().getLocation(toLaneLastPosition);
392 
393         double lastFraction = laneChange.updateAndGetFraction(timeStep, laneChangeDirectionality);
394         OTSPoint3D lastPoint =
395             new OTSPoint3D(fromLast.x * (1 - lastFraction) + toLast.x * lastFraction, fromLast.y * (1 - lastFraction)
396                 + toLast.y * lastFraction, fromLast.z * (1 - lastFraction) + toLast.z * lastFraction);
397         OTSPoint3D firstPoint = new OTSPoint3D(startPosition);
398         OTSLine3D path = new OTSLine3D(firstPoint, lastPoint);
399 
400         double t = timeStep.si;
401         Acceleration a = new Acceleration((2.0 * (path.getLength().si - startSpeed.si * t)) / (t * t), AccelerationUnit.SI);
402         Speed endSpeed = startSpeed.plus(a.multiplyBy(timeStep));
403         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
404         if (endSpeed.lt(Speed.ZERO))
405         {
406             Duration brakingTime = startSpeed.divideBy(acceleration.multiplyBy(-1.0));
407             segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
408             segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
409         }
410         else
411         {
412             segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
413         }
414         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, fromLanes);
415     }
416 
417     /**
418      * Lane change status across operational plans.
419      * <p>
420      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
421      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
422      * <p>
423      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
424      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
425      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
426      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
427      */
428     public static class LaneChange implements Serializable
429     {
430 
431         /** */
432         private static final long serialVersionUID = 20160811L;
433 
434         /** Lane change progress. */
435         private Duration laneChangeProgress;
436 
437         /** Total lane change duration. */
438         private Duration laneChangeDuration;
439 
440         /** Whether the GTU is changing lane. */
441         private LateralDirectionality laneChangeDirectionality = null;
442 
443         /**
444          * Return whether the GTU is changing lane.
445          * @return whether the GTU is changing lane
446          */
447         public final boolean isChangingLane()
448         {
449             return this.laneChangeDirectionality != null;
450         }
451 
452         /**
453          * Return whether the GTU is changing left.
454          * @return whether the GTU is changing left
455          */
456         public final boolean isChangingLeft()
457         {
458             return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
459         }
460 
461         /**
462          * Return whether the GTU is changing right.
463          * @return whether the GTU is changing right
464          */
465         public final boolean isChangingRight()
466         {
467             return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
468         }
469 
470         /**
471          * Target lane of lane change.
472          * @return target lane of lane change
473          * @throws OperationalPlanException If no lane change is being performed.
474          */
475         public final RelativeLane getTargetLane() throws OperationalPlanException
476         {
477             Throw.when(!isChangingLane(), OperationalPlanException.class,
478                 "Target lane is requested, but no lane change is being performed.");
479             return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
480         }
481 
482         /**
483          * Sets the duration for a lane change. May be set during a lane change. Whenever the lane change progress exceeds the
484          * lane change duration, the lane change is finalized in the next time step.
485          * @param laneChangeDuration total lane change duration
486          */
487         public final void setLaneChangeDuration(final Duration laneChangeDuration)
488         {
489             this.laneChangeDuration = laneChangeDuration;
490         }
491 
492         /**
493          * Update the lane change and return the lateral fraction for the end of the coming time step. This method is for use by
494          * the operational plan builder. It adds the time step duration to the lane change progress. Lane changes are
495          * automatically initiated and finalized.
496          * @param timeStep coming time step duration
497          * @param laneChangeDirection direction of lane change
498          * @return lateral fraction for the end of the coming time step
499          */
500         final double updateAndGetFraction(final Duration timeStep, final LateralDirectionality laneChangeDirection)
501         {
502             if (!isChangingLane())
503             {
504                 // initiate lane change
505                 this.laneChangeProgress = Duration.ZERO;
506                 this.laneChangeDirectionality = laneChangeDirection;
507             }
508             // add current time step
509             this.laneChangeProgress.plus(timeStep);
510             // get lateral fraction at end of current time step
511             double fraction = this.laneChangeProgress.divideBy(this.laneChangeDuration).si;
512             if (fraction >= 1.0)
513             {
514                 // limit by 1 and finalize lane change
515                 this.laneChangeDirectionality = null;
516                 return 1.0;
517             }
518             return fraction;
519         }
520 
521         /** {@inheritDoc} */
522         public final String toString()
523         {
524             return "LaneChange [" + this.laneChangeDuration + " of " + this.laneChangeDuration + " to "
525                 + this.laneChangeDirectionality + "]";
526         }
527 
528     }
529 
530     /**
531      * 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
532      * before completing the given path, a truncated path that ends where the GTU stops is used instead. There is no guarantee
533      * that the OperationalPlan will lead to a complete stop.
534      * @param gtu the GTU for debugging purposes
535      * @param lanes a list of connected Lanes to do the driving on
536      * @param firstLanePosition position on the first lane with the reference point of the GTU
537      * @param distance distance to drive for reaching the end speed
538      * @param startTime the current time or a time in the future when the plan should start
539      * @param startSpeed the speed at the start of the path
540      * @param deceleration the deceleration to use if endSpeed &lt; startSpeed, provided as a NEGATIVE number
541      * @return the operational plan to accomplish the given end speed
542      * @throws OperationalPlanException when construction of the operational path fails
543      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
544      *             first lane
545      */
546     public static LaneBasedOperationalPlan buildStopPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
547         final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
548         final Acceleration deceleration) throws OperationalPlanException, OTSGeometryException
549     {
550         return buildMaximumAccelerationPlan(gtu, lanes, firstLanePosition, distance, startTime, startSpeed, new Speed(0.0,
551             SpeedUnit.SI), new Acceleration(1.0, AccelerationUnit.SI), deceleration);
552     }
553 
554     /*-
555     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
556         final Acceleration startAcceleration, final Speed maxSpeed, final Duration duration,
557         final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
558         final double laneChangeProgress, final DirectedPoint endPoint) throws OperationalPlanException
559     {
560         return buildSpatialPlan(gtu, startTime, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, duration,
561             fromLanes, toLanes, curvatureType, laneChangeProgress, endPoint);
562     }
563 
564     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
565         final DirectedPoint startPoint, final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
566         final Duration duration, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
567         final CurvatureType curvatureType, final double laneChangeProgress, final DirectedPoint endPoint)
568         throws OperationalPlanException
569     {
570 
571         // check
572         checkLaneDirections(fromLanes, toLanes);
573 
574         // get start fractional position on link
575         final CrossSectionLink startLink = fromLanes.get(0).getLane().getParentLink();
576         Direction start;
577         Direction end;
578         if (fromLanes.get(0).getDirection() == GTUDirectionality.DIR_PLUS)
579         {
580             start = startLink.getStartNode().getDirection();
581             end = startLink.getEndNode().getDirection();
582         }
583         else
584         {
585             start = startLink.getEndNode().getDirection();
586             end = startLink.getStartNode().getDirection();
587         }
588         double fStart = startLink.getDesignLine().projectFractional(start, end, startPoint.x, startPoint.y);
589 
590         // get end fractional position on link, and end link
591         double fEnd = 0;
592         CrossSectionLink endLink = null;
593         for (int i = 0; i < toLanes.size(); i++)
594         {
595             CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
596             if (toLanes.get(i).getDirection() == GTUDirectionality.DIR_PLUS)
597             {
598                 start = l.getStartNode().getDirection();
599                 end = l.getEndNode().getDirection();
600             }
601             else
602             {
603                 start = l.getEndNode().getDirection();
604                 end = l.getStartNode().getDirection();
605             }
606             fEnd = l.getDesignLine().projectFractional(start, end, endPoint.x, endPoint.y);
607             if (fEnd > 0 && fEnd < 1)
608             {
609                 endLink = l;
610                 break;
611             }
612         }
613         Throw.when(endLink == null, OperationalPlanException.class, "End point cannot be projected to to-lanes.");
614 
615         // build from-line and to-line
616         OTSLine3D from = null;
617         OTSLine3D to = null;
618         for (int i = 0; i < toLanes.size(); i++)
619         {
620             CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
621             try
622             {
623                 if (l == startLink)
624                 {
625                     from = fromLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
626                     to = toLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
627                 }
628                 else if (l == endLink)
629                 {
630                     from =
631                         OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
632                     to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
633                     break;
634                 }
635                 from = OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine());
636                 to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine());
637             }
638             catch (OTSGeometryException exception)
639             {
640                 throw new RuntimeException("Bug in buildSpatialPlan method.");
641             }
642         }
643 
644         // interpolate path
645         List<OTSPoint3D> line = new ArrayList<>();
646         line.add(new OTSPoint3D(startPoint.x, startPoint.y, startPoint.z));
647         if (curvatureType.equals(CurvatureType.LINEAR))
648         {
649             int n = (int) Math.ceil(32 * (1.0 - laneChangeProgress));
650             for (int i = 1; i < n; i++)
651             {
652                 double fraction = 1.0 * i / n;
653                 double f0 = laneChangeProgress + (1.0 - laneChangeProgress) * fraction;
654                 double f1 = 1.0 - f0;
655                 DirectedPoint p1;
656                 DirectedPoint p2;
657                 try
658                 {
659                     p1 = from.getLocationFraction(fraction);
660                     p2 = to.getLocationFraction(fraction);
661                 }
662                 catch (OTSGeometryException exception)
663                 {
664                     throw new RuntimeException("Bug in buildSpatialPlan method.");
665                 }
666                 line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
667             }
668         }
669         OTSLine3D path;
670         try
671         {
672             path = new OTSLine3D(line);
673         }
674         catch (OTSGeometryException exception)
675         {
676             throw new RuntimeException("Bug in buildSpatialPlan method.");
677         }
678 
679         // acceleration segments
680         List<Segment> segmentList = new ArrayList<>();
681         Acceleration b = startAcceleration.multiplyBy(-1.0);
682         if (startSpeed.lt(b.multiplyBy(duration)))
683         {
684             // will reach zero speed within duration
685             Duration d = startSpeed.divideBy(b);
686             segmentList.add(new AccelerationSegment(d, startAcceleration)); // decelerate to zero
687             segmentList.add(new SpeedSegment(duration.minus(d))); // stay at zero for the remainder of duration
688         }
689         else
690         {
691             segmentList.add(new AccelerationSegment(duration, startAcceleration));
692         }
693 
694         return new OperationalPlan(gtu, path, startTime, startSpeed, segmentList);
695     }
696 
697     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Acceleration startAcceleration,
698         final Speed maxSpeed, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
699         final CurvatureType curvatureType, final Duration duration) throws OperationalPlanException
700     {
701         return buildSpatialPlan(gtu, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, fromLanes, toLanes,
702             curvatureType, duration);
703     }
704 
705     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final DirectedPoint startPoint,
706         final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
707         final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
708         final Duration duration) throws OperationalPlanException
709     {
710         checkLaneDirections(fromLanes, toLanes);
711 
712         return null;
713     }
714 
715     private final static void checkLaneDirections(final List<LaneDirection> fromLanes, List<LaneDirection> toLanes)
716         throws OperationalPlanException
717     {
718         Throw.when(fromLanes == null || toLanes == null, OperationalPlanException.class, "Lane lists may not be null.");
719         Throw.when(fromLanes.isEmpty(), OperationalPlanException.class, "Lane lists may not be empty.");
720         Throw.when(fromLanes.size() != toLanes.size(), OperationalPlanException.class,
721             "Set of from lanes has different length than set of to lanes.");
722         for (int i = 0; i < fromLanes.size(); i++)
723         {
724             Throw.when(!fromLanes.get(i).getLane().getParentLink().equals(toLanes.get(i).getLane().getParentLink()),
725                 OperationalPlanException.class,
726                 "A lane in the from-lanes list is not on the same link as the lane at equal index in the to-lanes list.");
727         }
728     }
729 
730     /**
731      * Defines curvature.
732      * <p>
733      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
734      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
735      * <p>
736      * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 27, 2016 <br>
737      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
738      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
739      */
740     /*-
741     public enum CurvatureType
742     {
743         /** Linear lateral movement. */
744     /*-
745     LINEAR
746     {
747         public double[] getFractions(final double strartFraction)
748         {
749             return new double[1];
750         }
751     },
752 
753     /** */
754     /*-
755     SCURVE
756     {
757         public double[] getFractions(final double strartFraction)
758         {
759             return new double[1];
760         }
761     };
762 
763     public abstract double[] getFractions(double startFraction);
764     }
765      */
766 
767 }