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.DurationUnit;
11  import org.djunits.unit.LengthUnit;
12  import org.djunits.unit.SpeedUnit;
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-2017 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                             DurationUnit.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                             DurationUnit.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 =
238                                 new Duration(Solver.firstSolutionAfter(0, acceleration.si / 2, startSpeed.si, -distance.si),
239                                         DurationUnit.SI);
240                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, acceleration));
241                     }
242                     else
243                     {
244                         // we reach the (higher) end speed before the end of the segment. Make two segments.
245                         segmentList.add(new OperationalPlan.AccelerationSegment(t, acceleration));
246                         Duration duration = distance.minus(x).divideBy(endSpeed);
247                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
248                     }
249                 }
250                 else
251                 {
252                     Duration t = endSpeed.minus(startSpeed).divideBy(deceleration);
253                     Length x = startSpeed.multiplyBy(t).plus(deceleration.multiplyBy(0.5).multiplyBy(t).multiplyBy(t));
254                     if (x.ge(distance))
255                     {
256                         // we cannot reach the end speed in the given distance with the given deceleration
257                         Duration duration =
258                                 new Duration(Solver.firstSolutionAfter(0, deceleration.si / 2, startSpeed.si, -distance.si),
259                                         DurationUnit.SI);
260                         segmentList.add(new OperationalPlan.AccelerationSegment(duration, deceleration));
261                     }
262                     else
263                     {
264                         if (endSpeed.si == 0.0)
265                         {
266                             // if endSpeed == 0, we cannot reach the end of the path. Therefore, build a partial path.
267                             OTSLine3D partialPath = path.truncate(x.si);
268                             segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
269                             return new LaneBasedOperationalPlan(gtu, partialPath, startTime, startSpeed, segmentList, lanes);
270                         }
271                         // we reach the (lower) end speed, larger than zero, before the end of the segment. Make two segments.
272                         segmentList.add(new OperationalPlan.AccelerationSegment(t, deceleration));
273                         Duration duration = distance.minus(x).divideBy(endSpeed);
274                         segmentList.add(new OperationalPlan.SpeedSegment(duration));
275                     }
276                 }
277             }
278             catch (ValueException ve)
279             {
280                 throw new OperationalPlanException(ve);
281             }
282 
283         }
284         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
285     }
286 
287     /**
288      * Minimum distance of an operational plan path; anything shorter will be truncated to 0. <br>
289      * If objects related to e.g. molecular movements are simulated using this code, a setter for this parameter will be needed.
290      */
291     private static final Length MINIMUM_CREDIBLE_PATH_LENGTH = new Length(0.001, LengthUnit.METER);
292 
293     /**
294      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
295      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
296      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
297      * before completing the path, a truncated path that ends where the GTU stops is used instead.
298      * @param gtu the GTU for debugging purposes
299      * @param lanes a list of connected Lanes to do the driving on
300      * @param firstLanePosition position on the first lane with the reference point of the GTU
301      * @param startTime the current time or a time in the future when the plan should start
302      * @param startSpeed the speed at the start of the path
303      * @param acceleration the acceleration to use
304      * @param timeStep time step for the plan
305      * @return the operational plan to accomplish the given end speed
306      * @throws OperationalPlanException when the construction of the operational path fails
307      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
308      *             first lane
309      */
310     public static LaneBasedOperationalPlan buildAccelerationPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
311             final Length firstLanePosition, final Time startTime, final Speed startSpeed, final Acceleration acceleration,
312             final Duration timeStep) throws OperationalPlanException, OTSGeometryException
313     {
314 
315         if (startSpeed.si <= OperationalPlan.DRIFTING_SPEED_SI && acceleration.le(Acceleration.ZERO))
316         {
317             return new LaneBasedOperationalPlan(gtu, lanes.get(0).getCenterLine().getLocation(firstLanePosition), startTime,
318                     timeStep, lanes.get(0));
319         }
320         Length distance;
321         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
322         if (startSpeed.plus(acceleration.multiplyBy(timeStep)).lt0())
323         {
324             // will reach stand-still within time step
325             Duration brakingTime = startSpeed.divideBy(acceleration.neg());
326             segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
327             segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
328             distance = new Length(startSpeed.si * brakingTime.si + .5 * acceleration.si * brakingTime.si * brakingTime.si,
329                     LengthUnit.SI);
330         }
331         else
332         {
333             segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
334             distance =
335                     new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
336         }
337         if (distance.le(MINIMUM_CREDIBLE_PATH_LENGTH))
338         {
339             System.err.println("Path too short; replacing operational plan with distance " + distance + " by a wait plan");
340             return new LaneBasedOperationalPlan(gtu, gtu.getLocation(), startTime, timeStep, lanes.get(0));
341         }
342         OTSLine3D path;
343         try
344         {
345             path = makePath(lanes, firstLanePosition, distance);
346         }
347         catch (Exception e)
348         {
349             throw new Error("Path for acceleration plan could not be made.", e);
350         }
351         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, lanes);
352     }
353 
354     /**
355      * Build a plan with a path and a given start speed to try to reach a provided end speed. Acceleration or deceleration is as
356      * provided, until the end speed is reached. After this, constant end speed is used to reach the end point of the path.
357      * There is no guarantee that the end speed is actually reached by this plan. If the end speed is zero, and it is reached
358      * before completing the path, a truncated path that ends where the GTU stops is used instead.
359      * @param gtu the GTU for debugging purposes
360      * @param fromLanes lanes where the GTU changes from
361      * @param laneChangeDirectionality direction of lane change
362      * @param startPosition current position
363      * @param startTime the current time or a time in the future when the plan should start
364      * @param startSpeed the speed at the start of the path
365      * @param acceleration the acceleration to use
366      * @param timeStep time step for the plan
367      * @param laneChange lane change status
368      * @return the operational plan to accomplish the given end speed
369      * @throws OperationalPlanException when the construction of the operational path fails
370      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
371      *             first lane
372      */
373     @SuppressWarnings("checkstyle:parameternumber")
374     public static LaneBasedOperationalPlan buildAccelerationLaneChangePlan(final LaneBasedGTU gtu, final List<Lane> fromLanes,
375             final LateralDirectionality laneChangeDirectionality, final DirectedPoint startPosition, final Time startTime,
376             final Speed startSpeed, final Acceleration acceleration, final Duration timeStep, final LaneChange laneChange)
377             throws OperationalPlanException, OTSGeometryException
378     {
379         // on first call during lane change, use laneChangeDirectionality as laneChange.getDirection() is probably NONE
380         LateralDirectionality direction = laneChange.isChangingLane() ? laneChange.getDirection() : laneChangeDirectionality;
381         Length fromLaneDistance =
382                 new Length(startSpeed.si * timeStep.si + .5 * acceleration.si * timeStep.si * timeStep.si, LengthUnit.SI);
383         // TODO also for other driving directions, additional arguments in projectFractional?
384         double firstFractionalPosition = fromLanes.get(0).getCenterLine().projectFractional(
385                 fromLanes.get(0).getParentLink().getStartNode().getDirection(),
386                 fromLanes.get(0).getParentLink().getEndNode().getDirection(), startPosition.x, startPosition.y);
387         Length fromLaneFirstPosition = fromLanes.get(0).position(firstFractionalPosition);
388         Length cumulDistance = fromLanes.get(0).getLength().minus(fromLaneFirstPosition);
389         int lastLaneIndex = 0;
390         while (cumulDistance.lt(fromLaneDistance))
391         {
392             lastLaneIndex++;
393             cumulDistance = cumulDistance.plus(fromLanes.get(lastLaneIndex).getLength());
394         }
395         double lastFractionalPosition = fromLanes.get(lastLaneIndex).getLength().minus(cumulDistance.minus(fromLaneDistance)).si
396                 / fromLanes.get(lastLaneIndex).getLength().si;
397 
398         List<Lane> toLanes = new ArrayList<>();
399         for (Lane lane : fromLanes)
400         {
401             if (!lane.accessibleAdjacentLanes(direction, gtu.getGTUType()).isEmpty())
402             {
403                 toLanes.add(lane.accessibleAdjacentLanes(direction, gtu.getGTUType()).iterator().next());
404             }
405             else
406             {
407                 new Exception().printStackTrace();
408                 System.exit(-1);
409             }
410         }
411 
412         Length toLaneFirstPosition = toLanes.get(0).position(firstFractionalPosition);
413         Length fromLaneLastPosition = fromLanes.get(lastLaneIndex).position(lastFractionalPosition);
414         Length toLaneLastPosition = toLanes.get(lastLaneIndex).position(lastFractionalPosition);
415 
416         DirectedPoint fromFirst = fromLanes.get(0).getCenterLine().getLocation(fromLaneFirstPosition);
417         DirectedPoint toFirst = toLanes.get(0).getCenterLine().getLocation(toLaneFirstPosition);
418         DirectedPoint fromLast = fromLanes.get(lastLaneIndex).getCenterLine().getLocation(fromLaneLastPosition);
419         DirectedPoint toLast = toLanes.get(lastLaneIndex).getCenterLine().getLocation(toLaneLastPosition);
420 
421         double lastFraction = laneChange.updateAndGetFraction(timeStep, direction, gtu);
422         OTSPoint3D lastPoint = new OTSPoint3D(fromLast.x * (1 - lastFraction) + toLast.x * lastFraction,
423                 fromLast.y * (1 - lastFraction) + toLast.y * lastFraction,
424                 fromLast.z * (1 - lastFraction) + toLast.z * lastFraction);
425         OTSPoint3D firstPoint = new OTSPoint3D(startPosition);
426         OTSLine3D path = new OTSLine3D(firstPoint, lastPoint);
427 
428         double t = timeStep.si;
429         // why was this used?
430         // Acceleration a = new Acceleration((2.0 * (path.getLength().si - startSpeed.si * t)) / (t * t), AccelerationUnit.SI);
431         Speed endSpeed = startSpeed.plus(acceleration.multiplyBy(timeStep));
432         ArrayList<OperationalPlan.Segment> segmentList = new ArrayList<>();
433         if (endSpeed.lt0())
434         {
435             Duration brakingTime = startSpeed.divideBy(acceleration.neg());
436             if (brakingTime.si > 0.0)
437             {
438                 segmentList.add(new OperationalPlan.AccelerationSegment(brakingTime, acceleration));
439             }
440             segmentList.add(new OperationalPlan.SpeedSegment(timeStep.minus(brakingTime)));
441         }
442         else
443         {
444             segmentList.add(new OperationalPlan.AccelerationSegment(timeStep, acceleration));
445         }
446         return new LaneBasedOperationalPlan(gtu, path, startTime, startSpeed, segmentList, fromLanes, toLanes, lastLaneIndex,
447                 lastFractionalPosition);
448     }
449 
450     /**
451      * Lane change status across operational plans.
452      * <p>
453      * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
454      * <br>
455      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
456      * <p>
457      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 26, 2016 <br>
458      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
459      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
460      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
461      */
462     public static class LaneChange implements Serializable
463     {
464 
465         /** */
466         private static final long serialVersionUID = 20160811L;
467 
468         /** Lane change progress. */
469         private Duration laneChangeProgress;
470 
471         /** Total lane change duration. */
472         private Duration laneChangeDuration;
473 
474         /** Whether the GTU is changing lane. */
475         private LateralDirectionality laneChangeDirectionality = null;
476 
477         /**
478          * Return whether the GTU is changing lane.
479          * @return whether the GTU is changing lane
480          */
481         public final boolean isChangingLane()
482         {
483             return this.laneChangeDirectionality != null;
484         }
485 
486         /**
487          * Return whether the GTU is changing left.
488          * @return whether the GTU is changing left
489          */
490         public final boolean isChangingLeft()
491         {
492             return LateralDirectionality.LEFT.equals(this.laneChangeDirectionality);
493         }
494 
495         /**
496          * Return whether the GTU is changing right.
497          * @return whether the GTU is changing right
498          */
499         public final boolean isChangingRight()
500         {
501             return LateralDirectionality.RIGHT.equals(this.laneChangeDirectionality);
502         }
503 
504         /**
505          * Return lateral lane change direction.
506          * @return LateralDirectionality; lateral lane change direction
507          */
508         public final LateralDirectionality getDirection()
509         {
510             return this.laneChangeDirectionality;
511         }
512 
513         /**
514          * @param laneChangeDirectionality set laneChangeDirectionality.
515          */
516         public final void setLaneChangeDirectionality(final LateralDirectionality laneChangeDirectionality)
517         {
518             this.laneChangeDirectionality = laneChangeDirectionality;
519         }
520 
521         /**
522          * Second lane of lane change relative to the reference lane. Note that the reference lane may either be the source or
523          * the target lane. Thus, the second lane during a lane change may either be the left or right lane, regardless of the
524          * lane change direction.
525          * @param gtu LaneBasedGTU; gtu
526          * @return target lane of lane change
527          * @throws OperationalPlanException If no lane change is being performed.
528          */
529         public final RelativeLane getSecondLane(final LaneBasedGTU gtu) throws OperationalPlanException
530         {
531             Throw.when(!isChangingLane(), OperationalPlanException.class,
532                     "Target lane is requested, but no lane change is being performed.");
533             Map<Lane, Length> map;
534             DirectedLanePosition dlp;
535             try
536             {
537                 map = gtu.positions(gtu.getReference());
538                 dlp = gtu.getReferencePosition();
539             }
540             catch (GTUException exception)
541             {
542                 throw new OperationalPlanException("Second lane of lane change could not be determined.", exception);
543             }
544             Set<Lane> accessibleLanes = dlp.getLane().accessibleAdjacentLanes(this.laneChangeDirectionality, gtu.getGTUType());
545             if (!accessibleLanes.isEmpty() && map.containsKey(accessibleLanes.iterator().next()))
546             {
547                 return isChangingLeft() ? RelativeLane.LEFT : RelativeLane.RIGHT;
548             }
549             return isChangingLeft() ? RelativeLane.RIGHT : RelativeLane.LEFT;
550         }
551 
552         /**
553          * Sets the duration for a lane change. May be set during a lane change. Whenever the lane change progress exceeds the
554          * lane change duration, the lane change is finalized in the next time step.
555          * @param laneChangeDuration total lane change duration
556          */
557         public final void setLaneChangeDuration(final Duration laneChangeDuration)
558         {
559             this.laneChangeDuration = laneChangeDuration;
560         }
561 
562         /**
563          * Update the lane change and return the lateral fraction for the end of the coming time step. This method is for use by
564          * the operational plan builder. It adds the time step duration to the lane change progress. Lane changes are
565          * automatically initiated and finalized.
566          * @param timeStep coming time step duration
567          * @param laneChangeDirection direction of lane change
568          * @param gtu GTU
569          * @return lateral fraction for the end of the coming time step
570          */
571         // TODO remove GTU argument (only needed for instantaneous lane change hack)
572         final double updateAndGetFraction(final Duration timeStep, final LateralDirectionality laneChangeDirection,
573                 final LaneBasedGTU gtu)
574         {
575             if (!isChangingLane())
576             {
577                 // initiate lane change
578                 this.laneChangeProgress = Duration.ZERO;
579                 this.laneChangeDirectionality = laneChangeDirection;
580                 try
581                 {
582                     ((AbstractLaneBasedGTU) gtu).initLaneChange(laneChangeDirection);
583                 }
584                 catch (GTUException exception)
585                 {
586                     throw new RuntimeException("Error during lane change initialization.", exception);
587                 }
588             }
589             // add current time step
590             this.laneChangeProgress = this.laneChangeProgress.plus(timeStep);
591             // get lateral fraction at end of current time step
592             double fraction = this.laneChangeProgress.divideBy(this.laneChangeDuration).si;
593             if (fraction >= 1.0)
594             {
595                 // TODO this elsewhere based on path
596                 try
597                 {
598                     ((AbstractLaneBasedGTU) gtu).finalizeLaneChange(laneChangeDirection,
599                             gtu.getSimulator().getSimulatorTime().getTime().plus(timeStep));
600                 }
601                 catch (GTUException exception)
602                 {
603                     throw new RuntimeException("Error during lane change finalization.", exception);
604                 }
605                 // limit by 1 and finalize lane change
606                 this.laneChangeDirectionality = null;
607                 return 1.0;
608             }
609             return fraction;
610         }
611 
612         /** {@inheritDoc} */
613         public final String toString()
614         {
615             return "LaneChange [" + this.laneChangeProgress + " of " + this.laneChangeDuration + " to "
616                     + this.laneChangeDirectionality + "]";
617         }
618 
619     }
620 
621     /**
622      * 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
623      * before completing the given path, a truncated path that ends where the GTU stops is used instead. There is no guarantee
624      * that the OperationalPlan will lead to a complete stop.
625      * @param gtu the GTU for debugging purposes
626      * @param lanes a list of connected Lanes to do the driving on
627      * @param firstLanePosition position on the first lane with the reference point of the GTU
628      * @param distance distance to drive for reaching the end speed
629      * @param startTime the current time or a time in the future when the plan should start
630      * @param startSpeed the speed at the start of the path
631      * @param deceleration the deceleration to use if endSpeed &lt; startSpeed, provided as a NEGATIVE number
632      * @return the operational plan to accomplish the given end speed
633      * @throws OperationalPlanException when construction of the operational path fails
634      * @throws OTSGeometryException in case the lanes are not connected or firstLanePositiion is larger than the length of the
635      *             first lane
636      */
637     public static LaneBasedOperationalPlan buildStopPlan(final LaneBasedGTU gtu, final List<Lane> lanes,
638             final Length firstLanePosition, final Length distance, final Time startTime, final Speed startSpeed,
639             final Acceleration deceleration) throws OperationalPlanException, OTSGeometryException
640     {
641         return buildMaximumAccelerationPlan(gtu, lanes, firstLanePosition, distance, startTime, startSpeed,
642                 new Speed(0.0, SpeedUnit.SI), new Acceleration(1.0, AccelerationUnit.SI), deceleration);
643     }
644 
645     /*-
646     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
647         final Acceleration startAcceleration, final Speed maxSpeed, final Duration duration,
648         final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
649         final double laneChangeProgress, final DirectedPoint endPoint) throws OperationalPlanException
650     {
651         return buildSpatialPlan(gtu, startTime, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, duration,
652             fromLanes, toLanes, curvatureType, laneChangeProgress, endPoint);
653     }
654     
655     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Time startTime,
656         final DirectedPoint startPoint, final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
657         final Duration duration, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
658         final CurvatureType curvatureType, final double laneChangeProgress, final DirectedPoint endPoint)
659         throws OperationalPlanException
660     {
661     
662         // check
663         checkLaneDirections(fromLanes, toLanes);
664     
665         // get start fractional position on link
666         final CrossSectionLink startLink = fromLanes.get(0).getLane().getParentLink();
667         Direction start;
668         Direction end;
669         if (fromLanes.get(0).getDirection() == GTUDirectionality.DIR_PLUS)
670         {
671             start = startLink.getStartNode().getDirection();
672             end = startLink.getEndNode().getDirection();
673         }
674         else
675         {
676             start = startLink.getEndNode().getDirection();
677             end = startLink.getStartNode().getDirection();
678         }
679         double fStart = startLink.getDesignLine().projectFractional(start, end, startPoint.x, startPoint.y);
680     
681         // get end fractional position on link, and end link
682         double fEnd = 0;
683         CrossSectionLink endLink = null;
684         for (int i = 0; i < toLanes.size(); i++)
685         {
686             CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
687             if (toLanes.get(i).getDirection() == GTUDirectionality.DIR_PLUS)
688             {
689                 start = l.getStartNode().getDirection();
690                 end = l.getEndNode().getDirection();
691             }
692             else
693             {
694                 start = l.getEndNode().getDirection();
695                 end = l.getStartNode().getDirection();
696             }
697             fEnd = l.getDesignLine().projectFractional(start, end, endPoint.x, endPoint.y);
698             if (fEnd > 0 && fEnd < 1)
699             {
700                 endLink = l;
701                 break;
702             }
703         }
704         Throw.when(endLink == null, OperationalPlanException.class, "End point cannot be projected to to-lanes.");
705     
706         // build from-line and to-line
707         OTSLine3D from = null;
708         OTSLine3D to = null;
709         for (int i = 0; i < toLanes.size(); i++)
710         {
711             CrossSectionLink l = toLanes.get(i).getLane().getParentLink();
712             try
713             {
714                 if (l == startLink)
715                 {
716                     from = fromLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
717                     to = toLanes.get(i).getLane().getCenterLine().extractFractional(fStart, 1);
718                 }
719                 else if (l == endLink)
720                 {
721                     from =
722                         OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
723                     to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine().extractFractional(0, fEnd));
724                     break;
725                 }
726                 from = OTSLine3D.concatenate(from, fromLanes.get(i).getLane().getCenterLine());
727                 to = OTSLine3D.concatenate(to, toLanes.get(i).getLane().getCenterLine());
728             }
729             catch (OTSGeometryException exception)
730             {
731                 throw new RuntimeException("Bug in buildSpatialPlan method.");
732             }
733         }
734     
735         // interpolate path
736         List<OTSPoint3D> line = new ArrayList<>();
737         line.add(new OTSPoint3D(startPoint.x, startPoint.y, startPoint.z));
738         if (curvatureType.equals(CurvatureType.LINEAR))
739         {
740             int n = (int) Math.ceil(32 * (1.0 - laneChangeProgress));
741             for (int i = 1; i < n; i++)
742             {
743                 double fraction = 1.0 * i / n;
744                 double f0 = laneChangeProgress + (1.0 - laneChangeProgress) * fraction;
745                 double f1 = 1.0 - f0;
746                 DirectedPoint p1;
747                 DirectedPoint p2;
748                 try
749                 {
750                     p1 = from.getLocationFraction(fraction);
751                     p2 = to.getLocationFraction(fraction);
752                 }
753                 catch (OTSGeometryException exception)
754                 {
755                     throw new RuntimeException("Bug in buildSpatialPlan method.");
756                 }
757                 line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
758             }
759         }
760         OTSLine3D path;
761         try
762         {
763             path = new OTSLine3D(line);
764         }
765         catch (OTSGeometryException exception)
766         {
767             throw new RuntimeException("Bug in buildSpatialPlan method.");
768         }
769     
770         // acceleration segments
771         List<Segment> segmentList = new ArrayList<>();
772         Acceleration b = startAcceleration.neg();
773         if (startSpeed.lt(b.multiplyBy(duration)))
774         {
775             // will reach zero speed within duration
776             Duration d = startSpeed.divideBy(b);
777             segmentList.add(new AccelerationSegment(d, startAcceleration)); // decelerate to zero
778             segmentList.add(new SpeedSegment(duration.minus(d))); // stay at zero for the remainder of duration
779         }
780         else
781         {
782             segmentList.add(new AccelerationSegment(duration, startAcceleration));
783         }
784     
785         return new OperationalPlan(gtu, path, startTime, startSpeed, segmentList);
786     }
787     
788     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final Acceleration startAcceleration,
789         final Speed maxSpeed, final List<LaneDirection> fromLanes, List<LaneDirection> toLanes,
790         final CurvatureType curvatureType, final Duration duration) throws OperationalPlanException
791     {
792         return buildSpatialPlan(gtu, gtu.getLocation(), gtu.getSpeed(), startAcceleration, maxSpeed, fromLanes, toLanes,
793             curvatureType, duration);
794     }
795     
796     public static OperationalPlan buildSpatialPlan(final LaneBasedGTU gtu, final DirectedPoint startPoint,
797         final Speed startSpeed, final Acceleration startAcceleration, final Speed maxSpeed,
798         final List<LaneDirection> fromLanes, List<LaneDirection> toLanes, final CurvatureType curvatureType,
799         final Duration duration) throws OperationalPlanException
800     {
801         checkLaneDirections(fromLanes, toLanes);
802     
803         return null;
804     }
805     
806     private final static void checkLaneDirections(final List<LaneDirection> fromLanes, List<LaneDirection> toLanes)
807         throws OperationalPlanException
808     {
809         Throw.when(fromLanes == null || toLanes == null, OperationalPlanException.class, "Lane lists may not be null.");
810         Throw.when(fromLanes.isEmpty(), OperationalPlanException.class, "Lane lists may not be empty.");
811         Throw.when(fromLanes.size() != toLanes.size(), OperationalPlanException.class,
812             "Set of from lanes has different length than set of to lanes.");
813         for (int i = 0; i < fromLanes.size(); i++)
814         {
815             Throw.when(!fromLanes.get(i).getLane().getParentLink().equals(toLanes.get(i).getLane().getParentLink()),
816                 OperationalPlanException.class,
817                 "A lane in the from-lanes list is not on the same link as the lane at equal index in the to-lanes list.");
818         }
819     }
820     
821     /**
822      * Defines curvature.
823      * <p>
824      * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
825      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
826      * <p>
827      * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 27, 2016 <br>
828      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
829      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
830      */
831     /*-
832     public enum CurvatureType
833     {
834         /** Linear lateral movement. */
835     /*-
836     LINEAR
837     {
838         public double[] getFractions(final double strartFraction)
839         {
840             return new double[1];
841         }
842     },
843     
844     /** */
845     /*-
846     SCURVE
847     {
848         public double[] getFractions(final double strartFraction)
849         {
850             return new double[1];
851         }
852     };
853     
854     public abstract double[] getFractions(double startFraction);
855     }
856      */
857 
858 }