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