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